Detailed Explanation of C++ Classes and Objects (Part 2)—Practice Functions with Code

Table of contents

First, talk about the constructor

 1.1 Constructor body assignment

 1. 2 Initialization list 

1.21 Custom type members 

1.22 const member variables

1.23 Reference member variables  

1. 24 The "pit" of the initialization list

1. 3 explicit keyword 

Two, static members

2.1 Concept 

2.2 Features 

Three, Friends

3. 1 Friend function

3. 2 Friend class 

Features: 

3. 3 Inner classes (understand)

3.31 Concept

Fourth, anonymous objects (understand)

Five, some optimizations of the compiler for copying objects

1. Passing parameters by value

2. Return by value

3. Implicit typing

4. In an expression, continuous construction + copy construction -> optimized to a construction

5. In an expression, continuous copy construction + copy construction -> optimize a copy construction

6. In an expression, continuous copy construction + assignment overload -> cannot be optimized

epilogue


First, talk about the constructor

 1.1 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 the object already has an initial value 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 .

Here's the past member initialization, which is not appropriate in this scenario:

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 10)   // 对于在函数体中初始化,必须写缺省参数,否则编译报错。
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour)    
	{
		_year = year;
		Time t(hour);
		_t = t;   
	}
private:
	int _year;
	Time _t; 
};

int main()
{
	Date a(2023,2);
	return 0;
}

 

 1. 2 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.
class Date
{
public:
Date(int year, int month, int day)
     : _year(year)
     , _month(month)
     , _day(day)
 {}
private:
  // 成员变量声明
  int _year;
  int _month;
  int _day;
};
1. Each member variable can only . (Initialization can only be initialized once)
(Note: If it is only built-in type initialization, then there is not much difference between initialization in the function body and initialization list initialization .)
2. The class contains the following members, which 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.21 Custom type members 

 Use the initialization list to complete, custom type initialization, see the following code:

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 11)   // 区别于函数内构造(见1.1代码,这里必须加),全缺省参数可加可不加。
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour)  
		:_t(hour)
	{
		_year = year;
	}
private:
	int _year = 100; // 缺省值,c++11打了个补丁,在内置类型初始化没有给值值时,给缺省值。
	Time _t;1 
};

int main()
{
	Date a(2023,2);
	return 0;
}

So we can see the combined internal initialization of the function body and list initialization:

1. If it is an internal initialization of the function, it still needs to be initialized before the assignment, and then assigned, several initializations are required in the middle, which is cumbersome.

2. If you initialize in the initialization list, you can directly display the initialization without having a default constructor with all defaults.

in conclusion:

  • It is recommended to use list initialization for custom type members , and constructors without all default parameters must use list initialization.
  • The built-in type is recommended to be written in the initialization list, or it can be written in the function body, both are optional. (Unless for the code to look good, it needs to be written as an internal initialization of the function)

1.22   const member variables

       The reason is such as: const int x must be initialized where it is defined. (This is how I understand it, it is related to the permission nature of the const xx type )

class Date
{
public:
	Date(int year, const int x)
		: _z(x)
	{
		_year = year;
	}
private:
	int _year;
	const int _z;
};

int main()
{
	int x = 0;  // 用const修饰,就是想等传参;不修,则是缩小权限传参。
	Date a(2023, x);
	return 0;
}

1.23  Reference member variables  

        A reference is an "alias" for another variable, and its nature is that modification is not allowed, so it must be initialized when it is defined. 

class Date
{
public:
	Date(int year, int& x)
		: _z(x)
	{
		_year = year;
	}
private:
	int _year;
	int& _z;
};

int main()
{
	int x = 0;
	Date a(2023, x);
	return 0;
}

1. 24 The "pit" of the initialization list

See what happens with the following code:

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();
}
/*A. 输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值*/

 The result is D:

Analysis: 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.

1. 3 explicit keyword 

 First let's look at the following code:


#include <iostream>
using namespace std;

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

void test()
{
	Date x(10);   // 直接调用构造函数
	Date a = 100; // (隐式转化int->Date类型)构造一个Date类型的临时变量 + 
	              //拷贝构造 + 优化(a的拷贝构造无法查看) —> 直接调用构造函数
}

( Note: Regarding the compiler's optimization of copying, we will talk about it later in this section) 

And this time the explicit keyword is used to modify the constructor, the function is: prohibit type conversion.

 explicit  Date(int year)
    {
        _year = year;
    }

So Date a = 100, an error is reported

Two, static members

2.1 Concept 

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 .
Usage scenario: Static modifies global variables, which can be accessed arbitrarily outside the class, so how to design a global variable that can only accessed by the class ?

 For example:  interview questions: implement a class, and calculate how many class objects are created in the program.

class A
{
public:
	A(int i)
		:_i(i)
	{
		_scout++;
	}

	static int Getscout()   // 类外无法取得static 成员, 所以需要一个类成员函数取得。
	{
		return _scout;      // 在静态区中寻找_scout
	}

	A(A& k)
	{
		_i = k._i;
		_scout++;
	}
private:
	static int _scout; // 声明。 (注意:缺省值为初始化列表提供的,而static成员是在类外定义)
	int _i = 0;
};
int A::_scout = 0;     // 初始化, A::通过类域定义----目的是突破类的限制

int main()
{
	A a(1);
	A b(2);
	A c(3);
	// 静态成员公有
	//cout << c._scout << endl;
	//cout << A::_scout << endl;     // 两种方式并非去访问类,而是为了突破类域,去静态区去寻找
	// 静态成员私有
	cout << c.Getscout() << endl;  // 这里访问成员函数,然后在静态区中寻找静态成员。
	cout << A::Getscout() << endl; // 通过类域访问static成员
}

2.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 .
3. Class static members can be accessed by class name::static member or object.static member.
4. Static member functions have no hidden this pointer , cannot access any non-static members, and can access static member variables.
5. Static members are also members of the class, restricted by public, protected, and private access qualifiers (you need to use member functions to access private static member variables if necessary ).

Three, Friends

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

3. 1 Friend function

 Function: The 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.

Features:

  • 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 a class definition, regardless of 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.

Use as follows:

class A
{
public:
	friend int func(const A& a);  // 友元声明
private:
	int _i;
};

int  func(const A& a)   // 普通函数
{
	return a._i;
}

3. 2 Friend class 

Features: 

  • 1. All member functions of a friend class can be friend functions of another class, and can access non-public members of another class.
  • 2. Friendship cannot be passed. (If C is a friend of B and B is a friend of A , it cannot be said that C is a friend of A. )
  • 3. The friendship relationship cannot be inherited, and I will give you a detailed introduction when it is inherited .
  • 4. 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, but you can't access the private member variables in the Date class in the Time class. )

Here is the practice of point 4:

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;    // 在Date中声明一个Time类,过去是无法在Time类外直接访问其私有成员。
};

3. 3 Inner classes (understand)

Java is used more, and C++ is used less, here is only for understanding.

3.31 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 . (External and internal have this equal relationship)
(Note: The inner class is a friend class of the outer class, and the outer class is not a friend class of the inner class. See the definition of the friend class. The inner class can access . But the outer class Class is not a friend of an inner class.)

Use a piece of code to test its internal and external class relationships:

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

	class Time
	{
	public:
		Time(int hour = 0, int minute = 0, int secoud = 0)
			:_hour(hour)
			, _minute(minute)
			, _secoud(secoud)
		{}

		void func(Date& _d) // 内部内类,Date天生是Time的友元类,所以可以通过对象直接访问Date其内部私有成员。
		{
			_d._year = _hour;
			_d._month = _minute;
			_d._day = _secoud;
			cout << _d._year << endl;
			cout << _d._month << endl;
			cout << _d._day << endl;
		}
	private:
		int _hour;
		int _minute;
		int _secoud;

	};

    void fundate()
	{
		cout << _t._hour;  // 向内部类直接访问私有失败
	}

private:
	int _year ;
	int _month ;
	int _day  ;
};

int main()
{
	Date::Time b;
	Date z;
	b.func(z);
	return 0;
}

characteristic:
1. The inner class can be defined as public, protected, or private in the outer class.
2. Note that the inner class can directly access the static members of the outer class without the object/class name of the outer class.
class A
{
public:

	class B 
	{
	public:
		void func()
		{
			cout << z << endl;  // 访问外部类中的静态变量
		}
	private:
		int _b = 100;
	};

private:
	int _i = 10;
	static int z;
};

int A::z = 10;
int main()
{
	A::B a;
	a.func();
}

3. sizeof( external class )= external class, has nothing to do with the inner class, the following is the verification code
class A
{
public:
	class B
	{
	public:
	private:
		int _b;
	};
private:
	int _i;
};

int main()
{
	cout << sizeof(A); //运行可知为4字节
}

Fourth, anonymous objects (understand)

 For example, let's look at the following code:

 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().Sum_Solution(10);
 return 0;
}

Five, some optimizations of the compiler for copying objects

In the process of passing parameters and returning values, generally the compiler will do some optimizations to reduce the copying of objects, which is still very useful in some scenarios.
The following is the test code of VS under the debug version :
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "构造" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "拷贝构造" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "赋值构造" << 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;
}
int main()
{
	//1. 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	//2.  传值返回
	f2();
	cout << endl;
	//3. 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	//4. 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
	//5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
	//6. 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

 1. Passing parameters by value

result: 

2. Return by value

class A
{
public:
	A()
	{
		cout << "构造" << endl;
	}

	A(const A& z)
	{
		cout << "拷贝构造" << endl;
	}

	~A()
	{
		cout << "析构" << endl;
	}
private:
	int _a = 1;
};

A func()
{
	A a;        // 1次构造
	return a;   // 1次拷贝构造
}

int main()
{
	A k = func(); // 1次拷贝构造,但是编译器会优化,把2次拷贝构造优化为1次
	return 0;

 It can be seen that 1 construction, 1 copy; but why this is the case, we explain it with the following picture:

 3. Implicit typing

result:

 

4. In an expression, continuous construction + copy construction -> optimized to a construction

 

 result:

5. In an expression, continuous copy construction + copy construction -> optimize a copy construction

 

 result:

6. In an expression, continuous copy construction + assignment overload -> cannot be optimized

 

 Summary: Code optimization is a function of the compiler. The code optimization in the release version is stronger than that in the debug version. At the same time, different compilers have different degrees of optimization.

epilogue

This section is over here, thank you friends for browsing, if you have any suggestions, welcome to comment in the comment area; if you bring some gains to your friends, please leave your likes, your likes and concerns will become bloggers The driving force of the master's creation .

おすすめ

転載: blog.csdn.net/qq_72112924/article/details/130643046