[C++] Core summary of classes and objects

Catalog of classes and objects:

1. Preliminary understanding of process-oriented and object-oriented

2. Introduction + definition of class (struct ==> class)

2.1 The difference between custom type struct and class

2.2 In what storage area are classes placed in memory?

2.3 How to define functions in classes

2.3.1 Separation of declarations and definitions (enhanced code readability, highly recommended)

2.3.2 Declaration and definition together (implicit inline function)

2.3.3 How to declare variables in a class

3. Class access qualifier + encapsulation + scope

3.1 Access qualifiers

3.2 Packaging

3.3 Class Scope

4. Instantiation of a class (the process of creating an object from a class type) + calculating the size of the class (considering memory alignment)

4.1 What is a class? What is an object?

4.2 Doubts about class size calculation

4.3 Storage method of class objects

4.4 Structure memory alignment rules (must know, often asked in interviews)

4.5 Calculation of empty class size (frequently tested in interviews)

5. Hidden this pointer

5.1 this pointer of non-static member function

5.2Characteristics of this pointer

5.3Where does this pointer exist?

5.4 Regarding the problem that this pointer is empty

6. Six default member functions of the class

6.1 Constructor (initialization and resource application within the object, not the creation of the object)

6.1.1 Feature Analysis - Automatic Generation (Feature 5)

6.1.2 Characteristic Analysis - Selection Processing

6.1.3 Feature Analysis - Default Construction (Feature 7)

6.1.4 C++11 patch - default value of member variables (declaration, not initialization)

6.2 Destructor (destruction of the object, not cleanup of resources in the object)

6.3 Copy construction (initializing an existing object to create an object)

6.3.1 What is the copy constructor?

6.3.2 How to write the copy constructor

6.3.3 Why add const to the copy construction parameters? ?

6.3.4 Deep and shallow copy (simple explanation)

6.3.5 Scenarios for calling copy construction

6.3.6 How to improve program efficiency

6.4 Operator overloading (not default member function)

6.4.1 Introduction

6.4.2 Location of operator overloaded functions

6.4.3 Characteristics of operator overloading

6.4.4 Common operator overloading

6.5 Assignment overloading (default member function)

6.5.1 Basic knowledge

6.5.2 Characteristic Analysis - Function Format

6.5.3 Feature Analysis - Overloading as member function (avoiding conflicts)

6.5.4 Characteristic Analysis - Deep and Shallow Copy

 6.6 Get address and const get address overloading (these two functions are not important, but the const member is important)

6.6.1const member function

6.6.2 Get address and reload

6.6.3 const address overloading

6.6.4 <<Stream insertion and>>Stream extraction

 Operator overloading for stream insertion and stream extraction

6.7 Summary

7. Initialization list (declaration is not enough, objects must be defined)

7.1 What declaration is an initialization list?

7.2 Small details of initializing variables

7.3 When must initialization variables be used (key points)

7.4 Suggestions on the use of initialization lists

7.5 The order in which member variables are declared in a class is the order in which they are initialized in the initialization list, regardless of their order in the initialization list.

8. express keyword

8.1 About type conversion

8.2 How to prevent implicit type conversions

9. static members

9.1 Concept

9. Static member variables and functions

9.1 Concept

Interview question: Implement a class and count how many class objects are created in the program;

9.2static member variables

9.2.1 Feature 1

9.2.2 Feature 2: In-class declaration, out-of-class initialization

9.2.3 Feature 3

 9.3static member function (a little bit like success or failure)

10. Friendly Yuan

10.1 Overloading of input and output (details are in the last item of operator overloading above)

10.2 Friend functions

11. Internal classes

12. Anonymous objects (only for laziness)

13. Compiler optimization (already mentioned in static)


1. Preliminary understanding of process-oriented and object-oriented

C language is process-oriented, focusing on the process, analyzing the steps to solve the problem, and gradually solving the problem through function calls; while C++ is based on object-oriented, focusing on objects, splitting one thing into different objects, Completed by interaction between objects. Let’s take laundry as an example

Process-oriented – solve the problem step-by-step:

Object-oriented – solve problems through interactions between objects:

Another example is our takeout system: process-oriented focuses on how customers should place orders, how merchants should prepare dishes, and how riders should deliver takeaways; while object-oriented focuses on the relationship between the four objects of customers, merchants, and riders. For example, after the customer places an order, the merchant delivers the meal, and then the rider delivers the meal, without having to worry about process-oriented issues such as how the customer places the order, how the merchant delivers the meal, and how the rider delivers the meal.


2. Introduction + definition of class (struct ==> class)

2.1 The difference between custom type struct and class

The struct in C language can only define variables, but the struct in C++ can not only define variables but also functions. It has the same effect as the class keyword. Both can define classes . Of course, this is C++'s use of struct in order to be compatible with the C language. Changes made, so the members of the class defined by struct are public by default , that is, anyone can use it, because in C language we can directly obtain all the data in the structure

Members of a class defined by class in C++ are private by default and are not restricted within the class. Variables in the class, that is, data in the class, cannot be directly obtained outside the class. This is also a major feature of object-oriented languages. Encapsulation, you can only access data through functions in the class, you cannot directly access the data in the class

//C语言
struct Student
{
	char name[20];
	char id[11];
	int weight;
	int height;
};

int main()
{
	struct Student stu1 = { "zhangsan", "2202101001", 60, 180 };
}
//C++
struct Stack
{
	//类体,由成员函数和成员变量组成
	void Init(int N = 4)
	{
		top = 0;
		capacity = 0;// 访问限定符限制的是类外面的,类里面不会被限制
	}
	void Push(int x)
	{

	}
	int* array;
	int top;
	int capacity;//C++把类看作一个整体,编译器搜索的时候会在整个类里面去搜索。C语言为了编译的效率只会向上搜索。
};//不要丢掉分号

The struct here is used in C++. It can not only define functions but also variables.

Xiaoyang's note : C++ structures directly use structName to represent classes without adding the struct keyword. However, C++ is compatible with all usages of C language structures. It is no problem to define variables using the way we used struct + structName before :

typedef struct ListNode
{
	/*struct*/ListNode* next;  //SListNode 可以直接代表这个类,所以此处可以不用加 struct
	int data;
}SL;

int main()
{
	//C语言用法 加truct
	struct ListNode* sl1;
	SL* sl2;

	//C++用法 直接结构体名字上
	ListNode* sl3;
}

 Finally, in C++, I prefer to use class instead of struct , and variables are called attributes/member variables, and functions are called member functions /member methods.

2.2 In what storage area are classes placed in memory?

a. First of all, everyone needs to know that a class is just an abstract description, and an object is a concrete thing. Just like int, double, char and other types, they are just a description, and the variables they define are concrete things. Therefore, there is no such thing as a class in the program, because it is not a concrete thing, and natural classes do not occupy memory.

b. Description information of the class. When the program is compiled, it only needs its semantics. It is not needed during runtime, because by the time of the runtime, the code has been converted into binary instructions. Binary instructions command the computer to do objects. There is no such concept as a class in the interaction between classes. Of course, if you want to use the member functions in the class, the member functions are in the code segment, so for the class alone, it is just an abstract description that facilitates programmers to write programs.

c. In a C++ program, the instructions after compilation and linking, from a macro perspective, are actually interactions between objects using interfaces and interactions between a single object and an interface. How can there be classes in this macro concept? Once the compiler understands the semantics of the class, the class is useless, so there is no need to allocate memory to it.

In general, classes are just for people to solve problems. The compiler does not know what to do, so no memory is allocated to classes.

2.3 How to define functions in classes

class className
{
	 //... 
};

The contents of the class body are called members of the class:

Variables in a class are called attributes or member variables of the class;

Functions in a class are called methods or member functions of the class

2.3.1 Separation of declarations and definitions (enhanced code readability, highly recommended)

a. If the declaration and definition are separated, the function will be treated as a normal function , a function stack frame will be established at the place of call , and stack space will be allocated .
b. When defining a function, add the class domain name before the function name.

//使用时,要指定类域
class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

Even for more modularity, you can also do this: the class declaration is placed in the .h file, and the member function definition is placed in the .cpp file (note: the class name + domain qualifier needs to be used before the member function name)

2.3.2 Declaration and definition together (implicit inline function)

class Person
{
public:
	void PrintPersonInfo() //inline void PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}
private:
	char _name[20];
	char _gender[3];
	int  _age;
};

    void PrintPersonInfo() // inline void PrintPersonInfo()

If the declaration and definition are placed in the class, the function will make an inline request to the compiler . Whether it agrees or not depends on the compiler. It is good to understand this knowledge.

2.3.3 How to declare variables in a class

It is recommended to use underline to declare member variables.

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};

3. Class access qualifier + encapsulation + scope

3.1 Access qualifiers

  • Members modified by public can be directly accessed outside the class;
  • Protected and private modified members cannot be directly accessed outside the class (protected and private are similar here);
  • The access scope starts from the position where the access qualifier appears until the next access qualifier appears;
  • If there is no access qualifier later, the scope ends at } , which is the class;
  • The default access rights of class are private and struct is public (because struct must be compatible with C);

Note: The access modifier qualifier only limits access rights outside the class, and can be accessed at will within the class; and the access qualifier is only useful at compile time. When the data is mapped to memory, there is no difference in access qualifiers.

3.2 Packaging

Organically combine data and methods of operating data, hide the properties and implementation details of objects, and only expose interfaces to interact with objects.

Encapsulation is essentially a kind of management that makes it easier for users to use classes. In the early stages of data structure, we used C language to implement the stack. Among them, the function interface for returning the top element of the stack – Top, is a good example of encapsulation. effect

3.3 Class Scope

A class defines a new scope, and all members of the class are within the scope of the class. When defining members outside a class, you need to use the :: scope operator to indicate which class scope the member belongs to.

Note: The class domain is different from the namespace domain we studied before. The namespace domain stores the definitions of variables and functions. Although functions can be defined in the class domain , for variables, they are only declared, not variables. To open up space , only objects instantiated with this class will open up space ; this is why the member variables in the structure and the class cannot be initialized directly, but the variables must be defined first.

Therefore, it is illegal to call functions in class fields, and objects must be created (operators, classes are ., structures are ->)


4. Instantiation of a class (the process of creating an object from a class type) + calculating the size of the class (considering memory alignment)

4.1 What is a class? What is an object?

A declaration is a declaration. What is written in the member variable private is called a declaration, which is to tell which family members there are.

Definition object Because the essence of a class is a drawing, a template, and a framework, it is called a definition object.

Creating an object is building a house. With classes, objects are created.

The most vivid example: if the class is a drawing, the object is a house, the member variables in the class are the members of each house, and the function is like the public fitness equipment in the community (how the function is stored will be explained in detail in 4.3 below, do not miss it)

1. A class describes an object. It is something like a model. It limits the members of the class. Defining a class does not allocate actual memory space to store it; for example: the student information form filled in when enrolling . It can be regarded as a class to describe specific student information ;

2. A class can instantiate multiple objects, and the instantiated objects occupy actual physical space and store class member variables;

Instantiating objects from a class is like using architectural design drawings to build a house in reality. A class is like a design drawing. It only designs what is needed, but there is no physical building . Similarly, a class is just a design that is instantiated. Objects can actually store data and occupy physical space.

4.2 Doubts about class size calculation

In the C language stage, we learned how to calculate the size of a structure type. For the upgraded version of the structure-class, a class can have both member variables and member functions. Then an object of a class contains What? How do we calculate the size of a class?

class A
{
public:
	void PrintA()
	// 从操作系统角度来看,不可能把所有的指令都存下来,只存函数的地址,指针大小4个字节,所以大小应该是5个字节,内存对齐就是8个字节
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	cout << sizeof(A) << endl;
}

There are even member functions in the class that need to be calculated, so the size of the class seems very complicated, but I think readers will be enlightened when they see the memory alignment of the structure. Please watch me break it down~

4.3 Storage method of class objects

a.

In order to save the space occupied by instantiated objects, we extract the member functions of each object and place them in the public code section. In this way, when using the function, each object only needs to call it in the public code section, which contains The effective addresses of all member functions of this class

b.

The instructions formed after the function is compiled are placed in the code segment by the compiler, so the compiler can easily find the location of the instruction in the code segment when calling the function, and the compiler will not Instruction confusion caused by member functions in different classes

That is, the size of a class is actually the sum of the "member variables" in the class.

verify:

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	char _a;
};

int main()
{
	cout << sizeof(A) << endl;
}

We see that the size of class A is 1, which means that only the address of the member variable _a is stored, but the address of the member function PrintA is not stored.

4.4 Structure memory alignment rules (must know, often asked in interviews)

The class size is calculated in exactly the same way as the structure size, both requiring memory alignment.

Click to enter: Structure memory alignment rules icon-default.png?t=N7T8http://t.csdn.cn/5KtHd

4.5 Calculation of empty class size (frequently tested in interviews)

What we discussed above is the size of ordinary classes. So for some special classes, such as empty classes or classes with only member functions and no member variables, what is their size? Is it 0 or some other value?

class B
{
};

class C
{
	void PrintC()
	{
		cout << "PrintC()" << endl;
	}
	void InitC()
	{
		cout << "InitC()" << endl;
	}
};

int main()
{
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;
}

First of all, we all know that the function body does not take up space, so why is the size of the empty class 1? Reason 1: Even if it is an empty class, objects can be created, so size is given. Reason 2: It’s to occupy space


5. Hidden this pointer

5.1 this pointer of non-static member function

a.
We know that a member function can be called by multiple objects, so how does the member function know which object it is currently acting on? What if 10 objects all call the same function, and the function only acts on the first object? Can't meet our needs

b.
Therefore, the C++ compiler adds a hidden this pointer to each " non-static member function " as a formal parameter of the function , and stipulates that the parameter must be at the leftmost position of the function parameter , and what is stored in this pointer is the object. address , all operations to access object member variables in the function body are completed through this pointer, but these operations are transparent to the user, the user does not need to manually pass the object address, the compiler can automatically complete (which object calls , pass the address of which object to this)

c.


We cannot pass this pointer manually . This is the job of the compiler and we cannot grab it. However, we can use this pointer inside the function body.

verify:

#include <stdio.h>
#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;//使用this指针访问到对象的成员变量_year
		_month = month;
		_day = day;
	}
	
	void Print()// 在函数体内部我们可以使用this指针。
	{
		cout << this << endl;
		cout << this->_year << "-" << this->_month << "-" << _day << endl;//我们加了this,编译器就不加了,我们不加,编译器就会加
	}
private:
	int _year;     
	int _month;    
	int _day;     
};
int main()
{
	Date d1;
	d1.Init(2023, 9, 9);

	Date d2;
	d2.Init(2023, 9, 10);

	d1.Print();//d1调用,访问的就是d1的成员
	d2.Print();//d2调用,访问的就是d2的成员

	cout << &d1 << endl;
	cout << &d2 << endl;
	
	return 0;
}

 From the result, we can see that this pointer is indeed the address of the object:

5.2Characteristics of this pointer

  • This pointer can only be used inside a "member function";
  • The this pointer is decorated with const, and const is located behind the pointer*; that is, this itself cannot be modified, but the object it points to can be modified ( we can modify the value of the member variable through the this pointer, but we cannot let this point to other objects )
  • The this pointer is essentially a formal parameter of the "member function". When the object calls the member function, the object address is passed to the this parameter as an actual parameter, so the this pointer is not stored in the object ;
  • The this pointer is the first implicit pointer parameter of the "member function". Generally, the compiler pushes it on the stack when establishing the function stack frame of the "member function" and does not require the user to actively pass it. (Note: Since this pointer needs to be called frequently in member functions, VS has optimized it and passed it through the ecx register by the compiler)

Three ways to write const and pointers:
const Date* p1;

Date const* p2;
The above two ways of writing are equivalent. const is placed to the left of the asterisk , and what is modified is * p1 and * p2, which are the objects pointed to by the pointer.

Date* const p3; // The following writing method modifies p3,const is placed on the right side of *, and the pointer variable p3 itself is modified.

Our this pointer uses the second method, because the pointer of this pointer cannot be changed.

Understood from another perspective: const is understood to be right-compatible, and it protects the first thing that appears on the right.

5.3Where does this pointer exist?

Our first reaction is to store it in the object, so the question arises, when we calculate the size of the class, do we also calculate the this pointer? Explain that this pointer must not be stored in the object

Answer: As a function parameter, this pointer exists in the stack frame of the function, and the function stack frame opens up space on the stack area, so this pointer exists on the stack area; however, the VS compiler has optimized this pointer and uses The ecx register saves this pointer

Xiaoyang's note: If the member function is treated as inline, this will not exist, and there will be no question of where it exists. That is to say, the calling position is directly expanded, such as the internal _str, which is directly replaced with s._str , so that you can access it without this pointer

5.4 Regarding the problem that this pointer is empty

The this pointer can be null when passed as a parameter, but if a null this pointer is used in a member function, it will cause a dereference to the null pointer.

//下面两段程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A  //程序1
{
public:
    void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}
//***********************************//
class A  //程序2
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

Answer: Program 1 runs normally. Here’s why:

First, although we use the null pointer A to access the member function Print, since the member function does not exist in the object, but in the code segment, the compiler will not access the member function through the class object p, that is, p will not be dereferenced;

Second, when the object is a pointer type, the compiler will directly pass the pointer as a formal parameter to the this pointer of the Print function, and this can be null when passed as a parameter. We do not use this inside the Print function. pointer dereference

Program 2 crashes. Here’s why:

Although Program 2 can run normally at p->Print, within the Print function, _a will be converted to this->_a, and a null pointer dereference occurs. This is passed by p, and p is empty, so there is a null pointer. pointer dereference


6. Six default member functions of the class

We mentioned above that the type occupies one byte of empty class. Is there nothing in the empty class? Or does he have it but we can't see it?


In fact, there is something in the empty class. It has 6 member functions generated by the compiler by default. If we do not take the initiative to write the default member functions, the compiler will automatically generate them.

When using C language to practice elementary data structures, such as linear lists, linked lists, stacks, queues, binary trees, sorting, etc., you may often make two mistakes, especially the second mistake, which can be said to be very common:

  1. When using a data structure to create a variable, forget to initialize it and directly perform operations such as insertion;
  2. Forget to release the dynamically opened space after use and return directly;

C++ grew up on the basis of C language - correcting some deficiencies in C language and adding object-oriented ideas; facing the above problems of C language, C++ designed default members

Default member function: When the user does not implement it explicitly, the member function automatically generated by the compiler is called the default member function

6.1 Constructor (initialization and resource application within the object, not the creation of the object)

The constructor is a special member function. It should be noted that although the name of the constructor is called constructor, the task of the constructor is not to create the object, but to complete the initialization of the object after the object is created; at the same time, the constructor cannot be called by the user . , but is automatically called by the compiler when creating a class type object , and is only called once during the entire life cycle of the object

The constructor has the following characteristics:

  1. The function name is the same as the class name;
  2. No return value;
  3. The compiler automatically calls the corresponding constructor when the object is instantiated;
  4. The constructor supports overloading and default parameters;
  5. If there is no explicitly defined constructor in the class, the C++ compiler will automatically generate a parameterless default constructor, but once the user explicitly defines it, the compiler will no longer automatically generate it;
  6. The constructor does not process built-in types and calls its own default constructor for custom types;
  7. Both the parameterless constructor and the fully default constructor are called default constructors, and there can only be one default constructor (easy to get confused)

6.1.1 Feature Analysis - Automatic Generation (Feature 5)

If there is no explicitly defined constructor in the class, the C++ compiler will automatically generate a parameterless default constructor, but once the user explicitly defines it, the compiler will no longer automatically generate it. Now let us verify:

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

It can be seen that in the above date class, we did not explicitly implement the constructor ourselves, so the compiler should generate a default constructor without parameters to complete the initialization work:

But we found a problem. The default constructor does not seem to have completed the initialization work, that is, _year, _month, _day in the d1 object are still random values. Is it because the default constructor generated by the compiler here is of no use? To answer this question we need the sixth characteristic of the constructor

6.1.2 Characteristic Analysis - Selection Processing

 The sixth characteristic of the constructor is as follows: the constructor does not process built-in types and calls its own default constructor for custom types.

For this feature, we use the three classes Date, Stack and Myqueue to compare and understand:

(Myqueue is 232. Use stack to implement queue - LeetCode )

Note: When debugging in vscode, you must break the point first and then debug.

Date:

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

Stack: 

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail\n");
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;

		cout << "Stack 构造" << endl;
	}

	void Push(int x)
	{
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

Queue:

class MyQueue
{
public:
	void Push(int x)
	{
		_pushST.Push(x);
	}

	Stack _pushST;
	Stack _popST;
};

Analysis: 1. Stack’s member variables are all built-in types, so when we do not explicitly define a constructor, the compiler automatically generates a default constructor, but the default-generated constructor does not process built-in types, so here What we see are random values; the same is true for the Date class, that is, the constructor automatically generated by the compiler does not meet our needs, so we need to manually define the constructor

        2. For MyQueue, all its member variables are custom types, so even if we do not provide a constructor, the constructor automatically generated by the compiler will call the default constructor of the custom type to meet the needs.

        3. So, when do we need to provide our own constructor, and when do we use the constructor generated by the compiler by default? Could it be that all the built-in types are defined by myself, and all the custom types are generated by default? The answer is : demand-oriented – when the constructor generated by the compiler by default can meet our needs, we do not need to provide our own constructor, such as MyQueue; when the constructor provided by the compiler cannot meet our needs, we need Define it yourself, such as Date/Stack;

6.1.3 Feature Analysis - Default Construction (Feature 7)

There are two things to note here:

1. Although the constructor supports overloading and default parameters, parameterless construction and parameterized full default construction cannot appear at the same time, because ambiguity will occur when calling (note, I am talking about full default here)

At the same time, when there are multiple parameters, many overloads can be formed, making the constructor very redundant , so generally we will only explicitly define a fully default constructor , because this one can be constructed and represented Many parameter situations;

2. When we call the no-argument constructor or the all-default constructor to initialize an object, do not put parentheses after the object. This will make the compiler unclear whether it is instantiating the object or declaring a function (be sure to pay attention)

 Okay, now let us continue to explain feature 7:
The seventh feature of the constructor is as follows: the parameterless constructor and the fully default constructor are both called default constructors, and there can only be one default constructor.

The meaning of the above sentence is that when we instantiate an object using no-parameter method, the compiler will automatically call the default constructor of the object, and there are three default constructors: the no-parameter constructor automatically provided by the compiler, Explicitly defined no-argument constructor, explicitly defined all-default constructor

If there is no default constructor in the class, then we must pass parameters when instantiating the object:

Date d1(2023,9,28); //This way it can be compiled

6.1.4 C++11 patch - default value of member variables (declaration, not initialization)

Essentially: it is to make up for the loophole that the default constructor generated by the compiler is useless (I create objects and do not write constructors. Indeed, the compiler has its own constructor, but it has not been initialized when I opened it. This is nonsense)

After the above study, we found that the automatically generated default constructor does not handle built-in types. The characteristics of custom types that need to be processed make the constructor very complicated, because general classes have built-in type member variables that need to be initialized. This This makes the constructor generated by the compiler by default seem to have no effect.

In C++11, a patch has been made for the defect that built-in type members are not initialized, that is: built-in type member variables can be given default values ​​when they are declared in a class; the default value means that if the constructor does not initialize the variable Initialized, the variable will use the default value:

Summary: Giving default values ​​to member variables here does not mean initializing them, because the member variables in the class are only declared. Only after the object is instantiated does it have physical space to store data; and the default dynamic memory does not It's hard to understand. It's equivalent to me designing a drawing of a house. I know how big a certain room is, so I can mark it on the drawing. When the house is actually built, the size can be given according to the mark.


6.2 Destructor (destruction of the object, not cleanup of resources in the object)

characteristic:

  1. The destructor name is preceded by the character ~ before the class name.
  2. No parameters and no return value type.
  3. A class can only have one destructor. If not explicitly defined, the system will automatically generate a default destructor. Note: Destructors cannot be overloaded .
  4. At the end of the object's life cycle, that is, when the object is about to be destroyed, the object's life will generally end as the stack frame is destroyed. At this time, the C++ compilation system will automatically call the destructor.

a. The destructor generated by the compiler by default will not process built-in types . At the end of the object's life, the operating system will automatically reclaim the memory of the built-in type. However, for custom types, the destructor generated by the compiler by default will be called. The destructor of this class type .

b. It is worth noting that since the default destructor generated by the compiler does not process built-in types, this also lays the hidden danger for its functional defects. For example, what about the space on the heap opened by malloc in the built-in type? In this case, continuing to rely on the default destructor generated by the compiler obviously cannot satisfy the resource cleanup work. At this time, we need to manually return the requested space to the operating system. For example, the destructor of the stack class needs to be written by ourselves, and its constructor also needs to be written by ourselves, because the default constructor provided by the compiler cannot meet our requirements, so whether to write a destructor or not is still based on demand. a question! !

~Stack()
	{
		free(_array);
		_array = nullptr;
		_top = _capacity = 0;
	}

6.3 Copy construction (initializing an existing object to create an object)

characteristic:

  1. The copy constructor is an overloaded form of the constructor. When we instantiate an object using copy construction, the compiler no longer calls the constructor.
  2. There is only one parameter to the copy constructor and it must be a reference to a class type object . If you use the pass-by-value method, the compiler will directly report an error because it will cause infinite recursive calls.
  3. If not explicitly defined, the compiler will generate a default copy constructor
  4. The default copy constructor directly copies built-in types in bytes - shallow copy, calling its own copy constructor for custom types

6.3.1 What is the copy constructor?

It is actually an overloaded form of the constructor

6.3.2 How to write the copy constructor

There is only one parameter to the copy constructor and it must be a reference to a class type object . If you use the pass-by-value method, the compiler will directly report an error because it will cause infinite recursive calls.

 Date(const Date d)   // 错误写法:编译报错,会引发无穷递归
 {
	 _year = d._year;
	 _month = d._month;
	 _day = d._day;
 }

 The reason why infinite calls will occur when passing value as a formal parameter of copy construction is as follows:

There is a potential knowledge blind spot here, that is, passing by value will be copied by value , and the temporary variables generated will use copy construction. In general: passing by value will have an impact on the following, but passing by reference will be integrated. There will be no impact

The same is true in recursion. In some places, you need to pass a reference or pointer, which is the same. In some places, you need to pass a copy. Changes in the next layer will not affect the upper layer. This is the logic in practice. You need to wait until you do more questions and write more codes to realize it.

 The picture below can only be seen clearly if you enlarge the web page:

6.3.3 Why add const to the copy construction parameters? ?

The parameters of the copy constructor are usually modified with const . This is to avoid copy errors inside the function, similar to the following:

6.3.4 Deep and shallow copy (simple explanation)

If not explicitly defined, the compiler will generate a default copy constructor. The default copy constructor function copies the object in byte order according to the memory storage . This type of copy is called shallow copy, or value copy.

Why does deep copy exist? ?

In the Stack class: There are pointers in its built-in types. Once copy construction occurs, the pointers in the two objects point to the same space (because the default space will not be opened) , then the lives of the two objects end. The destructor called at this time will release the space pointed to by the same pointer twice. The address released the second time is an invalid address. This address does not point to a valid space at all, and naturally an error will occur in the program.

 Summarize:

If there is no resource application in the class, you do not need to manually implement the copy constructor, just use the one automatically generated by the compiler; if there is a resource application in the class, you need to define the copy constructor yourself, otherwise shallow copies and the same space may occur The situation of being destructed multiple times;

In fact, copy constructors and function destructors are very similar in terms of resource management. It can be understood that if you need to write a destructor, you need to write a copy constructor, and if you don’t need to write a destructor, you don’t need to write a copy constructor.

6.3.5 Scenarios for calling copy construction

  • Create a new object using an existing object
  • The function parameter type is a class type object
  • The function return value type is a class type object

6.3.6 How to improve program efficiency

In order to improve program efficiency, generally try to use reference types when transferring parameters to objects. When returning, use references as much as possible according to the actual scenario.


6.4 Operator overloading (not default member function)

6.4.1 Introduction

In order to enhance the readability of the code , C++ introduces operator overloading for custom types. Operator overloading is a function with a special function name – its function -  named keyword operator + the operator symbol that needs to be overloaded, also has its return value type , the function name and parameter list, its return value type and parameter list are similar to ordinary functions; in other words, operator overloaded functions only have a special function name, and are the same as ordinary functions in other aspects.

void operator+=(Date& d, int day)
{
	d._day += day;
	while (d._day > GetMonthDay(d._year, d._month))
	{
		d._day -= GetMonthDay(d._year, d._month);
		d._month++;

		if (d._month > 12)
		{
			d._month -= 12;
			d._year++;
		}
	}
}

6.4.2 Location of operator overloaded functions

If you actually write our AddDay and operator+= functions above, you will find a problem: the member functions _year, _month, and _day in the class are all private, and we cannot modify them directly outside the class;

But we cannot directly make member variables shared, so the encapsulation line of the class cannot be guaranteed; so what if we put the function inside the class?

 The above situation is caused by the this pointer we mentioned in the fifth point above: the first parameter of each member function of the class is a hidden this pointer , which points to a specific object of the class. , and this cannot be passed or written out explicitly, but it can be used explicitly inside the function.

In other words, originally the += operator can only have two operands, so the function obtained by overloading += using the operator can only have two parameters; however, in order to use the member variables of the class, we put the function in Inside the class, the compiler automatically passed the address of the object and used a this pointer to receive it in the function, causing the function parameters to become three; so the error "operator += has too many parameters" appeared.

So in order to solve this problem, when we define the operator+= function, we only explicitly pass one parameter - the right operand , and the left operand is automatically passed by the compiler; when we need to operate the left operand inside the function, we also Just operate this pointer directly

	void operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;

		if (_month > 12)
		{
			_month -= 12;
			_year++;
		}
	}
}

The total code is as follows:

#include<iostream>
using namespace std;
class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
	 _year = year;
	 _month = month;
	 _day = day;
 }
int GetMonthDay(int year,int month)
{
	static int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
	if((month==2) && ((year%4==0 && year%100!=0) || (year%400==0)))
	return 29;
	
	return day[month];
}
void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	void operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;

		if (_month > 12)
		{
			_month -= 12;
			_year++;
		}
	}
}
private:
	 int _year;
	 int _month;
	 int _day;
};

int main()
{
	 Date d1;
	 d1+=100;
	 d1.Print();
	 return 0;
}

Notice

1. When we place a function inside a class, no matter how many operands there are, this points to the first operand by default; 

2. The problem of not being able to access private member variables of a class from outside the class can actually be solved using friends, which we will learn about later;

6.4.3 Characteristics of operator overloading

  • New operators cannot be created by concatenating other symbols: such as operator@;
  • Overloaded operators must have a class type parameter (because operator overloading can only be used for custom types);
  • The meaning of operators used for built-in types cannot be changed, that is, operator overloading cannot be used for built-in types;
  • When overloaded as a member function of a class, its formal parameters appear to be 1 less than the number of operands because the first parameter of the member function is hidden this;
  • The following 5 operators cannot be overloaded: .* :: sizeof . ?: Note that this often appears in multiple-choice questions in written exams, especially the .* operator. I hope everyone remembers that it is . * and not *

6.4.4 Common operator overloading

 Common operator overloads include: operator+ (+), operator- (-), operator* (*), operator/(/), operator+= (+=), operator-= (-=), operator== (== ), operator= (=), operator> (>), operator< (<), operator>= (>=), operator<= (<=), operator!= (!=), operator++ (++), operator-- (–) etc.

Among them, there are some differences between operator++ and operator-- because ++ and - are divided into prefix and postfix. Although both can make variables increase by 1, their return values ​​are different; but because ++ and – have only one operand, and this operand will be automatically passed by the compiler; so normal operator++ and operator-- cannot distinguish between the two; ultimately, C++ stipulates: post-fixed ++/– overloading When adding an additional parameter of type int, this parameter is not passed when calling the function and is automatically passed by the compiler;

Secondly, operator= in the above overloaded function is one of the default member functions – assignment overloaded function


6.5 Assignment overloading (default member function)

6.5.1 Basic knowledge

The assignment overloaded function is one of the six default member functions of C++. It is also a type of operator overloading. Its function is to assign values ​​between two existing objects. Its characteristics are as follows:

  1. Format specifications for assignment overloading;
  2. The assignment operator can only be overloaded as a member function of the class and cannot be overloaded as a global function ;
  3. If not explicitly defined, the compiler will generate a default assignment overloaded function;
  4. The default assignment overloaded function directly copies the built-in type in bytes - shallow copy, calling its own assignment overloaded function for the custom type;

Among them, assignment overloading and copy construction have a very disgusting little problem:

d1=d2; is a copy because it is two objects that already exist.

Date d3 (d2) is initialized because one exists and one does not exist

So, is Date d1=d2 copy construction or assignment overloading? The answer is copy construction , because the d1 object is created

6.5.2 Characteristic Analysis - Function Format

The format of assignment overloaded functions generally has the following requirements:

Tip1: Use references as parameters and modify them with const

We know that when using parameters by value, the function parameters are a temporary copy of the actual parameters, so passing parameters by value will call the copy constructor; when using references as parameters, the formal parameters are aliases of the actual parameters , thus reducing Calling copy construction consumes time and space; in addition, assignment overloading will only change the assigned object, not the assigning object , so we use const to prevent misoperations inside the function

void operator=(const Date& d);

 Tip2: Use references as return values ​​and the return value is *this

We can perform continuous assignments d1=d2=d3 to built-in types, so we need to impose certain constraints and restrictions on the return value of the function.

At the same time, since our object is created by a class, when the assignment and overloading function is completed, the object still exists, so the return value can be directly referenced to improve efficiency (the object is not opened in the function space, but the scope is in the class)

In addition, we generally use the left operand as the return value of the function, which is the object pointed to by this pointer. To sum up, we need to return *this

Date& operator=(const Date& d);
{
    //...
    return *this;
}

Tip3: Check whether you assign a value to yourself

The following situation may occur when users call member functions: Date d1; Date& d2 = d1; d1 = d2; This situation is not a big problem for objects that only need shallow copies, but for resource applications, For objects that need to be deep copied, uncontrollable things will happen.

if(this == &d)  //比较两个对象的地址是否相同
	return *this;

In summary:

//赋值重载
Date& operator=(const Date& d)
{
    //自我赋值
    if (this == &d)  
    {
        return *this;
    }

    _year = d._year;
    _month = d._month;
    _day = d._day;

    return *this;
}

6.5.3 Feature Analysis - Overloading as member function (avoiding conflicts)

 The assignment operator can only be overloaded into a class member function and cannot be overloaded into a global function . This is because the assignment overloaded function is one of the six default member functions. If we do not show the implementation, the compiler will generate it by default; at this time, the user If you implement a global assignment operator overload outside the class, it will conflict with the default assignment operator overload generated by the compiler in the class, causing a link error.

 

6.5.4 Characteristic Analysis - Deep and Shallow Copy

 The characteristics of the assignment overloaded function are very similar to the copy constructor –  if we do not explicitly define the assignment overload, the compiler will automatically generate an assignment overload, and the automatically generated function will directly copy the built-in type in bytes. , the custom type will call its own assignment overloaded function

The situation here is very similar to the situation of Stack's default destructor, but it is more serious - the automatically generated assignment overloaded function performs a shallow copy, making st1._a and st2._a point to the same space, while st1 and st2 objects When destroyed, the compiler will automatically call the destructor, causing the space pointed by st2._a to be destructed twice; at the same time, the space originally pointed by st1._a has not been released, so a memory leak has occurred.

 Summarize:

The automatically generated assignment overloaded function handles member variables in the same way as the destructor - built-in types are copied by value in byte mode, and custom types are called with their own assignment overloaded functions; we can understand it as: you need to write A class with a destructor needs to write an assignment and overloaded function. A class that does not need a destructor does not need to write an assignment and overloaded function.


 6.6 Get address and const get address overloading (these two functions are not important, but the const member is important)

6.6.1 const member functions

We call  the const-modified "member function" a const member function . The const-modified class member function actually modifies the this pointer implicit in the member function , indicating that any member variable in the class pointed to by this cannot be modified in the member function. to modify

When we define a read-only Date object, the compiler will report an error when we call the member function Print of d.

The reason is that the first parameter of the class member function is the this pointer by default, and the this pointer is Date* const this (this is restricted, but Date* is not restricted) , and the type of our first parameter, d, is const Date* (restricted type, indicating read-only, not writable) ; when assigning a read-only variable to a read-write variable, the permissions are expanded , causing the compiler to report an error

In order to solve the above problem, C++ allows us to define const member functions, that is, use const modification at the end of the function. The const only modifies the first parameter of the function , that is, the type of this pointer becomes  const Date* const this; the function's Other parameters are not affected

Therefore, when we implement a class, if we do not need to change the first parameter of the member function of the class, that is, do not change *this, then we should use const to modify this pointer so that the const object of the class can be used in other ( member) function can also call this function

In general: As long as you don’t want the content of the *this member to change, you can add const after the function.

think:

  • Can a const object call non-const member functions? -- No, the authority is expanded;
  • Can non-const objects call const member functions? -- Yes, permissions are reduced;
  • Can other non-const member functions be called within a const member function? -- No, the authority is expanded;
  • Can other const member functions be called within a non-const member function? -- Yes, permissions are reduced;

6.6.2 Get address and reload

The address overloaded function is one of the six default member functions of C++. It is also a type of operator overloading. Its function is to return the address of the object.

Date* operator&()
{
    return this;
}

6.6.3 const address overloading

const address overloading is also one of the six default member functions of C++. It is an overloaded function of address overloading, and its function is to return the address of the const object.

const Date* operator&() const
{
    return this;
}

scenes to be used:

In some rare special cases, we need to implement the address overloading and const address overloading functions ourselves. For example, if we are not allowed to obtain the address of the object, then we can directly return nullptr inside the function:

//取地址重载
Date* operator&()
{
    return nullptr;
}

//const 取地址重载
const Date* operator&() const
{
    return nullptr;
}

6.6.4 <<Stream insertion and>>Stream extraction

 Operator overloading for stream insertion and stream extraction


6.7 Summary

There are six default member functions in C++ classes - construction, destruction, copy construction, assignment overloading, address overloading, const address overloading. The first four functions are very important and very complex. We need to according to the specific requirements. The situation determines whether explicit definition is needed, and the last two functions usually do not need to be explicitly defined, just use the ones generated by the compiler by default.

Constructor:

  • The constructor completes the initialization of the object and is automatically called by the compiler when instantiating the object;
  • Default constructor refers to a constructor that does not require passing parameters. There are three types - automatically generated by the compiler, explicitly defined and without parameters, explicitly defined and completely defaulted;
  • If the user explicitly defines the constructor, the compiler will initialize according to the contents of the constructor. If the user does not explicitly define the constructor, the compiler will call the default-generated constructor;
  • The constructor generated by default does not process built-in types, and for custom types, the default constructor of the custom type will be called;
  • In order to make up for the defect that the constructor does not handle built-in types, C++11 has made a patch - allowing default values ​​to be given where member variables are declared; if the constructor does not initialize the variable, the variable will be initialized as default value;
  • The constructor also has an initialization list. The existence of the initialization list is of great significance. We will explain the specific content in the next heading.

Destructor:

  • The destructor completes the cleanup of resources in the object and is automatically called by the compiler when the object is destroyed;
  • If the user explicitly defines the destructor, the compiler will destruct it based on the contents of the destructor; if the user does not explicitly define it, the compiler will call the default generated destructor;
  • The destructor generated by default does not process built-in types, and will call the destructor of the custom type for custom types;
  • If there are resource applications in the class, such as dynamically opening space and opening files, then we need to explicitly define the destructor;

Copy construction:

  • The copy constructor uses an existing object to initialize another object that is being instantiated, and is automatically called by the compiler when instantiating the object;
  • The parameters of the copy constructor must be reference types, otherwise the compiler will report an error - value transfer will cause infinite recursion of the copy constructor;
  • If the user explicitly defines the copy constructor, the compiler will copy according to the contents of the copy constructor; if the user does not explicitly define it, the compiler will call the default generated copy constructor;
  • The copy constructor generated by default completes value copy (shallow copy) for built-in types, and for custom types, the copy constructor of the custom type will be called;
  • When there is dynamic allocation of space in the class, direct value copying will cause the two pointers to point to the same dynamic memory, causing the same space to be destructed twice when the object is destroyed; so in this case we need to explicitly define it ourselves The copy constructor completes deep copy;

Operator overloading:

  • Operator overloading is a syntax introduced by C++ to enhance the readability of the code. It can only be used for custom types. Its function name is the operator keyword plus the relevant operator;
  • Since the operator overloaded function usually accesses the member variables of the class, we generally define it as a member function of the class; at the same time, because one parameter of the member function of the class is the hidden this pointer, it seems to have one less parameter;
  • Function overloading can also be formed between overloaded functions of the same operator, such as operator++ and operator++(int);

Assignment overloading:

  • The assignment overloaded function assigns data in an existing object to another existing object. Note that it is not initialized and needs to be explicitly called by yourself; it is a type of operator overloading;
  • If the user explicitly defines the assignment overloaded function, the compiler will assign the value based on the content of the assignment overloaded function; if the user does not explicitly define it, the compiler will call the default generated assignment overloaded function;
  • The assignment overloaded function generated by default completes value copy (shallow copy) for built-in types, and for custom types, the assignment overloaded function of the custom type will be called;
  • Like the copy constructor, the assignment overloaded function also has the problem of deep and shallow copies, and its difference from the copy constructor is that it is also likely to cause memory leaks; so when there is dynamic space opening in the class, we need to do it ourselves Explicitly define assignment overloaded functions to release the original space and complete deep copies;
  • In order to improve function efficiency and protect objects, references are usually used as parameters and modified with const; at the same time, in order to satisfy continuous assignment, references are usually used as return values, and the left operand is generally returned, that is, *this;
  • The assignment overload function must be defined as a member function of the class, otherwise the assignment overload generated by the compiler by default will conflict with the custom assignment overload outside the class;

const member function:

  • Due to the problems of permission expansion, reduction and translation when passing parameters by pointers and references, const type objects cannot call member functions, because the this pointer of the member function is non-const by default, and there is a problem of permission expansion when passing parameters between the two. ;
  • At the same time, in order to improve function efficiency and protect the object, we generally modify the second parameter of the member function with const, which means that the object cannot call other member functions within the member function;
  • In order to solve this problem, C++ designed the const member function - add const modification at the end of the function. The const only modifies the this pointer and does not modify other parameters of the function;
  • So if we design a class, as long as the member function does not change the first object, we recommend using const modification in the end;

Address overloading and const address overloading:

  • Address overloading and const Address overloading is to obtain the address of an object/a read-only object and needs to be explicitly called by yourself; they belong to operator overloading, and they also constitute function overloading;
  • In most cases, we will not explicitly implement these two functions, and can use the ones generated by the compiler by default; only in rare cases we need to define them ourselves, such as to prevent users from obtaining the address of an object;

7. Initialization list (declaration is not enough, objects must be defined)

A declaration is a declaration. What is written in the member variable private is called a declaration, which is to tell which family members there are.

Definition object Because the essence of a class is a drawing, a template, and a framework, it is called a definition object.

Creating an object is building a house. With classes, objects are created.

7.1 What declaration is an initialization list?

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

The constructor function body executes the assignment statement, and the member variables can only be defined and initialized in the initialization list.

class Date
{
public:
	Date(int year, int month, int day)
		 : _year(year)// 初始化列表
	     , _month(month)
	     , _day(day)
	 {}
private:
	int _year;
	int _month;
	int _day;
};

7.2 Small details of initializing variables

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

And you cannot use = ; only parentheses can be used

7.3 When must initialization variables be used (key points)

  • const modification
  • Reference member variables
  • When there is no appropriate default constructor in a custom type class, it must be initialized in the initialization list.

Member variables cannot be assigned initial values ​​through the constructor.

Reason: For the const modification, the reasons why it must be used are similar to the reasons why references must be used, because both must be initialized when they are defined, and they cannot be modified in the place they are initialized . The most straightforward definition of an initialization list is: defining objects, so these two cannot be assigned values, and an initialization list must be used; for custom types that do not have a default constructor, we must also initialize them at the initialization list. Otherwise the compiler will report an error.

7.4 Suggestions on the use of initialization lists

In the previous C++11, we made a patch called based on default values, which is actually initialized through the initialization list (even if it is not written, it will go)

In other words, no matter whether we use an initialization list or not, the member variables of the class will first be initialized using the initialization list.

Since he has to leave every time, I will try my best not to write assignment statements in the function body in the future, and just write the initialization list directly.

7.5 The order in which member variables are declared in a class is the order in which they are initialized in the initialization list, regardless of their order in the initialization list.

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}

private:
	int _a2;
	int _a1;
};

Since _a2 is declared before _a1 in the class, the _a2(_a1) statement is executed first in the initialization list, and _a1 is still a random value at this time, so _a2 eventually outputs a random value.


8. express keyword

8.1 About type conversion

The constructor can not only construct and initialize objects, but also has the function of type conversion for a single parameter or a constructor that has default values ​​except for the first parameter. Single parameters have been supported since C++98, and multiple parameters have only been supported since C++11.

8.2 How to prevent implicit type conversions

Once the constructor is modified with explicit, implicit conversion of the constructor will be prohibited.

This does not seem to be of much use, but it is actually very necessary in projects. For example, if I compare the size of the char type, I pass two parameters, but if I write a function with int parameters, it will convert 1.1 and 1.2 It turns into two numbers of equal size, but no error is reported for this big problem of implicit type conversion, which is very fatal. So the meaning of this keyword is to report an error when unwanted implicit type conversion occurs .


9. static members

9.1 Concept

  1. The fundamental reason why a static function cannot access non-static members is because it does not have this pointer , because the function calls in the object and the access of member variables are actually done through the invisible this pointer. You do not have this pointer now. Of course, these non-static members cannot be accessed.
  2. Class static members can be accessed using class name::static member or object.static member (the first call with class name is easy to understand, but for the second one it is because of the object. The compiler will find the class where the object is located . thus finding the static variable)
  3. Static members are shared by all class objects, do not belong to a specific object, and are stored in the static area.
  4. Static member variables must be defined outside the class. The static keyword is not added when defining. It is just declared in the class.
  5. Global static, local static, class static, their life cycles are all global, but their scopes are different.

Non-static member functions can access static member functions/members; static member functions cannot access non-static member functions/members, but can only access static member functions/variables (a feeling of success and failure)

9. Static member variables and functions

9.1 Concept

  1. The fundamental reason why a static function cannot access non-static members is because it does not have this pointer , because the function calls in the object and the access of member variables are actually done through the invisible this pointer. You do not have this pointer now. Of course, these non-static members cannot be accessed.
  2. Class static members can be accessed using class name::static member or object.static member (the first call with class name is easy to understand, but for the second one it is because of the object. The compiler will find the class where the object is located . thus finding the static variable)
  3. Static members are shared by all class objects, do not belong to a specific object, and are stored in the static area.
  4. Static member variables must be defined outside the class. The static keyword is not added when defining. It is just declared in the class.
  5. Global static, local static, class static, their life cycles are all global, but their scopes are different.

Non-static member functions can access static member functions/members; static member functions cannot access non-static member functions/members, but can only access static member functions/variables (a feeling of success and failure)

Class members declared as static are called static members of the class . Member variables modified with static are called static member variables, and member functions modified with static are called static member functions . Let’s use an interview question to elicit Relevant knowledge points about static members of classes;

Interview question: Implement a class and count how many class objects are created in the program;

We know that when a class creates an object, it will definitely call the constructor or copy constructor, so we only need to define a global variable, and then let it increment in the constructor and copy constructor, as follows:

int N = 0;
class A
{
public:
	A(int i = 0)
		:_i(i)
	{
		N++;
	}

	A(const A& a)
	{
		_i = a._i;
		N++;
	}

private:
	int _i;
};

 

Although using global variables can achieve our goals very easily, we do not recommend using global variables because global variables can be modified by anyone and are very unsafe; so we need to use another safer method – static members. variable

9.2static member variables

Static member variables refer to member variables modified with the static keyword. Its characteristics are as follows:

  • Static members are shared by all class objects, do not belong to a specific object, and are stored in the static area;
  • Static member variables must be defined outside the class. The static keyword is not added when defining, and it is just declared in the class;
  • Access to static member variables is subject to class domain and access qualifiers;

9.2.1 Feature 1

Since the static member variable opens up space in the static area (data segment) and is not in the object, it does not belong to a single object, but is shared by all objects.

class A
{
public:
	A(int m = 0)
		:_m(m)
	{}

public:
	int _m;
	static int _n;
};

int A::_n = 0;

It can be seen that when we set the static member variables of the class to public and define and initialize them outside the class, we can access them directly through the class name + domain qualifier or through a null pointer object, which shows that _n does not exists inside the object

9.2.2 Feature 2: In-class declaration, out-of-class initialization

Static member variables of a class are only declared in the class. They must be defined outside the class and the class domain needs to be specified when defining. They are not defined and initialized in the initialization list because creating a new object will not change its value. Static variables must be placed In the static storage area, it is separated from the object, so it must be created first, and then directly referenced when creating the object, so that each object can be accessed and the class name can be accessed (if you really can’t understand it, you can understand it as language feature, doing so is illegal)

9.2.3 Feature 3

Access to static member variables is subject to the class domain and access qualifier (my destiny is given by the static area, but I am controlled by the class)

Static member variables are not much different from ordinary member variables when accessed. They are also subject to the class domain and access qualifier, but because they do not exist in the object, we can directly access them through A::;

Note: It can be seen that static member variables are only restricted by the class domain when they are defined and declared, and are not restricted by the access qualifier. This is a special case. Just remember it.

So the function we created the global variable before can use static members:

class A
{
public:
	A(int i = 0)
		:_i(i)
	{
		_n++;
	}

	A(const A& a)
	{
		_i = a._i;
		_n++;
	}

private:
	int _i;
	static int _n;
};

int A::_n = 0;

But now there is a new problem: in order to ensure the encapsulation of the class, we need to set the member variables to private, but this prevents us from getting them outside the class, so what should we do? To address this problem, C++ designed static member functions

 9.3static member function (a little bit like success or failure)

Static member functions refer to member functions modified with the static keyword. Its characteristics are as follows:

  • Static member functions have no hidden this pointer and cannot access any non-static members.
  • Static members are also members of the class and are also subject to the class domain and access qualifiers.

Since static member functions do not have a hidden this pointer, we naturally do not need to pass the address of the object when calling, that is, we can call it directly through the class name + domain qualifier without creating an object; but accordingly, Without this pointer, we cannot call non-static member variables and member functions, because non-static member variables need to instantiate objects to open up space, and calls to non-static member functions need to pass the address of the object.

static int GetN()
	{
		return _n;
	}

 

Note: Although static member functions cannot call non-static members, non-static member functions can call static members (the compiler does not pass the object address when calling static members)

Finally, let us do an exercise related to static members: Find 1+2+3+…+n   to detect and detect yourself!


10. Friendly Yuan

10.1 Overloading of input and output (details are in the last item of operator overloading above)

In C++, we use cin and cout with the stream insertion >> and stream extraction << symbols to complete data input and output, and they can automatically recognize built-in types;

So how do they input and output data and automatically identify built-in types? The answer is operator overloading and function overloading;

 

It can be seen that cin and cout are two global objects of the istream and ostream classes respectively. The stream extraction operator >> is overloaded in the istream class, and the stream insertion operator << is overloaded in the osteam class, so cin and The cout object can complete the input and output of data; at the same time, istream and ostream also perform function overloading when overloading operators, so they can automatically identify the data type.

Then, for our own custom types, we can also overload operators << and >> to support input and output data; let's take Date as an example:

But there is a problem here: if the operator is overloaded as a member function of the class , then the left operand of the operator must be an object of this class, because the type of this pointer is the type of this class, that is, if we want to use << >> Overloaded as a member function of the class, then the object d of this class must be the operand, that is, we must
call it

But this obviously goes against our original intention - the purpose of our operator overloading is to improve the readability of the program , and the above is likely to cause a lot of trouble in the use of the function; so for << >> we can only re- loaded as global function

But overloading as a global function will cause a new problem - the private data of the class cannot be accessed outside the class; but it is impossible for us to change the private data of the class to be public. This is too costly, so is there a way? Can private members of a class be accessed directly outside the class? Yes, it is a friend (of course you can also write a shared function to absorb the value of the private area)

10.2 Friend functions

Friend functions can directly access private members of a class . They are ordinary functions defined outside the class and do not belong to any class , but they need to be declared inside the class . The friend keyword is required when declaring  ; as follows:

#include<iostream>
using namespace std;
class Date
{
	//友元声明 -- 可以放置在类的任意位置
	friend istream& operator>>(istream& in, Date& d);
	friend ostream& operator<<(ostream& out, const Date& d);

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

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

//流提取
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year;
	in >> d._month;
	in >> d._day;
	return in;
}

//流插入
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day;
	return out;
}
int main()
{
     Date d1;
	 cout << d1 ;
	 return 0;
}

Lamb Note:

1. Since stream insertion and stream extraction have less overloaded content and are called frequently, we can define them as inline functions.

2. In order to support continuous input and continuous output, we need to set the return value of the function to a reference to the istream and ostream objects.

3. Why do we need to add references to function parameters? First of all, the reference to the first parameter is essential because ostream type objects do not support copying. The reference to the second function parameter does not need to be added. This is essentially to improve efficiency.

Summary of friend functions:

  • 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 and are not restricted by class access qualifiers;
  • A function can be a friend function of multiple classes;
  • The principle of calling friend functions is the same as that of ordinary functions;

11. Internal classes

Concept: If a class is defined inside another class, this class is called an inner class; the inner class is an independent class, it does not belong to the outer class, and the members of the inner class cannot be accessed through the objects of the outer class; the outer class Does not have any superior access to inner classes

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}

	//内部类
	class B
	{
	public:
		B(int b = 0)
			:_b(b)
		{}

	private:
		int _b;
	};

private:
	int _a;
};

Inner classes have the following characteristics:

  1. The inner class is naturally a friend of the outer class, so 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. The inner class can be defined in the public, protected, or private locations of the outer class, but when the inner class instantiates an object, it is subject to the restrictions of the class domain and access qualifier of the outer class;
  3. Inner classes can directly access static members in outer classes without requiring the object/class name of the outer class;
  4. The inner class is an independent class and does not belong to the outer class, so sizeof (external class) == outer class;
  5. Inner classes are rarely used in C++ and frequently used in Java, so everyone just needs to know that they exist;

12. Anonymous objects (only for laziness)

In C++, in addition to creating objects using class name + object name, we can also directly use class names to create anonymous objects. Anonymous objects are the same as normal objects. The constructor is automatically called when created and the destructor is automatically called when destroyed. ;But the life cycle of an anonymous object is only the line it is defined in, and the next line will be destroyed immediately.

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

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

private:
	int _a;
};


13. Compiler optimization (already mentioned in static)

Guess you like

Origin blog.csdn.net/weixin_62985813/article/details/132781493