La base des notes d'étude C++

avant-propos

  Cette note d'étude est un résumé d'étude personnelle. Certains points de connaissance relativement simples et communs ne sont pas inclus. Il s'agit principalement de compléter et d'expliquer les doutes dans le processus d'apprentissage. Cette note est uniquement à titre de référence et d'étude, ne convient pas aux tutoriels.

1. Points de connaissance fragmentaires

Constantes : #defineet const, les constantes ne peuvent pas être modifiées lorsque le programme est en cours d'exécution
1. Les macro constantes sont-elles des chaînes ?
Réponse : Sa fonction est de définir un identifiant et une chaîne avec la directive de prétraitement du compilateur #define. Lorsque l'identifiant est trouvé dans le programme source, il sera remplacé par la chaîne spécifiée.
2. Les identificateurs sont sensibles à la casse et il est préférable d'en connaître la signification par leur nom.

insérez la description de l'image ici
Par défaut, les décimales sont traitées comme une double précision et f est ajouté à la simple précision. Lors de la sortie des décimales par défaut, il y a 6 chiffres significatifs (à la fois en simple et en double précision).
Notation scientifique : 1e-3, 1e-6
Chaîne :
Style C :
insérez la description de l'image ici

Style C++ :
insérez la description de l'image ici
ifIl existe trois types de structures
switchauxquelles il faut prêter attention lors de leur utilisation, breakles effets de l'ajout et de la non-ajout, et defaultles effets de l'ajout ou de la non-ajout.
Génération de nombres aléatoires :

int num = rand()% 100 + 1: // rand()%100 +1生成 0+1 99 +1 随机数

graine aléatoire

//添加随机数种子 作用利用当前系统时间生成随机数,防止每次随机数都一样
srand((unsigned int)time (NULL)):

insérez la description de l'image ici
switchIl ne peut s'agir que d'un entier ou d'un type de caractère, car les types d'entier et de caractère peuvent en fait être compris comme un type de données, mais le nombre d'octets occupés est différent.
Instruction de sautbreak :

Fonction : Il est utilisé pour sortir de la structure de sélection ou breakde la synchronisation de la structure de boucle :

  • Apparaît dans l'instruction conditionnelle du commutateur, la fonction est de terminer le cas et de sortir du commutateur
  • Apparaît dans une instruction de boucle, la fonction est de sauter hors de l'instruction de boucle actuelle
  • Apparaît dans une boucle imbriquée, sortant 最近de l'instruction de la boucle interne

Notez l'utilisation mixte de break dans les boucles et les sélections, break ne fonctionne que sur les instructions liées à lui-même.

goto //跳转到某一句

Il n'est pas recommandé d'utiliser, brisant la structure d'opération descendante de C++

goto FLAG;//直接跳转到下面的FLAG:后面的语句
cout << "3"<< endl;
cout << "4" << endl;
FLAG:
cout << "4" << endl;
system("pause") ;

Tableau :
Trois façons de définir un tableau dimensionnel :

1. Nom du tableau de type de données [longueur du tableau]
2. Nom du tableau de type de données [longueur du tableau] = {valeur1, valeur2...}
3. Nom du tableau de type de données[] = {valeur1, valeur2...} ;

Caractéristiques du tableau : chaque élément du tableau est placé dans un espace mémoire continu du même type de données.
La longueur du tableau ne peut pas être déterminée par des variables.
L'objectif du nom de tableau unidimensionnel

1. Peut compter la longueur du tableau entier en mémoire
2. Peut obtenir la première adresse du tableau en mémoire

Le nom du tableau est un
tri à bulle constant :

1. Comparez les éléments adjacents. Si le premier est plus grand que le second, échangez-les tous les deux.
2. Faites le même travail pour chaque paire d'éléments adjacents, et après exécution, trouvez la première valeur maximale.
3. Répétez les étapes ci-dessus, chaque fois que le nombre de comparaisons -1, jusqu'à ce qu'aucune comparaison ne soit requise

insérez la description de l'image ici
Tableau à deux dimensions : le nombre de colonnes ne peut pas être omis

1. Nom du tableau de type de données [numéro de ligne] [numéro de colonne]
2. Nom du tableau de type de données [numéro de ligne] [numéro de colonne]={ { { données 1, données 2, données 3}, {données 4, données 5, data 6}}
3. Nom du tableau de type de données[rows][columns]={data 1, data 2, data 3, data 4, data 5, data 6} 4. nom du tableau de type de données[][columns]= {
Données 1, Data 2, Data 3, Data 4, Data 5, Data 6} Le compilateur peut capturer automatiquement le nombre de lignes

Nom du tableau à deux dimensions :

1. Vous pouvez visualiser la taille de l'espace mémoire occupé
2. Vous pouvez visualiser la première adresse du tableau à deux dimensions

insérez la description de l'image ici
Fonction :
La définition d'une fonction comporte généralement 5 étapes

1. Type de valeur de retour
2. Nom de la fonction
3. Liste des paramètres
4. Instruction du corps de la fonction
5. Expression de retour

Passage par valeur :
les modifications de paramètres formels n'affecteront pas les paramètres réels et
voidles valeurs de retour ne peuvent pas être ajoutées

Quatre formes fonctionnelles :

1. Pas de participation, pas de retour
2. Participation, pas de retour
3. Pas de participation, retour
4. Participation, retour

Déclaration de fonction :
dites au compilateur que la fonction existe à l'avance, le compilateur doit la voir à l'avance, la déclaration peut être plusieurs fois, mais la définition ne peut être qu'une seule fois.

Ecriture d'un fichier de fonction :
l'écriture d'un fichier de fonction comporte généralement 4 étapes :

1. Créez .hun fichier d'en-tête avec le nom du suffixe 2. Créez un fichier source avec
le nom du suffixe 3. Écrivez la déclaration de la fonction dans le fichier d'en-tête Généralement, la bibliothèque à utiliser dans le fichier source doit également être écrite ici, par exemple , 4. Dans Ecrire la définition de la fonction dans le fichier source.cpp
iostreamusing namespace std;

  Il ne suffit pas de suivre les étapes ci-dessus. Vous devez également inclure un fichier d'en-tête personnalisé dans le fichier source, qui doit inclure le nom du prix du fichier d'en-tête que vous avez créé. Le but de cette étape est d'inclure l'en-tête personnalisé file Associées au fichier source, certaines déclarations requises dans le fichier source sont également écrites dans le fichier d'en-tête plutôt que dans le fichier source. Lors de son utilisation, il vous suffit d'ajouter le fichier d'en-tête du fichier que vous devez utiliser au début de l'endroit que vous utilisez.

insérez la description de l'image ici
insérez la description de l'image ici
insérez la description de l'image ici
Si le programme principal veut appeler la fonction définie à l'instant, il peut être utilisé directement en important directement le fichier d'en-tête.

insérez la description de l'image ici
Pointeur :
occupe généralement quatre octets, occupe le même espace sur chaque compilateur et n'a rien à voir avec le type de données.
Pointeurs nuls et pointeurs sauvages

Pointeur nul : La variable de pointeur pointe vers l'espace avec le numéro de mémoire 0
Objectif : Initialiser la variable de pointeur
Remarque : La mémoire pointée par le pointeur nul n'est pas accessible et le numéro de mémoire compris entre 0 et 255 est occupé par le système, il n'est pas accessible

//空指针
//1、空指针用于给指针变量进行初始化
int *p = NULL;
//2、空指针是不可以进行访问的,企图让p指向的内存填上数字100,是不可以的
*p = 100;

Le pointeur nul n'est pas accessible, car le nombre 0-255 en mémoire est occupé par le système.
Pointeur sauvage :
la variable de pointeur pointe vers un espace mémoire illégal

int main(
//野指针,随便让他们指向一个内存编号
//在程序中,尽量避免出现野指针
int *p = (int *)0x1100;
//访问野指针报错
cout << *p << end1;
system("pause");
return 0;

Pour cette partie, vous pouvez vous référer à ce qui suit : Résumé du pointeur sauvage C++

constLa décoration du pointeur
constpeut garantir que la valeur ne sera pas modifiée lorsque le pointeur est passé.
constIl existe trois cas de modification des pointeurs :

1. const pointeur modifié - pointeur constant
2. const constante modifiée - pointeur constant
3. const non seulement pointeur modifié, mais également constante modifiée

Voir qui est derrière le const?
Pointeur constant :

const int * p = &a;

Le pointage du pointeur peut être modifié, mais la valeur pointée par le pointeur ne peut pas être modifiée
en tant que constante de pointeur :

int * const p = &a;

Le pointage du pointeur ne peut pas être changé, mais la valeur pointée peut être changée
const signifie modifier le pointeur et modifier la constante :

const int * const p = &a;

Ni le pointage du pointeur ni la valeur pointée par le pointeur ne peuvent être modifiés.Méthode
de la mémoire :
  traduisez constdirectement en une constante, puis "*"traduisez-la en un pointeur pour distinguer clairement entre un pointeur constant et un pointeur constant const. Qu'il s'agisse d'un pointeur ou un doigt "*", celui qui le suit ne peut être modifié ou manipulé.
insérez la description de l'image ici

Astuces : Voir constsi celui de droite est un pointeur ou une constante, si c'est un pointeur, c'est un pointeur constant, si c'est une constante, c'est un pointeur constant

Structure :
Syntaxe :

struct 结构体名 {
    
    结构体成员列表 };

Il existe trois manières de créer des variables via des structures :

  1. struct nom de la structure variable nom de l'étoile.
  2. struct structure name nom de la variable = {membre 1 valeur, membre 2 valeur...}.
  3. Les variables sont d'ailleurs créées lors de la définition des structures.

Remarque : En C++, il peut être omis lors de la création d'une variable de structure struct, mais il ne peut pas être omis en C.
Résumer:

1 : Le mot-clé lors de la définition d'une structure est struct, qui ne peut pas être omis
2 : Lors de la création d'une structure, le mot-clé struct peut être omis
3 : La variable de structure utilise l'opérateur " ." pour accéder aux membres

Pointeur de structure :

Utilisez l'opérateur -> pour accéder aux propriétés de la structure via le pointeur de structure

Il y a deux phrases dans le code ci-dessus que j'ai oublié d'expliquer:

system("pause")//按任意键退出系统
system("cls")//清屏

L'opération ternaire peut être placée dans l'instruction de sortie :
insérez la description de l'image ici

switch case:

Si le cas est suivi d'une instruction composée, il doit être entouré d'accolades.

Idées de suppression de tableau :

Trouvez l'étiquette à supprimer et les données suivantes avanceront dans leur ensemble.

2. Noyau C++

2.1. Partition mémoire

Modèle de partition de mémoire :
ici c'est très important, très important, très important !

Lorsque le programme C++ est exécuté, la direction générale de la mémoire est divisée en 4régions

  • Zone de code : stocke le code binaire du corps de la fonction, géré par le système d'exploitation.
  • Zone globale : stocker les variables globales et les variables et constantes statiques
  • Zone de pile : automatiquement allouée et libérée par le compilateur, stockant les valeurs des paramètres de la fonction , les variables locales , etc.
  • Zone de tas : allouée et libérée par le programmeur, si le programmeur ne la libère pas, elle sera récupérée par le système d'exploitation à la fin du programme

La signification des quatre zones de mémoire :

Les données stockées dans différents domaines sont dotées de cycles de vie différents, ce qui nous donne une programmation plus flexible

Avant l'exécution du programme : Une fois le programme compilé, un programme exécutable
est généré . Avant que le programme ne soit exécuté, il est divisé en deux zones : la zone de code et la zone d'indicatif régional :exe

Stocker les instructions machine exécutées par le CPU.
La zone de code est partagée . Le but du partage est que pour les programmes fréquemment exécutés, une seule copie du code est nécessaire dans la mémoire. La
zone de code est en lecture seule . La raison de faire il est en lecture seule Empêche un programme de modifier accidentellement ses instructions

Zone globale :

Les variables globales et les variables statiques sont stockées ici.
La zone globale contient également la zone constante, les constantes de chaîne et d'autres constantes sont également stockées ici
.Les données de cette zone sont libérées par le système d'exploitation après la fin du programme.

Constante :
décoration constante de chaîne
const, divisée en global et local

insérez la description de l'image ici
insérez la description de l'image ici
Résumé : C++ est divisé en zone globale et en zone de code
avant l'exécution du programme

La zone de code est caractérisée par le partage et la lecture seule.
Les variables globales, les variables statiques et les constantes sont stockées
dans la zone globale. constLes constantes globales modifiées et les constantes de chaîne sont stockées dans la zone constante. Les variables locales modifiées par const ne sont pas placées dans la zone zone globale. Les constantes de macro ne sont pas allouées au tas ou à la zone globale, elles sont simplement remplacées par leurs valeurs constantes définies lors de la phase de prétraitement. Par conséquent, les constantes de macro n'existent que dans le code et ne se verront pas allouer d'espace mémoire lors de l'exécution du programme.

Après l'exécution du programme :
zone de pile :

Alloué et libéré automatiquement par le compilateur, stockant les valeurs des paramètres de la fonction, les variables locales, etc.

Remarque : Ne renvoyez pas l'adresse de la variable locale, les données créées dans la zone de pile seront automatiquement libérées par le compilateur , comme le montre l'exemple suivant :

int* func()
{
    
    
	int a = 10;//局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
	return &a; //返回局部变量的地址
}
int main() {
    
    
	//接受func函数的返回值
	int *p = func();
	cout << *p << endl; //第一次可以打印正确的数字,是因为编译器做了一次保留,只做一次保留; 
	cout << *p << endl; //第二次这个数据就不再保留了,此时相当于野指针
	system(pause");
	return 0;
}

Zone de tas :

Il est alloué et libéré par le programmeur. Si le programmeur ne le libère pas, il sera récupéré par le système d'exploitation à la fin du programme.
En C++, il est principalement utilisé pour newouvrir de la mémoire dans la zone de tas

int *func()
{
    
    
	//利用new关键字 可以将数据开辟到堆区
	//指针本质上也是局部变量,放在栈上,指针保存的数据是放在堆区
	int *p = new int(10); //返回的也是地址
	return p;
}
int main() {
    
    
	//在堆区开辟数据
	int *p = func();
	cout << *p << endl; 
	//只要不手动释放,在程序退出前可以一直输出
	cout << *p << endl; 
	cout << *p << endl; 
	cout << *p << endl; 
	system(pause");
	return 0;
}

nouvel opérateur :

En C++, l'opérateur new est utilisé pour développer des données dans la zone de tas. Les données développées dans la zone de tas sont développées manuellement par le programmeur et publiées manuellement. La version utilise la syntaxe de l'opérateur : , les données renvoyées sont les données créées par le pointeur utilisant new, et les données correspondant aux données delete
seront retournées type de new 数据类型pointeur

void test010()
{
    
    
	int * p = func();
	cout << *p <<endl;
	cout << *p <<endl;
	cout << *p <<endl;
	//堆区的数据 由程序员管理开辟,程序员管理释放
	//如果想释放堆区的数据,利用关键字 delete
	delete p; //释放p指向的内容
	//报错,读取访问权限冲突,此时再访问就是野指针
	cout << *p <<endl;
}

Créez un tableau dans le tas :

void test02(){
    
    
	//创建10整型数据的数组,在堆区
	int * arr = new int[10]: //10代表数组有10个元素,返回数组的首地址
	}

//释放堆区数组
//释放数组的时候 要加[]才可以
delete[] arr;

2.2. Références

Des alias de référence
sont donnés aux variables Dans un même espace de stockage, si l'une change, l'autre change également.
Pour citer la syntaxe de base :

type de données et alias = nom d'origine

Avis:

  • la référence doitinitialisation, vous devez indiquer au compilateur de quel alias il s'agit lorsque vous lancez
  • Une fois la référence initialisée, elle ne peut plus être modifiée.Ne peut être qu'un alias pour une variable, Du début à la fin
int a = 10;
//1、引用必须初始化//int &b;  错误,必须要初始化
int &b = a;
//2、引用在初始化后,不可以改变
int c = 20;
//赋值操作,而不是更改引用,相当于把c的值赋给了a和b,a和b完全等价,最终a,b,c三个值都是20.
b = c;

Cité comme arguments des fonctions :

Fonction : Lors du passage de paramètres dans une fonction, la technique de référence peut être utilisée pour laisser le paramètre formel modifier le paramètre réel.
Avantages : Cela peut simplifier le pointeur pour modifier le paramètre réel.

Avis:

Lors de l'utilisation d'une référence comme paramètre de fonction, le paramètre formel et le paramètre réel partagent le même espace mémoire. C'est-à-dire que la valeur du paramètre réel est directement transmise au paramètre formel, et la modification du paramètre formel dans la fonction sera directement répercutée sur le paramètre réel.

Résumer:

Le passage de paramètres par référence a le même effet que le passage par adresse. La syntaxe des citations est plus claire et plus simple.

Référence en tant que valeur de retour d'une fonction :
une référence peut exister en tant que valeur de retour d'une fonction.
Avis:

1. Ne renvoyez pas les références de variables locales, comme mentionné ci-dessusNe pas retourner l'adresse d'une variable localeIl y a une raison.
2. Appels de fonction en tant que lvalues

//返回局部变量引用,会引起意想不到的错误,相当于野指针 
int& test01() {
    
    
	int a = 10; //局部变量
	return a;
}

//返回静态变量引用
int& test02() {
    
    
	static int a = 20; //静态变量,存放在全局区,程序运行完才释放
	return a;
}

int main() {
    
    

	//不能返回局部变量的引用。这里说明一下,函数返回的是引用,接受的时候可以用引用也可以不用引用,
	//取决于是否需要对返回值进行修改。
	int& ref = test01();
	//第一次正确,编译器做了一次保留
	cout << "ref = " << ref << endl;
	//第二次错误,因为a的内存已释放
	cout << "ref = " << ref << endl;

	//如果函数做左值,那么必须返回引用
	//上面声明的是静态的,这里可以无限次使用
	int& ref2 = test02();
	//下面两句都能正常输出
	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;
	
	//如果函数的返回值是引用,这个函数的调用可以作为左值
	test02() = 1000;

	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;

	system("pause");

	return 0;
}

Expliquez à nouveau le code ci-dessus :
insérez la description de l'image ici

La nature du devis :

L'essence de la référence est implémentée dans C++ en tant quepointeur constant

C++Il est recommandé d'utiliser la technologie de référence , car la syntaxe est pratique et l'essence de la référence est une constante de pointeur, mais toutes les opérations de pointeur sont effectuées pour nous par le compilateur

//发现是引用,转换为 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;
    
	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;
    
	func(a);
	return 0;
}

Dans l'exemple ci-dessus, ref est trouvé en interne comme étant une référence, et il est automatiquement converti au format suivant : *ref = 20

Référence constante :
modifiez les paramètres formels pour éviter les abus, et utilisez la modification autant que C++possible dans l'écriture standard . La référence doit renvoyer à un espace mémoire légal :constconst

int a = 10:
int & ref = 10;//引用必须引一块合法的内存空间(栈区或者堆区的数据),这里会报错,因为10是放在常量区的
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
    
    
	//v += 10;
	cout << v << endl;
}

int main() {
    
    

	//int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
	/*加入const就可以了,编译器将代码修改为:int temp = 10; const int& ref = temp;
	创建了一个临时变量就可以引用了,
	其实这里引用的是个中间变量,我们找不到他的原名,原名是编译器起的,我们只能通过别名操作。*/
	const int& ref = 10; //加入const之后变为只读,不可修改

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

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

	system("pause");

	return 0;
}

Les références constantes sont généralement écrites dans des paramètres formels

2.3. Fonctions

Les paramètres par défaut de la fonction :

Si les données sont transmises, utilisez les vôtres, sinon utilisez la valeur par défaut.
S'il existe déjà un paramètre par défaut à une certaine position, il doit y avoir des paramètres par défaut à partir de cette position, c'est-à-dire que le paramètre par défaut doit être placé derrière.
Si la déclaration de la fonction a des paramètres par défaut, alors l'implémentation de la fonction ne peut pas avoir de paramètres par défaut, ou seulement la déclaration et l'implémentation peuvent avoir des paramètres par défaut.

Paramètres d'espace réservé des fonctions :
Syntaxe : 返回值类型 函数名 (数据类型){}
  Lors du passage des paramètres, les espaces réservés doivent également remplir les paramètres. Les paramètres d'espace réservé peuvent également avoir des paramètres par défaut, qui seront utilisés ultérieurement. Lorsqu'il existe des paramètres par défaut, il n'est pas nécessaire de passer des paramètres.

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

int main() {
    
    

	func(10,10); //占位参数必须填补,否者报错

	system("pause");

	return 0;
}

Surcharge de fonctions :
les noms de fonctions peuvent être identiques.
Condition de surcharge : trois conditions sont remplies en même temps, le noyau est que les paramètres doivent être différents

  • Sous la même portée, c'est-à-dire que la fonction que vous voulez appeler vient du même endroit
  • même nom de fonction
  • paramètres de fonctiondifférents typesouLe nombre est différentouordre différent

Remarque : La valeur de retour
d'une fonction ne peut pas être utilisée comme condition de surcharge.

//函数重载需要函数都在同一个作用域下
void func()
{
    
    
	cout << "func 的调用!" << endl;
}
void func(int a)
{
    
    
	cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
    
    
	cout << "func (double a)的调用!" << endl;
}
void func(int a ,double b)
{
    
    
	cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b)
{
    
    
	cout << "func (double a ,int b)的调用!" << endl;
}

//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
    
    
//	cout << "func (double a ,int b)的调用!" << endl;
//}


int main() {
    
    

	func();
	func(10);
	func(3.14);
	func(10,3.14);
	func(3.14 , 10);
	
	system("pause");

	return 0;
}

Remarque sur la surcharge de fonctions :

  • Les références peuvent être utilisées comme conditions de surcharge
  • La surcharge de fonction rencontre les paramètres par défaut de la fonction

Exemple:

//函数重载注意事项
//1、引用作为重载条件
void func(int &a)
{
    
    
	cout << "func (int &a) 调用 " << endl;
}

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,并且int& a = 10也不合法,但是const int& a = 10,相当于创建了一个临时的变量


	//func2(10); //碰到默认参数产生歧义,不知道调用哪个了,需要避免这种情况。有函数重载的时候尽量不要使用默认参数。这里传两个参数可以。

	system("pause");

	return 0;
}

Le cœur de la surcharge de fonctions : il ne peut y avoir aucune ambiguïté lors de l'appel d'une fonction.

2.4. Classes et objets

C++Le contenu le plus important dans l'orienté objet :encapsulationhériterpolymorphisme. Commençons par l'encapsulation.
Emballer:

L'encapsulation est C++l'une des trois principales caractéristiques de l'orienté objet
. 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

Signification du paquet :

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

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

Regardez un exemple de classe simple : créez une classe de cercle, trouvez la circonférence du cercle

//圆周率
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;

	system("pause");

	return 0;
}

Concevez 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.

//学生类
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();

	system("pause");

	return 0;
}

Accès aux classes et aux objets : y compris les fonctions et les variables

  1. public public les membres d'autorisation sont accessibles au sein de la classe et peuvent être consultés en dehors de la classe, généralement utilisés pour les interfaces externes
  2. la classe membre d'autorisation protégée protégée peut accéder au contenu protégé de la classe parent lorsque l'enfant ne peut pas accéder à l'héritage
  3. privé La classe membre de l'autorisation privée est accessible en dehors de la classe et n'est pas accessible. Lors de l'héritage, le fils ne peut pas accéder au contenu privé de la classe parent

Voyons un 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; //私有权限类外访问不到

	system("pause");

	return 0;
}


structLa différence entre les permissions protégées et les permissions privées qui ne sont pas accessibles en dehors de la classe class:
D'après la définition de la classe, on peut voir que la définition de struct et de classe est très similaire.

En C++ structet classla seule différence est queLes droits d'accès par défaut sont différents

la différence:

  • structL'autorisation par défaut est publique, si le membre n'ajoute pas de modificateur d'autorisation, la valeur par défaut estpublic
  • classL'autorisation par défaut est privée, si le membre n'ajoute pas de modificateur d'autorisation, la valeur par défaut estprivate
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;
}

On peut également voir à partir de l'exemple ci-dessus que
définir les propriétés des membres comme privées : cela se fait généralement dans les projets, et l'avantage des classes est la confidentialité.

Avantage 1 : Définissez tous les attributs de membre comme privés et vous pouvez contrôler vous-même les autorisations de lecture et d'écriture
Avantage 2 : Pour les autorisations d'écriture, nous pouvons vérifier la validité des données

class Person {
    
    
public:

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

	//设置年龄
	void setAge(int age) {
    
    
		if (age < 0 || age > 150) {
    
    
			cout << "请输入正确的年龄!" << endl;
			return;
		}
		m_Age = age;
	}

	//获取年龄 
	int getAge() {
    
    
		return m_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("xh");
	//cout << "爱人: " << p.m_Lover << endl;  //只写属性,不可以读取

	system("pause");

	return 0;
}

Une classe peut avoir une autre classe comme membre, voir l'exemple ci-dessous.

#include<iostream>
using namespace std;

//点和圆的关系
// 点类
class Point
{
    
    
public:
	//设置x坐标
	void setX(int x)
	{
    
    
		m_X = x;
	}
	//获取x坐标
	int getX()
	{
    
    
		return m_X;
	}
	//设置Y坐标
	void setY(int y)
	{
    
    
		m_Y = y;
	}
	//获取Y坐标
	int getY()
	{
    
    
		return m_Y;
	}

private:
	int m_X;
	int m_Y;

};

//圆类
class Circle
{
    
    
private:
	int m_R;
	Point m_Center;

public:
	//设置半径
	void setR(int r)
	{
    
    
		m_R = r;
	}
	//获取半径
	int getR()
	{
    
    
		return m_R;
	}
	//设置圆心
	void setCenter(Point center)
	{
    
    
		m_Center = center;
	}
	//获取圆心
	Point getCenter()
	{
    
    
		return m_Center;
	}
};

//判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
    
    
	//计算两点之间距离平方
	int distance =
		(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
	//计算半径平方
	int rDistance = c.getR() * c.getR();
	//判断关系
	if (distance == rDistance)
	{
    
    
		cout << "点在圆上" << endl;
	}
	else if (distance > rDistance)
	{
    
    
		cout << "点在圆外" << endl;
	}
	else
	{
    
    
		cout << "点在圆内" << endl;
	}
}

int main() {
    
    
	//创建圆
	Circle c;
	c.setR(10);
	Point center;
	center.setX(10);
	center.setY(10);
	c.setCenter(center);
	//创建点
	Point p;
	p.setX(10);
	p.setY(10);
	//判断关系
	isInCircle(c, p);

	system("pause");
	return 0;
}

  Le code ci-dessus a-t-il l'air désordonné, sans aucun niveau, ce qui est très défavorable au développement technique. Trions-le par type de fichier.
insérez la description de l'image ici
insérez la description de l'image ici
insérez la description de l'image ici
insérez la description de l'image ici
Après avoir terminé ce qui précède, le code semble-t-il beaucoup plus concis ?

2.4.1 Initialisation et nettoyage des objets

2.4.2 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, après avoir utilisé un objet ou une variable, s'il n'est pas nettoyé à temps, cela 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 consiste à attribuer des valeurs aux propriétés de membre de l'objet lors de la création de l'objet. Le constructeur est déterminé par le compilateurappel automatique, pas besoin d'appeler manuellement.
  • Destructeur : le rôle principal est dans l'objetavant la destructionLe système appelle automatiquement pour effectuer des travaux de nettoyage.

Syntaxe du constructeur :类名(){}

  1. Constructeur, pas de valeur de retour et pas d'écriturevoid
  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. Lorsque le programme appelle l'objet, ilappel automatiqueConstruction, pas besoin d'appeler manuellement, et il ne sera appelé qu'une seule fois

Syntaxe du destructeur : ~类名(){}

  1. Destructeur, pas de valeur de retour et pas d'écriturevoid
  2. Le nom de la fonction est le même que le nom de la classe, préfixez le nom avec un symbole~
  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

Noyau : un est responsable de l'initialisation et un est responsable de la destruction.

class Person
{
    
    
public:
	//构造函数
	Person()
	{
    
    
		//如果自己不写,系统默认为空,这里什么都不写
		cout << "Person的构造函数调用" << endl;
	}
	//析构函数
	~Person()
	{
    
    
	//如果自己不写,系统默认为空,这里什么都不写
		cout << "Person的析构函数调用" << endl;
	}

};
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
    
    
	Person p; //在栈上的数据,test01执行完毕后释放对象。如果把创建对象放到main函数中,要等程序运行完才释放,即对象销毁后才释放
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

2.4.3 Classement et appel du constructeur

Deux méthodes de classement :

  1. Selon les paramètres, il est divisé en : construction avec paramètres et construction sans paramètres (défaut) chantier
  2. Divisé par type : construction ordinaire et construction de copie

Trois méthodes d'appel :

  1. ​Parenthèses​
  2. méthode d'affichage
  3. méthode de conversion implicite
//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; //默认构造函数
	Person p11(10);//有参构造函数
	Person p12(p1);//拷贝构造函数,传对象
	//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明,不认为是创建对象
	//Person p2();

	//2.2 显式法
	Person p2; //默认构造函数
	Person p21 = Person(10); //有参构造函数,可以理解成Person p21(10)
	Person p22 = Person(p2); //拷贝构造函数,可以理解成Person p22(p2)
	//Person(10)单独写就是匿名对象,当前行结束之后,马上析构,即这个对象调用完,下面的函数还没执行就已经销毁了
	//不要用拷贝构造函数初始化匿名对象:Person(p2),会提示Person p2重定义,因为此时编译器会认为 Person p2;上面已经有一个了

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

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

int main() {
    
    
	test01();
	//test02();
	system("pause");
	return 0;
}

2.4.4. Quand appeler le constructeur de copie

Trois fois:

  • Initialiser un nouvel objet en utilisant un objet déjà créé
  • La méthode de passage de valeur consiste à passer des valeurs aux paramètres de la fonction, notez que ce n'est pas une référence
  • Renvoie l'objet local par valeur, notez qu'il ne s'agit pas d'une référence
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不会改变 这里定义的p,因为拷贝的是临时的副本,不一个内存空间
	doWork(p);
}

//3. 以值方式返回局部对象,局部对象有个特点,函数执行完立即释放
Person doWork2()
{
    
    
	Person p1;
	cout << (int *)&p1 << endl;
	//以值的方式返回,这里返回的并不是p1本身,而是根据p1来创建一个新的对象再返回
	return p1;
}

void test03()
{
    
    
	//函数执行完会调用析构函数
	Person p = doWork2();
	cout << (int *)&p << endl;
}


int main() {
    
    

	//test01();
	//test02();
	test03(); 	//执行完之后会有两个拷贝构造和两个析构函数打印出来。这里的析构函数要等程序运行完才会执行
	system("pause");
	return 0;
}

Règles d'appel du constructeur :
par défaut, tant qu'une classe est écrite, c++le compilateur ajoutera au moins une classe3 fonctions

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 l'attribut, même s'il n'est pas écrit par lui-même, le compilateur l'écrira par lui-même.

Par exemple, dans le code suivant, même si le constructeur de copie n'est pas écrit, le Person p2(p1);constructeur de copie sera automatiquement créé dans la classe lors de l'exécution de cette phrase, et p1l'attribut sera assigné à p2, et le compilateur fera une copie du attribut

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

	cout << "p2的年龄为: " << p2.age << endl;
}
  • Si un constructeur avec des paramètres est écrit dans la classe, mais qu'un constructeur par défaut (sans paramètre) n'est pas écrit, le compilateur ne fournira pas de constructeur par défaut sans paramètres. Si le constructeur par défaut est appelé, une erreur sera signalée, mais si un constructeur de copie n'est pas écrit, un constructeur de copie sera toujours fourni. L'exemple a été donné ci-dessus. Autrement dit, après avoir écrit une structure paramétrée, le compilateur ne fournit plus de structure par défaut.
  • Si seul le constructeur de copie est écrit dans la classe, mais que le constructeur par défaut et le constructeur paramétré ne sont pas écrits, le compilateur ne le fournira plus. Si le constructeur par défaut et le constructeur paramétré sont appelés à nouveau, une erreur sera signalée. C'est-à-dire qu'après avoir écrit la construction de copie, l'autre n'est plus fournie.

Prenons un exemple de la question ci-dessus :

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();
	system("pause");
	return 0;
}

Résumé :
La construction par défaut (sans paramètre), la construction avec paramètres et la construction par copie sont trois constructeurs. Seuls ces derniers sont fournis. Tant que les constructeurs précédents ne sont pas écrits par eux-mêmes, le compilateur ne les fournira pas.

2.4.5. Copie profonde et copie superficielle

Ce point de connaissance est très important, et il est souvent demandé lors des entretiens.

  • Copie superficielle : opération de copie d'affectation simple dans le compilateur
  • Copie en profondeur : réappliquez de l'espace dans la zone de tas et effectuez des opérations de copie
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();
	system("pause");
	return 0;
}

Si vous exécutez le code ci-dessus,
insérez la description de l'image ici
vous trouverez les résultats suivants : Voyons pourquoi cette erreur est signalée : La raison pour
insérez la description de l'image ici
  laquelle le code ci-dessus a un problème est qu'il y a un problème avec la hauteur, qui est créée dans la zone de tas. le bas et les données de la zone de tas sont copiées par défaut lors de la création de l'objet , c'est-à-dire que les données de la zone de tas sont copiées vers . Une fois que le programme a fini de s'exécuter, il doit exécuter le destructeur pour libérer la somme , mais la somme est placée sur la pile, les données de la pile sont d'abord libérées, puis les données de la pile sont libérées. les données dans la zone de tas sont libérées à plusieurs reprises et un conflit. La raison du problème est causée par la copie superficielle du constructeur de copie ? Comment le résoudre? Ce problème peut être résolu en utilisant le test approfondi. Il peut être résolu en annulant les commentaires ci-dessus, en implémentant le constructeur de copie par vous-même, en résolvant les problèmes causés par la copie superficielle et en n'utilisant pas la copie de construction par défaut du système. Comme indiqué dans le code de la figure ci-dessus, rouvrez une zone de tas pour stocker les données copiées. Résumer:p1p1p2p1p2p2p1p2p1p2p2p1/* */
insérez la description de l'image ici

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

2.4.6. Liste d'initialisation

C++ fournit une syntaxe de liste d'initialisation pour initialiser les propriétés. Nous recommandons cette façon d'écrire en ingénierie. Notez le placement des deux-points.
grammaire:构造函数():属性1(值1),属性2(值2)... {}

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) //相当于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();


	system("pause");

	return 0;
}

2.4.7 Objets de classe en tant que membres de classes

C++Un membre d'une classe peut être un objet d'une autre classe, on appelle ce membre un objet membre . Dans la relation entre le point et le cercle ci-dessus, il a en fait été montré une fois.

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

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

	string m_PhoneName;

};

class Person
{
    
    
public:

	//初始化列表可以告诉编译器调用哪一个构造函数
	/*Phone是一个类,这里给pName传过来的是个字符串,但是没有报错是因为什么呢?因为这里相当于隐式转换法
	Phone m_Pname = pName。这里就展示了类对象也可以用初始化列表的方式赋初值。*/
	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();
	system("pause");
	return 0;
}

Résumé :
Une conversion de type implicite se produit lors du passage

2.4.8. Membres statiques

Les membres statiques sont divisés en :
Variables de membre statique :

  • Tous les objets partagent les mêmes données
  • La mémoire est allouée pendant la phase de compilation et la mémoire est allouée avant l'exécution du programme .
  • Déclaration en classe, initialisation hors classe
  • accessible par nom de 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
  • accessible par nom de classe

Examinez d'abord les variables membres statiques :

class Person
{
    
    
	
public:
	//静态成员变量特点:
	//1 在编译阶段分配内存
	//2 所有对象共享同一份数据
	//3 类内声明,类外初始化,必须要做的,否则没法访问
	static int m_A; //静态成员变量

};
//类外初始化,注意初始化的时候作用域
int Person::m_A = 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; //共享同一份数据,输出200
	cout << "p2.m_A = " << p2.m_A << endl;//输出200
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

La déclaration en classe ci-dessus et l'initialisation hors classe signifient qu'elle doit être définie dans la classe et affectée en dehors de la classe, mais faites attention à la classe sous laquelle se trouve l'espace de déclaration.
insérez la description de l'image ici
Regardez le code complet, faites attention à la section commentaire :

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();
	system("pause");
	return 0;
}

Regardons à nouveau la fonction membre statique :

  • Les programmes partagent une fonction
  • Les fonctions membres statiques ne peuvent accéder qu'aux variables membres statiques
    et sont accessibles via le nom de la classe

insérez la description de l'image ici
  Pourquoi les fonctions membres statiques ne donnent-elles pas accès aux propriétés membres non statiques ? Étant donné que les données de la fonction membre statique n'ont qu'une seule copie en mémoire, ce qui précède m_Bn'est pas une variable membre non statique, elle doit être accessible via l'objet, lorsque nous appelons la fonction statique ci-dessus, l'intérieur du corps de la fonction ne savoir quel objet est modifié ci-dessous m_B. Par exemple, deux objets sont créés p1, p2, p1pour appeler la fonction membre statique ci-dessus, il y en a un dans la fonction m_B=200, dans le corps de la fonction statique, m_Bje ne sais pas p1comment le changer 200, bien que p1la fonction membre statique soit appelée , mais dans le corps de la fonction n'est pas Cela ne reflète pas qu'il s'agit d' p1un membre, et c'est p2la même chose pour la même raison. C'est m_Apeut-être parce qu'il n'appartient à aucun objet.

Les fonctions membres statiques ont également des droits d'accès :

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

2.4.9. Modèle d'objet C++ et ce pointeur

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

  1. En C++, les variables membres et les fonctions membres d'une classe sont stockées séparément
  2. seulvariable de membre non statiqueSur les objets qui appartiennent à une classe, comme les variables membres statiques, les fonctions membres statiques n'appartiennent pas à un certain objet et les fonctions membres non statiques n'appartiennent pas à un objet de classe. Il n'y a qu'une seule copie des fonctions membres non statiques, qui sont stockées séparément des variables membres statiques, c'est-à-dire que les fonctions n'appartiennent pas aux objets de classe.
class Person {
    
    
public:
	Person() {
    
    
		mA = 0;
	}
	//非静态成员变量占对象空间
	int mA;
	//静态成员变量不占对象空间
	static int mB; 
	//函数也不占对象空间,所有函数共享一个函数实例
	void func() {
    
    
		cout << "mA:" << this->mA << endl;
	}
	//静态成员函数也不占对象空间
	static void sfunc() {
    
    
	}
};
int main() {
    
    
	//一个空对象的占员工内存空间是1个字节,即C++编译器会为每个空对象分配一个字节空间,是为了区分空对象的占内存的位置
	//每个空对象也应该有一个独一无二的内存地址
	cout << sizeof(Person) << endl;
	system("pause");
	return 0;
}

  Lorsqu'il n'y a pas de membre dans un seul objet, le compilateur l'ouvriraun octetEspace. Lorsqu'il contient une variable membre non statique, l'objet occupe le nombre d'octets de la variable membre, c'est-à-dire que la mémoire est allouée en fonction de la variable membre. Cette variable membre non statique appartient à l'objet de la classe. S'il y a des variables membres statiques dans la classe, alors les variables membres statiques n'appartiennent pas à l'objet de classe, et l'objet de classe n'ouvrira pas de mémoire pour cela. S'il existe une fonction membre non statique dans la classe, elle n'appartient pas à l'objet de classe et ne lui alloue pas de mémoire. S'il existe une fonction membre statique dans la classe, elle n'appartient pas à l'objet de classe et la mémoire ne lui sera pas non plus allouée. Ce que je veux dire ci-dessus, c'est que les variables membres et les fonctions membres sont stockées séparément.
insérez la description de l'image ici
En fin de compte, seulementvariable de membre non statiqueappartient à l'objet de classe. Aucune mémoire n'est allouée sur les objets qui n'appartiennent pas à la classe.

2.4.9.2. ce pointeur

  Nous venons de dire que certains membres définis dans l'objet de classe n'appartiennent pas à l'objet de classe, alors comment distinguer quel objet est utilisé lors de la création de différents objets ?
  Nous savons que C++les variables membres et les fonctions membres sont stockées séparément, et 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++En fournissant un pointeur d'objet spécial, thispointeur ( une signification Pythondans la racine self), résolvez les problèmes ci-dessus. Le pointeur this pointe vers l'objet auquel appartient la fonction membre appelée , et celui qui l'appelle pointe vers qui .
thisUn pointeur est un pointeur implicite dans chaque fonction membre non statique. La fonction membre non statique a un pointeur this par défaut. Il peut être utilisé directement sans le définir vous-même.
thisLe pointeur n'a pas besoin d'être défini, il peut être utilisé directement pour utiliser
le pointeur this :

  • Lorsque le paramètre formel et la variable membre ont le même nom, ils peuvent être thisdistingués par des pointeurs. résoudre les conflits de noms
  • Pour renvoyer l'objet lui-même dans une fonction membre non statique d'une classe, utilisezretour*this. retourner l'objet lui-même

nous allons jeter un coup d'oeilconflit de nomUn exemple de:

class Person
{
    
    
public:
	/*
	这里在编译的时候不会出现错误,但是最终的程序输出结果不是10,这里想做的是把传过来的值赋值给成员变量age,
	但是呢成员变量和age同名,出现了名称冲突。规范写法应该是区分开来,成员属性前面加个m,即m_age,表示成员属性,
	即下面的/**/注释部分写法。或者通过this指针去解决。
	 */
	Person(int age)
	{
    
    
		age = age;
	}
	int age;
	/*
		Person(int age)
	{
		m_age = age;
	}
	int m_age;*/
};

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

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

insérez la description de l'image ici
Deuxième solution : en thisrésolvant.

class Person
{
    
    
public:
	Person(int age)
	{
    
    
		//1、当形参和成员变量同名时,可用this指针来区分
		//this指针指向被调用成员函数所属的对象,此时的this就相当于p1.
		this->age = age;
	}
	int age;
};

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

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

voir ci-dessousobjet de retourUtiliser seul *this:

class Person
{
    
    
public:

	Person(int age)
	{
    
    
		//1、当形参和成员变量同名时,可用this指针来区分
		this->age = age;
	}
	/*注意,这里返回的是引用,返回的还是对象本身,如果这里返回的不是引用直接Person而不是Person&,即直接返回值,那么下面的代码
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);输出结果还是20。为什么呢?因为如果这里返回的是值的
	话,在调用完p2.PersonAddPerson(p1)第一次之后p2的年龄加了10岁,这里没问题,但是呢这里返回的已经不是p2的本体了,而是按照本
	体创建了一个新的数据调用了拷贝构造函数,我们知道在拷贝构造函数调用构造函数时,用值的方式返回会复制一份新的数据出来,相当于这
	里的person已经和自身的不一样了。即每次返回的都是一个新的东西,跟原来已经不一样了。*/
	Person& PersonAddPerson(Person p)
	{
    
    
		this->age += p.age;
		//返回对象本身
		//this指向对象本身,而*this指向的是p2这个对象本体
		return *this;
	}

	int age;
};

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

	Person p2(10);
	//上面如果改成返回值的话这里输出就是20,因为值返回是拷贝构造创建新的对象,值一直都是10,加10返回就是20。
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

insérez la description de l'image ici
La différence entre la valeur de retour de la fonction et la référence de retour : le retour par valeur copiera une nouvelle donnée, tandis que la référence se retournera elle-même. Idées de programmation en chaîne.

2.4.9.3 Fonction membre d'accès au pointeur nul

En C++, le pointeur null peut également appeler la fonction membre, mais faites également attention à ce que le pointeur this soit utilisé.
Si thisle pointeur est utilisé, il doit être jugé pour garantir la robustesse du code.

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

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

	void ShowPerson() {
    
    
		//加上这句话,防止下面空指针代码出错
		if (this == NULL) {
    
    
			return;
		}
		//其实这里的mAge默认的是this->mAge,而传入的指针是NULL,不指向任何东西,这里会报错。
		cout << mAge << endl;
	}

public:
	int mAge;
};

void test01()
{
    
    
	//空指针,没有指向任何确切的对象
	Person * p = NULL;
	p->ShowClassName(); //空指针,可以调用成员函数,单独调用这行代码是不会出错的
	//调用这行代码会出错
	p->ShowPerson();  //但是如果成员函数中用到了this指针,就不可以了
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

À partir du code ci-dessus, on peut voir que le pointeur null peut accéder aux membres de la classe, mais soyez prudent lorsque vous l'utilisez. Lorsqu'aucun attribut de membre n'est utilisé dans la classe, un pointeur null peut être utilisé pour accéder au membre de la classe.

2.4.9.4. fonction membre modifiée const

Fonction constante :

  • Une fois la fonction membre ajoutée, constnous appelons cette fonction une fonction constante
  • Les attributs de membre ne peuvent pas être modifiés dans les fonctions constantes
  • Après avoir ajouté des mots-clés lors de la déclaration des propriétés des membres mutable, ils peuvent toujours être modifiés dans les fonctions constantes

Objet constant :

  • constL'objet est appelé un objet constant avant de déclarer l'objet
  • Les objets constants ne peuvent appeler que des fonctions constantes

Regardons d'abord la fonction constante, cet endroit est un peu déroutant, voir le code et les commentaires suivants :

class Person {
    
    
public:

	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
	void ShowPerson() const 
	{
    
    

		//this = NULL; //this指针是不能修改指针的指向的,但是指向的值是可以修改的,this的本意相当于:Person* const this;
		/*
		但是this指针指向的对象的数据是可以修改的,这里的前提是void ShowPerson() const中的const没加,如果加了也不能修改值了。
		总结:
		如果不加const呢,在这里的this指针就相当于Person * const this,指向不可修改,但是指向的值可以修改,
		如果加了const呢,就相当于const Person * const this,即指向不能改,指向的内容也不能改,
		那么这个const加在哪合适呢,编译器想来想去还是加载函数后面吧,于是就有了void ShowPerson() const*/
		this->mA = 100; 
		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		//变量前面加了mutable,表示可以修改
		//this->m_B = 100;
	}
	
public:
	int m_A;
	mutable int m_B; //可修改 可变的,即使在常函数中也可以修改
};


//const修饰函数
void test01() {
    
    

	Person p;   
	//当利用p去调用成员函数ShowPerson()的时候,类中的this就指向这个p。
	p.ShowPerson();
	cout << person.m_A << endl;
	p.showPerson();

}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Le code ci-dessus montre que thisl'essence du pointeur est unpointeur constant, le pointage du pointeur ne peut pas être modifié.
Reprenons constl' objet constant modifié :

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就不能改了

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

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

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


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

	const Person person; //在对象前加上const变为常量对象,不允许修改指针指向的值,对象的属性不允许修改
	cout << person.m_A << endl;
	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量

	//常对象访问成员函数
	person.MyFunc(); //常对象能调用const的函数,不能调用普通成员函数,因为普通成员函数可以修改成员变量

}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

  L'attribut membre ne peut pas être modifié dans la fonction constante. La raison en est que la fonction constante modifie le pointeur constet thisque thisle pointeur lui-même est un pointeur constant, donc après l'avoir ajouté, cosntmême la valeur pointée par le pointeur ne peut pas être modifiée. Si vous insistez pour le modifier, vous pouvez l'ajouter mutable. Les objets constants ne peuvent appeler que des fonctions constantes.

2.4.10.Tomomoto

  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 des amis. Un ami doit déclarer certaines fonctions spéciales ou certaines classes spéciales pour accéder aux membres privés de cette classe en tant que bon ami d'une autre classe.
Le mot-clé pour un ami estami
Il existe trois méthodes de mise en œuvre des amis :

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

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;
	//上面类里面如果没有friend void goodGay(Building * building)这个声明就会报错,无法访问私有属性。
	cout << "好基友正在访问: " << building->m_BedRoom << endl;
}


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

int main(){
    
    

	test01();

	system("pause");
	return 0;
}

Comme vous pouvez le voir ci-dessus, tant que vous placez la fonction globale devant la classe friend, vous pouvez accéder aux attributs privés de la
classe. La classe est un ami :
le but d'un ami est de permettre à une classe d'accéder aux membres privés dans une autre classe.

class Building; //为了防止下面写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;//卧室
};
//在类外声明构造函数,注意加上作用于,哪个类下面的。下面使用了this指针,如果使用初始化列表的方式就不能使用this指针了
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();
	system("pause");
	return 0;
}

Il y a une note implicite ci-dessus, à savoir si elle peut être utilisée lorsque le constructeur est initialisé, thisvous pouvez vous référer aux deux articles de blog suivants :

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();
	system("pause");
	return 0;
}

2.4.11. Surcharge de l'opérateur

Plus surcharge d'opérateur :
  concept de surcharge d'opérateur : redéfinir l'opérateur existant et lui donner une autre fonction pour s'adapter aux différents types de données. Par exemple, si je veux ajouter deux personnes, qu'est-ce que cela signifie d'ajouter deux personnes ? Regardez la légende ci-dessous : Pour ajouter deux objets, nous avons créé une Person AddPersonfonction membre/globale, mais chacun a un nom différent, donc le compilateur a donné un nom commun operator+, en utilisant la fonction du compilateur Le nom peut simplifier la méthode d'appel et être utilisé directement +. Il convient de noter que si la surcharge est implémentée via une fonction globale, deux variables doivent être transmises.
insérez la description de l'image ici
Prenons un exemple :
insérez la description de l'image ici
que dois-je faire si l'erreur ci-dessus est signalée ? Il doit être résolu en surchargeant.

class Person {
    
    
public:
	Person(int a, int b)
	{
    
    
		this->m_A = a;
		this->m_B = b;
	}
	//成员函数实现 + 号运算符重载
	Person operator+(const Person& p)
	{
    
    
		//创建一个临时的Person变量
		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 = p1.operator+(p2);
	//简化方法
	Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
	
	//全局函数重载的本质调用:Person p3 = operator+(p1,p2)
	Person p4 = p3 + 10; //相当于 Person p4 = operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
	
	//运算符重载也可以发生函数重载
	Person p4 = p3+10;
}

int main() {
    
    
	test();
	system("pause");
	return 0;
}

Résumé 1 : Il est impossible de modifier les opérateurs des expressions des types de données intégrés. Par exemple, deux entiers 1+1=2 ne peuvent pas être transformés en 1-1=0.
Résumé 2 : N'abusez pas de la surcharge des opérateurs Par exemple, opérateur+ est évidemment une opération d'addition, mais la fonction du code est la soustraction.

Opérateur de décalage vers la gauche :
  << la surcharge peut générer un contenu personnalisé. Par exemple, si vous souhaitez générer directement les variables membres dans la sortie du nom de classe, cela est normalement impossible, mais vous pouvez le faire via la surcharge.
insérez la description de l'image ici

class Person {
    
    
	friend ostream& operator<<(ostream& out, Person& p);

public:

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

	/*如果这里参数写的是Person& p,最后调用的时候变成p.operator<<(p),简化版本变成了p<<p,
	不是我们要的。那如果传cout呢(cout也是对象,可以传),最后调用的时候变成p.operator<<(cout),
	简化版本为p<<cout,也不是我们想要的结果。成员函数无法实现移位运算符的重载*/
	//void operator<<(Person& p){
    
    
	//}

private:
	int m_A;
	int m_B;
};

//只能全局函数实现左移重载
/*
ostream(输出流)对象,全局只能有一个,必须用引用方式传过来。注意这里的返回类型ostream。重载的核心operator<<。
这里还要注意到友元,私有类型。这里返回ostream才可以无限使用<<输出,不然只能使用一次。
这里的使用的是引用,cout是别名,可以换成其他的名字。这里关于cout可以是别名仍有疑问,待更近一步研究(第二遍的理解:cout在可以理解成内部定义好的关键词,只有一个,全局成员函数只是通过引用的方式接收他而已,所以想起什么名字起什么名字)。
*/
//注意,这里cout还必须是引用,因为全局只有一个cout对象
ostream& operator<<(ostream& cout, Person& p) {
    
    
	cout << "a:" << p.m_A << " b:" << p.m_B;
	return cout;
}

void test() {
    
    

	Person p1(10, 20);
	cout << p1 << "hello world" << endl; //链式编程,因为自己实现了重载,因此可以实现cout << p1也可以实现cout << "hello world",相当于调用的函数不一样。
}

int main() {
    
    

	test();
	system("pause");
	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. La surcharge ne peut pas être obtenue via les fonctions membres.

Surcharge de l'opérateur d'incrément :
Fonction : Réalisez vos propres données entières en surchargeant l'opérateur d'incrément
insérez la description de l'image ici

class MyInteger {
    
    

	friend ostream& operator<<(ostream& out, MyInteger myint);
public:
	MyInteger() {
    
    
		m_Num = 0;
	}
	//前置++,注意这里返回的是引用而不是值,如果返回值的话只能执行一次++,不可以连续多个++,如++(++a)仍然只做一次++。
	MyInteger& operator++() {
    
    
		//先++
		m_Num++;
		//再返回
		return *this;
	}

	//后置++,这里不能返回引用,如果返回的是引用相当于返回的是局部对象的引用,局部对象这里运行完立即释放,后面在操作就是非法的,这里有个占位参数int,只能写int,可以区分前置和后置,不然重载错误
	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();
	system("pause");
	return 0;
}

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

Surcharge de l'opérateur d'affectation :
le compilateur C++ ajoute en fait un total de 4 fonctions à au moins 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
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);
	//p2 = p1; //赋值操作,将p1的所有数据赋值给p2,此时p1和p2中的堆中的数据指向同一个位置,如果析构函数中使用的是浅拷贝将会报错,内存释放两次。
	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;
	system("pause");
	return 0;
}

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

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

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();
	system("pause");
	return 0;
}

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

	/*匿名对象调用MyAdd()代替add,执行完立即释放。匿名函数对象MyAdd(),首先他是个匿名对象,
	然后他又重载了(),所以我们叫它仿函数,所以又叫匿名函数对象。*/
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main() {
    
    
	test01();
	test02();
	system("pause");
	return 0;
}

2.5 Héritage

  Il existe une relation particulière entre certaines classes. Par exemple, nous voyons que de nombreux sites Web ont un en-tête commun, un bas commun et même une liste gauche commune. Seul le contenu central est différent. Ensuite, nous utilisons l'écriture ordinaire et l'héritage est utilisé pour implémenter le contenu des pages Web du didacticiel Java, C++ et Python, et jeter un œil à la signification et aux 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();
	system("pause");
	return 0;
}

Comme vous pouvez le voir, bon nombre des codes ci-dessus sont répétés.
Regardons l'utilisation de l'héritage :

//公共页面
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();
	system("pause");
	return 0;
}

Comme vous pouvez le voir, les méthodes héritées peuvent réduire considérablement la duplication de code.
Résumer:

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

class A : public B;

La classe A est appelée classe enfant 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é.

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é

insérez la description de l'image ici
L'extrême gauche de l'image ci-dessus est l'héritage public, qui est mal écrit sur l'image.
Regardons les trois exemples suivants :

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; //其他类只能访问到公共权限
	//s1.m_B;  报错,m_B到了子类Son1中变成了保护权限,类外不能访问保护权限
}

//保护继承
class Base2
{
    
    
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2:protected Base2
{
    
    
public:
	void func()
	{
    
    
		m_A; //父类中的公共成员,到子类中变成了保护权限
		m_B; //父类中的保护成员,到子类中变成了保护权限
		//父类中的私有成员,不可访问
		//m_C; //不可访问
	}
};
void myClass2()
{
    
    
	Son2 s;
	//报错,m_A到了子类Son2中变成了保护权限,类外不能访问保护权限
	//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; //父类中的公共成员到子类中变成了私有成员
		m_B; //父类中的保护成员到子类中变成了私有成员
		//m_C; //不可访问
	}
};
class GrandSon3 :public Son3
{
    
    
public:
	void func()
	{
    
    
		//Son3是私有继承,所以即使孙子以公共的方式继承父类Son3的属性在GrandSon3中都无法访问到,会直接报错
		//m_A;
		//m_B;
		//m_C;
	}
};

2.5.1 Modèle d'objet dans l'héritage

Toutes les propriétés de membre non statiques de la classe parent seront héritées, mais les propriétés de membre privées de la classe parent sont masquées par le compilateur, elles ne sont donc pas accessibles, mais elles sont en effet héritées.

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

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

void test01()
{
    
    
	//输出结果是12,父类中的所有非静态成员都别继承下去。父类中的私有属性是被编译器给隐藏了,因此访问不到,但是确实是被继承下去了。
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

Vous pouvez également utiliser l'outil d'invite de commande du développeur pour voir quelles propriétés sont héritées. Les étapes spécifiques sont les suivantes :
insérez la description de l'image ici
insérez la description de l'image ici

2.5.2 Ordre de construction et de destruction en succession

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

	system("pause");

	return 0;
}

insérez la description de l'image ici
S'il y a héritage dans la sous-classe, lors de la création de l'objet de la sous-classe, en raison de l'opération d'héritage, la sous-classe obtiendra définitivement les propriétés et les membres de la classe parente, et créera également l'objet de la classe parente, donc elle créera la classe parente objet Lors de l'appel de l'objet de classe parent.

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.

2.5.3. Héritage de la méthode de traitement du même nom

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();
	system("pause");
	return EXIT_SUCCESS;
}
  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 (y compris la surcharge) et ajoute une portée pour accéder à la fonction portant le même nom dans le parent. classe

2.5.4. Héritage des membres statiques avec la même méthode de traitement de nom

  Examinez d'abord les caractéristiques des variables membres statiques mentionnées précédemment : tous les objets partagent les mêmes données, la mémoire est allouée lors de la compilation, et les caractéristiques des fonctions membres statiques déclarées dans la classe et initialisées en dehors de la classe : seules les variables membres statiques sont accessibles, et les variables membres non statiques ne sont pas
accessibles et tous les objets partagent une donnée.
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
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();
	//通过类名访问父类中的func()。
	Son::Base::func();
	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问,如果下面的Base不加还是报错。
	Son::Base::func(100);
}
int main() {
    
    

	//test01();
	test02();

	system("pause");

	return 0;
}

Résumé : La méthode de traitement des membres statiques portant le même nom est la même que celle des membres non statiques, sauf qu'il existe deux méthodes d'accès (via des objets et via des noms de classe) et que les membres non statiques ne sont accessibles que via des objets.

2.5.5 Héritage multiple

C++ permet à une classe d'hériter de plusieurs classes
Grammaire : class 子类 :继承方式 父类1 , 继承方式 父类2...
l'héritage multiple peut entraîner l'apparition de membres portant le même nom dans la classe parente, et la portée doit être ajoutée pour la distinguer.
Dans le développement réel de C++, il n'est pas recommandé d'utiliser plusieurs héritage

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;
	//输出16,两个父类里面都继承了
	cout << "sizeof Son = " << sizeof(s) << endl;
	//当父类中出现同名的时候,使用的时候需要加作用域,所以一般不建议使用多继承
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {
    
    
	test01();
	system("pause");
	return 0;
}

2.5.6 Héritage des diamants

Concept d'héritage de diamant :

​ Deux classes dérivées héritent de la même classe de base​, et une certaine classe hérite de deux classes dérivées en même temps. Ce type d'héritage est appelé héritage diamant, ou héritage diamant

Regardez un exemple :

  1. Les moutons héritent des données des animaux et les chameaux héritent également des données des animaux.Lorsque les chevaux de boue d'herbe utilisent des données, parce que les moutons et les chameaux héritent des mêmes données, une ambiguïté surgit à ce moment.
  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.
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;
	//这份数据我们知道 只要有一份就可以,菱形继承导致数据有两份,资源浪费。那么这里输出的应该是100还是200呢?怎么解决这个问题?利用虚继承可以解决菱形继承的问题,继承之前 加上关键字 virtual 变为虚继承。这个时候的输出为200,包括上面的输出也变为了200。当我们发生虚继承之后只保留最后一次的,也就是只能有一份数据。
	cout << "st.m_Age = " << st.m_Age << endl;
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

Jetez un coup d'œil avec l'outil d'invite de commande du développeur.
insérez la description de l'image ici
insérez la description de l'image ici
La figure ci-dessus vbptrest le pointeur de classe de base virtuelle, qui pointera vers vbtable(table de classe de base virtuelle)

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 d'héritage du diamant (pointeur de classe de base virtuel, il y aura un pointeur de fonction virtuelle plus tard)
  • Utilisation non recommandée

2.6 Polymorphisme

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

Le polymorphisme se divise en deux catégories :

  • Polymorphisme statique :surcharge de fonctionsetsurcharge de l'opérateurAppartient au polymorphisme statique, réutiliser le nom de la fonction
  • Polymorphisme dynamique : les classes dérivées et les fonctions virtuelles implémentent le polymorphisme d'exécution,Habituellement, nous nous référons au polymorphisme en tant que polymorphisme dynamique

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

  • Adresse de fonction polymorphe statiquereliure précoce- La phase de compilation détermine l'adresse de la fonction
  • Adresse de fonction polymorphe dynamiquereliure tardive- Le runtime détermine l'adresse de la fonction

insérez la description de l'image ici
En C++, la conversion de type entre parent et enfant est autorisée sans conversion de type obligatoire, et le pointeur ou la référence de la classe parent peut pointer directement vers l'objet de la sous-classe.
La sortie de code ci-dessus est l' animal parlant , car maintenant l'adresse de notre fonction appartient à la liaison d'adresse, l'objet entrant a été lié à l' Animaladresse et l'adresse de la fonction a été déterminée lors de la phase de compilation. Si vous voulez exécuter Let the cat talk, l'adresse de cette fonction ne peut pas être liée à l'avance, elle doit être liée pendant la phase d'exécution et l'adresse est liée plus tard. Comment faire? Il s'agit de l'ajouter devant la fonction de la classe parent virtualAprès l'avoir ajouté, virtualles sous-classes suivantes peuvent réaliser la liaison tardive d'adresse après avoir implémenté la fonction portant le même nom que la classe parent.

Le polymorphisme satisfait les conditions :

1. Il existe une relation d'héritage
2. La sous-classe remplace la fonction virtuelle dans la classe parente, et le mot-clé virtuel peut être écrit ou non

Usage polymorphe :

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

Voir l'exemple ci-dessous :

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、子类重写父类中的虚函数,virtual关键字可写可不写
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
    
    
	Cat cat;
	DoSpeak(cat);
	
	Dog dog;
	DoSpeak(dog);
}


int main() {
    
    
	test01();
	system("pause");
	return 0;
}

Le polymorphisme dynamique satisfait les conditions :

1. Il existe une relation d'héritage
2. Sous-classesRemplacer la fonction virtuelle dans la classe parent, virtualle mot clé peut s'écrire ou non

Usage polymorphe :

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

2.6.1 Principes de bas niveau du polymorphisme

insérez la description de l'image ici
  Le cœur de la figure ci-dessus est la table de fonctions virtuelles, et la table de fonctions virtuelles de la sous-classe sera remplacée par l'adresse de fonction de la sous-classe. Il existe un pointeur de table de fonctions virtuelles dans la classe parente , qui pointe vers la fonction virtuelle dans la classe parente. Lorsque la sous-classe réécrit la fonction dans la classe parente, la référence ou le pointeur de l'objet de la classe parente pointe vers la sous-classe et le polymorphisme A ce moment, le pointeur pointe sur la sous-classe. Faites attention à la différence entre la distinction et le pointeur de classe de base virtuelle ci-dessus.
Avantages du polymorphisme :

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

Examinons un cas pour implémenter une calculatrice :

//普通实现
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();
	system("pause");
	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

2.6.2 Fonctions virtuelles pures et classes abstraites

  Dans le polymorphisme, généralement l'implémentation de la fonction virtuelle dans la classe parent n'a pas de sens, appelant principalement le contenu réécrit par la sous-classe, de sorte que la fonction virtuelle peut être changée en une fonction virtuelle pure . Il n'est pas nécessaire d'écrire des fonctions virtuelles pures lorsque la classe parent a du sens.
Syntaxe de la fonction virtuelle pure : virtual 返回值类型 函数名 (参数列表)= 0 ;
lorsqu'il y a une fonction virtuelle pure dans une classe, cette classe est aussi appeléeclasse abstraite
Les classes abstraites sont principalement utilisées pour implémenter des interfaces et le polymorphisme, et ne peuvent être utilisées que sous la forme de pointeurs ou de références , c'est-à-dire que des pointeurs ou des références à des classes abstraites peuvent être créés.
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
class Base
{
    
    
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	//抽象类无法实例化对象
	//子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
	virtual void func() = 0; //纯虚函数,前面必须加virtual才可以等于0
};

class Son :public Base
{
    
    
public:
	//子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
	virtual void func() 
	{
    
    
		cout << "func调用" << endl;
	};
};

void test01()
{
    
    
	Base * base = NULL;
	// Base b;// 错误,抽象类无法实例化对象
	//base = new Base; // 错误,抽象类无法实例化对象。无论栈还是堆方式都不可以
	
	base = new Son;
	base->func();
	delete base;//记得销毁
}

int main() {
    
    

	test01();
	system("pause");
	return 0;
}

Technologie polymorphe : le pointeur de la classe mère est utilisé pour faire référence ou pointer vers l'objet de la sous-classe

Prenons un autre exemple et faisons différentes boissons.

//抽象制作饮品
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();
	system("pause");
	return 0;
}

Le cœur du polymorphisme consiste à rechercher des fonctions réécrites à partir de sous-classes via des pointeurs de classe parent ou des références à des objets similaires.

2.6.3 Destructeur virtuel et destructeur virtuel pur

  Introduisons le destructeur virtuel et le destructeur virtuel pur. Pourquoi avons-nous besoin de savoir cela ? Nous savons que lorsque le polymorphisme est utilisé, le pointeur ou la référence de la classe parent est utilisé pour pointer vers l'objet de la sous-classe, mais le pointeur de la classe parent ne peut pas libérer le code destructeur dans la sous-classe , donc les données de la sous-classe sont ouvertes à la zone de tas, alors lorsque le pointeur de classe parent est là, deleteles données de la sous-classe ne peuvent pas être libérées, ce qui provoquera une fuite de mémoire

Lorsque le polymorphisme est utilisé, s'il y a des attributs dans la sous-classe qui sont alloués à 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 la fonction destructeur de la classe parent en
virtual destructeur ou destructeur virtuel pur

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

  • Cela peut résoudre le problème que le pointeur de classe parent ne peut pas 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 y en a un dans le code 纯虚析构, la classe est une classe abstraite et les objets ne peuvent pas être instanciés. Cela n'entre pas en conflit avec la fonction virtuelle pure ci-dessus, et le destructeur est également une fonction.

Syntaxe du destructeur virtuel :

virtual ~类名(){}

Syntaxe de destructeur virtuel pur :

virtual ~类名() = 0;

类名::~类名(){}

class Animal {
    
    
public:

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

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

	/*纯虚析构,利用纯虚析构和虚析构都可以解决,只能用一个。纯虚析构里面也要写上代码实现,不然直接写这句话报错,
	类外写上代码实现。区分跟纯虚函数的区别,纯虚函数可以直接写等于0*/
	//有了纯虚析构 之后 ,这个类也属于抽象类,无法实例化对象
	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();
	system("pause");
	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. Les deux sont corrects. La différence est qu'après avoir écrit un destructeur virtuel pur, cette classe est une classe abstraite.
  2. S'il n'y a pas de données de zone de tas dans la sous-classe, elle ne peut pas être écrite en tant que destructeur virtuel ou destructeur virtuel pur
  3. Une classe avec un destructeur virtuel pur est aussi une classe abstraite
  4. Les destructeurs virtuels et virtuels purs doivent avoirlettre spécifiquenombre réalisé.
  5. Le destructeur virtuel pur dans la classe parent est également une classe abstraite et l'objet ne peut pas être instancié.

2.7. Documents

2.7.1. Opération de fichier

Les données générées lorsque le programme est en cours d'exécution sont toutes des données temporaires et seront publiées une fois le programme terminé.
Les opérations de C++ sur les fichiers sont également basées sur des opérations orientées objet

Les données peuvent être conservées dans des fichiers

Les opérations sur les fichiers en C++ doivent inclure les fichiers d'en-tête< fstream >

Il existe deux types de fichiers :

  1. Fichiers texte - les fichiers sont stockés sur votre ordinateur sous forme de texte au format ASCII
  2. Fichiers binaires - les fichiers sont stockés sur l'ordinateur sous forme de texte binaire et les utilisateurs ne peuvent généralement pas les lire directement

Trois catégories de dossiers d'exploitation :

  1. ofstream : opération d'écriture
  2. ifstream : opération de lecture
  3. fstream : opérations de lecture et d'écriture

2.7.2. Fichiers texte

Les étapes pour écrire un fichier sont les suivantes :

  1. Inclure les fichiers d'en-tête
    #include <fstream>
  2. Créez un objet stream
    de stream ofs ;
  3. Ouvrez le fichier
    ofs.open("file path", open method);
  4. writedata
    ofs << "données écrites" ;
  5. Fermez le fichier
    ofs.close();

Méthode d'ouverture de fichier :

méthode ouverte expliquer
ios :: dans ouvrir le fichier pour le lire
ios :: sortie ouvrir le fichier pour l'écriture
ios :: mangé Position initiale : fin de fichier
ios :: appli Ajout pour écrire des fichiers
ios :: tronc Si le fichier existe, supprimez-le d'abord, puis créez-le
ios :: binaire mode binaire

Remarque : La méthode d'ouverture de fichier peut être utilisée conjointement avec l'opérateur |

Par exemple : écrire un fichier en mode binaireios::binary | ios:: out

#include <fstream>

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

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

	ofs.close();
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

Une fois le programme ci-dessus exécuté, un fichier txt sera généré à l'emplacement du programme
.

  • Les opérations sur les fichiers doivent inclure le fichier d'en-tête fstream
  • Les fichiers lus peuvent utiliser la classe ofstream ou fstream
  • Lors de l'ouverture d'un fichier, vous devez spécifier le chemin du fichier d'opération et la méthode d'ouverture
  • Utilisez << pour écrire des données dans le fichier
  • Une fois l'opération terminée, fermez le fichier

2.7.3. Lecture de fichiers

Les étapes de lecture d'un fichier sont similaires à celles de l'écriture d'un fichier, mais il existe d'autres méthodes de lecture

Les étapes pour lire un fichier sont les suivantes :

  1. Inclure les fichiers d'en-tête
    #include <fstream>
  2. Créez un objet flux
    ifstream ifs ;
  3. Ouvrez le fichier et jugez si le fichier est ouvert avec succès
    ifs.open("file path", open method);
  4. Lire les données
    Quatre façons de lire
  5. Fermez le fichier
    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))
	//{
    
    
	//	cout << buf << endl;
	//}

	char c;
	while ((c = ifs.get()) != EOF)
	{
    
    
		cout << c;
	}

	ifs.close();


}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

2.7.4. Ecriture de fichier binaire

Lire et écrire des fichiers en mode binaire

Ouvert avec à spécifier commeios :: binaire

2.7.5 Écrire des fichiers

L'écriture de fichiers en mode binaire utilise principalement l'objet stream pour appeler la fonction membre write

Prototype de fonction :ostream& write(const char * buffer,int len);

Explication des paramètres : le tampon de pointeur de caractère pointe vers un espace de stockage en mémoire. len est le nombre d'octets lus et écrits

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

	//4、写文件
	ofs.write((const char *)&p, sizeof(p));

	//5、关闭文件
	ofs.close();
}

int main() {
    
    

	test01();

	system("pause");

	return 0;
}
  • L'objet de flux d'entrée de fichier peut lire des données en mode binaire via la fonction de lecture

2.7.6. Lecture de fichiers

La lecture de fichiers en mode binaire utilise principalement l'objet stream pour appeler la fonction membre read

Prototype de fonction :istream& read(char *buffer,int len);

Explication des paramètres : le tampon de pointeur de caractère pointe vers un espace de stockage en mémoire. len est le nombre d'octets lus et écrits

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

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

int main() {
    
    

	test01();

	system("pause");

	return 0;
}

Je suppose que tu aimes

Origine blog.csdn.net/qq_38683460/article/details/128235905
conseillé
Classement