C++ Object Oriented/Core Programming/Introduction to C++

1 Memory partition model

When the C++ program is executed, the general direction of memory is divided into 4 areas

  • Code area: store the binary code of the function body, managed by the operating system
  • Global area: store global variables and static variables and constants (string constants, constants modified by const)
  • Stack area: automatically allocated and released by the compiler, storing function parameter values, local variables, etc.
  • Heap area: allocated and released by the programmer, if the programmer does not release it, it will be reclaimed by the operating system at the end of the program

1.1 Before the program runs

After the program is compiled, an exe executable program is generated, which is divided into two areas before the program is executed:

code area:

Store the machine instructions executed by the CPU. The code area is shared. The purpose of sharing is that for frequently executed programs, only one copy of the code is required in the memory.

The code area is read-only, the reason for making it read-only is to prevent the program from accidentally modifying its instructions

Global zone:

The data in this area is released by the operating system after the program ends


1.2 After the program runs

Stack area:

Automatically allocated and released by the compiler, storing function parameter values, local variables, etc.

Note: Do not return the address of the local variable , the data created in the stack area will be automatically released by the compiler

int * func()
{
    
    
	int a = 10;
	return &a;
}

int main()
{
    
    
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;

	return 0;
}

Heap area:

It is allocated and released by the programmer. If the programmer does not release it, it will be reclaimed by the operating system at the end of the program

Mainly use new to open up memory in the heap area

int* func()
{
    
    
	int* a = new int(10);
	return a;
}

int main()
{
    
    
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;

	return 0;
}

The data developed in the heap area is manually developed by the programmer, released manually, and released using the delete operator

The data created by new will return a pointer of the type corresponding to the data


1.3 new implements dynamic memory allocation

  • First usage: assigning a P = new T;variable

  • Parameter explanation: T is any type name, P is a pointer of type T*

  • Function: dynamically allocate a memory space of sizeof(T) bytes, and assign the starting address of the memory space to P

int* pn;
pn = new int;
*pn = 5;

  • Second usage: allocate an P = new T[N];array
  • Parameter explanation: T is any type name, P is a pointer of T* type, N is the number of array elements to be allocated, and can be an integer expression
  • Function: dynamically allocate a piece of memory space with a size of N*sizeof(T) bytes, and assign the starting address of the memory space to P
int* pn;
int i = 5;
pn = new int[i * 20];
pn[0] = 20;

  • The memory space dynamically allocated by new must be released by delete, otherwise it will occupy the space all the time, which may cause insufficient memory space for the operating system or other applications to run
delete 指针;  // 该指针必须指向 new 出来的空间

int* p = new int;
*p = 4;
delete p;
delete p;  // 不能多次 delete
delete [] 指针;  // 该指针必须指向 new 出来的空间

int* p = new int[20];
p[0] = 9;
delete [] p;


2 quotes

Function: alias the variable

Syntax 数据类型& 别名 = 原名:

  • Supplements or notes for pointers and references:
int a = 10;
// 等价 int* const b = &a;这里也说明了为啥引用一旦初始化之后,就不能再改变了
int& b = a; 

a = 100;  // 此时,a 和 b 的值都是 100
b = 200;  // 此时,a 和 b 的值都是 200


int x = 4;
const int& y = x;  // 注意这里的 const 修饰的 y,而不是 x !!!

x = 9;  // 这里仍然可以修改 x 的值,x 和 y 的值都是 9
// y = 99;  // 错误!!因为是 const 修饰的 y

----------------------------------------

// 指针复习
int n = 4;

int* ptr = &n;
*ptr = 10000;

// 限定 p,既不能修改 p 的指向,也不能修改 p 指向的值,这里说指向的值,是不能用 *p 修改 n 的值
const int* const p = &n;
n = 10;
// *ptr = 9;  // 错误!!表达式必须是可以修改的左值

Features:

  • reference must be initialized

  • After the reference is initialized, it cannot be changed (because the essence of the reference is actually a pseudo pointer)

    int rat;
    int& rodent = rat;  // rodent 和 *ptr 是等价的
    int* const ptr = &rat;  // 指针常量,不能改变指针的指向,但是可以改变指向的值
    
  • When the formal parameter is a const reference and the actual parameter does not match, c++ will generate a temporary variable, and its behavior is similar to passing by value. In order to ensure that the original data is not modified, a temporary variable will be used to store the value. The following two situations will generate a temporary variable Variable:
    1. The type of the actual parameter is correct, but it is not an lvalue (a data object that can be referenced, that is, an object that can be accessed by address)
    2. The type of the actual parameter is incorrect, but it can be converted to the correct type

  • If the declaration specifies the reference as const, C++ will generate a temporary variable when necessary, but the reference variable modified by const cannot be changed later

double refcube(const double& a)
{
    
    
 	return a*a*a;
}

double side = 3.0;
long edge = 5L;
double lens[4] = {
    
    2.0, 5.0, 10.0, 12.0};

double c1 = refcube(side);  // a 是 side
double c2 = refcube(lens[2]);  // a 是 lens[2]
double c3 = refcube(edge);  // 虽然 c3 的结果是 125,但是 a 是临时变量
  • Now, if it is an ordinary reference modified parameter, the actual parameter type does not match, and an error will be reported. Unlike before, there will be a temporary variable conversion process
  • Here are some additional knowledge points: non-lvalues ​​include literal constants (except string constants, which have their own addresses) and expressions containing multiple items
  • When a function passes parameters, references allow formal parameters to modify actual parameters, which can simplify pointer modification of actual parameters
  • The reference must be a variable on the stack or heap, and cannot directly refer to a constant. As int& a = 10;mentioned here, the constant cannot be directly referenced because the constant is in memory and is a temporary address, which can be released at any time

Summary: The effect of passing parameters by reference is the same as passing by address, and the syntax of reference is more concise


  • A reference is a return value that can be used as a function

NOTE: Do not return local variable references

Usage: function call as lvalue

#include<iostream>
#include<string>

using namespace std;

// 返回局部变量引用
int& test01() {
    
    
	int a = 10;  // 局部变量
	return a;
}

// 返回静态变量引用
int& test02() {
    
    
	static int a = 20;
	return a;
}

int main() {
    
    
	// 不能返回局部变量的引用
	int& ref = test01();
	cout << "ref = " << ref << endl;  // ref = 10
	//cout << "ref = " << ref << endl;  // test01 返回的是局部变量,函数调用完之后,a 的内存被释放了,因此 ref 也被释放

	int& ref2 = test02();
	cout << "ref2 = " << ref2 << endl;  // ref2 = 20
	cout << "ref2 = " << ref2 << endl;  // ref2 = 20
  
	// 如果函数做左值,那么必须返回引用
	test02() = 1000;
	cout << "ref2 = " << ref2 << endl;  // ref2 = 1000
	cout << "ref2 = " << ref2 << endl;  // ref2 = 1000
   
	return 0;
}

The essence of the reference is implemented inside C++ as a pointer constant , but all pointer operations are done for us by the compiler

// 发现是引用,转换为 int* const ref = &a;
void func(int& ref)
{
    
    
	ref = 100;  // ref 是引用,转换为 *ref = 100
}
int main()
{
    
    
	int a = 10;
    
   	// 自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
	int& ref = a; 
	ref = 20;  // 内部发现 ref 是引用,自动帮我们转换为: *ref = 20;
    
	func(a);

	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;

	return 0;
}
  • Constant references are mainly used to modify formal parameters to prevent misuse
// 引用使用的场景,通常用来修饰形参
void showValue(const int& v)
{
    
    
	// v += 10;  // 因为是 const 修饰,所以不能修改 v 的值
	cout << v << endl;
}

int main()
{
    
    
	// int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
	// 但是如果是 const 修饰的引用,编译器优化代码 int temp = 10; const int& ref = temp;
	const int& ref = 10;

	// ref = 100;  // 加入const后不可以修改变量
	cout << ref << endl;

	// 函数中利用常量引用防止误操作修改实参
	int a = 10;
	showValue(a);

	return 0;
}

Replenish:

  • You cannot assign a constant pointer to a non-constant pointer, but vice versa; you cannot assign a constant reference to a non-constant reference, and vice versa
int a = 10;
const int& x = a;
// int& y = x;  // 这里会报错,因为将 int& 类型的引用绑定到 const int& 类型的初始值设定时,限定符会被丢弃

-----------------------------------------------

int b = 10;
int& m = b;
const int& n = m;  // 这里是可以正常编译的

b = 20;  // b m n 的值都是 20
m = 49;  // b m n 的值都是 49
// n = 99;  // 错误!!表达式必须是可以修改的左值

-----------------------------------------------

// 指针同上,但是也可以通过强制类型转换的方式,把常量指针赋值给非常量指针
const int* p1;
int* p2;

p1 = p2;  // ok
p2 = p1;  // error

p2 = (int*) p1;  // ok,强制转换

Constant member functions added:

  • Add the const keyword after the member function declaration of the class, then the member function is a constant member function

  • During the execution of a constant member function, the object it acts on should not be modified (the value of the member variable cannot be modified, except for the static member variable; nor can the same non-constant member function be called [because it may change], except for the static member function)

  • Two member functions with the same name and parameters, but one is const and the other is not, count as overloading


3 functions

3.1 Ordinary functions

  • There can be placeholder parameters in the formal parameter list of the function, which are used as placeholders, and the place must be filled when calling the function

  • The purpose of function parameters being default is to improve the scalability of the program

Grammar 返回值类型 函数名 (数据类型){}:

// 函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
    
    
	cout << "this is func" << endl;
}

int main()
{
    
    
	func(10, 10);  // 占位参数必须填补

	return 0;
}

Function overloading: function names can be the same, improving reusability

Function overloading satisfies the conditions:

  • under the same scope
  • same function name
  • The function parameters are of different types or numbers or orders

Note: The return value of a function cannot be used as a condition for function overloading

Note on function overloading:

  • references as overload conditions
  • Function overloading encounters function default parameters
// 1、引用作为重载条件
void func(int& a)  // int& a = 10; 错误!
{
    
    
	cout << "func (int& a) 调用 " << endl;
}

// const int& a = 10; 注意这个是合法的!
// 相当于 int tmp = 10; const int& a = tmp;
void func(const int& a) 
{
    
    
	cout << "func (const int& a) 调用 " << endl;
}

// 2、函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
    
    
	cout << "func2(int a, int b = 10) 调用" << endl;
}

void func2(int a)
{
    
    
	cout << "func2(int a) 调用" << endl;
}

int main()
{
    
    
	int a = 10;
	func(a);  // 调用无 const
	func(10);  // 调用有 const

	// func2(10);  // 碰到默认参数产生歧义,需要避免

	return 0;
}

3.2 Inline functions

  • Function calls have time overhead. If the function itself has only a few statements, the execution is very fast. In contrast, the overhead of calling functions will be much larger

  • In order to reduce the overhead of function calls, an inline function mechanism is introduced. When the compiler processes a call statement for an inline function, it inserts the code of the entire function into the call statement instead of generating a statement that calls the function

  • Add the inline keyword before the function definition to define an inline function

inline int Max(int a, int b)
{
    
    
	if (a > b) return a;
	return b;
}

4 Classes and Objects

Structured programming: program = data structure + algorithm

Object Oriented Program = Class + Class + ... + Class

The process of designing a program is the process of designing a class!

Object-oriented programming method:

  • Summarize the common characteristics (attributes) of certain types of objective things to form a data structure
  • Summarize the behaviors that such things can perform to form a function that can be used to manipulate the data structure (this step is called "abstraction")

  • Class member functions and class definitions are written separately (definitions outside the class need to use the :: operator to limit the scope)
class CRectangle
{
    
    
public:
	int w, h;
	int Area();  // 声明成员函数
	int Perimeter();
	void Init(int w_, int h_);
};

int CRectangle::Area()
{
    
    
	return w * h;
}

int CRectangle::Perimeter()
{
    
    
	return 2 * (w + h);
}

void CRectangle::Init(int w_, int h_)
{
    
    
	w = w_; h = h_;
}

The three major features of C++ object-oriented are: encapsulation, inheritance, and polymorphism

C++ believes that everything is an object, and objects have attributes and behaviors


4.1 Packaging

  • Variables in a class are called member variables, functions in a class are called member functions, variables defined by a class, also called instances of a class, are also "objects"

  • Each object has its own storage space. If a member variable of one object is changed, it will not affect another object.

  • Through a certain grammatical form, the data structure and the function that operates the data structure are "bundled" together to form a class, so that the data structure and the algorithm that operates the data structure present a close relationship, which is "encapsulation"

The meaning of packaging:

  • properties and behavior as a whole, represented as things
  • Control attributes and behaviors with permissions

There are three types of access rights:

  1. public public permissions can be accessed within the class and can be accessed outside the class
  2. protected The protection permission class can be accessed outside the class and cannot be accessed
  3. private private permission class can be accessed outside the class can not be accessed

The mechanism of setting private members is called "hiding". The purpose of hiding is to force the access to member variables to be carried out through member functions. After modifying the type and other attributes of member variables in the future, only modify the member functions. Otherwise, all statements that directly access member variables need to be modified

Inside a member function of a class, you can access:

  • All properties and functions of the current object
  • All properties and functions of other objects of the same type

Outside the member functions of a class, only the public members of objects of that class can be accessed

The only difference between struct and class in C++ is the default access rights

  • struct default permissions are public
  • class default permission is private

4.2 Object initialization and cleanup

Object-oriented in C++ comes from life, each object has initial settings and cleanup data before object destruction


4.2.1 Constructors and destructors

Object initialization and cleanup are two very important security issues

C++ uses constructors and destructors to solve the above problems. These two functions will be automatically called by the compiler to complete object initialization and cleanup.

The initialization and cleanup of objects is what the compiler forces us to do, so if we don't provide construction and destruction, the compiler will provide

The constructor and destructor provided by the compiler are empty implementations

  • Constructor: Assign values ​​to the member properties of the object when creating the object. The constructor is automatically called by the compiler without manual call
  • Destructor: The system automatically calls before the object is destroyed to perform some cleanup work

Constructor syntax 类名(){}:

  1. Constructor, no return value and no void
  2. The function name is the same as the class name
  3. Constructors can have parameters, so overloading can occur
  4. The program will automatically call the constructor when calling the object, no need to call it manually, and it will only be called once

Destructor syntax ~类名(){}:

  1. Destructor, no return value and no void
  2. The function name is the same as the class name, precede the name with the symbol ~
  3. Destructors cannot have parameters, so overloading cannot occur
  4. The program will automatically call the destructor before the object is destroyed, no need to call it manually, and it will only be called once

There are two classifications of destructors:

  • Divided according to parameters: construction with parameters and construction without parameters (default)
  • Divided by type: ordinary construction and copy construction

Three ways to call the destructor:

  • bracketing
  • display method
  • implicit conversion method
// 1、构造函数分类
class Person 
{
    
    
public:
	// 无参(默认)构造函数
	Person() {
    
    
		cout << "无参构造函数!" << endl;
	}

	// 有参构造函数
	Person(int a) {
    
    
		age = a;
		cout << "有参构造函数!" << endl;
	}

	// 拷贝构造函数
	Person(const Person& p) {
    
      // 限定拷贝的时候,不能更改实参
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}

	// 析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
	}
public:
	int age;
};

// 2、构造函数的调用
void test01() {
    
    
	Person p;  // 调用无参构造函数
}

// 调用有参的构造函数
void test02() {
    
    

	// 2.1 括号法(常用)
	Person p1(10);

	// Person p2();  // 注意1:调用无参构造函数不能加括号,否则编译器认为这是函数声明

	// 2.2 显式法
	Person p2 = Person(10); 
	Person p3 = Person(p2);
	// Person(10);  // 单独写就是匿名对象,当前行结束之后,马上析构

	// 注意2:这里不要利用拷贝构造函数初始化匿名对象,编译器会认为是对象声明
	// Person(p9);  // Person(p9) == Person p9;

	// 2.3 隐式转换法
	Person p4 = 10;  // Person p4 = Person(10); 
	Person p5 = p4;  // Person p5 = Person(p4);  // 拷贝构造

	// Person p5(p4);
}

There are usually three situations in which the copy constructor is called in C++:

  • Use one object to initialize another object of the same kind
  • If a function has a parameter that is an object of class A, then when the function is called, the copy constructor of class A will be called
  • If the return value of the function is an object of class A, when the function returns, the copy transformation function of A is called
class Person
{
    
    
public:
	Person() {
    
    
		cout << "无参构造函数!" << endl;
		mAge = 0;
	}
	Person(int age) {
    
    
		cout << "有参构造函数!" << endl;
		mAge = age;
	}
	Person(const Person& p) {
    
    
		cout << "拷贝构造函数!" << endl;
		mAge = p.mAge;
	}

	~Person() {
    
    
		cout << "析构函数!" << endl;
	}
public:
	int mAge;
};

// 1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
    
    
	Person man(100);  // p 对象已经创建完毕
	Person newman(man);  // 调用拷贝构造函数
	Person newman2 = man;  // 拷贝构造
	// 隐式转换法
	// Person newman3 = man;  // 不是调用拷贝构造函数
}

// 2. 值传递的方式给函数参数传值
// 相当于Person p1 = p;
void doWork(Person p1) {
    
    }
void test02()
{
    
    
	Person p;  // 无参构造函数
	doWork(p);
}

// 3. 以值方式返回局部对象
Person doWork2()
{
    
    
	Person p1;  // 局部对象在 doWork2() 作用完后,随即被释放
	cout << (int*)&p1 << endl;

	// 注意这里返回 p1 是可以的,因为他在这里创建了一个副本,这个副本指的是拷贝构造函数产生的对象,生命周期要看它被使用到什么时候!只有返回引用和指针类型的数据才会报错
	return p1;  // 这里返回的不是 p1 本身,而是创建一个 p1 副本返回
}

void test03()
{
    
    
	Person p = doWork2();
	cout << (int*)&p << endl;
}

By default, the compiler adds at least 3 functions to a class

1. Default constructor (no parameters, function body is empty)

2. Default destructor (no parameters, function body is empty)

3. The default copy constructor, which copies the value of the property

Constructor calling rules:

  • If the user defines a constructor with parameters, C++ no longer provides a default no-argument construction, but will provide a default copy construction
  • If a user defines a copy constructor, c++ no longer provides another constructor

Note: Constructor knowledge points supplement

  1. If you define a parameterized constructor, you can no longer use a parameterless constructor to define variables
class Complex
{
    
    
private:
	double real, imag;
public:
	Complex(double r, double i = 0);
};

Complex::Complex(double r, double i)
{
    
    
	real = r, imag = i;
}

int main()
{
    
    
	Complex c1;  // error,缺少构造函数的参数
	Complex* pc = new Complex;  // error,缺少构造函数的参数
	Complex c1(2);  // OK
	Complex* pc = new Complex;  // OK

	return 0;
}

  1. Use of constructors in arrays
class CSample
{
    
    
	int x;
public:
	CSample()
	{
    
    
		cout << "Constructor 1 Called" << endl;
	}
	CSample()
	{
    
    
		x = n;
		cout << "Constructor 2 Called" << endl;
	}
};

int main()
{
    
    
	CSample array[2];  // 输出两次 Constructor 1 Called
	cout << "step1" << endl;

	CSample array2[2] = {
    
     4, 5 };
	cout << "step2" << endl;  // 输出两次 Constructor 2 Called

	CSample array3[2] = {
    
    3};
	cout << "step3" << endl;  // Constructor 2 Called,Constructor 1 Called

	CSample* array4 = new CSample[2];  // 输出两次 Constructor 1 Called

	delete[] array4;

	return 0;
}


Replenish:

01 copy constructor:

  • Only one parameter, a reference to an object of the same kind
  • The shape is like X::X( X& ) or X::X( const X& ), choose one of the two, and note that the parameter must be a reference! The latter can take constant objects as arguments
  • If no copy constructor is defined, the compiler generates a default copy constructor, and the default copy constructor completes the copy function

Notice:

  • Assignment between objects does not cause the copy constructor to be called
class CMyclass
{
    
    
public:
	int n;
	CMyclass() {
    
    };
	CMyclass(CMyclass& c) {
    
    
		n = 2 * c.n;  // 拷贝构造函数一般不这么做,这里只是为了举例子
	};
};

int main()
{
    
    
	CMyclass c1, c2;
	c1.n = 5;
	c2 = c1;  // 赋值语句,不是初始化语句,因此这里不会调用拷贝构造函数
	CMyclass c3(c1);  // 会调用拷贝构造函数

	cout << "c2.n=" << c2.n << ",";  // 5
	cout << "c3.n=" << c3.n << endl;  // 10

	return 0;
}

02 Use of constant reference parameters

void fun(CMyclass obj_)
{
    
    
	cout << "fun" << endl;
}
  • For the above function, the generation of formal parameters when calling will cause the call of the copy constructor, which is expensive. Therefore, consider using a CMyclass& reference type as a parameter
  • If you want the value of the actual parameter not to be changed in the function, you can add the const keyword

03 type conversion constructor

  • The purpose of defining a conversion constructor is to achieve automatic conversion of types
  • There is only one parameter, and the constructor that is not a copy constructor can generally be regarded as a conversion constructor
  • When needed, the compilation system will automatically call the conversion constructor to create an unnamed temporary object (or temporary variable)
class Complex
{
    
    
public:
	double real, imag;
	Complex(int i)  // 类型转换构造函数,如果碰到类型不匹配的,那么会生成临时变量来接收
	{
    
    
		cout << "IntConstructor called" << endl;
		real = i;
		imag = 0;
	}

	Complex(double r, double i) {
    
     real = r; imag = i; }
};

int main()
{
    
    
	Complex c1(7, 8);
	Complex c2 = 12;
	// 因为这里直接赋值的话,类型是不匹配的。9 被自动转为临时 Complex 对象
	c1 = 9;

	cout << c1.real << "," << c1.imag << endl;

	return 0;
}

04 Destructors and arrays

  • At the end of the object array declaration cycle, the destructor of each element of the object array will be called
class Ctest
{
    
    
public:
	~Ctest() {
    
     cout << "destructor called" << endl; }
};

int main()
{
    
    
	Ctest array[2];  // 输出两次 destructor called
	cout << "End main" << endl;

	return 0;
}

05 Destructor and operator delete

  • The delete operation results in a destructor call
  • If new is an object array, [] should be written when using delete to release, otherwise only delete an object (call the destructor once)
Ctest* pTest;
pTest = new Ctest;  // 构造函数调用
delete pTest;  // 析构函数调用
------------------------------------------
pTest = new Ctest[3];  // 调用 3 次构造函数
delete[] pTest;  // 调用 3 次析构函数

06 The destructor is called after the object is returned as a function

class CMyclass
{
    
    
public:
	~CMyclass() {
    
     cout << "destructor" << endl; }
};

CMyclass obj;  // 全局函数变量,整个程序结束时,也会调用析构函数

// 这里的形参被传入参数时,调用 CMyclass 的构造函数,当参数对象消亡,也会调用析构函数
CMyclass fun(CMyclass sobj)
{
    
    
	// 这里注意 2 个点
	// 1.这里生成返回的临时对象
	return sobj;  // 函数调用返回时,返回的是生成的临时对象
}

int main()
{
    
    
	// 函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用
	// 这里是用临时对象给 obj 赋值
	// 2.临时对象的生命周期在执行完下行语句后消亡
	obj = fun(obj);  // 输出 3 次 destructor

	return 0;
}

07 When to call constructors and destructors

class CMyclass
{
    
    
	int id;
public:
	CMyclass(int i)  // 类型转换构造函数
	{
    
    
		id = i;
		cout << "id = " << id << " constructed" << endl;
	}
	~CMyclass() {
    
     cout << "id = " << id << " destructed" << endl; }
};

CMyclass d1(1);  // 因为这里有全局变量,所以 d1 调用构造函数比 main 中的还早

void Func()
{
    
    
	static CMyclass d2(2);
	CMyclass d3(3); 

	cout << "func" << endl;
}

int main()
{
    
    

	/* 程序执行输出结果
	id = 1 constructed
	id = 4 constructed
	id = 6 constructed
	id = 6 destructed
	main
	id = 5 constructed
	id = 5 destructed
	id = 2 constructed
	id = 3 constructed
	func
	id = 3 destructed
	main ends!
	id = 6 destructed
	id = 2 destructed
	id = 1 destructed
	*/

	CMyclass d4(4);
	d4 = 6;
	cout << "main" << endl;

	{
    
    CMyclass d5(5); }  // 局部变量,遇到有大括号后,变量使用结束

	Func();

	cout << "main ends!" << endl;

	return 0;
}

4.2.2 Deep Copy and Shallow Copy

Shallow copy: the reference is used as the return value of the function, which will cause repeated release of memory in the heap area (usually a problem that sometimes occurs when calling a constructor)

Deep copy: re-apply for space in the heap area and perform copy operations

class Person
{
    
    
public:
	// 无参(默认)构造函数
	Person() {
    
    
		cout << "无参构造函数!" << endl;
	}

	// 有参构造函数
	Person(int age, int height) {
    
    
		cout << "有参构造函数!" << endl;

		m_age = age;
		m_height = new int(height);
	}

	// 自定义拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person& p) {
    
    
		cout << "Person 拷贝构造函数!" << endl;

		m_age = p.m_age;
		// m_Height = p.m_height;  // 编译器默认写的浅拷贝

		// 如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_height = new int(*p.m_height);  // 这里要加括号,是固定用法,记住就行
	}

	// 析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
    
    
			delete m_height;
			m_height = NULL;
		}
	}
public:
	int m_age;
	int* m_height;  // 指针变量存放在栈,指针指向的值存放在堆中
};

void test01()
{
    
    
	Person p1(18, 180);  // 因为 m_height 是指针变量,所以,*m_height 的值被存放在堆中

	// 因为 p1 和 p2 是在栈中存储的,所以存储的时候,是先存储 p1 再存储 p2
	// 因此在释放(调用析构函数)的时候,先释放 p2 再释放 p1
	Person p2(p1);  // 拷贝构造函数,是浅拷贝。所以,p1 和 p2 的 m_height 指向的是同一块内存空间

	// 注意:这里用指针,是为了更好地突出深拷贝和浅拷贝面临的问题
	cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
	cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

Summary: If the attribute is opened in the heap area, you must provide a copy constructor yourself to prevent problems caused by shallow copying


4.2.3 Initialization List

Role: used to initialize properties

Syntax 构造函数():属性1(值1),属性2(值2)... {}:

Member objects and closed classes: A class with member objects is called an enclosing class

class Person
{
    
    
public:
	// 初始化列表方式初始化
	Person(int a, int b, int c): m_A(a), m_B(b), m_C(c) {
    
    }

	void PrintPerson()
	{
    
    
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}

private:
	int m_A;
	int m_B;
	int m_C;
};

int main() 
{
    
    
	Person p(1, 2, 3);
	p.PrintPerson();

	return 0;
}

4.2.4 Class objects as class members

Object member: A member of a class is an object of another class. Class B has object A as a member, and A is an object member

class A {
    
    }
class B
{
    
    
	A a;
}

When creating a B object, the order of construction is: first call the constructor of the object member (A), and then call the construction of this class; the order of destruction is reversed

Copy constructor of enclosing class

class A
{
    
    
public:
	A() {
    
     cout << "default" << endl; }
	A(A &a) {
    
     cout << "copy" << endl; }
};

class B {
    
     A a; };  // 既有无参构造函数,也有拷贝构造函数

int main()
{
    
    
	/*输出
	default
	copy*/

	// 说明 b2.a 是用类 A 的拷贝构造函数初始化的。而且调用拷贝构造函数时的实参就是 b1.a
	B b1;  // 这里输出的是 default
	B b2(b1);  // 此时,拷贝构造函数不是简单地把 b1 拷贝给 b2,是用类 A 的拷贝构造函数初始化的

	return 0;
}

4.2.5 Static members

  • Static member is to add keyword static before member variable and member function, called static member

  • Ordinary member variables have their own copy for each object, while static member variables have only one copy, which is shared by all objects
  • sizeof does not evaluate static member variables
  • Ordinary member functions must be specific to an object, while static member functions are not specific to an object; therefore, static members can be accessed without an object

// 1.类名::成员名
CRectangle::PrintTotal();

// 2.对象名.成员名
CRectangle r;
r.PrintTotal();  // 这里要注意,PrintTotal 并不是作用在 r 上的

// 3.指针->成员名
CRectangle* p = &r;
p->PrintTotal();  // 这里要注意,PrintTotal 并不是作用在 p 上的

// 4.引用.成员名
CRectangle& ref = r;
int n = ref.nTotalNumber;  // 这里要注意,nTotalNumber并不是作用在 ref 上的

  • Static member variables are essentially global variables, even if an object does not exist, the static member variables of the class also exist
  • Static member functions are essentially global functions

Static members are divided into:

  • static member variable

    • All objects share the same data
    • Allocate memory during compilation
    • In-class declaration, out-of-class initialization
  • ​​Static member functions

    • All objects share the same function
    • Static member functions can only access static member variables

The purpose of the mechanism of setting static members is to write some closely related global variables and functions into the class, which looks like a whole and is easy to maintain and understand

Static member variables must be declared or initialized once in the file defining the class, otherwise the compilation will pass and the link will not pass


4.3 C++ object model and this pointer

In C++, member variables and member functions in a class are stored separately

Only non-static member variables belong to objects of the class

class Person
{
    
    
public:
	Person()
	{
    
    
		mA = 0;
	}

	int mA;  // 非静态成员变量占对象空间
	static int mB;  // 静态成员变量不占对象空间

	// 普通方法也不占对象空间,所有方法共享一个函数实例
	void func() {
    
    
		cout << "mA:" << this->mA << endl;
	}

	// 静态成员函数也不占对象空间
	static void sfunc() {
    
    
	}
};

int main()
{
    
    
	cout << sizeof(Person) << endl;  // 4

	return 0;
}
  • The this pointer points to the object to which the called member function belongs

  • The this pointer is a pointer implicit in every non-static member function

  • The this pointer does not need to be defined, it can be used directly

The purpose of this pointer:

  • When the formal parameter and the member variable (variable in the class) have the same name, the this pointer can be used to distinguish them
  • To return the object itself **** (object outside the class) in the non-static member function of the class , you can use return *this
class Person
{
    
    
public:
	Person(int age)
	{
    
    
		// 1、当形参和成员变量同名时,可用 this 指针来区分
		this->age = age;  // this 指向的是类内非静态成员变量
	}

	Person& PersonAddPerson(Person p)
	{
    
    
		this->age += p.age;

		return *this;  // 返回对象本身,后续可以进行链式调用
	}

	int age;
};

void test01()
{
    
    
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;  // p1.age = 10

	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);  // 链式调用必须得是对象
	cout << "p2.age = " << p2.age << endl;  // p2.age = 40
}

In C++, the null pointer can also call member functions, but also pay attention to whether the this pointer is used

If this pointer is used, it needs to be judged to ensure the robustness of the code

// 空指针访问成员函数
class Person {
    
    
public:

	void ShowClassName()
	{
    
    
		cout << "我是Person类!" << endl;
	}

	void ShowPerson()
	{
    
    
		if (this == NULL) {
    
    
			return;
		}

	        // 因为 p 是空指针,没有一个确切的对象,更无法访问它的值了
		cout << mAge << endl;  // 这里其实有隐藏 buff,mAge 其实是 this->mAge
	}

public:
	int mAge;
};

void test01()
{
    
    
	Person* p = NULL;
	p->ShowClassName();  // 空指针,可以调用成员函数
	// p->ShowPerson();  // 但是如果成员函数中用到了 this 指针,就不可以了
}

4.4 Friends

There are two types of friend classification: friend function and friend class

Friend: In the program, some private attributes also want to be accessed by some special functions or classes outside the class

Purpose: Let a function or class access private members in another class

The relationship between friends cannot be passed, nor can it be inherited

Three Realizations of Friends

  • Global functions as friends
  • class as friend
  • member function as friend

Only introduce member functions as friends, others are similar

class Building;
class goodGay
{
    
    
public:
   	// 类中声明,类外定义,定义需要使用 :: 运算符
	goodGay();
	void visit();  // 只让 visit 函数作为 Building 的好朋友,可以发访问 Building 中私有内容
	void visit2();   // 不让 vist2 访问私有成员
private:
	Building* building;
};

class Building
{
    
    
	// 告诉编译器 goodGay 类中的 visit 成员函数是 Building 好朋友,可以访问私有内容
	friend void goodGay::visit();  // 注意哦:这里要声明作用域,因为 visit 是全局函数,否则会出错

public:
	Building();

public:
	string m_SittingRoom;  // 客厅

private:
	string m_BedRoom;  // 卧室
};

// 构造函数的定义(这里是为了锻炼,写在类内也可以的)
// a::b -- a 是限定作用域,意思就是 a 的 b
Building::Building()
{
    
    
	this->m_SittingRoom = "客厅";
	this->m_BedRoom = "卧室";
}

goodGay::goodGay()
{
    
    
	building = new Building;
}

void goodGay::visit()
{
    
    
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
    
    
	cout << "好基友正在访问" << building->m_SittingRoom << endl;
	// cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
    
    
	goodGay  gg;
	gg.visit();

}

4.5 Operator overloading

Operator overloading concept: redefine existing operators and give them another function to adapt to different data types

The essence of operator overloading is function overloading, which can be overloaded as an ordinary function or as a member function


4.5.1 Plus operator overloading

Function: realize the operation of adding two custom data types

class Person
{
    
    
public:
	Person() {
    
    };
	Person(int a, int b)
	{
    
    
		this->m_A = a;
		this->m_B = b;
	}

	// 成员函数实现 + 号运算符重载
	Person operator+(const Person& p) {
    
    
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}
public:
	int m_A;
	int m_B;
};

// 全局函数实现 + 号运算符重载
// Person operator+(const Person& p1, const Person& p2) {
    
    
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

// 运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)  
{
    
    
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {
    
    
	Person p1(10, 10);
	Person p2(20, 20);

	// 成员函数方式
	Person p3 = p2 + p1;  // 相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;

	Person p4 = p3 + 10;  // 相当于 operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}

Summarize:

  • It is not possible to change the operators for expressions of built-in data types

  • Don't abuse operator overloading


4.5.2 Left shift operator overloading

Role: can output custom data types

class Person
{
    
    
	friend ostream& operator<<(ostream& out, Person& p);
public:
	Person(int a, int b)
	{
    
    
		this->m_A = a;
		this->m_B = b;
	}
	// 成员函数 实现不了  p << cout 不是我们想要的效果
	//void operator<<(Person& p){
    
    
	//}
private:
	int m_A;
	int m_B;
};

// 全局函数实现左移重载
// ostream 对象只能有一个
ostream& operator<<(ostream& out, Person& p)
{
    
    
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() 
{
    
    
	Person p1(10, 20);
	cout << p1 << "hello world" << endl;  // 链式编程
}

Summary: Overloading the left shift operator with friends can realize the output of custom data types


4.5.3 Increment operator overloading

Function: Realize your own integer data increment by overloading the increment operator

class MyInteger
{
    
    
	friend ostream& operator<<(ostream& out, MyInteger myint);
public:
	MyInteger() {
    
    
		m_Num = 0;
	}
	// 前置++;注意这里的返回值类型必须要有 &,否则多次使 用 ++,不起效果(只有第一次会实现 ++),因为在第一次使用完这个值之后就会释放掉了
    	// 返回引用是为了一直对一个数进行递增
	MyInteger& operator++() {
    
    
		m_Num++;  // 先++

		return *this;  // 再返回
	}

	// 后置++;这里其实是前置++函数的重载
	MyInteger operator++(int) {
    
      // 注意这里需要有一个占位符,用来区分前置、后置,因为函数返回类型不作为重载的条件
		// 先返回
		MyInteger temp = *this;  // 记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后 ++
		m_Num++;
		return temp;  // 后置 ++,一定要返回的是值,因为这里用到了局部变量 temp
	}
private:
	int m_Num;
};

ostream& operator<<(ostream& out, MyInteger myint) {
    
    
	out << myint.m_Num;
	return out;
}

// 前置++ 先++ 再返回
void test01() {
    
    
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

// 后置++ 先返回 再++
void test02() {
    
    
	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}

Summary: pre-increment return reference, post-increment return value


4.5.4 Assignment operator overloading

  • assignment operator, which can only be overloaded as a member function

The c++ compiler adds at least 4 functions to a class

  1. Default constructor (no parameters, function body is empty)
  2. Default destructor (no parameters, function body is empty)
  3. The default copy constructor, which copies the value of the property
  4. Assignment operator operator=, copy the value of the attribute

If there are attributes in the class pointing to the heap area, there will also be deep and shallow copy problems when doing assignment operations

class Person
{
    
    
public:
	Person(int age)
	{
    
    
		// 将年龄数据开辟到堆区
		m_Age = new int(age);
	}

	// 重载赋值运算符
	Person& operator=(Person &p)
	{
    
    
		if (m_Age != NULL)
		{
    
    
			delete m_Age;
			m_Age = NULL;
		}
		// 编译器提供的代码是浅拷贝
		// m_Age = p.m_Age;

		// 提供深拷贝,解决浅拷贝的问题
		m_Age = new int(*p.m_Age);

		// 返回自身
		return *this;
	}

	~Person()
	{
    
    
		if (m_Age != NULL)
		{
    
    
			delete m_Age;
			m_Age = NULL;
		}
	}
	// 年龄的指针
	int *m_Age;
};

void test01()
{
    
    
	Person p1(18);
	Person p2(20);
	Person p3(30);

	p3 = p2 = p1;  // 赋值操作

	cout << "p1的年龄为:" << *p1.m_Age << endl;  // 18
	cout << "p2的年龄为:" << *p2.m_Age << endl;  // 18
	cout << "p3的年龄为:" << *p3.m_Age << endl;  // 18
}

int main()
{
    
    
	test01();

	int a = 10;
	int b = 20;
	int c = 30;

	c = b = a;
	cout << "a = " << a << endl;  // 10
	cout << "b = " << b << endl;  // 10
	cout << "c = " << c << endl;  // 10

	return 0;
}

4.5.5 Relational operator overloading

Role: Overload relational operators, allowing two custom type objects to perform comparison operations

class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person& p)
	{
    
    
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
    
    
			return true;
		}
		else
		{
    
    
			return false;
		}
	}

	bool operator!=(Person& p)
	{
    
    
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
    
    
			return false;
		}
		else
		{
    
    
			return true;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{
    
    
	//int a = 0;
	//int b = 0;

	Person a("孙悟空", 18);
	Person b("孙悟空", 18);

	if (a == b)
	{
    
    
		cout << "a和b相等" << endl;
	}
	else
	{
    
    
		cout << "a和b不相等" << endl;
	}

	if (a != b)
	{
    
    
		cout << "a和b不相等" << endl;
	}
	else
	{
    
    
		cout << "a和b相等" << endl;
	}
}

4.5.6 Function call operator overloading

  • The function call operator () can also be overloaded
  • Because the method used after overloading is very similar to the call of a function, it is called a functor
  • Functor has no fixed way of writing and is very flexible
class MyPrint
{
    
    
public:
	void operator()(string text)
	{
    
    
		cout << text << endl;
	}
};

void test01()
{
    
    
	// 重载的()操作符,也称为仿函数
	MyPrint myFunc;
	myFunc("hello world");
}

class MyAdd
{
    
    
public:
	int operator()(int v1, int v2)
	{
    
    
		return v1 + v2;
	}
};

void test02()
{
    
    
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;

	// 匿名对象调用  
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

4.6 Inheritance

In addition to the commonality of the upper level, the members of the lower level also have their own characteristics. Inheritance can be used to reduce duplication of code

Inherited syntax class 子类 : 继承方式 父类:

There are three types of inheritance:

  • public inheritance
  • protected inheritance
  • private inheritance
class Base1
{
    
    
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

// 公共继承
class Son1: public Base1
{
    
    
public:
	void func()
	{
    
    
		m_A;  // 可访问 public 权限
		m_B;  // 可访问 protected 权限
		// m_C;  // 不可访问
	}
};

void myClass()
{
    
    
	Son1 s1;
	s1.m_A;  // 其他类只能访问到公共权限
}

// 保护继承
class Base2
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son2: protected Base2
{
    
    
public:
	void func()
	{
    
    
		m_A;  // 可访问 protected 权限
		m_B;  // 可访问 protected 权限
		// m_C; // 不可访问
	}
};

void myClass2()
{
    
    
	Son2 s;
	// s.m_A;  // 不可访问
}

// 私有继承
class Base3
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son3: private Base3
{
    
    
public:
	void func()
	{
    
    
		m_A;  // 父类公共权限可访问,但是变量权限改为 private 权限
		m_B;  // 父类保护权限可访问,但是变量权限改为 private 权限
		//m_C;  // 父类私有权限不可访问
	}
};

class GrandSon3: public Son3
{
    
    
public:
	void func()
	{
    
    
		// Son3 是私有继承,所以继承 Son3 的属性在 GrandSon3 中都无法访问到
		//m_A;
		//m_B;
		//m_C;
	}
};

Conclusion: The private members in the parent class are also inherited by the subclass, but they are hidden by the compiler and cannot be accessed


  • After the subclass inherits the parent class, when the subclass object is created, the constructor of the parent class will also be called
  • First call the parent class constructor, and then call the subclass constructor, the order of destruction is opposite to that of construction

In the derived class object, it contains the base class object, and the storage location of the base class object is located before the new member variable of the derived class object

  • Inheritance: "is" relationship

    • Base class A, B is derived class
    • Logical requirement: a B object is also an A object

  • Conforms to: the relationship of "has"

    • Class C "has" member variable k, and k is an object of class D, then C and D are compound relations
    • General logical requirement: D objects are inherent properties or components of C objects

When a member with the same name appears in the subclass and the parent class:

  • To access the members of the same name of the subclass, you can directly access it
  • To access members with the same name of the parent class, scope needs to be added, parent class name::member name

When creating an object of a derived class,
1) execute the constructor of the base class first to initialize the members inherited from the base class in the derived class object;

2) Execute the constructor of the member object class to initialize the member object in the derived class object

3) Finally execute the derived class's own constructor

There are two ways to call the base class constructor:

- 显式方式:在派生类的构造函数中,为基类的构造函数提供参数  
- 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数自动调用基类的默认构造函数

Assignment compatibility rules for public inheritance :

class base{
    
    };
class derived: public base{
    
    };
base b;
derived d;

1) An object of a derived class can be assigned to a base class object (a derived class object is a base class object
b = d; // 把 d 内容拷贝给 b)

2) Derived class objects can initialize base class references

base& br = d;

3) The address of the derived class object can be assigned to the base class pointer

base* pb = &d;

C++ allows a class to inherit from multiple classes

Syntax **: class 子类 :继承方式 父类1 , 继承方式 父类2...**

Multiple inheritance may cause the appearance of members with the same name in the parent class, which needs to be distinguished by scope

It is not recommended to use multiple inheritance in C++ actual development

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-Og55A1P9-1685438574381)(assets/image-20230527124733-qjm1cxw.png)]

Two derived classes inherit from the same base class, and another class inherits two derived classes at the same time. This kind of inheritance is called diamond inheritance, or diamond inheritance.

  • The main problem caused by diamond inheritance is that subclasses inherit two copies of the same data, resulting in waste of resources and meaninglessness
  • Using virtual inheritance can solve the diamond inheritance problem
class Animal
{
    
    
public:
	int m_Age;
};

// 继承前加 virtual 关键字后,变为虚继承
// 此时公共的父类 Animal 称为虚基类
class Sheep : virtual public Animal {
    
    };
// 直接基类 Tuo
class Tuo   : virtual public Animal {
    
    };
// 派生类 SheepTuo,可以不用加 virtual
class SheepTuo : public Sheep, public Tuo {
    
    };

void test01()
{
    
    
	SheepTuo st;
	st.Sheep::m_Age = 100;  // 表示将 st.m_Age 赋值为 100,但是呢,m_Age 是在 Sheep 作用域下的
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;  // 200
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;  // 200
	cout << "st.m_Age = " << st.m_Age << endl;  // 200
}

4.7 Polymorphism

Polymorphism is one of the three object-oriented features of C++

Polymorphism is divided into two categories

  • Static polymorphism: function overloading and operator overloading belong to static polymorphism, reuse function names
  • Dynamic Polymorphism: Derived classes and virtual functions implement runtime polymorphism

The difference between static polymorphism and dynamic polymorphism:

  • Static polymorphic function address early binding - determine the function address during compilation
  • Dynamic polymorphic function address late binding - the runtime determines the function address

Polymorphism satisfies the conditions:

  • Inheritance
  • The subclass overrides the virtual function in the parent class

Polymorphic usage conditions:

  • A parent class pointer or reference points to a subclass object

The key to "polymorphism" is that when a virtual function is called through a base class pointer or reference, it is not sure at compile time whether the function of the base class or a derived class is being called, and it is determined at runtime—"dynamic binding"

Rewriting: The function return value type, function name, and parameter list are exactly the same, which is called rewriting

In polymorphism, the implementation of virtual functions in the parent class is usually meaningless, mainly calling the content rewritten by the subclass

So you can change the virtual function to a pure virtual function

Pure virtual function syntax virtual 返回值类型 函数名 (参数列表)= 0 ;:

When a class has a pure virtual function, this class is also called an abstract class

Abstract class features:

  • Could not instantiate object
  • The subclass must override the pure virtual function in the abstract class, otherwise it also belongs to the abstract class
class Base
{
    
    
public:
	// 纯虚函数
	// 类中只要有一个纯虚函数就称为抽象类
	// 抽象类无法实例化对象
	// 子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
};

class Son: public Base
{
    
    
public:
	virtual void func() 
	{
    
    
		cout << "func调用" << endl;
	};
};

void test01()
{
    
    
	Base* base = NULL;
	// base = new Base;  // 错误,抽象类无法实例化对象
	base = new Son;
	base->func();
	delete base;  // 记得销毁
}

When using polymorphism, if there are properties in the subclass that are allocated to the heap area, then the parent class pointer cannot call the destructor code of the subclass when it is released

Solution: Change the destructor in the parent class to virtual destructor or pure virtual destructor

  • In the definition of a class, a member function with the virtual keyword is a virtual function
  • The virtual keyword is only used in the function declaration in the class definition, not when writing the function body
  • Constructors and static member functions cannot be virtual

Look at the example to understand the implementation principle of polymorphism:

class Base
{
    
    
public:
	int i;
	virtual void Print() {
    
     cout << "Base:Print"; }
};

class Derived : public Base
{
    
    
public:
	int n;
	virtual void Print() {
    
     cout << "Derived:Print"; }
};

int main()
{
    
    
	Derived d;
	cout << sizeof(Base) << ", " << sizeof(Derived);  // 8, 12

	return 0;
}

According to the above code, it can be seen that each output has 4 more bytes, which leads to the key to polymorphic implementation—virtual function table

Every class with virtual functions has a virtual function table, and any object of this class has a pointer to the virtual function table. The virtual function address of this class is listed in the virtual function table, and the extra 4 bytes are used to put the address of the virtual function table

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-BldKicUh-1685438574382)(assets/image-20230530155046-huvsjqw.png)]


When deleting a derived class object through a base class pointer, usually only the destructor of the base class can be called. However, to delete a derived class object, the destructor of the derived class should be called first, and then the destructor of the base class should be called

class son
{
    
    
public:
	~son() {
    
     cout << "bye from son" << endl; }
};

class grandson :public son
{
    
    
	~grandson() {
    
     cout << "bye from grandson" << endl; }

};

int main()
{
    
    
	son* pson;
	pson = new grandson();
	delete pson;  // 输出 bye from son

	return 0;
}

Solution: Declare the destructor of the base class as virtual.
The destructor of the derived class can be virtual.
When deleting a derived class object through the pointer of the base class, first call the destructor of the derived class, and then call the destructor of the base class. Constructor

class son
{
    
    
public:
	virtual ~son() {
    
     cout << "bye from son" << endl; }
};

class grandson :public son
{
    
    
	~grandson() {
    
     cout << "bye from grandson" << endl; }
};

int main()
{
    
    
	son* pson;
	pson = new grandson();
	// 先输出 bye from grandson,在输出 bye from son
	delete pson;

	return 0;
}

Generally speaking, if a class defines a virtual function, the destructor should also be defined as a virtual function. Alternatively, a class intended to be used as a base class should also define the destructor as a virtual function

Note: Virtual functions are not allowed as constructors


Common features of virtual destructor and pure virtual destructor:

  • It can solve the parent class pointer to release the subclass object
  • need to have a specific function implementation

The difference between virtual destructor and pure virtual destructor:

  • If it is a pure virtual destructor, the class is an abstract class and cannot instantiate an object

Virtual destructor syntax:

virtual ~类名(){}​​

Pure virtual destructor syntax:

virtual ~类名() = 0;

类名::~类名(){}​​

  • Pure virtual functions can be called in member functions of abstract classes , but cannot be called in constructors or destructors

Summarize:

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类

5 file operations

The data generated when the program is running is all temporary data, and will be released once the program finishes running

Data can be persisted through files

File operations in C++ need to include header files

There are two file types:

  1. Text files - files are stored on your computer as text in ASCII
  2. Binary files - files are stored on the computer in binary form of text, and users generally cannot read them directly

Three categories of operating files:

  1. ofstream: write operation (output to file)
  2. ifstream: read operation (input from file)
  3. fstream: read and write operations

5.1 Text files

01 The steps to write a file are as follows:

  1. Include the header
    #include <fstream>file
  2. Create a stream
    ofstream ofs;object
  3. Open the file
    ofs.open("文件路径", 打开方式);
  4. write data
    ofs << "写入的数据";
  5. close file
    ofs.close();

File open method:

open method explain
ios::in open file for reading
ios::out open file for writing
ios::ate Initial position: end of file
ios::app Appending to write files
ios::trunk If the file exists, delete it first, then create it
ios::binary binary mode

Note: The file opening method can be used in conjunction with the | operator

For example: write ios::binary | ios:: outfiles

#include <fstream>

void test01()
{
    
    
	ofstream ofs;
	ofs.open("test.txt", ios::out);

	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;

	ofs.close();
}

02 The steps to read the file are as follows:

  1. Include the header file
    #include <fstream>
  2. Create a stream object
    ifstream ifs;
  3. Open the file and judge whether the file is opened successfully
    ifs.open("文件路径", 打开方式);
  4. read data
    四种方式读取
  5. close file
    ifs.close();
#include <fstream>
#include <string>

void test01()
{
    
    
	ifstream ifs;
	ifs.open("test.txt", ios::in);

	if (!ifs.is_open())
	{
    
    
		cout << "文件打开失败" << endl;
		return;
	}

	// 第一种方式
	//char buf[1024] = { 0 };
	//while (ifs >> buf)  // 按行读取
	//{
    
    
	//	cout << buf << endl;
	//}

	// 第二种
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf, sizeof(buf)))  // 按行读取
	//{
    
    
	//	cout << buf << endl;
	//}

	// 第三种
	//string buf;
	//while (getline(ifs, buf))  // ifs -- 文件输入流对象,buf -- 文件读取之后存储的地方
	//{
    
    
	//	cout << buf << endl;
	//}

    	// 第四种,不推荐,使用前三种最好
	char c;
	while ((c = ifs.get()) != EOF)  // get() -- 按字符读取,EOF -- 文件尾部
	{
    
    
		cout << c;
	}

	ifs.close();
}

03 File read and write pointers

ofstream fout("a1.out", ios::app);  // 以添加的方式打开
long location = fout.tellp();  // 获取写指针的位置
location = 10;
fout.seekp(location);  // 将写指针移动到第 10 个字节处
fout.seekp(location, ios::beg);  // 从头数 location
fout.seekp(location, ios::cur);  // 从当前数 location
fout.seekp(location, ios::end);  // 从尾部数 location

5.2 Binaries

Read and write files in binary mode

The opening method should be specified as ios::binary

01 Writing a file mainly uses the stream object to call the member function write

Function prototype ostream& write(const char* buffer, long len);:

Parameter explanation: the character pointer buffer points to a storage space in the memory, and len is the number of bytes read and written

#include <fstream>
#include <string>

class Person
{
    
    
public:
	char m_Name[64];
	int m_Age;
};

void test01()
{
    
    
	// 1、包含头文件
	// 2、创建输出流对象
	ofstream ofs("person.txt", ios::out | ios::binary);
		  
	// 3、打开文件
	//ofs.open("person.txt", ios::out | ios::binary);
	
	Person p = {
    
    "张三", 18};  // p 存储在栈中,而{"张三", 18}存储在堆中
	
	// 4、写文件
	// write(const char* _Str, streamsize_Count),所以这里要进行强转,这里的_Str是要传地址的!
	ofs.write((const char*)& p, sizeof(p));
	
	// 5、关闭文件
	ofs.close();
}

02 Reading files mainly uses the stream object to call the member function read

Function prototype istream& read(char *buffer, long len);:

Parameter explanation: the character pointer buffer points to a storage space in the memory, and len is the number of bytes read and written

#include <fstream>
#include <string>

class Person
{
    
    
public:
	char m_Name[64];
	int m_Age;
};

void test01()
{
    
    
    	ifstream ifs("person.txt", ios::in | ios::binary);
	if (!ifs.is_open())
	{
    
    
		cout << "文件打开失败" << endl;
	}

	Person p;
	ifs.read((char *)&p, sizeof(p));  // 这里 read 的第一个参数也是要传地址的!

	cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}

Guess you like

Origin blog.csdn.net/Coder_J7/article/details/130798291