[C++ Essence Shop] 6. C++ classes and objects (below) knowledge supplements and compiler optimization for classes and objects

Table of contents

1. Talk about structure again

1.1 Initialization of member variables (initialization list)

1.2 Behavior of initialization lists

1.3 explicit keyword

 2. Static members in the class

2.1 Static member variables

2.2 Static member functions

3. Tomomoto

3.1 Friend functions

3.1 Friend class

4. Inner classes

 5. Anonymous objects

 6. Compiler optimization when copying objects

 

1. Talk about structure again

1.1 Initialization of member variables (initialization list)

        Why do we still have to look at the problem of initialization, because there is a big misunderstanding here, we all know that when creating an object, the constructor will be called to initialize the members, so we will regard the following code as initialization, but in fact the following Constructor code can only be called assignment.

class Date
{
public:
	Date(int year,int month,int day)
	{
		_year = year;   //因为初始化只有一次,
		_month = month; //而在函数里面可以多次赋值,不能叫做初始化
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

        The real initialization is carried out in the initialization list. The position of the initialization list is in front of the constructor "{}", which is composed of a colon followed by multiple member variables separated by commas. Each member variable is followed by Brackets, inside the brackets is the initial value, the order of initialization has nothing to do with the order of the member list, it is initialized according to the order of declaration of the members, as follows:

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

        And the following three members must be initialized using the initialization list:

  1. const member variables: because const members are constant and cannot be assigned, but the function body is assigned, so it can only be initialized in the initialization list ( specifically related to the behavior of the initialization list, which will be described later )
  2. Reference member variable: Because the reference must be initialized when it is defined, and the referenced entity cannot be changed, if it is performed in the function body, it is essentially an assignment overload.
  3. Custom type without default construction: Because the custom type will call the default construction for initialization, if there is no construction, it must be initialized explicitly.

1.2 Behavior of initialization lists

        I think everyone should be very confused, why is initialization in the initialization list and assignment in the function body? The execution time of the initialization list is before the function body. When we call the constructor, the compiler will first perform default initialization in the order of declaration. If there is a construction initial value in the initialization list, the default initialization will not be performed, and To perform explicit initialization, so whether we have written the initialization list or not, it will be initialized before the function body is executed, which explains why some members can only be initialized in the initialization list and the order and initialization of the initialization list The order does not matter. The initialization list is just a reference for the compiler. It can be considered that if you (list) have it when the compiler executes, I will follow yours, and if not, I will follow my own. Once the const member is initialized, it cannot be assigned, so it is grammatically stipulated that const must be artificially initialized to the constructor, which is why const must be initialized in the initialization list.

        And when we execute the function body, all members have already completed the default initialization and initialization. At this time, the so-called "initialization" is actually assignment (copy or assignment overload).

        Whenever we recommend using the initialization list for initialization, the first is that it is not easy to make mistakes, and the second is the unity of rules.

1.3 explicit keyword

        If the constructor has only a single parameter or all but the first parameter has no default value, there will be implicit type conversion in some cases. for example

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
//
class B
{
public:
	B(int a, int b = 1, int c = 1)
		:_a(a)
        ,_b(b)
        ,_c(c)
	{}
private:
	int _a;
	int _b;
	int _c;
};

int main()
{
	A a1 = 1;  //这俩种情况都会发生隐式类型转换 转换成一个无名类型的对象进行拷贝
	B b1 = 1;
    //C++11
    B b2 = { 1, 2, 3}; //c++11支持的括号初始化,将数组进行类型转换再构造,            
                       //想了解可以看我的c++11专栏


}

23c9ecf132be4e57b0a78ea0b99255ae.png

         For the bracket initialization mentioned in the code, you can read this article of mine: [C++11] Unified list initialization ({} initialization)_Ziyi Banjie's Blog-CSDN Blog

        Back to the topic, sometimes we don’t want the type conversion mentioned above to happen, so we have the explicit keyword. If you use this keyword to modify the constructor, this type conversion will not happen. At the same time, the parentheses of C++11 The initialization feature is also disabled.

class A
{
public:
	explicit A(int a)
		:_a(a)
	{}
private:
	int _a;
};
//
class B
{
public:
	explicit B(int a, int b = 1, int c = 1)
		:_a(a)
		, _b(b)
		, _c(c)
	{}
private:
	int _a;
	int _b;
	int _c;
};
int main()
{
	A a1 = 1;  //报错:E0415	不存在从 "int" 转换到 "A" 的适当构造函数
	B b1 = 1;  //报错:E0415	不存在从 "int" 转换到 "B" 的适当构造函数
	//C++11
	B b2 = { 1, 2, 3 };   //报错:E2334	复制列表初始化不能使用标记为“显式”的构造函数
}

 Error:c5bc0a3024c047f48e01265fc3cd5e8f.png

 

 2. Static members in the class

        Static members in a class are very different from other members in the class. Non-static member variables are initialized in the initialization list, but static member variables cannot be initialized in the initialization list because static members do not belong to an object. Static members are stored in the static area and shared by all objects of the same type

2.1 Static member variables

        Static member variables belong to all objects of the same type, and its definition and initialization are performed outside the class. When defining, you need to add the class name:: in front of the variable name.

int A::_a = 0;
class A
{
public:
private:
	static int _a;
};

2.2 Static member functions

        Since static functions belong to each class object, not to an object, static member functions do not have this pointer, so they cannot access non-static members of the class. Non-static member functions can call static member functions, but static member functions cannot call non-static functions. Someone may have a question: Isn't non-static member functions also shared, why can't they be accessed by static member functions? This is because non-static member functions need to pass in this pointer, while static member functions do not have this pointer.

int A::_a = 0;
class A
{
public:
	static void test() {  }
	void test1() { test(); }
private:
	static int _a;
};

3. Tomomoto

        Friend is a way to break through encapsulation and allow external members to access their own private members, but excessive use will break encapsulation.

3.1 Friend functions

        The declaration of a friend function is to add the friend keyword in front of the function declaration in the class , and declare it as a friend function. This function can access the private members of the object. It needs to be used in some special cases, such as "<<" stream writing symbol overload, since the left operand of "<<" must be an ostream object, so the first parameter must be of ostream type, but if defined as Member function, the first parameter becomes the this pointer, which will cause us to become "object <<cout" when we use "<<", which is inconsistent with our habits, so the symbol written by the stream is heavy The load must be defined as a global function, and must also be able to access the private members of the class. At this time, the friend function can play a role. as follows:

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << ' ' << d._month << ' ' << d._day << endl;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
  •  Friend functions can access all members of the class, but not members of the class
  • Friend functions cannot be modified with const
  • Friend functions are not restricted by access qualifiers

3.1 Friend class

        If a class A declares a friend class B, then all members of class B can access all members of class A, but class A cannot access members of class B. Friend relationship cannot be passed and inherited (inheritance will be discussed later), for example, C is a friend of D, D is a friend of E, but C is not a friend of E.

class A
{
	friend class B;
private:
	int _aa;
	int _ab;
	int _ac;
};
class B
{
	void print()
	{
		cout << _atest._aa << ' ' << _atest._ab << ' ' << _atest._ac << endl;
	}

private:
	A _atest;
};

4. Inner classes

        When a class is defined inside another class, the class is called an inner class. The inner class is equivalent to the friend class of the outer class. You can access all members of the inner class through object parameters. When accessing static members, you can directly access them without object parameters, but the outer class cannot access the members of the inner class. The inner class is an independent class and has no effect on the size of the outer class. Access to inner classes by outer members is restricted by access-qualification modifiers.

class A
{
public:
	class B
	{
		void print(const A& a)
		{
			cout << a._a;
		}
	};
private:
	int _a;
};

 5. Anonymous objects

        The definition of an anonymous object is to add a parenthesis after the object. The anonymous object does not need to be named, and the declaration cycle is only one line. After use, the destructor is automatically called and destroyed.

class A
{
public:
	static int _a;
};
int A::_a = 0;

int main()
{
	A();   //匿名对象
	cout << A()._a;
}

 6. Compiler optimization when copying objects

        When we pass parameters or return by value, we may perform frequent construction and copy construction. If the object is large, it will cause great resource consumption, so the compiler makes some optimizations for this. (Only for construction, assignment overloading will not be optimized)

class A
{
public:
	A(int a = 0)
	{
		cout << "A(int a = 0)" << endl;
	}
	A(const A& aa)
	{
		cout << "A(const A & aa)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};
void f1(A a)
{}
A f2()
{
	A a;
	return a;
}
int main()
{
	A a;
	f1(1);  //连续构造+拷贝构造->直接构造
	f1(A(2)); //连续构造+拷贝构造->直接构造
	A a1 = f2(); //拷贝构造+拷贝构造->优化为一个拷贝构造
}

a385f1784f714926b72221a51c924dcd.png

 

 

Guess you like

Origin blog.csdn.net/qq_64293926/article/details/132220097