[C++ Core] Un article pour comprendre le C++ orienté objet (super détaillé !)

1. Classes et objets

Les trois fonctionnalités principales du C++ orienté objet sont :encapsulation, héritage, polymorphisme

C++ pensetout est objet, avec ses propriétés et son comportement sur l'objet

Par exemple:

Les personnes peuvent être utilisées comme des objets, les attributs incluent le nom, l'âge, la taille, le poids... et les comportements incluent marcher, courir, sauter, manger, chanter...

​ Une voiture peut également être utilisée comme un objet. Ses propriétés incluent les pneus, les volants, les lumières... et ses comportements incluent le transport de personnes, la musique, l'allumage des climatiseurs...

de même natureobjet, on peut appeler abstraitementtype, les gens appartiennent aux êtres humains et les voitures appartiennent à la catégorie des véhicules

1.1 Forfait

1.1.1 Signification de l'encapsulation

L'encapsulation est l'une des trois principales fonctionnalités orientées objet de C++. La signification de l'encapsulation :

  • Attributs et comportements dans leur ensemble, représentant les choses de la vie
  • Contrôlez les attributs et les comportements avec des autorisations

Forfait signifiant un :

Lors de la conception d'une classe, les attributs et les comportements sont écrits ensemble pour représenter les choses

grammaire: class 类名{ 访问权限: 属性 / 行为 };

Exemple 1 : Concevoir une classe de cercle et trouver la circonférence du cercle

Exemple de code :

//圆周率
const double PI = 3.14;

//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物

//封装一个圆类,求圆的周长
//class代表设计一个类,后面跟着的是类名
class Circle
{
    
    
public:  //访问权限  公共的权限

	//属性
	int m_r;//半径

	//行为
	//获取到圆的周长
	double calculateZC()
	{
    
    
		//2 * pi  * r
		//获取圆的周长
		return  2 * PI * m_r;
	}
};

int main() {
    
    

	//通过圆类,创建圆的对象
	// c1就是一个具体的圆
	Circle c1;
	c1.m_r = 10; //给圆对象的半径 进行赋值操作

	//2 * pi * 10 = = 62.8
	cout << "圆的周长为: " << c1.calculateZC() << endl;
	
	return 0;
}

Exemple 2 : concevoir une classe d'étudiants, les attributs incluent le nom et le numéro d'étudiant, vous pouvez attribuer des valeurs au nom et au numéro d'étudiant, et vous pouvez afficher le nom et le numéro d'étudiant de l'étudiant

Code de l'exemple 2 :

//学生类
class Student {
    
    
public:
	void setName(string name) {
    
    
		m_name = name;
	}
	void setID(int id) {
    
    
		m_id = id;
	}

	void showStudent() {
    
    
		cout << "name:" << m_name << " ID:" << m_id << endl;
	}
public:
	string m_name;
	int m_id;
};

int main() {
    
    

	Student stu;
	stu.setName("德玛西亚");
	stu.setID(250);
	stu.showStudent();

	return 0;
}

Paquet signifiant deux :

Lors de la conception d'une classe, les attributs et les comportements peuvent être placés sous différentes autorisations à contrôler. Il existe trois types d'autorisations d'accès :

  1. autorité publique publique
  2. autorisations protégées protégées
  3. autorisation privée privée

Exemple:

//三种权限
//公共权限  public     类内可以访问  类外可以访问
//保护权限  protected  类内可以访问  类外不可以访问
//私有权限  private    类内可以访问  类外不可以访问

class Person
{
    
    
	//姓名  公共权限
public:
	string m_Name;

	//汽车  保护权限
protected:
	string m_Car;

	//银行卡密码  私有权限
private:
	int m_Password;

public:
	void func()
	{
    
    
		m_Name = "张三";
		m_Car = "拖拉机";
		m_Password = 123456;
	}
};

int main() {
    
    

	Person p;
	p.m_Name = "李四";
	//p.m_Car = "奔驰";  //保护权限类外访问不到
	//p.m_Password = 123; //私有权限类外访问不到

	return 0;
}

1.1.2 Différence entre structure et classe

La seule différence entre struct et class en C++ est que les droits d'accès par défaut sont différents , la différence :

  • les autorisations par défaut de la structure sont publiques
  • l'autorisation par défaut de la classe est privée
class C1
{
    
    
	int  m_A; //默认是私有权限
};

struct C2
{
    
    
	int m_A;  //默认是公共权限
};

int main() {
    
    

	C1 c1;
	c1.m_A = 10; //错误,访问权限是私有

	C2 c2;
	c2.m_A = 10; //正确,访问权限是公共

	system("pause");

	return 0;
}

1.1.3 Les attributs des membres sont définis sur privé

Avantage 1 : Définissez tous les attributs de membre sur privé, vous pouvez contrôler vous-même les autorisations de lecture et d'écriture

Avantage 2 : Pour les autorisations en écriture, nous pouvons vérifier la validité des données

Exemple:

class Person {
    
    
public:

	//姓名设置可读可写
	void setName(string name) {
    
    
		m_Name = name;
	}
	string getName()
	{
    
    
		return m_Name;
	}

	//获取年龄 
	int getAge() {
    
    
		return m_Age;
	}
	//设置年龄
	void setAge(int age) {
    
    
		if (age < 0 || age > 150) {
    
    
			cout << "你个老妖精!" << endl;
			return;
		}
		m_Age = age;
	}

	//情人设置为只写
	void setLover(string lover) {
    
    
		m_Lover = lover;
	}

private:
	string m_Name; //可读可写  姓名
	
	int m_Age; //只读  年龄

	string m_Lover; //只写  情人
};

int main() {
    
    

	Person p;
	//姓名设置
	p.setName("张三");
	cout << "姓名: " << p.getName() << endl;

	//年龄设置
	p.setAge(50);
	cout << "年龄: " << p.getAge() << endl;

	//情人设置
	p.setLover("苍井");
	//cout << "情人: " << p.m_Lover << endl;  //只写属性,不可以读取

	return 0;
}

1.2 Initialisation et nettoyage des objets

  • Les produits électroniques que nous achetons dans la vie ont essentiellement des paramètres d'usine, et nous supprimerons certaines de nos propres informations et données pour assurer la sécurité lorsque nous ne les utiliserons pas un jour
  • L'orientation objet en C++ vient de la vie, et chaque objet aura également des paramètres initiaux et des paramètres pour nettoyer les données avant que l'objet ne soit détruit.

1.2.1 Constructeur et destructeur

L'initialisation et le nettoyage des objets sont également deux problèmes de sécurité très importants. Un objet ou une variable n'a pas d'état initial et les conséquences de son utilisation sont inconnues. De même, si un objet ou une variable n'est pas nettoyé à temps après l'avoir utilisé, il entraînera également certains problèmes de sécurité.

C++ utilise des constructeurs et des destructeurs pour résoudre les problèmes ci-dessus. Ces deux fonctions seront automatiquement appelées par le compilateur pour terminer l'initialisation et le nettoyage de l'objet.

L'initialisation et le nettoyage des objets sont ce que le compilateur nous oblige à faire, donc si nous ne fournissons pas la construction et la destruction, le compilateur fournira

Les constructeurs et les destructeurs fournis par le compilateur sont des implémentations vides.

  • Constructeur : La fonction principale est d'affecter des valeurs aux propriétés membres de l'objet lors de la création de l'objet. Le constructeur est automatiquement appelé par le compilateur sans appel manuel.
  • Destructeur : La fonction principale est que le système appelle automatiquement avant que l'objet ne soit détruit pour effectuer des travaux de nettoyage.

Syntaxe du constructeur :类名(){}

  1. Constructeur, pas de valeur de retour et pas de vide
  2. Le nom de la fonction est le même que le nom de la classe
  3. Les constructeurs peuvent avoir des paramètres, donc une surcharge peut se produire
  4. Le programme appellera automatiquement le constructeur lors de l'appel de l'objet, pas besoin de l'appeler manuellement, et il ne sera appelé qu'une seule fois

Syntaxe du destructeur : ~类名(){}

  1. Destructeur, pas de valeur de retour et pas de vide
  2. Le nom de la fonction est le même que le nom de la classe et le symbole ~ est ajouté avant le nom
  3. Les destructeurs ne peuvent pas avoir de paramètres, donc la surcharge ne peut pas se produire
  4. Le programme appellera automatiquement le destructeur avant que l'objet ne soit détruit, pas besoin de l'appeler manuellement, et il ne sera appelé qu'une seule fois
class Person
{
    
    
public:
	//构造函数
	Person()
	{
    
    
		cout << "Person的构造函数调用" << endl;
	}
	//析构函数
	~Person()
	{
    
    
		cout << "Person的析构函数调用" << endl;
	}

};

void test01()
{
    
    
	Person p;
}

int main() {
    
    
	
	test01();

	return 0;
}

1.2.2 Classement et appel du constructeur

Deux méthodes de classement :

​ Divisé par paramètres : construction paramétrée et construction non paramétrique

Divisé par type : construction ordinaire et construction de copie

Trois méthodes d'appel :

Parenthèses

méthode d'affichage

​ Méthode de conversion implicite

Exemple:

//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);
	//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
	//Person p2();

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

	//2.3 隐式转换法
	Person p4 = 10; // Person p4 = Person(10); 
	Person p5 = p4; // Person p5 = Person(p4); 

	//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
	//Person p5(p4);
}

int main() {
    
    

	test01();
	//test02();
	return 0;
}

1.2.3 Quand appeler le constructeur de copie

Il existe généralement trois situations dans lesquelles le constructeur de copie est appelé en C++

  • Initialiser un nouvel objet en utilisant un objet déjà créé
  • La façon de passer par valeur est de passer des valeurs aux paramètres de la fonction
  • renvoie l'objet local par valeur

Exemple:

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;
	//newman3 = man; //不是调用拷贝构造函数,赋值操作
}

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

//3. 以值方式返回局部对象
Person doWork2()
{
    
    
	Person p1;
	cout << (int *)&p1 << endl;
	return p1;
}

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


int main() {
    
    

	//test01();
	//test02();
	test03();

	return 0;
}

1.2.4 Règles d'appel des constructeurs

Par défaut, le compilateur c++ ajoute au moins 3 fonctions à une classe

1. Constructeur par défaut (pas de paramètres, le corps de la fonction est vide)

2. Destructeur par défaut (pas de paramètres, le corps de la fonction est vide)

3. Le constructeur de copie par défaut, qui copie la valeur de la propriété

Les règles d'appel du constructeur sont les suivantes :

  • Si l'utilisateur définit un constructeur avec des paramètres, C++ ne fournira plus de construction sans argument par défaut, mais fournira une construction de copie par défaut

  • Si l'utilisateur définit un constructeur de copie, C++ ne fournira plus d'autres constructeurs

Exemple:

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;
};

void test01()
{
    
    
	Person p1(18);
	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
	Person p2(p1);

	cout << "p2的年龄为: " << p2.age << endl;
}

void test02()
{
    
    
	//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
	Person p1; //此时如果用户自己没有提供默认构造,会出错
	Person p2(10); //用户提供的有参
	Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供

	//如果用户提供拷贝构造,编译器不会提供其他构造函数
	Person p4; //此时如果用户自己没有提供默认构造,会出错
	Person p5(10); //此时如果用户自己没有提供有参,会出错
	Person p6(p5); //用户自己提供拷贝构造
}

int main() {
    
    

	test01();

	return 0;
}

1.2.5 Copie profonde et copie superficielle

La copie profonde et superficielle est une question d'entretien classique, et c'est aussi un piège courant

Copie superficielle : opération de copie d'affectation simple

Copie en profondeur : réappliquez de l'espace dans la zone de tas et effectuez des opérations de copie

Exemple:

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 << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;
		m_height = new int(*p.m_height);
		
	}

	//析构函数
	~Person() {
    
    
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
    
    
			delete m_height;
		}
	}
public:
	int m_age;
	int* m_height;
};

void test01()
{
    
    
	Person p1(18, 180);

	Person p2(p1);

	cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

	cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

int main() {
    
    

	test01();

	return 0;
}

Résumé : Si l'attribut est ouvert dans la zone de tas, vous devez fournir vous-même un constructeur de copie pour éviter les problèmes causés par la copie superficielle.

1.2.6 Liste d'initialisation

effet:

C++ fournit une syntaxe de liste d'initialisation pour initialiser les propriétés

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

Exemple:

class Person {
    
    
public:

	传统方式初始化
	//Person(int a, int b, int c) {
    
    
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化
	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;
}

1.2.7 Objets de classe en tant que membres de classe

Un membre d'une classe C++ peut être un objet d'une autre classe, on appelle ce membre un objet membre

Par exemple:

class A {
    
    }
class B
{
    
    
    A a;
}

Il y a l'objet A en tant que membre de la classe B, et A est un membre d'objet, donc lors de la création d'un objet de B, qui construira et détruira A et B en premier ?

Exemple:

class Phone
{
    
    
public:
	Phone(string name)
	{
    
    
		m_PhoneName = name;
		cout << "Phone构造" << endl;
	}

	~Phone()
	{
    
    
		cout << "Phone析构" << endl;
	}
	string m_PhoneName;

};

class Person
{
    
    
public:

	//初始化列表可以告诉编译器调用哪一个构造函数
	Person(string name, string pName) :m_Name(name), m_Phone(pName)
	{
    
    
		cout << "Person构造" << endl;
	}

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

	void playGame()
	{
    
    
		cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
	}

	string m_Name;
	Phone m_Phone;

};
void test01()
{
    
    
	//当类中成员是其他类对象时,我们称该成员为 对象成员
	//构造的顺序是 :先调用对象成员的构造,再调用本类构造
	//析构顺序与构造相反
	Person p("张三" , "苹果X");
	p.playGame();

}


int main() {
    
    

	test01();

	return 0;
}

1.2.8 Membres statiques

Le membre statique consiste à ajouter le mot-clé static avant la variable membre et la fonction membre, appelée membre statique

Les membres statiques sont divisés en :

  • variable de membre statique
    • Tous les objets partagent les mêmes données
    • Allouer de la mémoire lors de la compilation
    • Déclaration en classe, initialisation hors classe
  • fonction membre statique
    • Tous les objets partagent la même fonction
    • Les fonctions membres statiques ne peuvent accéder qu'aux variables membres statiques

Exemple 1 : variable de membre statique

class Person
{
    
    
	
public:

	static int m_A; //静态成员变量

	//静态成员变量特点:
	//1 在编译阶段分配内存
	//2 类内声明,类外初始化
	//3 所有对象共享同一份数据

private:
	static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
    
    
	//静态成员变量两种访问方式

	//1、通过对象
	Person p1;
	p1.m_A = 100;
	cout << "p1.m_A = " << p1.m_A << endl;

	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
	cout << "p2.m_A = " << p2.m_A << endl;

	//2、通过类名
	cout << "m_A = " << Person::m_A << endl;


	//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}

int main() {
    
    

	test01();

	return 0;
}

Exemple 2 : Fonction membre statique

class Person
{
    
    

public:

	//静态成员函数特点:
	//1 程序共享一个函数
	//2 静态成员函数只能访问静态成员变量
	
	static void func()
	{
    
    
		cout << "func调用" << endl;
		m_A = 100;
		//m_B = 100; //错误,不可以访问非静态成员变量
	}

	static int m_A; //静态成员变量
	int m_B; // 
private:

	//静态成员函数也是有访问权限的
	static void func2()
	{
    
    
		cout << "func2调用" << endl;
	}
};
int Person::m_A = 10;


void test01()
{
    
    
	//静态成员变量两种访问方式

	//1、通过对象
	Person p1;
	p1.func();

	//2、通过类名
	Person::func();


	//Person::func2(); //私有权限访问不到
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

1.3 Modèle d'objet C++ et ce pointeur

1.3.1 Les variables membres et les fonctions membres sont stockées séparément

En C++, les variables membres et les fonctions membres d'une classe sont stockées séparément, et seules les variables membres non statiques appartiennent aux objets de la classe

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;

	system("pause");

	return 0;
}

1.3.2 Le concept de ce pointeur

Grâce à 4.3.1, nous savons que les variables membres et les fonctions membres sont stockées séparément en C++

Chaque fonction membre non statique ne générera qu'une instance de fonction, ce qui signifie que plusieurs objets du même type partageront un morceau de code

La question est donc : comment ce morceau de code distingue-t-il quel objet s'appelle lui-même ?

C++ résout les problèmes ci-dessus en fournissant un pointeur d'objet spécial, le pointeur this. Le pointeur this pointe vers l'objet auquel appartient la fonction membre appelée

Le pointeur this est un pointeur implicite dans chaque fonction membre non statique

Le pointeur this n'a pas besoin d'être défini, il peut être utilisé directement

Le but de ce pointeur :

  • Lorsque le paramètre formel et la variable membre ont le même nom, le pointeur this peut être utilisé pour les distinguer
  • Pour renvoyer l'objet lui-même dans une fonction membre non statique d'une classe, utilisez return *this
class Person
{
    
    
public:

	Person(int age)
	{
    
    
		//1、当形参和成员变量同名时,可用this指针来区分
		this->age = age;
	}

	Person& PersonAddPerson(Person p)
	{
    
    
		this->age += p.age;
		//返回对象本身
		return *this;
	}

	int age;
};

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

	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}

int main() {
    
    

	test01();

	return 0;
}

1.3.3 Fonction membre d'accès au pointeur nul

Les pointeurs nuls en C++ peuvent également appeler des fonctions membres, mais faites également attention à savoir si le pointeur this est utilisé

Si ce pointeur est utilisé, il doit être jugé pour assurer la robustesse du code

Exemple:

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

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

	void ShowPerson() {
    
    
		if (this == NULL) {
    
    
			return;
		}
		cout << mAge << endl;
	}

public:
	int mAge;
};

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

int main() {
    
    

	test01();

	return 0;
}

1.3.4 const fonction membre modifiée

Fonction constante :

  • Après avoir ajouté const à la fonction membre, nous appelons cette fonction une fonction constante
  • Les attributs de membre ne peuvent pas être modifiés dans les fonctions constantes
  • Une fois le mot-clé mutable ajouté à la déclaration d'attribut de membre, il peut toujours être modifié dans la fonction constante

Objet constant :

  • Ajouter const avant de déclarer un objet pour l'appeler un objet constant
  • Les objets constants ne peuvent appeler que des fonctions constantes

Exemple:

class Person {
    
    
public:
	Person() {
    
    
		m_A = 0;
		m_B = 0;
	}

	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	void ShowPerson() const {
    
    
		//const Type* const pointer;
		//this = NULL; //不能修改指针的指向 Person* const this;
		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的

		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		this->m_B = 100;
	}

	void MyFunc() const {
    
    
		//mA = 10000;
	}

public:
	int m_A;
	mutable int m_B; //可修改 可变的
};


//const修饰对象  常对象
void test01() {
    
    

	const Person person; //常量对象  
	cout << person.m_A << endl;
	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量

	//常对象访问成员函数
	person.MyFunc(); //常对象不能调用const的函数

}

int main() {
    
    

	test01();

	return 0;
}

1.4 Tomomoto

Dans la vie, votre logement dispose d'un salon (Public) et d'une chambre (Privé)

Tous les invités du salon peuvent entrer, mais votre chambre est privée, ce qui signifie que vous seul pouvez entrer

Mais, vous pouvez également autoriser vos bonnes copines et amis gays à entrer.

Dans le programme, certains attributs privés veulent également être accessibles par certaines fonctions ou classes spéciales en dehors de la classe, vous devez donc utiliser la technologie d'amis

Le but d'un ami est de permettre à une fonction ou à une classe d'accéder aux membres privés d'une autre classe

Le mot-clé pour un ami estami

Trois réalisations d'amis

  • Global fonctionne comme des amis
  • classe comme ami
  • fonction de membre en tant qu'ami

1.4.1 Fonctions globales en tant qu'amis

class Building
{
    
    
	//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容
	friend void goodGay(Building * building);

public:

	Building()
	{
    
    
		this->m_SittingRoom = "客厅";
		this->m_BedRoom = "卧室";
	}


public:
	string m_SittingRoom; //客厅

private:
	string m_BedRoom; //卧室
};


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


void test01()
{
    
    
	Building b;
	goodGay(&b);
}

int main(){
    
    

	test01();

	return 0;
}

1.4.2 Cours en tant qu'amis

class Building;
class goodGay
{
    
    
public:

	goodGay();
	void visit();

private:
	Building *building;
};


class Building
{
    
    
	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
	friend class goodGay;

public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};

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 test01()
{
    
    
	goodGay gg;
	gg.visit();

}

int main(){
    
    

	test01();

	return 0;
}

1.4.3 Le membre fonctionne comme un ami


class Building;
class goodGay
{
    
    
public:

	goodGay();
	void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
	void visit2(); 

private:
	Building *building;
};


class Building
{
    
    
	//告诉编译器  goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
	friend void goodGay::visit();

public:
	Building();

public:
	string m_SittingRoom; //客厅
private:
	string m_BedRoom;//卧室
};

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();

}

int main(){
    
    
    
	test01();

	return 0;
}

1.5 Surcharge de l'opérateur

Concept de surcharge des opérateurs : redéfinir les opérateurs existants et leur donner une autre fonction pour s'adapter aux différents types de données

1.5.1 Surcharge de l'opérateur Plus

Fonction : réaliser l'opération d'ajout de deux types de données personnalisés

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;

}

int main() {
    
    

	test();

	return 0;
}

Résumé 1 : Il est impossible de modifier les opérateurs des expressions des types de données intégrés

Résumé 2 : N'abusez pas de la surcharge des opérateurs

1.5.2 Surcharge de l'opérateur de décalage à gauche

Rôle : peut produire des types de données personnalisés

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; //链式编程
}

int main() {
    
    

	test();

	return 0;
}

Résumé : La surcharge de l'opérateur de décalage à gauche avec des amis peut réaliser la sortie de types de données personnalisés

1.5.3 Surcharge de l'opérateur d'incrémentation

Fonction : Réalisez vos propres données entières en surchargeant l'opérateur d'incrémentation

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;
	}

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;
}

int main() {
    
    

	test01();
	//test02();

	return 0;
}

Résumé : référence de retour avant incrémentation, valeur de retour après incrémentation

1.5.4 Surcharge des opérateurs d'affectation

Le compilateur c++ ajoute au moins 4 fonctions à une classe

  1. Constructeur par défaut (pas de paramètres, le corps de la fonction est vide)
  2. Destructeur par défaut (pas de paramètres, le corps de la fonction est vide)
  3. Le constructeur de copie par défaut, qui copie la valeur de la propriété
  4. Opérateur d'affectation operator=, copiez la valeur de l'attribut

S'il y a des attributs dans la classe pointant vers la zone de tas, il y aura également des problèmes de copie profonde et superficielle lors des opérations d'affectation

Exemple:

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;

	cout << "p2的年龄为:" << *p2.m_Age << endl;

	cout << "p3的年龄为:" << *p3.m_Age << endl;
}

int main() {
    
    

	test01();

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

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

	return 0;
}

1.5.5 Surcharge des opérateurs relationnels

**Fonction :** surcharge des opérateurs relationnels, permettant à deux objets de type personnalisé d'effectuer des opérations de comparaison

Exemple:

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;
	}
}


int main() {
    
    

	test01();

	return 0;
}

1.5.6 Surcharge de l'opérateur d'appel de fonction

  • L'opérateur d'appel de fonction () peut également être surchargé
  • Parce que la méthode utilisée après la surcharge est très similaire à l'appel d'une fonction, on l'appelle un foncteur
  • Functor n'a pas de méthode d'écriture fixe et est très flexible

Exemple:

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;
}

int main() {
    
    

	test01();
	test02();

	return 0;
}

1.6 Héritage

L'héritage est l'une des trois caractéristiques de l'orientation objet.

Nous pouvons envisager d'utiliser des techniques héritées pour réduire la duplication de code

1.6.1 Syntaxe de base de l'héritage

Par exemple, nous constatons que dans de nombreux sites Web, il existe des en-têtes publics, des fonds publics et même des listes de gauche publiques, seul le contenu central est différent.

Ensuite, nous utilisons la méthode d'écriture commune et la méthode d'écriture héritée pour réaliser le contenu de la page Web, et examinons la signification et les avantages de l'héritage.

Implémentation commune :

//Java页面
class Java 
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
    
    
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
    
    
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP 
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
	void content()
	{
    
    
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
    
    
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {
    
    

	test01();

	return 0;
}

Implémentation héritée :

//公共页面
class BasePage
{
    
    
public:
	void header()
	{
    
    
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void footer()
	{
    
    
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
    
    
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}

};

//Java页面
class Java : public BasePage
{
    
    
public:
	void content()
	{
    
    
		cout << "JAVA学科视频" << endl;
	}
};
//Python页面
class Python : public BasePage
{
    
    
public:
	void content()
	{
    
    
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP : public BasePage
{
    
    
public:
	void content()
	{
    
    
		cout << "C++学科视频" << endl;
	}
};

void test01()
{
    
    
	//Java页面
	cout << "Java下载视频页面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python页面
	cout << "Python下载视频页面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++页面
	cout << "C++下载视频页面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {
    
    

	test01();

	return 0;
}

Résumer:

Avantages de l'héritage:Le code dupliqué peut être réduit

classe A : publique B ;

Une classe est appelée sous-classe ou classe dérivée

La classe B est appelée classe parent ou classe de base

Membres de classes dérivées, comprenant deux parties :

Une classe est héritée de la classe de base et l'autre classe est un membre ajouté par lui-même.

Ceux hérités de la classe de base montrent leurs points communs, tandis que les membres nouvellement ajoutés reflètent leur individualité.

1.6.2 Méthode d'héritage

Syntaxe héritée :class 子类 : 继承方式 父类

Il existe trois types d'héritage :

  • héritage public
  • héritage protégé
  • héritage privé

Exemple:

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;
	}
};

1.6.3 Modèles d'objets dans l'héritage

Question : Lequel des membres hérités de la classe parent appartient à l'objet de la sous-classe ?

Exemple:

class Base
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};

//公共继承
class Son :public Base
{
    
    
public:
	int m_D;
};

void test01()
{
    
    
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {
    
    

	test01();

	return 0;
}

Après avoir ouvert la fenêtre de l'outil, accédez à la lettre de lecteur du fichier CPP actuel

Entrez ensuite : cl /d1 reportSingleClassLayout Le nom de fichier du nom de la classe à visualiser

Conclusion : les membres privés de la classe parent sont également hérités par la sous-classe, mais ils sont masqués par le compilateur et ne sont pas accessibles

1.6.4 Ordre de construction et de destruction en succession

Après que la sous-classe hérite de la classe parent, lorsque l'objet sous-classe est créé, le constructeur de la classe parent sera également appelé

Question : Qui vient en premier dans l'ordre de construction et de destruction de la classe mère et de la sous-classe ?

Exemple:

class Base 
{
    
    
public:
	Base()
	{
    
    
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
    
    
		cout << "Base析构函数!" << endl;
	}
};

class Son : public Base
{
    
    
public:
	Son()
	{
    
    
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
    
    
		cout << "Son析构函数!" << endl;
	}

};


void test01()
{
    
    
	//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
	Son s;
}

int main() {
    
    

	test01();

	return 0;
}

Résumé : Dans l'héritage, le constructeur de la classe parent est appelé en premier, puis le constructeur de la sous-classe est appelé. L'ordre de destruction est opposé à celui de la construction.

1.6.5 Héritage des membres du même nom

Question : Lorsqu'il existe des membres portant le même nom dans la sous-classe et la classe parent, comment accéder aux données portant le même nom dans la sous-classe ou la classe parent via l'objet sous-classe ?

  • L'accès aux membres de la sous-classe portant le même nom est accessible directement
  • L'accès aux membres portant le même nom que la classe parente nécessite une étendue

Exemple:

class Base {
    
    
public:
	Base()
	{
    
    
		m_A = 100;
	}

	void func()
	{
    
    
		cout << "Base - func()调用" << endl;
	}

	void func(int a)
	{
    
    
		cout << "Base - func(int a)调用" << endl;
	}

public:
	int m_A;
};


class Son : public Base {
    
    
public:
	Son()
	{
    
    
		m_A = 200;
	}

	//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
	//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
	void func()
	{
    
    
		cout << "Son - func()调用" << endl;
	}
public:
	int m_A;
};

void test01()
{
    
    
	Son s;

	cout << "Son下的m_A = " << s.m_A << endl;
	cout << "Base下的m_A = " << s.Base::m_A << endl;

	s.func();
	s.Base::func();
	s.Base::func(10);

}
int main() {
    
    

	test01();
	
	return EXIT_SUCCESS;
}

Résumer:

  1. Les objets de sous-classe peuvent accéder directement aux membres du même nom dans la sous-classe
  2. L'objet sous-classe plus la portée peut accéder aux membres du même nom de la classe parent
  3. Lorsque la sous-classe a une fonction membre portant le même nom que la classe parent, la sous-classe masque la fonction membre portant le même nom dans la classe parent et ajoute une portée pour accéder à la fonction portant le même nom dans la classe parent.

1.6.6 Héritage des membres statiques portant le même nom

Question : Comment accéder aux membres statiques portant le même nom dans l'héritage sur des objets de sous-classe ?

Les membres statiques et les membres non statiques portant le même nom sont gérés de la même manière

  • L'accès aux membres de la sous-classe portant le même nom est accessible directement
  • L'accès aux membres portant le même nom que la classe parente nécessite une étendue

Exemple:

class Base {
    
    
public:
	static void func()
	{
    
    
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
    
    
		cout << "Base - static void func(int a)" << endl;
	}

	static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
    
    
public:
	static void func()
	{
    
    
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};

int Son::m_A = 200;

//同名成员属性
void test01()
{
    
    
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//通过类名访问
	cout << "通过类名访问: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成员函数
void test02()
{
    
    
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	s.func();
	s.Base::func();

	cout << "通过类名访问: " << endl;
	Son::func();
	Son::Base::func();
	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
	Son::Base::func(100);
}
int main() {
    
    

	//test01();
	test02();

	return 0;
}

Résumé : Les membres statiques portant le même nom sont gérés de la même manière que les membres non statiques, sauf qu'il existe deux méthodes d'accès (par objet et par nom de classe)

1.6.7 Syntaxe d'héritage multiple

C++ permet à une classe d'hériter de plusieurs classes

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

L'héritage multiple peut provoquer l'apparition de membres portant le même nom dans la classe parente, qui doit être distinguée par la portée

Il n'est pas recommandé d'utiliser l'héritage multiple dans le développement réel de C++

Exemple:

class Base1 {
    
    
public:
	Base1()
	{
    
    
		m_A = 100;
	}
public:
	int m_A;
};

class Base2 {
    
    
public:
	Base2()
	{
    
    
		m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确
	}
public:
	int m_A;
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
class Son : public Base2, public Base1 
{
    
    
public:
	Son()
	{
    
    
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};


//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
    
    
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {
    
    

	test01();

	return 0;
}

Résumé : Dans l'héritage multiple, si le même nom apparaît dans la classe parente, la sous-classe doit ajouter une portée lors de son utilisation.

1.6.8 Héritage des diamants

Concept d'héritage de diamant :

​ Deux classes dérivées héritent de la même classe de base

Une autre classe hérite de deux classes dérivées en même temps

Ce type de succession est appelé succession de diamants, ou succession de diamants

Problème d'héritage de diamant :

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
    
  2. Le cheval de boue d'herbe a hérité de deux copies des données animales. En fait, nous devons être clairs sur le fait que nous n'avons besoin que d'une seule copie de ces données.

Exemple:

class Animal
{
    
    
public:
	int m_Age;
};

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

void test01()
{
    
    
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;

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


int main() {
    
    

	test01();

	return 0;
}

Résumer:

  • Le principal problème causé par l'héritage des diamants est que les sous-classes héritent de deux copies des mêmes données, ce qui entraîne un gaspillage de ressources et un non-sens.
  • L'utilisation de l'héritage virtuel peut résoudre le problème de l'héritage du diamant

1.7 Polymorphisme

1.7.1 Le concept de base du polymorphisme

Le polymorphisme est l'une des trois fonctionnalités orientées objet de C++

Le polymorphisme est divisé en deux catégories

  • Polymorphisme statique : la surcharge de fonctions et la surcharge d'opérateurs sont un polymorphisme statique, réutilisez les noms de fonctions
  • Polymorphisme dynamique : les classes dérivées et les fonctions virtuelles implémentent le polymorphisme d'exécution

La différence entre le polymorphisme statique et le polymorphisme dynamique :

  • Liaison anticipée de l'adresse de fonction polymorphe statique - détermine l'adresse de la fonction lors de la compilation
  • Liaison tardive d'adresse de fonction polymorphe dynamique - le runtime détermine l'adresse de fonction

Expliquons le polymorphisme à travers un cas

class Animal
{
    
    
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak()
	{
    
    
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
    
    
public:
	void speak()
	{
    
    
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal
{
    
    
public:

	void speak()
	{
    
    
		cout << "小狗在说话" << endl;
	}

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal)
{
    
    
	animal.speak();
}
//
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
    
    
	Cat cat;
	DoSpeak(cat);


	Dog dog;
	DoSpeak(dog);
}


int main() {
    
    

	test01();

	return 0;
}

Résumer:

Le polymorphisme satisfait la condition

  • Héritage
  • La sous-classe remplace la fonction virtuelle dans la classe parent

condition d'usage polymorphe

  • Un pointeur de classe parent ou une référence pointe vers un objet de sous-classe

Réécriture : la liste de paramètres de nom de fonction de type de valeur de retour de fonction est exactement la même que la réécriture

1.7.2 Cas de polymorphisme 1 - Classe Calculatrice

Descriptif du cas :

En utilisant respectivement la méthode d'écriture commune et la technologie polymorphe, concevez et implémentez la classe de calculatrice qui effectue des opérations sur deux opérandes

Avantages du polymorphisme :

  • L'organisation du code est claire
  • Lisible
  • Favorise une expansion et une maintenance précoces et tardives

Exemple:

//普通实现
class Calculator {
    
    
public:
	int getResult(string oper)
	{
    
    
		if (oper == "+") {
    
    
			return m_Num1 + m_Num2;
		}
		else if (oper == "-") {
    
    
			return m_Num1 - m_Num2;
		}
		else if (oper == "*") {
    
    
			return m_Num1 * m_Num2;
		}
		//如果要提供新的运算,需要修改源码
	}
public:
	int m_Num1;
	int m_Num2;
};

void test01()
{
    
    
	//普通实现测试
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;
	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;

	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;

	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}


//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
    
    
public :

	virtual int getResult()
	{
    
    
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 + m_Num2;
	}
};

//减法计算器
class SubCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 - m_Num2;
	}
};

//乘法计算器
class MulCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_Num1 * m_Num2;
	}
};


void test02()
{
    
    
	//创建加法计算器
	AbstractCalculator *abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  //用完了记得销毁

	//创建减法计算器
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  

	//创建乘法计算器
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

int main() {
    
    

	//test01();

	test02();

	return 0;
}

Résumé : Le développement C++ préconise l'utilisation d'une architecture de programme de conception de polymorphisme, car le polymorphisme présente de nombreux avantages

1.7.3 Fonctions virtuelles pures et classes abstraites

Dans le polymorphisme, l'implémentation de fonctions virtuelles dans la classe mère n'a généralement pas de sens, appelant principalement le contenu réécrit par la sous-classe

Vous pouvez donc changer la fonction virtuelle en fonction virtuelle pure

Syntaxe de la fonction virtuelle pure :virtual 返回值类型 函数名 (参数列表)= 0 ;

Lorsqu'il y a des fonctions virtuelles pures dans une classe, cette classe est aussi appeléeclasse abstraite

Caractéristiques de la classe abstraite :

  • Impossible d'instancier l'objet
  • La sous-classe doit remplacer la fonction virtuelle pure dans la classe abstraite, sinon elle appartient également à la classe abstraite

Exemple:

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;//记得销毁
}

int main() {
    
    

	test01();

	return 0;
}

1.7.4 Cas de polymorphisme 2 - Faire des boissons

Descriptif du cas :

Le processus général de fabrication des boissons est le suivant : faire bouillir de l'eau - infuser - verser dans la tasse - ajouter des accessoires

Utiliser la technologie polymorphe pour réaliser ce cas, fournir une classe de base abstraite pour la fabrication de boissons et fournir des sous-classes pour la fabrication de café et de thé

Exemple:

//抽象制作饮品
class AbstractDrinking {
    
    
public:
	//烧水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;
	//规定流程
	void MakeDrink() {
    
    
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//制作咖啡
class Coffee : public AbstractDrinking {
    
    
public:
	//烧水
	virtual void Boil() {
    
    
		cout << "煮农夫山泉!" << endl;
	}
	//冲泡
	virtual void Brew() {
    
    
		cout << "冲泡咖啡!" << endl;
	}
	//倒入杯中
	virtual void PourInCup() {
    
    
		cout << "将咖啡倒入杯中!" << endl;
	}
	//加入辅料
	virtual void PutSomething() {
    
    
		cout << "加入牛奶!" << endl;
	}
};

//制作茶水
class Tea : public AbstractDrinking {
    
    
public:
	//烧水
	virtual void Boil() {
    
    
		cout << "煮自来水!" << endl;
	}
	//冲泡
	virtual void Brew() {
    
    
		cout << "冲泡茶叶!" << endl;
	}
	//倒入杯中
	virtual void PourInCup() {
    
    
		cout << "将茶水倒入杯中!" << endl;
	}
	//加入辅料
	virtual void PutSomething() {
    
    
		cout << "加入枸杞!" << endl;
	}
};

//业务函数
void DoWork(AbstractDrinking* drink) {
    
    
	drink->MakeDrink();
	delete drink;
}

void test01() {
    
    
	DoWork(new Coffee);
	cout << "--------------" << endl;
	DoWork(new Tea);
}


int main() {
    
    

	test01();

	return 0;
}

1.7.5 Destruction virtuelle et pure virtuelle

Lors de l'utilisation du polymorphisme, s'il y a des propriétés dans la sous-classe qui sont allouées à la zone de tas, alors le pointeur de classe parent ne peut pas appeler le code destructeur de la sous-classe lorsqu'il est publié

Solution : changez le destructeur de la classe parent en destructeur virtuel ou en destructeur virtuel pur

Caractéristiques communes du destructeur virtuel et du destructeur virtuel pur :

  • Il peut résoudre le pointeur de classe parent pour libérer l'objet de sous-classe
  • besoin d'avoir une implémentation de fonction spécifique

La différence entre destructeur virtuel et destructeur virtuel pur :

  • S'il s'agit d'un destructeur virtuel pur, la classe est une classe abstraite et ne peut pas instancier un objet

Syntaxe du destructeur virtuel :virtual ~类名(){}

Syntaxe de destructeur virtuel pur :

virtual ~类名() = 0;

类名::~类名(){}

Exemple:

class Animal {
    
    
public:

	Animal()
	{
    
    
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;

	//析构函数加上virtual关键字,变成虚析构函数
	//virtual ~Animal()
	//{
    
    
	//	cout << "Animal虚析构函数调用!" << endl;
	//}


	virtual ~Animal() = 0;
};

Animal::~Animal()
{
    
    
	cout << "Animal 纯虚析构函数调用!" << endl;
}

//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

class Cat : public Animal {
    
    
public:
	Cat(string name)
	{
    
    
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
    
    
		cout << *m_Name <<  "小猫在说话!" << endl;
	}
	~Cat()
	{
    
    
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
    
    
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
    
    
	Animal *animal = new Cat("Tom");
	animal->Speak();

	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	//怎么解决?给基类增加一个虚析构函数
	//虚析构函数就是用来解决通过父类指针释放子类对象
	delete animal;
}

int main() {
    
    

	test01();

	return 0;
}

Résumer:

1. Le destructeur virtuel ou le destructeur virtuel pur est utilisé pour résoudre le problème de la libération d'objets de sous-classe via des pointeurs de classe parent

2. S'il n'y a pas de données de zone de tas dans la sous-classe, il n'est pas nécessaire de l'écrire comme un destructeur virtuel ou un destructeur virtuel pur

3. Une classe avec un destructeur virtuel pur est aussi une classe abstraite

1.7.6 Cas de polymorphisme 3 - Assemblage d'ordinateurs

Descriptif du cas :

Les principaux composants d'un ordinateur sont le processeur (pour le calcul), la carte graphique (pour l'affichage) et la clé USB (pour le stockage)

Encapsulez chaque pièce dans une classe de base abstraite et fournissez différents fabricants pour produire différentes pièces, tels que les fabricants Intel et les fabricants Lenovo

Créez une classe d'ordinateur pour fournir les fonctions qui font fonctionner l'ordinateur et appelez l'interface de chaque partie pour qu'elle fonctionne

Assemblez trois ordinateurs différents pour qu'ils fonctionnent pendant les tests

Exemple:

#include<iostream>
using namespace std;

//抽象CPU类
class CPU
{
    
    
public:
	//抽象的计算函数
	virtual void calculate() = 0;
};

//抽象显卡类
class VideoCard
{
    
    
public:
	//抽象的显示函数
	virtual void display() = 0;
};

//抽象内存条类
class Memory
{
    
    
public:
	//抽象的存储函数
	virtual void storage() = 0;
};

//电脑类
class Computer
{
    
    
public:
	Computer(CPU * cpu, VideoCard * vc, Memory * mem)
	{
    
    
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//提供工作的函数
	void work()
	{
    
    
		//让零件工作起来,调用接口
		m_cpu->calculate();

		m_vc->display();

		m_mem->storage();
	}

	//提供析构函数 释放3个电脑零件
	~Computer()
	{
    
    

		//释放CPU零件
		if (m_cpu != NULL)
		{
    
    
			delete m_cpu;
			m_cpu = NULL;
		}

		//释放显卡零件
		if (m_vc != NULL)
		{
    
    
			delete m_vc;
			m_vc = NULL;
		}

		//释放内存条零件
		if (m_mem != NULL)
		{
    
    
			delete m_mem;
			m_mem = NULL;
		}
	}

private:

	CPU * m_cpu; //CPU的零件指针
	VideoCard * m_vc; //显卡零件指针
	Memory * m_mem; //内存条零件指针
};

//具体厂商
//Intel厂商
class IntelCPU :public CPU
{
    
    
public:
	virtual void calculate()
	{
    
    
		cout << "Intel的CPU开始计算了!" << endl;
	}
};

class IntelVideoCard :public VideoCard
{
    
    
public:
	virtual void display()
	{
    
    
		cout << "Intel的显卡开始显示了!" << endl;
	}
};

class IntelMemory :public Memory
{
    
    
public:
	virtual void storage()
	{
    
    
		cout << "Intel的内存条开始存储了!" << endl;
	}
};

//Lenovo厂商
class LenovoCPU :public CPU
{
    
    
public:
	virtual void calculate()
	{
    
    
		cout << "Lenovo的CPU开始计算了!" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
    
    
public:
	virtual void display()
	{
    
    
		cout << "Lenovo的显卡开始显示了!" << endl;
	}
};

class LenovoMemory :public Memory
{
    
    
public:
	virtual void storage()
	{
    
    
		cout << "Lenovo的内存条开始存储了!" << endl;
	}
};


void test01()
{
    
    
	//第一台电脑零件
	CPU * intelCpu = new IntelCPU;
	VideoCard * intelCard = new IntelVideoCard;
	Memory * intelMem = new IntelMemory;

	cout << "第一台电脑开始工作:" << endl;
	//创建第一台电脑
	Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
	computer1->work();
	delete computer1;

	cout << "-----------------------" << endl;
	cout << "第二台电脑开始工作:" << endl;
	//第二台电脑组装
	Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
	computer2->work();
	delete computer2;

	cout << "-----------------------" << endl;
	cout << "第三台电脑开始工作:" << endl;
	//第三台电脑组装
	Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
	computer3->work();
	delete computer3;

}

Je suppose que tu aimes

Origine blog.csdn.net/cui_yonghua/article/details/131376206
conseillé
Classement