2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
In the previous chapter, we briefly mentioned some concepts of classes and objects. Now, let's review them together.
class is the keyword for defining a class, followed by the name of the class being defined, {} is the body of the class, followed by ";", and member functions defined in the class are expanded by default (inline)
An encapsulation format, divided into three types: public, private, and protected. The scope of access rights starts from the position where the access qualifier appears until the next access qualifier appears. If there is no access qualifier after it, the scope ends at }, that is, the class ends.
A class defines a new scope. All members of the class are in the scope of the class. When defining members outside the class body, you need to use the :: scope operator to indicate which class scope the member belongs to.
The following is the main film ///
• The process of creating an object in physical memory using a class type is called instantiating an object.
• A class is an abstract description of an object, a model-like thing that defines the member variables of a class. These member variables are only
It is a declaration, no space is allocated, and space is allocated only when an object is instantiated using the class.
• A class can instantiate multiple objects. The instantiated objects occupy actual physical space and store class member variables.
To sum it up in one sentence: space is allocated only when objects are instantiated using classes. (Just like building a house using blueprints takes up space)
- class Date
- {
- public:
- void Init(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- void Print()
- {
- cout << _year << "/" << _month << "/" << _day << endl;
- }
- private:
- // 这⾥只是声明,没有开空间
- int _year;
- int _month;
- int _day;
- };
In the above code, the member variables in private are just defined without opening up new space;
- // Date类实例化出对象d1和d2
- Date d1;
- Date d2;
Two new objects are instantiated, so new space is opened up.
• The first member is at offset 0 from the structure.
• Other member variables must be aligned to addresses that are integer multiples of a certain number (alignment factor).
• Note: Alignment = the smaller of the compiler's default alignment and the member size.
• The default alignment number in VS is 8
• The total size of the structure is an integer multiple of the maximum alignment factor (the minimum of the maximum of all variable types and the default alignment parameter).
• If a structure is nested, the nested structure is aligned to an integer multiple of its maximum alignment number, and the overall size of the structure
It is an integer multiple of all maximum logarithms (including logarithms of nested structures).
This is the knowledge we learned when we were using C language. Take the following question as an example:
- // 计算⼀下A实例化的对象是多⼤?
- class A
- {
- public:
- void Print()
- {
- cout << _ch << endl;
- }
- private:
- char _ch;
- int _i;
- };
Since there is no function call, we can directly look at the member variables. _ch is a char character type, 1 byte in size; _i is an int integer type, 4 bytes in size. The largest space occupied by all members is taken as the alignment number, so _ch is placed at the position with subscript 0, three spaces are left, and then _i is placed. In total, it occupies 8 bytes of space from 0 to 7, which is an integer multiple of the maximum alignment number and meets the conditions. Therefore, the answer to this question is 8.
What if it is these two questions?
- class B
- {
- public:
- void Print()
- {
- //...
- }
- };
-
- class C
- {
- };
We can find that there is an empty function in class B and no member in class C. In C language, we make both classes occupy 1 byte of space.
• DThe ate class has two member functions, Init and Print. There is no distinction between different objects in the function body. So when d1 calls Init and
When the Print function is called, how does the function know whether to access the d1 object or the d2 object? Here we need to see that C++ gives
An implicit this pointer solves this problem
• After the compiler compiles, the member functions of the class will add a pointer of the current class type in the first position of the parameter by default, which is called the this pointer. For example, the real prototype of Init of the Date class is void Init(Date* const this, int year,
int month, int day)
• When accessing member variables in a class member function, it is essentially accessed through the this pointer. For example, in the Init function, a value is assigned to _year.
>_year = year;
• C++ stipulates that the this pointer cannot be explicitly written in the position of actual parameters and formal parameters (the compiler will handle it during compilation), but the this pointer can be explicitly not used in the function body.
for example
- class Date
- {
- public:
- // void Init(Date* const this, int year, int month, int day)
- void Init(int year, int month, int day)
- {
- // 编译报错:error C2106: “=”: 左操作数必须为左值
- // this = nullptr;
- // this->_year = year;
- _year = year;
- this->_month = month;
- this->_day = day;
- }
- void Print()
- {
- cout << _year << "/" << _month << "/" << _day << endl;
- }
- private:
- // 这⾥只是声明,没有开空间
- int _year;
- int _month;
- int _day;
- };
-
- int main()
- {
- // Date类实例化出对象d1和d2
- Date d1;
- Date d2;
- // d1.Init(&d1, 2024, 3, 31);this指针
- d1.Init(2024, 3, 31);
-
- // d1.Init(&d2, 2024, 7, 5);
- d2.Init(2024, 7, 5);
- //不能自己写
- return 0;
- }
For the writing of actual parameters in the Init function, C++ will add an invisible constant this pointer (which cannot be modified) to the first variable of the function, as shown in the comments in the function.
No error is reported as long as the null pointer is not dereferenced
- class A
- {
- public:
- void Print()
- {
- cout << "A::Print()" << endl;
- }
- private:
- int _a;
- };
-
- int main()
- {
- A* p = nullptr;
- p->Print();
- return 0;
- }
Although p is assigned a null pointer and points to the Print function, it is not dereferenced, so no error is reported here. This program:
- class A
- {
- public:
- void Print()
- {
- cout << "A::Print()" << endl;
- cout << _a << endl;//这个地方多了一句
- }
- private:
- int _a;
- };
-
- int main()
- {
- A* p = nullptr;
- p->Print();
- return 0;
- }
Compared with the previous program, there is only one more sentence cout << _a << endl, but this time the null pointer points to a member variable, and the null pointer will be dereferenced, so the program will report an error.
Default member functions are member functions that are automatically generated by the compiler when the user does not explicitly implement them. For a class, the compiler will generate the following 6 default member functions by default if we do not write them. They are:
The constructor is a special member function. It should be noted that although the name of the constructor is construction, the main task of the constructor is not to open space to create objects (the local objects we often use are created when the stack frame is created, and the space is already opened), but to initialize the object when the object is instantiated. In essence, it wants to replace the initialization Init function.
1. The function name is the same as the class name.
2. No return value. (No need to write void)
3. When an object is instantiated, the system will automatically call the corresponding constructor.
4. Constructors can be overloaded.
5. If there is no explicit constructor defined in the class, the C++ compiler will automatically generate a default constructor without parameters. Once the user explicitly defines it, the compiler will no longer generate it.
The first five items are relatively simple, let's take an example:
- class Date
- {
- public:
- //1.⽆参构造函数,无需写void
- Date()//若不写,则构造函数会写出这一中无参构造函数
- {
- _year = 1;
- _month = 1;
- _day = 1;
- }
- //2.全缺省构造函数
- Date(int year = 1, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year;
- int _month;
- int _day;
- }
-
- int main()
- {
- Date d1;//此时相当于已经自动调用了Init函数
- Date d2;
-
- return 0;
- }
There are two constructors written in the program, one is a no-argument constructor and the other is a full default constructor. When we do not write a function, the program will write at most a no-argument constructor. In addition, as we said before, although these two functions are overloaded functions, they cannot exist at the same time due to calling ambiguity.
6. The no-parameter constructor, the full default constructor, and the constructor generated by the compiler by default when we do not write a constructor are all called default constructors. However, only one of these three functions exists, and they cannot exist at the same time. Although the no-parameter constructor and the full default constructor constitute function overloading, there will be ambiguity when calling. It should be noted that many students think that the default constructor is the one generated by the compiler by default, which is called the default constructor. In fact, the no-parameter constructor and the full default constructor are also default constructors. In summary, the constructor that can be called without passing actual parameters is called the default constructor.
The sixth feature is more complicated: the no-parameter constructor, the full default constructor, and the constructor generated by the compiler by default when we do not write a constructor are all called default constructors, and there can only be one. The default constructor must not be understood as the constructor generated by the compiler by default!
7. If we don't write it, the compiler generates the default constructor, which has no requirements for the initialization of built-in type member variables. In other words, whether to initialize is uncertain and depends on the compiler. For member variables of custom types, it is required to call the default constructor of this member variable for initialization. If this member variable does not have a default constructor, an error will be reported. If we want to initialize this member variable, we need to use an initialization list to solve it. The initialization list will be explained in detail in the next chapter.
The destructor is opposite to the constructor. The destructor does not complete the destruction of the object itself. For example, local objects exist in the stack frame. When the function ends, the stack frame is destroyed and it is released. We do not need to care about it. C++ stipulates that the destructor will be automatically called when the object is destroyed to complete the cleanup and release of resources in the object. The function of the destructor is similar to the Destroy function we implemented in Stack before. For example, Date does not have Destroy, which actually means that there are no resources to be released. So strictly speaking, Date does not need a destructor. Therefore, the destructor, like the Destroy function, should only be used for data structures that require non-local objects such as stacks and queues to apply for resources.
1. The destructor name is the class name with the character ~ added before it. (Similar to the bitwise negation in C)
2. No parameters and no return value. (This is similar to the construction, and there is no need to add void)
3. A class can have only one destructor. If not explicitly defined, the system will automatically generate a default destructor.
4. When the object's life cycle ends, the system will automatically call the destructor.
5. Similar to the constructor, we do not write the destructor automatically generated by the compiler to process the built-in type members. The custom type members will call their destructors.
6. It should also be noted that when we explicitly write the destructor, the destructor of the custom type member will also be called, which means that the destructor of the custom type member will be automatically called no matter what the situation is.
7. If there is no resource application in the class, the destructor can be omitted and the default destructor generated by the compiler can be used directly, such as Date; if the default generated destructor can be used, there is no need to explicitly write the destructor, such as MyQueue; but when there is a resource application, you must write the destructor yourself, otherwise it will cause resource leakage, such as Stack.
8. For multiple objects in a local scope, C++ stipulates that the one defined later will be destructed first.
Like the above constructor, the default destructor automatically generated by C++ is generally not very helpful to us, but it is convenient in some rare cases, such as using two stacks to implement a queue:
- public:
- Stack(int n = 4)//相当于Init
- {
- _a = (STDataType*)malloc(sizeof(STDataType) * n);
- if (nullptr == _a)
- {
- perror("malloc fail!");
- return;
- }
- _capacity = n;
- _top = 0;
- }
- ~Stack()//相当于Destroy
- {
- cout << "~Stack()" << endl;
- free(_a);
- _a = nullptr;
- _top = _capacity = 0;
- }
- private:
- STDataType* _a;
- size_t _capacity;
- size_t _top;
- };
-
- class MyQueue
- {
- public:
- //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
- // 显⽰写析构,也会⾃动调⽤Stack的析构
-
- private:
- Stack pushst;
- Stack popst;
- };
At this point, the compiler in MyQueue automatically calls the constructor (initialization) and destructor (destruction) of the stack.