[C++ Elementary] 4. Classes and Objects (Part 2)

First, talk about the constructor

constructor body assignment

When creating an object, the compiler calls the constructor to give each member variable in the object an appropriate initial value.

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

Although there is already an initial value in the object after the above constructor is called, it cannot be called the
initialization of the member variables in the object. The statement in the constructor body can only be called the initial value assignment, not the initial value. initialization. Because the initialization can only be initialized
once, and the constructor body can be assigned multiple times.

class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;//第一次赋值
		_year = 2001;//第二次赋值
		_year = 2022;//第三次赋值
		//.....
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

initialization list

Initialization list : Starts with a colon , followed by a comma-separated list of data members , each " member variable " followed by an initial value or expression enclosed in parentheses .

Such as the following form:

class Date
{
    
    
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    }
private:
	int _year;
	int _month;
	int _day;
};

The above is a simple way to write the initialization list of the date class constructor. When encountering a more complex class, the initialization list and the initialization in the function body can be mixed.

For example stack:

class Stack
{
    
    
public:
	/*Stack(int capacity = 4)
		:_a((int*)malloc(sizeof(int)*capacity))	//可以在初始化列表写
		, _top(0)
		, _capacity(capacity)
	{
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
	}*/

	// 初始化列表和函数体内初始化可以混着来
	Stack(int capacity = 4)
		: _top(0)
		, _capacity(capacity)
	{
    
    
		_a = (int*)malloc(sizeof(int) * capacity);	//可以在函数体内写
		if (_a == nullptr)
		{
    
    
			perror("malloc fail");
			exit(-1);
		}
		memset(_a, 0, sizeof(int) * capacity);
	}
private:
	int* _a;  // 声明
	int _top;
	int _capacity;
};

We know that before a class is instantiated, the member variables in the class are only declared, and once the object is instantiated, it is the overall definition of the class. When is each member of the object defined? - - - 初始化列表(definition of c++, no why)

There are the following three member variables in the class, which must be placed in the initialization list for initialization :

1. Reference member variables
When we learn about references, we know that variables of reference type must be given an initial value when they are defined.

int a = 10;
int& b = a;// 创建时就初始化

Since the members of the object are defined in the initialization list position, we can only define reference member variables in the initialization list

class B
{
    
    
public:
	B(int ref)
		: _ref(ref)
	{
    
    }
private:
	int& _ref;  // 引用
};

2. const member variables
Variables modified by const must also be given an initial value when they are defined.

const int a = 10;//correct 创建时就初始化
const int b;//error 创建时未初始化

So it must also be initialized using an initialization list.

class B
{
    
    
public:
	B()
		:_n(10)
	{
    
    }
private:
	const int _n; // const 成员变量
};

3. A member of a custom type (and the class has no default constructor)
If there is a class object in a class, and the class of the class object has no default constructor. Then we need to pass parameters to initialize it when instantiating this class object, so when instantiating a class object without a default constructor, we must use the initialization list to initialize it.
Here again, the default constructor refers to the constructor that can be called without passing parameters:
 1. We don't write the constructor that is automatically generated by the compiler.
 2. No-argument constructor.
 3. All default constructors.

class A //该类没有默认构造函数 
{
    
    
public:
	A(int val) //注:这个不叫默认构造函数(需要传参调用)
	{
    
    
		_val = val;
	}
private:
	int _val;
};

class B
{
    
    
public:
	B()
		:_a(2022) //必须使用初始化列表对其进行初始化
	{
    
    }
private:
	A _a; //自定义类型成员(该类没有默认构造函数)
};

So this is why there is an initialization list.

Notes on using initializer lists:

1. Each member variable can only appear once in the initialization list
 because initialization can only be performed once, so the same member variable cannot appear multiple times in the initialization list.

2. The class contains the following members, which must be placed in the initialization list for initialization: (in summary)

  • reference member variable
  • const member variable
  • A custom type member (and the class has no default constructor)
class A
{
    
    
public:
	A(int val)
		:_val(val)
	{
    
    }
private:
	int _val;
};
class B
{
    
    
public:
	B(int a, int ref)
		:_a(a)//必须在初始化列表传参初始化
		, _ref(ref)
		, _n(10)
	{
    
    }
private:
	A _a;// 没有默认构造函数
	int& _ref;  // 引用
	const int _n; // const 
};

3. Try to use the initialization list to initialize, because no matter whether you use the initialization list or not, for custom type member variables, you
must first use the initialization list to initialize.

 Because the initialization list is actually the place where the member variables of the object are defined when you instantiate an object, so whether you use the initialization list or not, you will go through such a process (the member variables need to be defined).

class Time {
    
    
public:
	Time(int hour = 0)
		:_hour(hour)
	{
    
    
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
 
class Date {
    
    
public:
	Date(int day)
	{
    
    }
private:
	int _day;
	Time _t;
};
 
int main()
{
    
    
	Date d(1);
}

4. The order in which member variables are declared in the class is the order in which they are initialized in the initialization list, regardless of their order in the initialization list. For
example:

class A
{
    
    
public:
	//由于先声明的是_a2在声明_a1,所以编译器先初始化_a2,在初始化_a1。
	//而_a1一开始是随机值,所以_a2是随机值,_a1是1
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{
    
    }

	void Print() {
    
    
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() {
    
    
	A aa(1);
	aa.Print();
}
A.输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

insert image description here

Overall summary:

Each member of the object is defined in the initialization list;
each member must go through the initialization list, even if it is not displayed in the initialization list, it will go;
if it is written in the initialization list, it will be initialized with the display;
if it is not in the initialization List display initialization:

  1. Variable types that must be initialized when they are defined must be placed in the initialization list for initialization.
  • reference member variable
  • const member variable
  • A custom type member (and the class has no default constructor)
  1. Built-in type, if there is a default value, use the default value, if not, use a random value
  2. For a custom type, call its default constructor by default. If there is no default constructor, it must be initialized by passing parameters in the initialization list, otherwise an error will be reported.

explicit keyword

1. Implicit type conversion:
 In fact, we have long been exposed to implicit type conversion (also mentioned in the explanation of return by value and return by reference), the following code is also called implicit type conversion:

double d = 1.1;
int i = d;//隐式类型转换

insert image description here
In this process, the compiler will first construct a temporary variable of type int to receive the value of d, and then assign the value of the temporary variable to i.

Because temporary variables have constant properties, we need to add const when we perform reference operations, because this involves permission issues (only pointers and references will consider permission issues).
insert image description here

2. Introduction to the explicit keyword

Constructors can not only construct and initialize objects, but also have the function of type conversion for a single parameter or a constructor with default values ​​except for the first parameter .

1. Single parameter or only need to pass a parameter value:

#include <iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 0) //单个参数的构造函数
		:_year(year)
	{
    
    }

	
	// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用
	 //Date(int year, int month = 1, int day = 1)
	 //: _year(year)
	 //, _month(month)
	 //, _day(day)
	 //{}
 
	void Print()
	{
    
    
		cout << _year << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	//隐式类型转换
	Date d1 = 2021; //支持该操作
	d1.Print();
	return 0;
}

Syntactically, Date d1 = 2021 in the code is equivalent to the following two lines of code:

Date tmp(2021); //先构造
Date d1(tmp); //再拷贝构造

In the early compilers, when the compiler encounters the code Date d1 = 2021, it will first construct a temporary object, and then use the temporary object to copy and construct d1; but the current compiler has been optimized, when it encounters Date When the code d1 = 2021, it will be processed according to the code Date d1(2021), which is called implicit type conversion.

Two, multi-parameter construction

class Date
{
    
    
public:
	// 多参数构造
	//explicit Date(int year, int month, int day)
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }

	
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1 = {
    
     2022, 10, 12 };
	// 等价于
	Date d2(2022, 10, 12 );

	const Date& d3 = {
    
     2022, 10, 12 };//引用,必须加const,临时变量具有常属性
	
	return 0;
}

**Note:** The implicit type conversion of multi-parameter construction is only supported by C++11, and C++98 does not support it. Only the implicit type conversion of single-parameter construction is supported.

For Date d1 = 2021, the readability of the code is not very good. If we want to prohibit the implicit conversion of the single-argument constructor, we can use the keyword explicit to modify the constructor.

class Date {
    
    
public:
	explicit Date(int year)
		: _year(year)
	{
    
    }
 
private:
	int _year;
};

insert image description here

2. Static members

Introduced - counts how many class objects are created in the class

If we want to count how many class objects are created in a class, we can use count to count

int N = 0;  // 全局变量

class A {
    
    
public:
	A(int a = 0)
		: _a(a)
	{
    
    
		++N;
	}
	A(const A& aa)
		: _a(aa._a)
	{
    
    
		++N;
	}

private:
	int _a;
};

void f(A a)
{
    
    }

int main(void)
{
    
    
	A a1;
	A a2 = 1;//隐式类型转换
	f(a1);

	cout << N << endl;

	return 0;
}

insert image description here
Since our N defines a global variable, we can modify it anywhere. If we modify it maliciously, the data we get will not be accurate.

int main()
{
    
    
	A a1;
	A a2 = 1;
	f(a1);
 
	cout << cnt << endl;
    count++;   // 我可以修改它 
	return 0;
}

Is there a way to fit N and class together? Let this N be used exclusively to calculate my class A.
Let's first try to define it as a member variable:

class A {
    
    
public:
	A(int a = 0)
		: _a(a)
	{
    
    
		++_N;
	}
	A(const A& aa)
		: _a(aa._a)
	{
    
    
		++_N;
	}

private:
	int _a;
	int _N // 定义成成员变量
};

But this still doesn't work! In this case, there is a count in each object. We hope that when each object is created, the same variable is ++, not that there is one in each object.
So what to do?

Static members can be defined in the class, and a static member is added in front of the member variable, which is a static member.

Let's look down:

concept

staticClass members declared as are called static members of the class. A member variable that is modified staticis called a static member variable ; statica member function that is modified is called a static member function . Static member variables must be initialized outside the class .

class A {
    
    
public:
	A(int a = 0)
		: _a(a)
	{
    
    
		++_N;
	}
	A(const A& aa)
		: _a(aa._a)
	{
    
    
		++_N;
	}
private:
	int _a;
	static int _N
	// 静态成员变量属于整个类,所有对象,生命周期在整个程序运行期间
};

characteristic

1. Static members are shared by all class objects, do not belong to a specific object, and are stored in the static area

#include <iostream>
using namespace std;
class Test
{
    
    
private:
	static int _n;
};
int main()
{
    
    
	cout << sizeof(Test) << endl;
	return 0;
}

As a result, the size of the Test class is calculated as 1, because the static member _n is stored in the static area, belongs to the entire class, and also belongs to all objects of the class. So when calculating the size of a class or the size of a class object, static members do not count towards the sum of their total sizes.

2. Static member variables must be defined outside the class. The static keyword is not added when defining, and only declared in the class

class Test
{
    
    
private:
	static int _n;//声明
};
// 静态成员变量的定义初始化
int Test::_n = 0;

3. The static members of the class can 类名::静态成员be 对象.静态成员accessed by or - - explained in detail below.

4. Static member functions have no hidden this pointer and cannot access any non-static members

class Test
{
    
    
public:
	//没有this指针,只能访问静态成员
	static void Fun()
	{
    
    
		cout << _a << endl; //error不能访问非静态成员
		cout << _n << endl; 
	}
private:
	int _a; //非静态成员
	static int _n; //静态成员
};

5. Static members are also members of the class, subject to the restrictions of public, protected, and private access qualifiers. - - - The life cycle is global, and the scope is affected by the class scope.

Access to static member functions

1. When the static member variable is public, we can access it outside the class. There are the following access methods:

class A {
    
    
public:
	A(int a = 0)
		: _a(a)
	{
    
    
		++_N;
	}
	A(const A& aa)
		: _a(aa._a)
	{
    
    
		++_N;
	}

private:
	int _a;

public:
	static int _N;
};

// 静态成员变量的定义初始化
int A::_N = 0;

void f(A a)
{
    
    }

int main(void)
{
    
    
	A a1;
	A a2 = 1;//隐式类型转换
	f(a1);
	/* 这里不是说是在 a1 里面找,这里只是帮助他突破类域罢了 */
	cout << a1._N << endl;
	cout << A::_N << endl; //2.通过类名突破类域进行访问
	return 0;
}

2. When the static member variable is private:

We can provide a public member function. We write a public GetN member function, let it return the value of _N, so that we can call this function outside the class, and we can access it.

Is there a better way? Let me access it without using an object?

Static member function:

static int GetN() {
    
    
    return _N;
}
class A {
    
    
public:
	A(int a = 0)
		: _a(a)
	{
    
    
		++_N;
	}
	A(const A& aa)
		: _a(aa._a)
	{
    
    
		++_N;
	}
	// 没有this指针,只能访问静态成员
	static int GetN()
	{
    
    
		return _N;
	}
private:
	int _a;

public:
	static int _N;
};

// 静态成员变量的定义初始化
int A::_N = 0;

void f(A a)
{
    
    }

int main(void)
{
    
    
	A a1;
	A a2 = 1;//隐式类型转换
	f(a1);

	cout << a1._N << endl;//1.通过对象调用成员函数进行访问
	cout << A::GetN() << endl; //2.通过类名调用静态成员函数进行访问

	return 0;
}

3. Friends

Friend is divided into friend function and friend class. Friends provide a way to break out of encapsulation, and sometimes convenience. But friends will increase the degree of coupling and destroy the encapsulation, so friends should not be used more often.

Friends are divided into: friend function and friend class

friend function

A friend function can directly access the private members of the class . It is an ordinary function defined outside the class and does not belong to any class , but it needs to be declared inside the class , and the friend keyword needs to be added when declaring .

When we implemented the date class in the last article, we overloaded the input and output operators: operator<<and operator>>.
We know that stream extraction and stream insertion operators cannot be overloaded as member functions of a class. Because the output stream object of cout and the implicit this pointer are preempting the position of the first parameter: the this pointer is the first parameter by default, that is, the left operand, but in actual use, cout needs to be the first formal parameter object to work normally use.

class Date
{
    
    
public:
    Date(int year, int month, int day)
        : _year(year)
        , _month(month)
        , _day(day)
    {
    
    }
    // d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
    // 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
    ostream & operator<<(ostream& _cout)
    {
    
    
        _cout << _year << "-" << _month << "-" << _day << endl;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};

So we want to overload operator<< as a global function, but in this case, it will cause no way to access members outside the class, so friends are needed to solve it. (operator>>same reason)

Therefore, if we want << and >> to automatically recognize our date class, we need to write the corresponding operator overloading function ourselves.

class Date
{
    
    
	//友元函数
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator>>(istream& _cin, Date& d);
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {
    
    }
private:
    int _year;
    int _month;
    int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
    
    
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
    
    
    _cin >> d._year;
    _cin >> d._month;
    _cin >> d._day;
    return _cin;
}
int main()
{
    
    
    Date d;
    cin >> d;
    cout << d << endl;
    return 0;
}

Note: where cout is a global object of the ostream class, cin is a global variable of the istream class, and the overloaded functions of the << and >> operators have return values ​​in order to realize continuous input and output operations.

Friend function description:

  • Friend functions can access private and protected members of a class, but not member functions of the class
  • Friend functions cannot be modified with const
  • Friend functions can be declared anywhere in the class definition, not restricted by class access qualifiers
  • A function can be a friend function of multiple classes
  • The principle of calling a friend function is the same as that of a normal function

friend class

All member functions of a friend class can be friend functions of another class, and can access non-public members of another class.

class A
{
    
    
	// 声明B类是A类的友元类,所以B类中的所有成员函数都可以是A类的友元函数
	friend class B;
public:
	A(int n = 0)
		:_n(n)
	{
    
    }
private:
	int _n;
};
class B
{
    
    
public:
	void Test(A& a)
	{
    
    
		// B类可以直接访问A类中的私有成员变量
		cout << a._n << endl;
	}
};

Friend class description:

  • The friendship relationship is one-way and not exchangeable.
    For example, in the above code, B is a friend of A, so the private member variables of class A can be directly accessed in class B, but the private member variables of class B cannot be accessed in class A.
  • Friend relationship cannot be transmitted
    If C is a friend of B and B is a friend of A, it cannot be explained that C is a friend of A.
  • The friendship relationship cannot be inherited, and I will give you a detailed introduction in the inheritance position.

Fourth, the inner class

Concept: If a class is defined inside another class, the inner class is called an inner class.
Notice:

  1. The inner class is an independent class, it does not belong to the outer class, let alone access the members of the inner class through the object of the outer class.
  2. Outer classes do not have any privileged access to inner classes
  3. The inner class is inherently a friend class of the outer class , and the inner class can access all members of the outer class through the object parameters of the outer class. But the outer class is not a friend of the inner class.

characteristic:

  1. The inner class can be defined in the public, protected, and private of the outer class.
  2. Note that the inner class can directly access the static members in the outer class without the object/class name of the outer class.
  3. sizeof(outer class) = outer class, has nothing to do with inner class.
// 相当于两个独立的类
// B类的访问受A的类域和访问限定符的限制
class A
{
    
    
private:
	int _a;
	static int k;

public: // B天生就是A的友元
	class B
	{
    
    
		int _b;

		void foo(const A& a)
		{
    
    
			cout << k << endl;//OK
			cout << a._a << endl;//OK
		}
	};
};

int A::k = 1;

int main()
{
    
    
	cout << sizeof(A) << endl;
	A aa;
	cout << sizeof(aa) << endl;
	A::B bb;

	return 0;
}

insert image description here

5. Anonymous objects

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}

	~A()
	{
    
    
		cout << "~A()" << endl;
	}
private:
	int _a;
};

class Solution {
    
    
public:
	int Sum_Solution(int n) {
    
    
		//...
		return n;
	}
};
A F()
{
    
    
	//A ret(10);
	//return ret;
	return A(10);
}
int main()
{
    
    
	// 有名对象
	A aa0;
	A aa1(1);
	A aa2 = 2;

	//A aa3();

	// 匿名对象 --声明周期当前这一行
	A();
	A(3);

	//Solution so;
	//so.Sum_Solution(10);

	Solution().Sum_Solution(10);

	return 0;
}

6. Some compiler optimizations when copying objects

Please see the following code:

class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
    
    
		cout << "A& operator=(const A& aa)" << endl;

		if (this != &aa)
		{
    
    
			_a = aa._a;
		}

		return *this;
	}

	~A()
	{
    
    
		cout << "~A()" << endl;
	}

private:
	int _a;
};

void f1(A aa)
{
    
    }

A f2()
{
    
    
	A aa;
	return aa;
}

A f3()
{
    
    
	/*A aa(10);
	return aa;*/

	return A(10);
}


int main()
{
    
    
	// 优化场景1
	A aa1 = 1;  // A tmp(1) + A aa1(tmp) -> 优化 A aa1(1)

	
	//优化场景2
	f1(A(1));  // 构造 + 拷贝构造  -> 优化 构造
	f1(1);  // 构造 + 拷贝构造  -> 优化 构造
	
	//优化场景3
	f2();	  // 构造+拷贝构造
	A ret = f2(); // 构造+拷贝构造+拷贝构造 ->优化 构造+拷贝构造

	//优化场景4
	A ret = f3();  //  构造+拷贝构造+拷贝构造 -> 优化 -> 构造


	return 0;
}

Optimization Scenario 1:

A aa1 = 1;  // A tmp(1) + A aa1(tmp) -> 优化 A aa1(1)
//构造 + 拷贝构造 ->优化 构造

Optimization Scenario 2:

f1(A(1));  // 构造 + 拷贝构造  -> 优化 构造
f1(1);  // 构造 + 拷贝构造  -> 优化 构造

Optimization Scenario 3:

f2();	  // 构造+拷贝构造
A ret = f2(); // 构造+拷贝构造+拷贝构造 ->优化 构造+拷贝构造

insert image description here

Optimization Scenario 4:

A ret = f3();  //  构造+拷贝构造+拷贝构造 -> 优化 -> 构造

insert image description here

Guess you like

Origin blog.csdn.net/m0_58124165/article/details/127917211