C Quack~~ [Surcharge d'opérateur dans la deuxième partie de la classe]

5. Surcharge des opérateurs d'affectation

5.1 Surcharge de l'opérateur

5.1.1 La notion d'opérateurs

Lors de l'apprentissage du langage C, nous pouvons utiliser des opérateurs pour opérer des types intégrés. Par exemple, comparer la taille de deux entiers, comparer si deux entiers veulent être égaux... alors pouvons-nous également utiliser des opérateurs pour opérer sur des types personnalisés
??

class Date
{
    
    

public:

	Date(int year = 2023, int month = 5, int day = 5)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
    
    
	Date d1(2023, 5, 6);
	Date d2(2003, 5, 7);

	bool ret = d1 > d2;
}

*****
error C2676: 二进制“>:“Date”不定义该运算符或到预定义运算符可接收的类型的转换
*****

Grâce au code ci-dessus, nous pouvons constater que ce n'est pas possible ~~.
En fait, sans le code comme preuve, nous pouvons également deviner <== car nous ne savons pas quoi comparer. Est-ce pour comparer les années de deux objets, ou pour comparer Le mois de deux objets, ou la date de deux objets ?
À partir de là, nous pouvons conclure que ⇒ les types intégrés peuvent utiliser directement des opérateurs, tandis que les types auto-définis ne peuvent être écrits que par eux-mêmes ~~ ( Bien sûr, celui-ci est inattendu, c'est la surcharge de l'opérateur d'affectation qui sera abordée plus tard)

Afin de résoudre l'utilisation d'opérateurs de types définis par l'utilisateur, le mot-clé opérateur est introduit. opérateur + opérateur ⇒ provoquera une surcharge de l'opérateur

C++ introduit la surcharge d'opérateur pour améliorer la lisibilité du code, et la surcharge d'opérateur est une fonction avec un nom de fonction spécial.

  • La forme générale d'une fonction : type de valeur de retour + opérateur + opérateur (liste de paramètres)
  • Une fonction peut avoir un type de retour, un nom de fonction et une liste de paramètres

point important:

  1. Impossible de créer de nouveaux opérateurs, tels que l'opérateur @
  2. L'opérande a au moins un type personnalisé ⇐ parce que la surcharge d'opérateur est utilisée pour les types personnalisés, s'ils sont tous des types intégrés, nous n'avons pas besoin et ne pouvons pas changer, le compilateur l'a fait par lui-même.
  3. Lorsqu'il est utilisé en tant que fonction membre, le paramètre formel est un de moins que l'opérande d'origine ⇐ car en tant que fonction membre, le premier paramètre formel a déjà été déterminé, qui est le pointeur this, mais le pointeur this n'est pas affiché dans la liste des paramètres
  4. Bien que le nom soit surcharge, cette surcharge a une signification différente de la surcharge de surcharge de fonction. La signification de cette surcharge est : permettre aux utilisateurs de savoir plus intuitivement que cet opérateur est équivalent à la fonction du type défini par l'utilisateur.
  5. Les fonctions de surcharge de l'opérateur peuvent constituer une surcharge
  6. Il y a cinq opérateurs qui ne peuvent pas être surchargés, .*(C'est une multiplication de points, je ne sais pas à quoi ça sert)) ::(Limite de portée) sizeof(Calculer la taille d'une variable ou d'un type) ?:(Trois yeux opérateur). (accesseur)

5...1.2 Emplacement des symboles de fret surchargé

D'une manière générale, la fonction d'opérateur de fret surchargé peut être placée dans une classe en tant que fonction membre , ou elle peut être placée directement dans une variable globale

  1. Surcharger le symbole de fret dans la variable globale
class Date
{
    
    

public:
    
    // 构造函数
	Date(int year = 2023, int month = 5, int day = 5)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	
//private:
	int _year;
	int _month;
	int _day;
};

bool operator== (const Date& x1, const Date& x2)
{
    
    
	if (x1._year == x2._year && x1._month == x2._month && x1._day == x2._day)
		return true;

	return false;
}

int main()
{
    
    
	Date d1(2023, 5, 6);
	Date d2(2003, 5, 7);

	bool ret = d1 == d2;
	cout << ret << endl;

}

*****
0
*****
  1. opérateur surchargé dans la classe
class Date
{
    
    

public:

	bool operator==(const Date& x)
	{
    
    
		if (_year == x._year && _month == x._month && _day == x._day)
			return true;

		return false;
	}

	// 构造函数
	Date(int year = 2023, int month = 5, int day = 5)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}


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


int main()
{
    
    
	Date d1(2023, 5, 6);
	Date d2(2003, 5, 7);

	bool ret = d1 == d2;
	cout << ret << endl;

}

*****
0
*****

En comparant les deux ensembles de codes ci-dessus, nous pouvons constater que :

  1. Par rapport à l'écriture dans des variables globales et agissant comme des fonctions membres, nous utilisons un opérande de moins
  2. Écrit dans la variable globale, nous ne pouvons pas utiliser l'objet pour accéder à la variable privée dans la classe (je lâche le privé ~)
  3. En fait, il existe deux manières d'utiliser des objets pour accéder à des variables privées : l'une consiste à agir en tant que fonction membre, et l'autre consiste à utiliser une fonction amie (cette suggestion peut être utilisée ou non, elle détruira l'encapsulation)

5.1.3 L'essence de la surcharge des opérateurs

Ce que nous savons déjà, c'est que l'utilisation d'opérateurs avec des types intégrés revient à remplacer des opérateurs tels que "a > b" par des instructions.
Alors, quelle est l'essence de l'utilisation de la surcharge d'opérateur ?

Plus tôt, nous savons déjà que la surcharge d'opérateur est implémentée par des fonctions, nous supposons donc avec audace que l'essence de l'utilisation de la surcharge d'opérateur est d'appeler des fonctions


Grâce aux preuves ci-dessus, nous avons constaté que les instructions d'utilisation de la surcharge d'opérateur et des fonctions d'appel sont les mêmes ⇒ l'essence de l'utilisation de la surcharge d'opérateur est d'appeler des fonctions


5.2 Surcharge des opérateurs d'affectation

Certains vieux fers ont des doutes : Nous n'avons pas fini de parler de la fonction membre par défaut auparavant, alors comment pouvons-nous reparler de surcharge d'opérateur ?
Dans les applications quotidiennes, l'affectation est très courante ⇒ donc l'opérateur d'affectation est une
opération de fonction membre par défaut Surcharge d'opérateur est la base de la surcharge d'opérateur d'affectation suivante ~~

Format de surcharge des opérateurs d'affectation :

  • Type de paramètre : nom de la classe const et, le passage de la référence peut améliorer l'efficacité
  • Type de valeur de retour : afin de prendre en charge l'affectation continue, renvoie le type de classe ; il est recommandé de renvoyer le type par référence, ce qui améliore également l'efficacité
  • Renvoyez *ceci : également pour prendre en charge l'affectation continue
  • Faites également attention si vous vous attribuez une valeur
  • La surcharge d'opérateur ordinaire peut être placée dans des variables globales, mais la surcharge d'opérateur d'affectation ne peut agir que comme une fonction membre ⇐ car il s'agit d'une fonction membre par défaut, et si elle est écrite à l'extérieur, cela provoquera une ambiguïté avec la surcharge d'opérateur d'affectation par défaut générée par le compilateur.

5.2.1 Compréhension approfondie - la surcharge de l'opérateur d'affectation par défaut générée par le compilateur

Le comportement de la surcharge de l'opérateur généré par le compilateur par défaut est le même que celui du constructeur de copie par défaut généré par le compilateur. Pour les
types intégrés : copie par octet de mémoire, équivalent à memcpy, qui est une copie superficielle, également appelée copie de valeur Pour les types personnalisés :
appellera son propre constructeur
de
copie

5.2.2 Compréhension approfondie - construction de copie et surcharge des opérateurs d'affectation

Construction de copie : utiliser un objet pour initialiser un autre objet (c'est-à-dire, initialisation directe lorsque l'objet est instancié)
surcharge de l'opérateur d'affectation : existant, copie de deux objets

class Date
{
    
    
public:
	// 赋值运算符重载
	Date operator=(const Date& x)
	{
    
    
		if (this != &x)
		{
    
    
			_year = x._year;
			_month = x._month;
			_day = x._day;
		}

		return *this;
	}

	Date(int year = 2023, int month = 5, int day = 5)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
    
    
		cout << _year << " " << _month << " " << _day << endl;
	}

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

int main()
{
    
    
	// 构造
	Date d1(2023,5,9);
	Date d4;
	d1.Print(); // 2023 5 9
	d4.Print(); // 2023 5 5 

	// 拷贝构造
	Date d2(d1);
	Date d3 = d1;
	d2.Print(); // 2023 5 9
	d3.Print(); // 2023 5 9

	// 赋值运算符重载
	d1 = d4;
	d1.Print(); // 2023 5 5
	d4.Print(); // 2023 5 5

}

Certains auront des doutes : d3 = d1 Pourquoi est-ce une construction par copie ? C'est évidemment une affectation ?
Concentrez-vous sur le point clé : la construction par copie consiste à initialiser un objet, et l'affectation est une opération entre deux objets existants ~~


5.2.3 Compréhension approfondie - les valeurs de passage et de retour des paramètres sont modifiées par des références

class Date
{
    
    
public:
	// 赋值运算符重载
	Date& operator=(const Date& x)
	{
    
    
		if (this != &x)
		{
    
    
			_year = x._year;
			_month = x._month;
			_day = x._day;
		}

		return *this;
	}

	Date(int year = 2023, int month = 5, int day = 5)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
    
    
		cout << _year << " " << _month << " " << _day << endl;
	}

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

int main()
{
    
    
	Date d1(2023, 5, 6);
	Date d2(2003, 5, 7);
	Date d3;

	d1 = d2 = d3;
	d1.Print();
	d2.Print();
	d3.Print();
}

*****
2023 5 5
2023 5 5 
2023 5 5

*****
  • Utilisez des références pour passer des paramètres : je pense que vous devez vous rappeler que les paramètres et les affectations de passage de types personnalisés appelleront automatiquement la construction de copie. Si nous n'écrivons pas de constructeur de copie, une copie superficielle sera utilisée. ⇒ Ainsi, lorsque les types personnalisés passent des paramètres, il est nécessaire pour passer une référence. Puisqu'il s'agit d'une affectation, l'objet passé ne changera pas. Il est recommandé de le modifier avec const
  • La valeur de retour utilise une référence : si nous renvoyons une référence, une copie temporaire ne sera pas générée et la construction de la copie ne sera pas appelée (la copie superficielle n'est pas évidente et le coût de la copie en profondeur est plus élevé)

Zone de questions :

  • Après avoir écrit ceci, certains amis peuvent demander : Pourquoi *ceci peut-il être renvoyé ici ? N'est-il pas déjà détruit après la sortie de la fonction ? ⇒ Alors la référence ne peut pas être renvoyée de cette manière ?

Le pointeur this est utilisé comme paramètre formel, et la portée est dans la fonction, mais ce que nous retournons est *this, et *this est (prenez d3 = d4 comme exemple), l'objet externe d3, il est donc possible de passez *cet retour.

  • Pour juger si vous devez vous attribuer une valeur, pourquoi devez-vous utiliser this != &d ?? Pourquoi ne pas utiliser *this != d ??

Ceci et &d sont tous deux des types intégrés, c'est (prenez d3 = d4 comme exemple) l'adresse de d3, et &d est l'adresse de d4 ⇒ vous pouvez directement utiliser des opérateurs pour faire fonctionner *ceci et d sont en fait des objets personnalisés
, *c'est d3, et d est une référence à d4 ⇒ Les deux types définis par l'utilisateur utilisent l'opérateur !=, et nous devons surcharger l'opérateur != ⇒ Après comparaison, il est plus pratique de comparer les adresses des
deux


Un serment est la chose la moins fiable au monde.Ce n'est que lorsque vous êtes utile aux autres que les autres garderont le serment.

Je suppose que tu aimes

Origine blog.csdn.net/qq_67549203/article/details/130544480
conseillé
Classement