[C++] Classes and Objects (4) Some compiler optimizations when the initialization list static member friend inner class anonymous object copies the object

foreword

This chapter is the final chapter of our classes and objects in C++, but the difficulty of this chapter is not too great. It is all the corners and corners of the class. It is enough to memorize and understand. I believe that after such a long time of learning classes and objects, you will understand Object-oriented also has a deeper understanding. Finally, after we have learned some knowledge points in the corners, let's talk about the understanding of classes and objects.


First, talk about the constructor

Although we have introduced the constructor in detail in Classes and Objects (2) , we still have to continue talking about the constructor here, because the constructor is too complicated (the father of C++ did not design it well at the beginning, and it was repeated many times later) Patching makes the constructor quite complicated), but this time talking about the constructor is not as difficult as before. This time we are talking about some fragmented knowledge of the constructor. Let's look at the code first and think again:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year=10, int month=10, int day=10)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
		_a = 10; //编译失败,const修饰的变量不能改变
	}
private:
	int _year;
	int _month;
	int _day;
	const int _a;//编译失败,const 变量未初始化
};

When creating an object, the compiler calls the constructor to give each member variable in the object an appropriate initial value. Although the object already has an initial value after the above constructor is called, it cannot be called a member variable in the object. For the initialization of variables, the statements in the constructor body can only be called initial value assignment, not initialization . Because the initialization can only be initialized once, and the constructor body can be assigned multiple times.

You may think that this kind of detailing is a bit too nitpicking, but it is not the case. Some variables are very important to initialization. For example, constmodified variables can only be initialized once and cannot be assigned, which requires us to carefully distinguish between initialization and assignment.

Let's add a constmodified variable to the member variable of the above code and see what happens?
insert image description here

constWe found that the compilation failed. Is it so difficult that such modified variables cannot be defined in the class ? No, in fact, we can assign values ​​to variables according to the default values ​​mentioned in Classes and Objects (2)const , but the default values ​​only appeared after the C++ update, so how did we solve the problem in C++ before the C++ update?

The answer is initialization lists!

1. Initialization list

First of all, when we think about a problem object definition, where are the members in the object defined? Solving this problem solves the above constproblem that the modified variable cannot be defined, because constthe modified variable must be initialized when it is defined, that is, the definition and initialization are together.
The answer is that the members in the object are specifically defined in the initialization list , so let us understand the initialization list together.

a. Definition

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.
Example code:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year , int month , int day)
		:_year(year)//初始化列表,也是成员变量定义的地方,这里才是真正的初始化
		,_month(month)
		,_day(day)
		,_a(10)
	{
    
    
		
	}
private:
	int _year;
	int _month;
	int _day;
	const int _a;
};
int main()
{
    
    
	Date d1(10,10,10);
	return 0;
}

insert image description here

b.Characteristics

①Each member variable can only appear once in the initialization list (initialization can only be initialized once)

insert image description here

②The class contains the following members, which must be placed in the initialization list for initialization:

  • Reference member variables (must be initialized when defined)
  • const member variables (must be initialized when defined)
  • When a member of a custom type and the class has no default constructor (no default constructor means that parameters must be passed during initialization)
  1. When we first look at the custom type members and the class does not have a default constructor , we do not initialize in the initialization list

insert image description here


2. When we look at the custom type member and the class has a default constructor , we do not initialize it in the initialization list.
insert image description here
The conclusion is: in the initialization list, if we don't write anything, the compiler will not deal with the built-in type, and call its default construction for the custom type.

Suggestion: Try to use initialization list initialization, because whether you use initialization list or not, for custom type member variables, you must first use initialization list initialization.

③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

Consider the result of the following code:

#include<iostream>
using namespace std;
class A
{
    
    
public:
	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();
}

insert image description here
The result may surprise you, let's analyze it carefully

insert image description here

2. 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, which has no default value.
Note: According to the definition, the constructor here must pass parameters

Let's look at the following piece of code

#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{
    
    }
private:
	int _a1;
	int _a2;
};
int main() {
    
    
	A aa1(1);  
	A aa2 = 1;//这里不是拷贝构造(拷贝构造是用一个对象初始化一个对象),
	         //这里也不是赋值重载(赋值重载是用一个已经初始化的对象赋值给另一个已经初始化过的对象)
	         //是否可以编译通过?
	return 0;
}

The answer is that it can be compiled and we can see its value by monitoring

insert image description here
So why can it be compiled here? The answer is implicit type conversion !
When explaining references, we once said that the reason why the following code can pass is that i will be implicitly converted to a double-type temporary variable, and the temporary variable is constant, and d refers to this double-type temporary variable!

int i = 10;
const double&d = i;

The same is true here, the specific process is as follows:
insert image description here
After understanding the principle of single-parameter type conversion and assignment, let's see how to do multiple parameters?


#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a,int b)
		:_a1(a)
		, _a2(b)
	{
    
    }
private:
	int _a1;
	int _a2;
};
int main() {
    
    
	A aa1(1,2);  //调用构造函数
	A aa2 = {
    
    1,2}; //多个参数类型转化
	return 0;
}

Here we can see that multiple parameters are what we { }use to give values
insert image description here

Note that single parameter conversion is supported by C++98, and multiple parameter conversion is supported by C++11

But sometimes we don't want this to happen, which requires us to modify the constructor with explicit, which will prohibit the implicit conversion of the constructor.

Let's add the same code. We have finally finished talking about the constructorexplicit
insert image description here
here ...we need to review a lot later!

Two, static members

1. Definition

Class members declared static are called static members of the class , member variables modified with static are called static member variables ; member functions modified with static are called static member functions .

Static member variables must be initialized outside the class ! Because initializer lists can only initialize non-static members

Example code:

#include<iostream>
using namespace std;
class A
{
    
    
public:
	A()
		:_a(10)
		,_b('a')
	{
    
    

	}
private:
	int _a;
	char _b;
	static int c;//这里不能给缺省值,缺省值是给初始化列表使用的,
	             //初始化列表只能初始化非静态成员
};
int A::c = 10;
int main()
{
    
    
	A aa;
	return 0;
}

insert image description here

2. Features

  1. Static members are shared by all class objects , do not belong to a specific object, and are stored in the static area
  2. Static member variables must be defined outside the class , the static keyword is not added when defining, and only declared in the class
  3. Class static members can be accessed by class name:: static member or object. static member
  4. Static members are also members of a class, subject to public, protected, private access qualifiers
  5. Static member functions do not have a hidden this pointer and cannot access any non-static members

insert image description here

3. Practice

Speaking of which, let's look at an interview question:
Please implement a class and calculate how many class objects are created in the program.
We first analyze the problem. To count how many class objects are created, we need to look at the unified characteristics of the class objects. We know that all created objects must go through the constructor (copy construction is also an overload of the constructor), so we only It is necessary to define a statistical variable of a static member (note feature 1: static members are shared by all class objects and do not belong to a specific object ), and every time an object is created, we just add one to this static statistical variable!

Example code:

#include<iostream>
using std::cout;
using std::endl;
class A
{
    
    
public:
	A()
		:_a(10)
	{
    
    
		_count++;
	}
	A(const A& tmp)
	{
    
    
		_a = tmp._a;
		_count++;
	}
private:
	static int _count;
	int _a;
};
int A::_count = 0;
int main()
{
    
    
	A aa1;
	A aa2;
	A aa3(aa1);
	A aa4(aa2);
	return 0;
}

insert image description here
Let's think about two more questions:

  1. Can a static member function call a non-static member function?
    The answer is: no! Static member functions do not have thispointers. Calling non-static member functions requires passing thispointers. Static member functions cannot pass pointers to non-static member functions this, so they cannot be called.
  2. Can a non-static member function call a static member function of a class?
    The answer is: yes! Non-static member functions have thispointers. Calling static member functions does not need to pass thispointers. Non-static member functions can pass pointers to static member functions this, so they can be called.

3. Friends

We know that it is generally impossible to access private members in a class outside the class, but when we want to access private members in a class outside the class, we need some special means, such as friend

1 Introduction

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

2. Friend function

A friend function can directly access the private members of a 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 keywords need to be added when declaring friend.

#include<iostream>
using namespace std;
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;
}

a.Characteristics

①Friend functions can access private and protected members of the class , but not member functions of the class
②Friend functions cannot be modified with const (friend functions have no thispointers)
③Friend functions can be declared anywhere in the class definition and are not affected by the class Access qualifier restrictions
④ 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

3. 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.

a.Characteristics

  • The friendship relationship is one-way and not exchangeable.
    For example, the following Time class and Date class, declare the Date class as its friend class in the Time class, then you can directly access the private member variables of the Time class in the Date class, but want to access the private members of the Date class in the Time class Variables do not.
  • 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.
  • Friend relationships cannot be inherited.
//友元类
class Time
{
    
    
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{
    
    }
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
	void SetTimeOfDate(int hour, int minute, int second)
	{
    
    
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

Fourth, the inner class

1. Definition

Concept: If a class is defined inside another class, the inner class is called an inner class . 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. Outer classes do not have any privileged access to inner classes .

Note: The inner class is the friend class of the outer class. See the definition of the friend class. 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.

2. Features:

  1. Inner classes can be defined as public, protected, and private in outer classes, and are affected by access qualifiers .
  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 .

example code

//内部类
#include<iostream>
using namespace std;
class A
{
    
    
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
    
    
	public:
		void foo(const A& a)
		{
    
    
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
    
    
	A::B b;
	b.foo(A());
	return 0;
}

5. Anonymous objects

1. Definition

An anonymous object means that we define an object but it has no name (there is also an anonymous structure in C language). The usage scenario of an anonymous object is usually: a variable is temporarily needed, but we don't want it to play a big role.
Defined format: class name ()

//例如 A是一个类
A();//定义一个匿名对象

2. Features

The life cycle of an anonymous object is only the line it is in, and it will automatically call the destructor after the next line.

//匿名对象
#include<iostream>
using namespace std;
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;
	}
};
int main()
{
    
    
	A aa1;
	// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa1();
	
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();
	A aa2(2);
	// 匿名对象在这样场景下就很好用,如果不定义匿名对象我们就要创建一个Solution这样的对象。
	Solution().Sum_Solution(10);
	return 0;
}

6. Some compiler optimizations when copying objects

With the development and iteration of the compiler, the current compiler is very intelligent. Generally, in the process of passing parameters and returning values ​​on some not too old compilers, the compiler will generally do some optimizations to reduce Object copy, this is still very useful in some scenarios.

Let's take a look together!

//拷贝对象时的一些编译器优化
#include<iostream>
using namespace std;
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()
{
    
    
	return A();
}

int main()
{
    
    
	// 传值传参 不优化
	A aa1;		
	f1(aa1);
	cout << "----------------------------------------------------" << endl;

	// 传值返回
	f2();    //不优化  调用一个构造函数+一个拷贝构造   因为是两行代码编译器不敢擅自优化
	cout << "----------------------------------------------------" << endl;

	// 隐式类型,构造+拷贝构造->优化为直接构造
	f1(1);
	// 一个表达式中,构造+连续拷贝构造->优化为一个构造
	f1(A(2));
	cout << "----------------------------------------------------" << endl;

	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << "----------------------------------------------------" << endl;

	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << "----------------------------------------------------" << endl;

	f3();	//一行中构造+拷贝构造 ->优化为一个构造
	A aa3 = f3();	//一行中构造+拷贝构造+拷贝构造 ->优化为一个构造
	return 0;
}

insert image description here

7. Understand classes and objects again

Physical computers in real life do not know, computers only know data in binary format. If you want the computer to recognize entities in real life, users must describe the entities through some object-oriented language, and then write programs to create objects before the computer can recognize them. For example, if you want the computer to recognize the washing machine, you need:

  1. The user first needs to abstract the reality of the washing machine entity—that is, to understand the washing machine at the level of human thought, what attributes the washing machine has, and what functions it has, that is, a process of abstract cognition of the washing machine
  2. After 1, people have a clear understanding of the washing machine in their minds, but the computer is still unclear at this time. To make the computer recognize the washing machine in people's imagination, people need to pass some kind of object-oriented language ( For example: C++, Java, Python, etc.) describe the washing machine with classes and input it into the computer
  3. After 2, there is a washing machine class in the computer, but the washing machine class only describes the washing machine object from the perspective of the computer. Through the washing machine class, each specific washing machine object can be instantiated. At this time, the computer can wash the washing machine What is it.
  4. The user can use the washing machine object in the computer to simulate the washing machine entity in reality.

In the class and object stage, everyone must realize that a class describes a certain type of entity (object), describes which attributes and methods the object has, and forms a new custom type after the description is completed , the specific object can be instantiated only with this custom type.

insert image description here

Guess you like

Origin blog.csdn.net/qq_65207641/article/details/129064346