[C++ Essence Shop] 5. Six fonctions membres par défaut des classes C++ et des classes d'objets (intermédiaires)

Table des matières

1. Six fonctions membres par défaut

2. Constructeur

2.1 Concept

2.2 Construction par défaut

2.2.1 Constructions par défaut générées par le système

2.2.2 Constructeur personnalisé par défaut

 2.3 Surcharge des constructeurs

3. Destructeur

3.1 Notion

 3.2 Destructeur généré par le système

 3.3 Destructeur personnalisé

4. Copier la construction

4.1 Notion

 4.2 Construction de copie générée par défaut (copie superficielle)

 4.3 Construction de copie personnalisée (copie profonde)

 5. Surcharge des opérateurs d’affectation

5.1 Surcharge de l'opérateur

5.2 Surcharge des opérateurs d'affectation

6. Surcharge des opérateurs d'adresse et d'adresse const

7. Pièce jointe : classe de date complète (le code dans l'article n'est pas extrait du code ici, il est temporairement tapé dans le but d'expliquer les points de connaissance, le code ici est la classe de date complète (extraite de Bittech), vous pouvez en tirer des leçons)


1. Six fonctions membres par défaut

        Quand nous pensons à une classe vide, nous devons penser qu’une classe qui ne contient rien est appelée une classe vide, mais ce n’est pas le cas. Lorsque rien n'est écrit dans une classe, le compilateur générera par défaut six fonctions membres par défaut pour compléter les fonctions de base d'une classe.

  1. Constructeur : l'initialisation des objets fonctionne
  2. Destructeur : travaux de nettoyage de l'espace
  3. Surcharge des opérateurs de construction et d'affectation de copie : copie d'objets, travaux de copie
  4. Surcharge de la récupération d'adresses et de la récupération const d'adresses : généralement, elle est rarement implémentée par elle-même, sauf s'il est nécessaire de renvoyer une adresse spéciale spécifiée à l'utilisateur.

2. Constructeur

2.1 Concept

        Le constructeur est une fonction membre spéciale qui est automatiquement appelée par le compilateur pour terminer l'initialisation de l'objet lors de la création de l'objet . Le constructeur n'a pas de valeur de retour et le nom de la fonction est le même que le nom de la classe.

        Les fonctionnalités du constructeur sont les suivantes :

  1. Le nom de la fonction est le même que le nom de la classe.
  2. Aucune valeur de retour.
  3. Le compilateur appelle automatiquement le constructeur correspondant lorsque l'objet est instancié.
  4. Les constructeurs peuvent être surchargés 
class Date
{
public:
	Date()
	{
		//构造函数
	}
private:

};

2.2 Construction par défaut

        Qu’est-ce que la construction par défaut ? Le constructeur sans paramètre et le constructeur par défaut sont appelés constructeurs par défaut et il ne peut y avoir qu'un seul constructeur par défaut. De manière générale, les types personnalisés que nous implémentons doivent avoir un constructeur par défaut, la raison sera décrite ci-dessous. Remarque : Les constructeurs sans argument, les constructeurs par défaut complets et les constructeurs que nous n'avons pas écrits pour être générés par le compilateur par défaut peuvent tous être considérés comme des constructeurs par défaut.

2.2.1 Constructions par défaut générées par le système

         Lorsque nous ne définissons pas manuellement le constructeur, le système générera par défaut un constructeur sans argument. Ce constructeur sans argument initialisera les variables membres de la classe, notamment en appelant son constructeur par défaut pour le type personnalisé (c'est pourquoi chaque classe doit avoir une structure par défaut), ne gère pas les types intégrés , oui, vous avez bien entendu, la structure par défaut générée par le système ne gère pas les types intégrés, c'est un très gros point de bug. Par exemple le cas suivant :

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
private:
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	cout << d._year << d._month << d._day << endl;
	return 0;
}

sortir:

Temps()
-858993460-858993460-858993460 

        On peut voir que le constructeur généré par défaut ne traitera pas les types intégrés et que sa construction par défaut sera appelée pour les types personnalisés. De ce fait, la structure par défaut générée par le système est inutile, donc un patch est appliqué à cette vulnérabilité en C++11 : En C++11, un patch est appliqué pour le défaut de non-initialisation de type intégré membres, à savoir : La variable membre de type intégrée peut recevoir une valeur par défaut lorsqu'elle est déclarée dans la classe. (Donnez une valeur par défaut au type intégré de la même manière que vous donnez une valeur par défaut) comme suit :

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	// 基本类型(内置类型)
	int _year = 2023;
	int _month = 8;
	int _day = 9;
private:
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	cout << d._year << ' ' << d._month << ' '  << d._day << endl;
	return 0;
}

sortir:

Temps()
2023 8 9

2.2.2 Constructeur personnalisé par défaut

        Il n'existe que deux types de constructeurs personnalisés par défaut, les constructeurs sans argument et les constructeurs par défaut complets , et un seul de ces deux types de constructeurs peut exister. Mais quelqu'un pourrait se demander : ces deux constructeurs ne constituent-ils pas une surcharge, pourquoi ne peuvent-ils pas exister en même temps ? Tout d'abord, la syntaxe stipule qu'une seule structure par défaut peut exister. De plus, bien que ces deux fonctions soient conformes à la syntaxe de surcharge de fonctions, il y aura une ambiguïté lors de leur appel. Il faut savoir qu'il y a des raisons à toute réglementation grammaticale. comme suit:

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a = 1, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d;  //这里出现了歧义,
			   //不知道调用的是全缺省的构造还是无参的构造
			   //因为这俩种函数都可以用 Date() 的形式来调用就出现了歧义
	return 0;
}

 Erreur :
C2668 'Date::Date' : appel ambigu à une fonction surchargée.

E0339 La classe "Date" contient plusieurs constructeurs par défaut.

        Mais si nous transmettons les paramètres réels au constructeur, nous pouvons l'appeler, donc la grammaire stipule qu'il ne peut y avoir qu'une seule construction par défaut. L'essentiel est que la situation ci-dessus provoquera une ambiguïté et que nous pouvons transmettre un paramètre réel alors que nous avons l'habitude de le faire. instancier. Pour éviter ce genre d'ambiguïté, même si deux structures par défaut sont définies à ce moment-là, aucune erreur ne sera toujours signalée, mais nous ne recommandons pas d'écrire de cette façon, car le risque dans le projet est énorme, comme le suivant :

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a = 1, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d(1,2);  //不建议,不能因为避免报错就去特殊处理初始化方式。
                  //不能同时定义俩个默认构造,即使编译器没有报错
	return 0;
}

 2.3 Surcharge des constructeurs

        Le constructeur prend en charge la surcharge, ce qui nous permet de gérer différents scénarios d'initialisation (ici j'insiste encore : la construction sans argument et la construction complète par défaut ne peuvent pas être définies en même temps, et il y aura une ambiguïté).

public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
	Date(int year, int month, int day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};

3. Destructeur

3.1 Notion

        La nature du destructeur est très similaire à celle du constructeur, mais contrairement à la fonction du constructeur, le destructeur est utilisé pour nettoyer l'espace après la destruction de l'objet, mais il ne complète pas la destruction de l'objet lui-même, et la destruction de l'objet local est effectuée par le compilateur de. Lorsque l'objet est détruit, il appellera automatiquement le destructeur pour terminer le nettoyage des ressources de l'objet.

        Le destructeur n'a aucun paramètre ni aucune valeur de retour comme le constructeur, et le nom est différent, et le destructeur est automatiquement appelé par le compilateur lorsque l'objet est détruit, ce qui est similaire au constructeur. Les caractéristiques sont les suivantes :

  1. Le nom du destructeur est préfixé par le caractère ~ avant le nom de la classe.
  2. Aucun paramètre et aucun type de retour.
  3.  Une classe ne peut avoir qu'un seul destructeur. S'il n'est pas explicitement défini, le système générera automatiquement un destructeur par défaut. Remarque : les destructeurs ne peuvent pas être surchargés.
  4. Lorsque le cycle de vie de l'objet se termine, le système de compilation C++ appelle automatiquement le destructeur.
class Date
{
public:
	~Date()
	{
		//析构函数
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
};

 3.2 Destructeur généré par le système

        S'il n'y a pas de définition explicite d'un destructeur dans une classe, le système générera automatiquement un destructeur par défaut. Ce destructeur sera appelé automatiquement à la fin du cycle de vie de l'objet. Le destructeur généré par défaut ne traite pas les types intégrés. Pour types personnalisés, son destructeur sera appelé. comme suit:

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

sortir:

~Date()
~Heure()

 3.3 Destructeur personnalisé

        Comme mentionné ci-dessus, le destructeur ne gère pas les types intégrés, mais dans notre utilisation quotidienne, nous impliquons souvent des applications mémoire (telles que malloc) et utilisons ensuite des types pointeurs pour stocker les adresses. Nous ne pouvons pas compter sur le système pour de tels espaces. le destructeur par défaut est généré pour éviter les fuites de mémoire, et vous devez définir votre propre destructeur pour le publier manuellement. 

class Date
{
public:
	Date()
	{
		p = (int*)malloc(10 * sizeof(int));
	}
	~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	int* p = nullptr;
};

4. Copier la construction

4.1 Notion

        La construction par copie est une forme surchargée de constructeur. La construction par copie n'a qu'un seul paramètre formel, qui est généralement une référence const de ce type (seules les références peuvent être transmises, pas les valeurs, et le passage des valeurs provoquera une récursion infinie). Appelé automatiquement par le compilateur lorsqu'un nouvel objet de type objet est créé.

        Passer par référence (correct):

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)  //拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	int* p = nullptr;
};

        Passer par valeur (récursivité infinie) :

 4.2 Construction de copie générée par défaut (copie superficielle)

        Lorsque nous ne définissons pas explicitement la construction de copie,une construction de copie par défaut sera automatiquement générée.La construction de copie par défaut copiera nos variables membres octet par octet,et deviendra également une copie de valeur ou une copie superficielle.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		p = (int*)malloc(10 * sizeof(int));
	}
    ~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
	int* p = nullptr;
};

int main()
{
	Date d1(12, 12, 12);
	Date d2(d1);   //引发异常
}

 4.3 Construction de copie personnalisée (copie profonde)

        En matière de gestion de la mémoire, la structure de copie générée par défaut par le compilateur ne suffit pas, il faut pour le moment en faire une copie complète. Qu'est-ce qu'une copie profonde : il s'agit de créer un nouvel objet et de copier la "valeur" (tous les éléments du tableau) des attributs de l'objet d'origine. Si c'est la situation ci-dessus, nous devons redemander un espace pour enregistrer les données pointées par dp.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		p = (int*)malloc(10 * sizeof(int));
		if (p == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		for (int i = 0; i < 10; i++)
		{
			p[i] = 10;
		}
	}
	~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		p = (int*)malloc(10 * sizeof(int));
		if (p == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		for (int i = 0; i < 10; i++)
		{
			p[i] = d.p[i];
		}
	}
private:
	int _year;
	int _month;
	int _day;
	int* p = nullptr;
};
int main()
{
	Date d1(12, 12, 12);
	Date d2(d1);
}

 sortir:

~Date()
~Date()

 5. Surcharge des opérateurs d’affectation

5.1 Surcharge de l'opérateur

        C++ introduit la surcharge d'opérateurs pour améliorer la lisibilité du code. La surcharge d'opérateurs est une fonction avec un nom de fonction spécial et possède également son type de valeur de retour, son nom de fonction et sa liste de paramètres. Le type de valeur de retour et la liste de paramètres sont similaires aux fonctions ordinaires. . Le nom de la fonction est : le mot-clé opérateur suivi du symbole de l'opérateur qui doit être surchargé. Prototype de fonction : opérateur d'opérateur de type valeur de retour (liste de paramètres).

Avis:

  1. De nouveaux opérateurs ne peuvent pas être créés en concaténant d'autres symboles : par exemple, l'opérateur surchargé Operator@ doit avoir un paramètre de type de classe.
  2. Opérateurs utilisés pour les types intégrés dont la signification ne peut pas être modifiée, par exemple : entier intégré +, dont la signification ne peut pas être modifiée
  3. Lorsqu'il est surchargé en tant que fonction membre de classe, ses paramètres formels semblent être inférieurs de 1 au nombre d'opérandes, car le premier paramètre de la fonction membre est le this caché.
  4. .* :: sizeof ?: . Notez que les 5 opérateurs ci-dessus ne peuvent pas être surchargés. Cela apparaît souvent dans les questions écrites à choix multiples.

        Nous avons deux manières de définir la surcharge d'opérateur. Elle peut être définie comme globale ou directement en tant que membre de fonction, mais lorsqu'elle est définie en tant que membre de fonction, les paramètres formels que nous voyons seront un de moins (car le pointeur this implique des paramètres), comme suit == à titre d'exemple :

//全局
bool operator==(const Date& d1, const Date& d2)
{
    return d1._year == d2._year
   && d1._month == d2._month
        && d1._day == d2._day;
}

//函数成员

class Date
{
public:
	bool operator==(const Date& d)
	{
		return (_year == d._year)
			&& (_month == d._month)
			&& (_day == d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};

        Ce à quoi il faut prêter attention dans la surcharge d'opérateurs est "++" et "--", car ces deux opérateurs ont la différence entre préposition et postposition, donc la distinction entre préposition et postposition est également faite comme suit (avec ++ pour exemple):

	Date& operator++();//前置
	Date operator++(int);//后置 多了一个int形参,仅作标记,没有实际含义

5.2 Surcharge des opérateurs d'affectation

        Si la fonction de surcharge d'opérateur d'affectation n'est pas explicitement définie, le compilateur générera automatiquement une surcharge d'opérateur d'affectation par défaut et la méthode de copie est une copie superficielle. La différence avec les autres fonctions de surcharge d'opérateurs est que la surcharge d'opérateur d'affectation doit être définie comme une fonction membre et ne peut pas être définie comme une fonction globale. Si elle est définie comme une fonction globale, elle sera automatiquement générée s'il n'y a pas de surcharge d'opérateur d'affectation dans le corps de classe, ce qui entrera en conflit avec notre définition globale.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	  
	Date& operator=(const Date& d) //引用传参避免拷贝提高效率								
	{                              //引用返回因为赋值运算符支持连续赋值 d1 = d2 = d3;
		if (this != &d)
		{
			_year= d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

private:
	int _year;
	int _month;
	int _day;
};

        Si le type implique une gestion de mémoire, une copie profonde est requise. Ici, c'est exactement la même chose que la structure de copie. Si vous ne la comprenez pas, vous pouvez regarder vers l'avenir.

6. Surcharge des opérateurs d'adresse et d'adresse const

        Ces deux compilateurs de surcharge d'opérateurs seront également générés par défaut. Dans la plupart des cas, nous n'avons pas besoin de le définir nous-mêmes, à moins que nous souhaitions que d'autres obtiennent le contenu spécifié !

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

7. Pièce jointe : classe de date complète (le code dans l'article n'est pas extrait du code ici, il est temporairement tapé dans le but d'expliquer les points de connaissance, le code ici est la classe de date complète (extraite de Bittech), vous pouvez en tirer des leçons)

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 2023, int month = 8, int day = 10);
	void Print() const;
	int GetMonthDay(int year, int month) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	int operator-(const Date& d) const;
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
private:
	int _year;
	int _month;
	int _day;
};
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
#include"Date.h"
int Date::GetMonthDay(int year, int month) const
{
	assert(month > 0 && month < 13);

	int monthArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400) == 0))
	{
		return 29;
	}
	else
	{
		return monthArray[month];
	}
}
Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13
		&& (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法,初始化失败" << endl;
	}
}
void Date::Print() const
{
	cout << _year << " " << _month << " " << _day << endl;
}
bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
bool Date::operator<(const Date& d) const
{
	return _year < d._year
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && _day < d._day);
}
bool Date::operator<=(const Date& d) const
{
	return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
	return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
	return !(*this < d);
}
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day) const
{
	Date tmp(*this);

	tmp += day;

	return tmp;
}
Date& Date::operator-=(int day) 
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}
Date Date::operator-(int day) const
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date tmp(*this);

	*this += 1;

	return tmp;
}
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n*flag;
}

Acho que você gosta

Origin blog.csdn.net/qq_64293926/article/details/132189479
Recomendado
Clasificación