Notes (C + +)
This article has some knowledge of C + + syntax and object-oriented, involving the bottom of assembly.
1. Essential differences between programming languages
Compiled language (independent of virtual machine) | scripting language | Compiled language (virtual machine dependent) |
---|---|---|
C,C++,object-C | PHP,Python,JavaScript | Java,Ruby |
- C + + code can be directly compiled into assembly code by the compiler, and then transferred from assembly code to machine language recognized by the machine. (disassembly can be carried out well)
- JS and PHP code can only be parsed into intermediate code by script engine, and then transformed into machine language by intermediate code.
- Java code is compiled into Class bytecode by compiler, and then transformed into machine language by JVM virtual machine.
Programming language is produced to solve problems in different scenarios.
2. Function overloading
C language does not support function overloading, and C + + supports function overloading
- Rules for function overloading:
- Same function name;
- Different parameter types, number and order
- be careful:
- The return value type is independent of function overloading;
- When calling a function, the implicit type conversion of an argument may also produce ambiguity.
- Essence:
name mangling or name decoration technology is adopted:
- The C + + compiler adapts and modifies symbolic names (such as function names) by default, and translates them into "named names" in some places;
- Overloading will produce multiple different function names, and different compilers (MSVC, g + +) have different generation rules.
#include <iostream> using namemspace std; int sum (int a, int b) { return a + b; } int sum (itn a, int b, int c) { return a + b + c; } double sum(int a, int b) { return a + b; //Error: overload is independent of function return value type } void display(long a) { cout << a << endl; } void display(double a) { cout << a << endl; } int main() { display(1); //The implicit conversion from int type to long and double type is ambiguous. cout << sum(1, 2) << endl; //Error: ambiguous. double or int may be returned cout << sum(1, 2, 3) << endl; // Constituent function overload return 0; }
3. Default parameters
As like as two peas, C++ allows the function to set default parameters, and the actual parameters can be omitted when invoking. (when using default parameters, the same effect is passed to the function into the function, putting the parameters into the stack). The rules are as follows:
- The default parameters can only be in the order from right to left;
- If the function is declared and implemented at the same time, the default parameters can only be placed in the function declaration;
- The values of default parameters can be constants and global symbols (global variables and function names);
- If the arguments of the function are often the same value, you can consider using the default parameter;
- Function overloading and default parameters may cause conflict and ambiguity (it is recommended to use default parameters first)
int age = 22; void test() { cout << "test()" << endl; } void display(int a = 11, int b = 22, int c = age, void (*func)() = test) //Formal parameters are global variables and function pointers (pointer variables pointing to the function (first address), that is, they are essentially pointer variables) { cout << "a is " << a << endl; cout << "b is " << b << endl; cout << "c is " << c << endl; func(); } void display(int a, int b = 20) { cout << "a is " << a << endl; } void display(int a) { cout << "a is " << a << endl; } int main() { display(10); //There is ambiguity display(); return 0; }
4. extern "C"
- The code modified by extern "C" will be compiled in the way of C language;
The first way to write:
extern "C" void func() { cout << "func()" << endl; } extern "C void func(int age) { cout << "func(int age)" << age << endl; } //When compiling this function, the compiler will report an error because C language does not support function overloading.
The second way to write:
extern "C" { void func() { cout << "func()" << endl; } void func(int age) { cout << "func(int age)" << age << endl; } }
- If the function has both declaration and implementation, the function declaration can be modified by extern "C", and the function implementation can not be modified.
extern "C" void func(); extern "C" void func(int age); /* extern "C" { void func(); void func(int age); } */ } void func() { cout << "func()" << endl; } void func(int age) { cout << "func(int age)" << age << endl; }
-
Due to the different compilation principles of C and C + +, the following operations may often occur in the mixed development of C and C + +.
When calling C language API, C + + needs to use extern "C" to modify the function declaration of C language:
Sometimes, extern "C" is directly used in writing C language code, so that it can be directly called by C + +. -
In C + + programs, the compiler defines macros__ cplusplus to distinguish between C and C + + environments.
We often use #ifnef, #define, #endif to prevent the contents of header files from being included repeatedly. -
#pragma once can prevent the contents of the whole file from being included repeatedly.
difference:
- #ifnef, #define, #endif are supported by the C\C + + standard and are not subject to any restrictions of the compiler;
- Some compilers do not support #pragma once (older compilers do not support it, such as before GCC version 3.4), and the compatibility is not good enough;
- #The #define, #define file can only be used for one part of the # define #.
5. inline function
- Using inline to modify the declaration or implementation of a function can make it an inline function. (it is suggested to add inline modification to both declaration and Implementation);
- characteristic:
1. The compiler will directly expand the function call into function body code;
2. It can reduce the cost of function call;
3. It will increase the size of the code. - be careful:
1. Try not to inline functions with more than 10 lines of code;
2. Even if some functions are declared as inline, they may not be inlined by the compiler, such as recursive functions.
3. Inline functions and macros can reduce the cost of function calls;
4. Compared with macros, inline functions have more syntax detection and function features.
#define sum(x) (x + x) / / macro definition inline int sum(int x) { return x + x; } //Inline function int a = 10; sum (++ a); //When the macro is executed, the result is 24; (macro definition is text replacement, which is equivalent to first + + A and then + + A + + a directly.) //When the inline function is executed, the result is 22. (execute + + A + + a).
6. Expression
In C + +, some expressions can be assigned:
int a = 1; int b = 2; (a = b) = 3; //Assigned to a (a < b ? a : b) = 4; //Assigned to b
7. const
- Const means constant, and the variable modified by const cannot be modified;
- If you modify a class or structure (pointer to), its members cannot be changed;
int age = 10; const int * p0 = &age; //You cannot modify the value of age indirectly through p0; int const *p1 = &age; //ditto int * const p2 = &age; //The value of pointer p2 cannot be modified; const int * const p3 = &age; //You can neither modify the value of p3 nor modify the value of age indirectly through p3; int const * const p4 = &age; //ditto.
- const modifies the content on its right.
A pointer decorated with const cannot be used to modify its memory value.
8. Reference
- In C language, the value of a variable can be obtained and modified indirectly by using Pointer;
- In C + + language, using Reference can play a more pointer like function;
int age = 20; //age is a reference int &age = age;
- Note:
- The reference is equivalent to the alias of the variable (basic data type, enumeration, structure, class, pointer, array, etc. can all have references);
- To calculate a reference is to calculate the variable that the reference points to;
- When defining, it must be initialized. Once a variable is pointed to, it cannot be changed, "from one to the end";
- You can use a reference to initialize another reference, which is equivalent to multiple aliases of a variable;
- There is no [referenced reference, pointer to reference, reference array].
- One of the values of a reference is that it is safer than a pointer and the return value of a function can be assigned.
- Nature of reference:
- The essence of reference is pointer, but the compiler weakens its function, so reference is a weakened pointer;
- A reference occupies the size of a pointer.
Reference to array:
- There are two common ways to write:
int array[] = {10, 20, 30}; int (& ref1)[3] = array; int * const & ref2 = array; //The array name is the address of the first element of the array, and the address value of the first element is a constant. Constants cannot be referenced for non constant quantities, so const should be added
9. const reference
-
References can be modified by const, so that data cannot be modified by reference and can become constant references;
-
const must be written to the left of the & sign to be a constant reference;
-
Features of const reference:
- It can point to temporary data (constants, expressions, function return values, etc.);
- Can point to different types of data;
- As a function parameter (this rule also applies to const pointers):
- Const and non const arguments can be accepted (non const references, only non const arguments can be accepted);
- Can be overloaded with non const references
-
When a constant reference points to different types of data, a temporary variable will be generated, that is, the reference does not point to the variable at initialization.
Namely:
int age = 10; const double & rage = age; age = 18; cout << age << endl; //age = 18; cout << rage << endl; //rage = 10;
10. Classes and objects
- In C + +, you can use · struct and class to define a class
- The difference between struct and class:
- The default member permission of struct is public
- The default member permission of class is private
//Class definition 1 struct Person { //Member variable int m_age; //Member function void run() { cout << m_age << "run()" << endl; } } //Class definition 2 class Person { public: //Member variable int m_age; //Member function void run() { cout << m_age << "run()" << endl; } } int main() { //Access the member variable of the person object Person person; //Call member function with person object Person.my_age = 20; person.run(); //The person object is accessed indirectly through a pointer Person *p = &person; p->m_age = 30; p->run(); }
-
In the above code, the memory of person object and p pointer is automatically allocated and recycled in the stack space of the function
-
Global variables are in the data segment and global area (static variables and constants are also in the global area), local variables are in the stack, and the heap stores the dynamic memory freely allocated and released by the programmer.
-
Object's memory layout
If there are multiple member variables in the class, what is the memory layout of the object?
struct Person { int m_id; int m_age; int m_height; void display() { cout << "m_id is " << m_id << endl; cout << "m_age is" << m_age << endl; cout << "m_height is" << m_height << endl; } }
11. this
- this is a pointer to the current object.
- When an object calls a member function, it will automatically pass in the memory address of the current object:
struct Person { int m_id; int m_age; int m_height; void display() { cout << "m_id is" << this->m_id << endl; cout << "m_age is" << fhis->m_age << endl; cout << "m_height is" << this->m_height << endl; } }
- The nature of accessing object members with pointers:
Person person; person.m_id = 10; person.m_age = 20; person.m_height = 30; Person *p = (Person *) &person.m_age; //person.m_age is int * type, but it should be changed to Person * type p->m_id = 40; p->m_age = 50; //Pass the address of the person object into this of the display person.display(); //The result is displayed as: 10 40 50 (directly transfer the address value of m_age, and find the memory unit with offset of 0 and 4 bytes in the memory space where the class is located in turn) //Pass the stored address of the p pointer to this of display p->display(); //The result is displayed as: 40 50 cc (the address value of the incoming m_age is displayed from [& person + 4], [& person + 4 + 4], [& person + 4 + 4])
To call a member function through an object is to pass the address value of the object into the this pointer of the member function;
To call through the pointer is to pass the address stored in the pointer to the this pointer.
- Principle: how to indirectly access the member variable of the object with pointer:
- Take the address of the object from the pointer;
- Using the address of the object and the offset of the member variable to calculate the address of the member variable;
- Access the storage space of member variables according to the address of member variables.
When opening up the function stack space, the compiler will initialize the stack content to 0xcccc (to prevent users from accessing memory at will), "cc" corresponds to the assembly instruction int 3 and interrupt instruction.
12. Packaging
- Privatize member variables and provide common getter s and setter s for the outside world to access member variables.
struct Person { private: int m_age; public: void setAge(int age) { this->m_age = age; } int getAge () { return this->m_age; } } Person person; person.setAge(20); cout << person.getAge() << endl;
13. Memory
13.1 key points
- Calling functions and executing function code is actually the CPU accessing the memory of the code area and executing instructions.
- When calling a function, you need to allocate additional space to store local variables inside the function.
- The code area can only be read by the CPU and cannot be changed, so it is used in other memory spaces.
- Local variables are stored in stack space and function code is stored in code area.
13.2 distribution of memory space
- Each application has its own independent memory space, which generally has the following areas:
- Code snippet (code area)
Used to store codes; - Data segment (global area)
It is used to store global variables, etc. (it exists in the whole process of program operation); - Stack space
Every time a function is called, it will be allocated a continuous stack space, which will be automatically recovered after the function is called;
Automatic distribution and recycling. - Heap space
Need to take the initiative to apply for and release.
- Code snippet (code area)
As shown in the figure below:
13.3 reactor space
- In the process of program running, in order to freely control the life cycle and size of memory, the memory in heap space will often be used.
- Reclaim heap space memory: this heap space memory can be reused by others (not cleared).
- Free space request \ heap:
1. malloc \ free 2. new \ delete 3. new [] \ delete []
-
be careful:
- After applying for heap space successfully, the address of that memory space will be returned;
- The application and release must be a 1-to-1 relationship, otherwise there may be a memory leak.
-
Many high-level programming languages now do not require developers to manage memory (such as Java), shielding many memory details. There are both advantages and disadvantages:
- Advantages: improve development efficiency and avoid improper use or leakage of memory;
- Disadvantages: it is not conducive for developers to understand the essence, always stay in API calls and surface syntax sugar, and have no way to start performance optimization.
13.3.1 malloc \ free
int *p = (int *)malloc(4); //Apply for a continuous 4-byte space in the heap. Because the malloc function returns the first address of the applied heap space and is a void * pointer, it is necessary to force the type to be converted to int *; *p = 4; free(p); //Free up the requested heap space
char * p = (char *) malloc(4); //p stores the address of the first byte in the heap memory applied for by the memory * p = 10; * (p + 1) = 11; //p[1] = 11; * (p + 2) = 12; //p[2] = 12; * (p + 3) = 13; //p[3] = 13; free(p); //Release all the space applied for
Remember: the requested heap space cannot be released twice.
13.3.2 new \ delete
int * p = new int; //Request bytes of int size from heap space * p = 10; delete p; //new and delete must correspond one by one char * p = new char; * p = 10; delete p;
13.3.3 new[] \ delete[]
char * p = new char[4]; delete [] p;
Remember: when applying for space with new [], you must use delete [] to free the space (otherwise only the elements of the first address will be released).
13.4 initialization of heap space
int * p1 = (int *) malloc (sizeof(int)); //*p1 not initialized int * p2 = (int *) malloc (sizeof(int)); memset(p2, 0, sizeof(int)); //Initialize each byte of * p2 to 0 int * p1 = new int; //Not initialized (determined by the platform, may be initialized) int * p2 = new int (); //Initialized to 0 (call memset function to initialize) int * p3 = new int(5); //Is initialized to 5; int * p4 = new int[3]; //Array element not initialized int * p5 = new int[3](); //All 3 array elements are initialized to 0; int * p6 = new int[3]{}; //All 3 array elements are initialized with bit 0; int * p7 = new int[3]{ 5 }; //The first element of the array is initialized to 5 and the other elements are initialized to 0
13.4.1memset
- memset function is a relatively fast method to clear the memory of large data structures (such as objects, arrays, etc.).
Person person; person.m_id = 1; person.m_age = 20; person.m_height = 180; memset (&person, 0, sizeof(person)); Person persons[] = {{1, 20, 180}, {2, 25, 165}, {3, 27, 170}}; memset(persons, 0, sizeof(persons));
- When the first element of the array is passed in, the sizeof() operator calculates the size of the entire array.
As described in Microsoft documentation:
When the sizeof operator is applied to an object of type char, it yields 1.
When the sizeofoperator is applied to an array, it yields the total number of bytes in that array, not the size of the pointer represented by the array identifier. To obtain the size of the pointer represented by the array identifier, pass it as a parameter to a function that uses sizeof.
Example:
#include <iostream> using namespace std; size_t getPtrSize( char *ptr ) { return sizeof( ptr ); } int main() { char szHello[] = "Hello, world!"; cout << "The size of a char is: " << sizeof( char ) << "\nThe length of " << szHello << " is: " << sizeof szHello << "\nThe size of the pointer is " << getPtrSize( szHello ) << endl; }
Display results:
The size of a char is: 1 The length of Hello, world! is: 14 The size of the pointer is 4
14. Object memory
- Object memory can exist in three places:
- Global area (data segment): global variable
- Stack space: global variables in functions
- Heap space: dynamically request memory (malloc, new, etc.)
//Global area Person g_person; int main() { //Stack space Person person; //Heap space person * p = new Person; //The pointer p is in the stack, and the applied memory of Person size is in the heap. return 0; }
15. constructor
-
Constructor (also known as constructor) is called automatically when an object is created. It is generally used to complete the initialization of the object;
-
characteristic:
- The function name has the same name as the class, has no return value (void cannot be written), can have parameters, can be overloaded, and can have multiple constructors;
- Once the constructor is customized, the object must be initialized with one of the customized constructors.
-
Note: objects allocated through malloc do not call the constructor.
-
By default, the compiler generates an empty parameterless constructor for each class. (wrong conclusion)
-
Correct understanding: under certain circumstances, the compiler will generate an empty parameterless constructor for the class. (in certain cases, the compiler omits the constructor in order to optimize the code)
15.1 call of constructor
struct Person { int m_age; Person () { cout << "Person()" << endl; } Person(int age) { cout << "Person(int age)" << endl; } }; //Global area Person g_p1; //Call Person() Person g_p2(); //This is a function declaration. The function name is g_p2, return value type is Person, no parameter Person g_p3(20); //Call Person(int) int main() { //Stack space Person p1; //Call Person() Person p2(); //This is a function declaration. The function name is p2. The return value type is Person and has no parameters Person p3(20); //Call Person(int) //Heap space Person * p4 = new Person; //Call Person() Person * p5 = new Person(); //Call Person() Person * p6 = new Person(20); //Call Person(int) return 0;
15.2 initialization of member variables by default
struct Person { int m_age; }; //Global area (member variable initialized to 0) Person g_p1; int main() { //Stack space (member variables are not initialized) Person p1; //Heap space Person * p2 = new Person; //Member variables are not initialized Person * p3 = new Person(); //The member variable is initialized to 0 Person * p4 = new Person[3]; //Member variables are not initialized Person * p5 = new Person[3](); //The member variables of the three Person objects are initialized to 0 Person * p6 = new Person[3]{}; //The member variables of the three Person objects are initialized to 0 return 0; }
-
If the constructor is customized, the member variables of other memory spaces will not be initialized by default except the global area, which needs to be initialized manually by the developer.
-
Object initialization:
Person() { memset(this, 0, sizeof(Person)); }
16. Destructor
- Destructors (also known as destructors) are called automatically when objects are destroyed. They are generally used to clean up objects.
- characteristic:
*The function name starts with ~, has the same name as the class, has no return value (void cannot be written), has no parameters, cannot be overloaded, and has only one destructor. - be careful:
*The destructor will not be called when the object allocated through malloc is free. - Constructors and destructors must be declared public before they can be used normally by the outside world.
16.1 memory management
struct Car { int m_price; }; struct Person { int m_age; Car m_car; }; Person p;
Schematic diagram of this code memory:
struct Car { int m_price; }; struct Person { int m_age; Car m_car; }; Person *p = new Person(); //The pointer p is in stack space and the Person object is in heap space
Schematic diagram of modified code memory:
struct Car { int m_price; } struct Person { int m_age; Car *m_car; Perosn () { m_car = new Car(); //Pointer M_ The Car is in heap space, and the Car object is also in heap space } ~Personj () { delete m_car; //Manually requested heap space must be released manually } }; Person *p = new Person(); //The pointer p is in stack space and the Person object is in heap space
Schematic diagram of modified code memory:
- The heap space requested inside the object is recycled inside the object.
struct Car { int m_price; }; struct Person { int m_age; Car * m_car; Person () { m_car = new Car(); //Car object in heap space } ~Person () { delete m_car; } }; Person p; //Person object in stack space
Schematic diagram of this code memory:
17. Separation of declaration and Realization
17.1 declaration
Including member variables and member functions are uniformly placed in the header file.
17.2 realization
Details of member function implementation
18. Namespace
- Namespaces can be used to avoid naming conflicts;
namespace QY { int m_age; class Person { }; void test() { } } int main() { QY::g_age = 20; QY::Person * p = new QY::Person(); QY::test(); return 0; //Equivalent to: using namespace std; //using QY::g_age; g_age = 20; Person * p = new Person (); test(); return 0; }
- Namespace does not affect memory layout.
18.1 nesting of namespaces
namespace QY { namespace SS { int g_age; } } int main() { QY::SS::g_age = 10; using namespace QY::SS; g_age = 20; using QY::SS::g_age; g_age = 30; return 0; }
- There is a default global namespace, and the namespaces we create are nested in it by default.
int g_no; namespace QY { namespace SS { int g_age } } int main() { ::g_no = 20; ::QY::SS::g_age = 30; return 0; }
18.2 namespace merging
- The following two expressions are equivalent:
//First: namespace QY { int g_age; } namespace QY { int g_no; } //Second: namespace QY { int g_age; int g_no; }
18.3 namespaces of other programming languages
- Java
- Package
- Objective-C
- Class prefix
19. Succession
- Inheritance allows subclasses to have all members of the parent class (variables \ functions)
struct Person { int m_age; void run () { cout << "Student::study()" << endl; } }; struct Student : Person { int m_no; void study () { cout << "Student ::study()" << endl; }; int main() { Student student; student.m_age = 20; student.m_no = 1; student.run(); student.study(); return 0;
- Relationship description:
- Student is a subclass (subclass, derived class)
- Person is a superclass
- There are no base classes like Java and Objective-C in C + +
- Java: java.lang.Object
- Objective-C: NSObject
19.1 memory layout of objects
20. Member access
-
There are three ways to inherit member access rights:
- Public is public and can be accessed anywhere (struct default)
- protected: it can be accessed inside the subclass and the current class
- Private: private, which can only be accessed inside the current class (class default)
-
The permission to access members of the parent class inside the subclass is the one with the least permission in the following two items
- Access rights of members themselves
- The way the parent class inherits
-
The most commonly used inheritance method in development is public, which can retain the access rights of the original members of the parent class
-
Access rights do not affect the memory layout of the object
-
class is private inheritance by default and struct is public inheritance by default (the difference between the two is only the problem of default access rights).
21. Initialization list
- characteristic:
- A convenient way to initialize member variables;
- Can only be used in constructors;
- The initialization order is only related to the declaration order of member variables.
- The following two expressions are equivalent:
struct Person() { int m_age; int m_height; Person(int age, int height) : m_age(age), m_height(height) { } }; struct Person { int m_age; int m_height; Person(int age, int height) { this->m_age = age; this->m_height = height; } };
In C + +, when the declaration of a function has default parameters, the implementation of the function cannot write default parameters.
int myAge() { return 10; } int myHeight() { return 120;} struct Person { int m_age; int m_height; Person(int age, int height) : m_height(myHeight()), m_age(myAge()) { } }; Person p(20, 180);
The return values are: 10, 170;
struct Person { int m_age; int m_height; Person(iint age, int height) : m_height(height), m_age(m_height) { } }; Person p(20, 180);
The return value is:
m_age = unknown;
m_height = 180;
21.1 the initialization list is used in conjunction with the default parameters
struct Person { int m_age; int m_height; Person(int age = 0, int height = 0) : m_age(age), m_height(height) { } }; int main() { Person person1; Person person2(18); Person person 3(20, 180); return 0; }
- If the function declaration and implementation are separate:
- The initialization list can only be written in the implementation of the function;
- Default parameters can only be written in function declarations.
21.2 mutual calls of constructors
struct Person { int m_age;j int m_height; Person() : Person (0,0) { } //The passed in this pointer points to the initial object Person (int age, int height) : m_age(age), m_height(height) {} };
- Note: the following program is written incorrectly. What is initialized is a temporary object.
struct Person { int m_age; int m_height; Person() { Person(0, 0); //A Person object is recreated in the Person constructor, but the passed in this pointer does not point to it. } Person (int age, int height) : m_age(age), m_height(height) {} };
21.3 constructor of parent class
- The constructor of the subclass will call the parameterless constructor of the parent class by default;
example:
#include <iostream> using namespace std; struct Person { int m_age; Person () { cout << "Person::Person" << endl; } }; struct Student : Person { Student () { cout << "Person::Student" << endl; } }; int main () { Student student; //The constructors of Person and Student will be called Person person; //Call only the constructor of Person return 0; }
result:
Person::person Student::student Person::person
- If the constructor of the subclass explicitly calls the parameterized constructor of the parent class, it will not call the parameterless constructor of the parent class by default;
example:
struct Person { int m_age; Person () { cout << "Person::Person ()" << endl; } Person(int age) { cout << "Person::Person(int age)" << endl; } }; struct Student : Person { int m_no; Student::Person (20) { //Explicitly call the parameterized constructor of the parent class cout << "Student :: Student()" << endl; } };
result:
Person :: Person(int age) Student :: Student()
- If the parent class lacks a parameterless constructor, the constructor of the child class must explicitly call the parameterless constructor of the parent class. (because the constructor of the subclass calls the parameterless constructor of the parent class by default, and the constructor with parameters is not called now, so the member variables of the parent class cannot be initialized)
example:
struct Person { int m_age; Person(int age) { cout << "Person::Person(int age)" << endl; } }; struct Student : Person { int m_no; Student::Person (20) { //Explicitly call the parameterized constructor of the parent class. If you don't call it, an error will be reported cout << "Student :: Student()" << endl; } };
21.4 example of constructor under inheritance system
class Person { int m_age; Person() : Person(0) { } Person(int age) : m_age(age) { } }; class Student : Person { int m_no; Student() : Student(0, 0) { } Student(int age, int no) : Person(age), m_no(no) { } //The constructor of the subclass calls the constructor of the parent class to initialize the private member variable of the parent class };
21.5 sequence of structure and Deconstruction
struct Person { Person() { cout << "Person :: Person() " << endl; ~Person() { cout << "Person :: ~Person() " << endl; } }; struct student::Person { Student() { // call Person::Person() cout << "Student :: Student()" << endl; } ~Student() { cout << "Student :: ~Student()" << endl; // call Person::~Person() } }; int main() { { Student student; } getchar(); return 0; }
22. Parent class pointer and child class pointer
- The parent class pointer can point to the child class object, which is safe (the member that the parent class pointer can access, and the child class can access). It is often used in development (the inheritance method must be public)
- It is not safe for a subclass pointer to a parent object.
struct Person { int m_age; }; struct Student : Person { //The default is public inheritance. When it is private inheritance, an error will be reported int m_score; };
Example 1:
Person * p = new Student(); //The compiler considers p to point to the Person object p->m_age = 10;
Example 2:
Student * p = (Student *) new Person(); //The compiler thinks that p points to the Student object p->m_age = 10; p->m_score = 100; //Will m_ The last four bytes of age are assigned directly
23. Polymorphism
- By default, the compiler will only call the corresponding function according to the pointer type, and there is no polymorphism.
- Polymorphism is a very important feature of object-oriented.
- The same operation acts on different objects, which can have different interpretations and produce different execution results;
- At runtime, you can identify the real object type and call the functions in the corresponding subclass.
- Polymorphic elements:
- The subclass overrides the member function of the parent class (override);
- The parent class pointer points to the child class object;
- Call the overridden member function with the parent class pointer.
23.1 virtual function
- Polymorphism in C + + is realized by virtual function;
- Virtual function: member function modified by virtual;
- **As long as it is declared as a virtual function in the parent class, the function rewritten in the subclass will automatically become a virtual function * * (that is, the virtual keyword can be omitted in the subclass).
23.2 virtual table
- The implementation principle of virtual function is virtual table, which stores the address of the virtual function to be called finally. This virtual table is also called virtual function table.
class Animal { public: int m_age; virtual void speak() { cout << "Animal :: speak()" << endl; } virtual void run() { cout << "Animal :: run()" << endl; } }; class Cat : public Animal { public: int m_life; void speak() { cout << "Cat ::speak() " << endl; } void run() { cout << "Cat ::run() " << endl; } }; Animal * cat = new Cat(); cat->m_age = 20; cat->speak(); cat->run();
23.2.1 memory distribution map of cat objects (under X86 environment)
All Cat objects (whether in global area, stack or heap) share the same virtual table.
23.2.2 virtual table assembly analysis
Function of virtual table: when compiling the program, the compiler can only judge the type of the class to which the object belongs according to the pointer type. However, when there is a virtual function, the first address of the virtual table is automatically bound in the object memory applied in the heap, so that the real member function of the object can be called.
class Animal { public: int m_age; virtual void speak() { cout << "Animal :: speak()" << endl; } virtual void run() { cout << "Animal :: run()" << endl; } }; class Cat : public Animal { public: int m_life; void speak() { cout << "Cat ::speak() " << endl; } void run() { cout << "Cat ::run() " << endl; } }; Animal * cat1 = new Cat(); Animal * cat2 = new Cat(); return 0;
The cat1 and cat2 objects in the code above share the same virtual table.
If the parent class is declared as a virtual function, the subclass override must have a virtual function.
23.3 calling the member function implementation of the parent class
class Animal { public : virtual void speak() { cout << "Animal :: speak() " << endl; } }; class Cat :: public Animal { public : void speak() { Ainmal :: speak(); //Call the member function of the parent class to implement speak() cout << "Cat :: speak()" << endl; } };
23.4 virtual destructor
- If the parent class pointer points to the subclass object, the destructor should be declared as a virtual function (virtual destructor)
- Only when the parent class pointer is delete d will the destructor of the subclass be called to ensure the integrity of the destructor (the destructor of the parent class is a virtual function, and the destructor of the subclass is also a virtual function)
- Only when the parent class pointer is delete d will the destructor of the subclass be called to ensure the integrity of the destructor (the destructor of the parent class is a virtual function, and the destructor of the subclass is also a virtual function)
23.5 pure virtual function
-
Pure virtual function: a virtual function that has no function body and is initialized to 0. It is used to define the interface specification
-
Abstract Class
- Classes containing pure virtual functions cannot be instantiated (objects cannot be created)
- Abstract classes can also contain non pure virtual functions and member variables (as long as there is a pure virtual function, it is an abstract class)
- If the parent class is an abstract class and the subclass does not completely override the pure virtual function, the subclass is still an abstract class
class Animal { virtual void speak () = 0; virtual void walk () = 0; }; class Dog : Animal { void walk () { cout << "Dog :: walk()" << endl; } }; class Hasiqi : Dog { void speak() { cout << "Hasiqi :: speak() " << endl; } }; int main() { Animal animal; //Animal is an abstract class and cannot be instantiated Dog dog; //Dog is an abstract class Hasiqi hasiqi; //Hasiqi inherits the member function of Dog and has overridden the member function of Animal. It is not an abstract class return 0; }
24. Multiple inheritance
- C + + allows a class to have multiple parent classes (not recommended, which will increase the complexity of programming)
class Student { public: int m_score; void study() { cout << "Student::study()" << endl; } }; class worker { public : int m_salary; void work() { cout << "worker::work() " << endl; } }; class Undergraduate: public student, public Worker { public : int m_grade; void play() { cout << "Udergraduate :: play()" << endl; } }; Udergraduate ug; ug.m_score = 100; ug.salary = 2000; ug.m_grade = 4; ug.study(); ug,work(); ug,play(); }
Schematic diagram of modified program memory:
24.1 constructor call under multi inheritance system
class Student { int m_score; public: Student(int score) { this->m_score = score; } }; class Workerj { int m_salary; publc: worker(int salary) { this->m_salary = salary; } }; class Udergraduate : public Student, public Worker { public: undergaduate(int score, int salary) : Student(score), Worker(salary) { } //Use the constructor of the parent class to initialize the private member variable of the parent class };
24.2 multiple inheritance - virtual functions
- If multiple parent classes inherited by the parent class have virtual functions, the child class object will generate multiple corresponding virtual tables
24.3 functions with the same name and member variables with the same name
24.3.1 homonymous function
class Student { public: void eat() { cout << "student :: eat() " << endl; } }; class Worker { public : void eat() { cout << "Worker ::eat()" << endl; } }; class Undergraduate : public Student, public Worker { public : void eat() { cout << "Undergraduate :: eat () " << endl; } }; Undergraduate ug; ug.Student::eat(); // Student::eat() ug,Worker::eat(); // Worker::eat() ug.Undergraduate :: eat(); // Undergraduate :: eat() ug.eat(); //Undergraduate::eat()
24.3.2 member variables with the same name
class Student { public : int m_age; }; class Undergraduate : public Student, public Worker { public : int m_age; }; Undergraduate ug; ug.m_age = 10; ug.Student:: m_age = 20; ug.Worker::m_age = 30; cout << ug.Undergraduate::m_age << endl; // 10; cout << ug.Student::m_age << endl; // 20 cout << ug.Worker::m_age << endl; // 30
Schematic diagram of this function memory:
24.3 diamond inheritance
- Problems caused by diamond inheritance:
- The lowest subclass inherits redundant and repeated member variables from the base class;
- The lowest subclass cannot access the members of the base class, which is ambiguous.
class Person { int m_age; }; class Student : public Person { int m_score; }; class Worker : public Person { int m_salary; }; class Undergraduate : public Student, public Worker { int m_grade; };
Object diagram:
Object memory distribution diagram:
24.4 virtual inheritance
- Virtual inheritance can solve the problems caused by diamond inheritance;
- The Person class is called a virtual base class.
class Person { int m_age = 1; }; class Student : virtual public Person { int m_score = 2; }; class Worker : virtual public Person { int m_salary = 3; }; class Undergraduate : public Student, public Worker { int m_grade = 4; };
Relationship diagram of this Code:
;
The red part represents the offset between the virtual table pointer and the beginning of this class (generally 0);
The green part represents the offset between the first member variable of the virtual base class and the beginning of this class.
25. static member
-
Static member: member variable \ function modified by static;
- It can be accessed through object (object. Static member), object pointer (object pointer - > static member) and class (class name:: static member);
-
Static member variables:
- It is stored in the data segment (global area, similar to global variables), and there is only one memory during the operation of the whole program;
- Compared with global variables, it can set access permissions (public, protected and private) to achieve the purpose of local sharing;
- It must be initialized outside the class. static is not allowed during initialization. If the declaration and implementation of the class are separated (initialized in the implementation. cpp).
-
Static member function:
- This pointer cannot be used internally (this is the address value used to store the object, and this pointer can only be used inside non static member functions);
- Cannot be a virtual function (virtual functions can only be non static member functions));
- You cannot access non static member variables \ functions internally, but only static member variables \ functions;
- Static member variables \ functions can be accessed inside non static member functions;
- Constructors and destructors cannot be static;
- When the declaration and implementation are separated, the implementation part cannot be static.
class Person { public : static int m_age; int m_height; int m_weight; }; int Person::m_age = 0; //Static member variables must be initialized outside class Student : public Person { } int main() { cout << &Person :: m_age << endl; cout << &Student :: m_age << endl; //Is a global variable, so the address value is the same return 0; }
class Person { public : static int m_age; int m_height; int m_weight; }; int Person::m_age = 0; //Static member variables must be initialized outside class Student : public Person { public: static int m_age; } static Student::m_age = 1; int main() { cout << &Person :: m_age << endl; cout << &Student :: m_age << endl; //The two global variables are different, so the address values are different return 0; }
Class inheritance is for non static variables.
25.1 example of static application
Number of car s:
```cpp class Car { int m_price; static int ms_count; //Only classes can access static member variables that cannot be accessed outside public: static int gerCount() { //You can call the getCount function through the class name return ms_count; } Car(int price = 0) : m_price(price) { ms_count ++; } ~Car() { ms_count--; } }; int Car::ms_count = 0; //Static member variables need to be initialized outside
25.2 singleton mode of static application
Singleton pattern: a design pattern that ensures that a class always creates only one object.
Implementation steps:
- Privatization of constructors;
- Define a private static member variable to point to the unique singleton object;
- Provide a public interface to access singleton objects.
class Rocket { private: Rocket() {} ~Rocket() {} //Prevent users from deleting objects in the generated Rocket static Rocket *ms_rocket; public: static Rocket *sharedRockert() { //Multithreading safety should be considered here if (ms_rocket == NULL) { ms_rocket = new Rocket(); } return ms_rocket; } static void deleteRocket() { // Multithreading safety should be considered here if (ms_rocket != NULL) { delete ms_rocket; //Only the objects in heap space are released ms_rocket = NULL; //ms_ The rocket pointer is still in the stack and will not be cleared } } };
26. const
- Const member: member variable and non static member function modified by const;
- const member variable:
- Must be initialized (class internal initialization), and the assignment can be initialized directly at the time of declaration;
- Non static const member variables can also be initialized in the initialization list.
- const member function (non static)
- **Const keyword is written after the parameter list. The declaration and implementation of the function must contain const ` ` * *:
- Non static member variables cannot be modified internally;
- Only const member function and static member function can be called internally;
- Non const member functions can call const member functions.
- Const member functions and non const member functions constitute overloads;
- Non const objects (pointers) call non const member functions first;
- Const object (pointer) can only call const member function and static member function
- **Const keyword is written after the parameter list. The declaration and implementation of the function must contain const ` ` * *:
example:
27. Reference type member
- Reference type member variables must be initialized (regardless of static);
- Initialize directly at the time of declaration;
- Initialize through the initialization list.
class Car { int age; int &m_price = age; public: Car(int &price) : m_price(price) { } };
28. Copy constructor
- Copy constructor is a kind of constructor (called when assigning a value to an existing object after creating a new object);
- When an existing object is used to create a new object (similar to copy), the copy constructor of the new object will be called for initialization;
- The format of the copy constructor is fixed and receives a const reference as an argument.
28.1 calling the copy constructor of the parent class
When initializing a subclass, if there is no obvious call to the copy constructor of the parent class, the constructor of the parent class will be used to initialize the member variables.
class Person { int m_age; public : Person(int age) : m_age(age) { Person(const Person &person) :m_age(person.age) { } }; class Student : public Person { int score; public : Student(int age, int score) : person(age), m_score(score) { } Student(const Student &student) : Person(student), m_score(student.m_score) { } //Call the copy constructor of the parent class to initialize the member variable indirectly };
28.2 copy constructor
Car car(100, "BWM 730Li"); Car car2 = car; Car car3(car2); Car car4; Car4 = car3;
- car2 and car3 are initialized through the copy constructor, while car and car4 are initialized through the non copy constructor.
- car4 = car3 is an assignment operation (shallow copy by default) and does not call the copy constructor.
28.3 light copy and deep copy
-
The default copy provided by the compiler is shallow copy:
- Copy all values of an object to another variable;
- If a member variable is a pointer, only the address value stored in the pointer will be copied, and the memory space pointed to by the pointer will not be copied;
- This may cause the heap space to be free multiple times.
-
If you need to implement deep copy, you need to customize the copy constructor:
- Copy the memory space pointed to by the pointer type member variable to the new memory space.
28.3.1 light copy
class Car { int m_price; char *m_name; public: Car(int price = 0, char *name = NULL) : m_price(price), m_name(name) { } void display() { cout << "price is" << m_price << ", name is " << m_name << endl; } }; Car *g_car; void test() { char name2[] = {'b', 'm', 'w', '\0'}; g_car = new Car(100, name2); } int main() { //const char * name = "bmw"; // char name2[] = {'b', 'm', 'w', '\0'}; test(); //As soon as the function ends, name2 memory is released, g_car became a wild pointer. g_car -> display(); return 0; }
- If the name array in the stack space is released, the pointer in the heap space still points to the memory area, which becomes a wild pointer, making it unsafe to access the space not used by the user.
Memory diagram of the program:
#include <iostream> using namespace std; class Car { int m_price; char* m_name; public : Car(int price = 0, const char* name = nullptr) : m_price(price) { //const parameters can receive constants and variables if (name == nullptr) return; //Request new heap space m_name = new char[strlen(name) + 1] {}; //Copy string data to new heap space strcpy(m_name, name); } ~Car() { if (m_name == nullptr) return; delete[] m_name; m_name = nullptr; } void display() { cout << "price is" << m_price << ", name is " << m_name << endl; } }; int main() { //char name[] = { 'b', 'm', 'w', '\0' }; Car car1(100, "bmw"); Car car2 = car1; //Car car2(car1); car2->display(); return 0; }
Schematic diagram of modified program memory:
- Pointer type variables will only copy the address value, and the contents in the heap space may be released twice.
28.3.2 deep copy
#include <iostream> using namespace std; class Car { int m_price; char* m_name; void copyName(const char* name) { if (name == nullptr) return; //Request new heap space m_name = new char[strlen(name) + 1] {}; //Copy string data to new heap space strcpy(m_name, name); } public : Car(int price = 0, const char* name = nullptr) : m_price(price) { //const parameters can receive constants and variables copyName(name); } Car(const Car &car) { //User-Defined Copy Constructor this->m_price = car.m_price; copyName(car.name); } ~Car() { if (this -> m_name != nullptr) { delete[] this->m_name; } void display() { cout << "price is" << m_price << ", name is " << m_name << endl; } }; int main() { //char name[] = { 'b', 'm', 'w', '\0' }; Car car1(100, "bmw"); Car car2 = car1; //Car car2(car1) car1->display(); return 0; }
- Copy the content pointed to by the pointer to the new storage space.
Memory diagram of the program:
29. Object type parameters and return values
- Using the object type as the parameter or return value of the function may produce some unnecessary intermediate objects (so try to use the reference of the object).
class Car { int m_price; public: Car() { } Car(int price) :m_price(price) {} Car(const Car &car) :m_price(car.m_price) { } }; void test1(Car car) { //The copy constructor is called when the arguments are combined } Car test2() { Car car(20); //Car(int price) return car; //The copy constructor is called } Car car1(10); //Car(int price) test1(car1); //Car(const Car &car) Car car2 = test2(); //Car(const Car &car) Car car3(30); ///Car(int price) car3 = test2(); //Car(const Car &car)
30. Anonymous object (temporary object)
- Anonymous object: an object that has no variable name and is not pointed to by a pointer. The destructor is called immediately after it is used up.
void test1(Car car) { } Car test2() { return Car(60); } Car(10); //Car(int price) Car(20).display(); // Car(int price) Car car1 = Car(30); // Car(int price) test1(Car(40)); // Car(int price) Car car3(50); // Car(int price) car3 = test2(); // Car(int price)
31. Implicit construction (transformation construction)
- Implicit construction exists in C + +: in some cases, single parameter constructors will be implicitly called.
void test1(Car car) { } Car test2() { return 70; } Car car1 = 10; // Car(int price) Car car2(20); // Car(int price) car2 = 30; // Car(int price), anonymous object test1(40); // Car(int price) Car car3 = test2(); // Car(int price)
- You can also use the keyword explicit to suppress implicit construction (you must explicitly call the constructor).
class Car { int m_price; pubic: Car() { } explicit Car(int price) :m_price(price) { } Car(const Car &car) :m_price(car.m_price) {} };
32. Constructor automatically generated by compiler
- The C + + compiler will automatically generate parameterless constructors for classes under certain circumstances, such as:
1. The member variable is initialized while being declared;
2. Virtual functions are defined (the compiler must create a virtual table for the object, and the object generates one more byte);
3. Virtual inherits other classes (the first byte of the created object is the virtual table address);
4. Contains a member of the object type, and this member has a constructor (generated or customized by the compiler);
Namely:
class Car { public: int m_price; Car() { } //The constructor is customized in the Car class }; class Person { public: Car car; // Generate call the constructor in car to generate Car object }; int main() { Person person; //Generate person object return 0; }
Note: however, when there is no constructor in the Car class, the constructor will not be called because no additional operations are specified for the person object.
- The parent class has a constructor (compiler generated or customized).
- Summary:
- After the object is created, when some additional operations need to be done (such as memory operation and function call), the compiler will generally automatically generate a parameterless constructor for it.
33. Friends
- Friends include friend functions and friend classes;
- If function A (non member function) is declared as A friend function of class C, then function A can directly access all members of class C objects;
- If class A is declared as a friend class of class C, all members of class a can directly access all members of class C objects;
- Friend destroys the encapsulation of object-oriented, but it can improve performance in some places where member variables are accessed frequently.
Example:
class Point { friend Point add(const Point &, const Point &); friend class Math; private: int m_x; int m_y; public: Point() { } Point(int x, int y) :m_x(x), m_y(y) { } }; Ponit add(const Point &p1, const Point &p2) { return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y); } class Math { void test() { Point point; point.m_x = 10; point.m_y = 20; } static void test2() { Point point; point.m_x = 10; point.m_y = 20; } };
34. Internal class
- If class A is defined inside class C, then class A is an inner class (nested class)
- Features of internal classes:
- Support public, protected and private permissions;
- Member functions can directly access all members of their external class objects (not vice versa);
- Member functions can directly access static members of their external classes without class name and object name;
- It will not affect the memory layout of the external class (that is, when creating an external class, the size of the class is the size of the member variables of the class);
- It can be declared inside the external class and defined outside the external class.
class Point { static void test1() { cout << "Point :: test1()" << endl; } static int ms_test2; int m_x; int m_y; public: class Math { public: void test3() { cout << "Point :: Math :: test3()" << endl; test1(); ms_test2 = 10; Point point; point.m_x = 10; point.m_y = 20; } }; };
34.1 inner class - separation of declaration and Implementation
Method 1:
class Point { class Math { void test(); }; }; void Point::Math::test() { }
Method 2:
class Point { class Math; }; class Point::Math { void test() { } };
Method 3:
class Point { class Math; }; class Point :: Math { void test(); } void Point :: Math :: test() { }
35. Local class
-
The classes defined inside a function are called local classes;
-
Characteristics of local classes:
- The scope is limited to the inside of the function;
- All its members must be defined inside the class, and static member variables are not allowed to be defined (when static member variables are defined inside the class, it means that they must be initialized before use, and the member can be assigned directly outside the class (outside the function), which is contrary to the first article);
- Member functions cannot directly access local variables of a function (local variables of a function only exist when the function is called) (except static variables)