Copier le constructeur et la fonction d'affectation en C ++

Ici, nous utilisons la classe String pour introduire ces deux fonctions: le
constructeur de copie est un constructeur spécial avec un seul paramètre formel, le paramètre formel (modification const couramment utilisée) est une référence au type de classe . Lorsqu'un nouvel objet est défini et initialisé avec un objet du même type, le constructeur de copie sera utilisé explicitement. Pourquoi le paramètre formel doit-il être une référence à ce type? Imaginez que si le paramètre formel est une instance de cette classe, car il s'agit d'un paramètre par valeur, nous copions le paramètre formel dans le paramètre réel et appelons le constructeur de copie. Si le constructeur de copie est autorisé à transmettre la valeur, la copie est appelée dans le constructeur de copie. Constructeur, qui forme des appels récursifs sans fin et provoque un débordement de pile.

string(const string &s);
//类成员,无返回值

La fonction d'affectation est également surchargée par l'opérateur d'affectation, car l'affectation doit être membre de la classe, puis son premier opérande est implicitement lié au pointeur this, c'est-à-dire qu'il est lié au pointeur de l'opérande gauche. Par conséquent, l'opérateur d'affectation accepte un seul paramètre formel et le paramètre formel est un objet du même type. L'opérande de droite est généralement transmis comme référence const.

string(const string &s);
//类成员,无返回值

Le constructeur de copie et la fonction d'affectation ne sont pas utilisés par tous les objets. En outre, s'ils ne sont pas activement écrits, le compilateur générera automatiquement la fonction par défaut de manière "copie de bits". Dans la conception de classe, la "copie de bits" doit être évitée. Si la classe contient des variables de pointeur, ces deux fonctions par défaut échoueront. Cela implique une copie profonde et une copie superficielle.

Il y a deux copies: copie en profondeur , peu profonde copie
lorsque l'affectation de classe signe égal se produit, appelez la fonction de copie, le cas où le constructeur de copie, le système appellera la fonction de copie par défaut est affiché dans une non définie - à savoir une copie peu profonde, il est possible de Remplissez une par une copie des membres. Lorsqu'il n'y a pas de pointeur dans le membre de données, une copie superficielle est possible.
Cependant, lorsqu'il y a un pointeur dans le membre de données, si une simple copie superficielle est utilisée, les deux pointeurs des deux types pointeront vers la même adresse , et lorsque l'objet est sur le point de se terminer, le destructeur sera appelé deux fois, provoquant le blocage du pointeur. Par conséquent, à ce stade, une copie complète doit être utilisée.
La différence entre la copie profonde et la copie superficielle est que la copie profonde s'appliquera pour un espace supplémentaire dans la mémoire du tas pour stocker les données , ce qui résout également le problème de la suspension du pointeur. Pointez sur différents espaces mémoire, mais le contenu est le même.
En bref, lorsqu'il y a un pointeur dans le membre de données, vous devez utiliser une copie complète .

class A{
	char * c;
}a, b;
 
//浅复制不会重新分配内存
//将a 赋给 b,缺省赋值函数的“位拷贝”意味着执行
a.c = b.c;
//从这行代码可以看出
//b.c 原有的内存没有释放
//a.c 和 b.c 指向同一块内存,任何一方的变动都会影响到另一方
//对象析构的时候,c 被释放了两次(a.c == b.c 指针一样)
 
//深复制需要自己处理里面的指针
class A{
	char *c;
	A& operator =(const A &b)
	{
		//隐含 this 指针
		if (this == &b)
			return *this;
		delete c;//释放原有内存资源
 
		//分配新的内存资源
		int length = strlen(b.c);
		c = new char[length + 1];
		strcpy(c, b.c);
 
		return *this;
	}
}a, b;
//这个是深复制,它有自定义的复制函数,赋值时,对指针动态分配了内存

Voici un résumé des différences spécifiques entre copie profonde et copie superficielle:

1. Lorsque l'état de l'objet de copie contient des références à d'autres objets, si le contenu pointé par l'objet de référence doit être copié, au lieu de se référer à l'adresse mémoire, il s'agit d'une copie complète, sinon c'est une copie superficielle.
2. La copie superficielle est l'affectation entre les données de membre. Lorsque la valeur est copiée, les deux objets ont des ressources communes. La copie complète consiste à copier une ressource en premier, l'objet a des ressources différentes (zone de mémoire), mais le contenu de la ressource (données en mémoire) est le même.
3. Contrairement à la copie superficielle, lorsque la copie profonde gère les références, si vous modifiez le contenu du nouvel objet, cela n'affectera pas le contenu de l'objet d'origine
. 4. Contrairement à la copie profonde, lorsque la ressource est libérée après une copie superficielle, l'attribution de la ressource peut être peu claire. (Lorsque le pointeur est inclus, les ressources d'une partie sont libérées, en fait, les ressources de l'autre partie sont également libérées en conséquence), ce qui provoque le programme à s'exécuter de manière incorrecte

Une autre différence entre la copie profonde et la copie superficielle est que lorsqu'elle est exécutée, la copie superficielle copie directement l'adresse mémoire, et la copie profonde doit rouvrir la zone de mémoire de même taille, puis copier la ressource entière.

Eh bien, avec la préfiguration précédente, commençons par parler du constructeur de copie et de la fonction d'affectation. En fait, la première partie a également introduit de nombreux

Prend ici la classe de chaîne comme exemple pour illustrer

class String
{
public:
	String(const char *str = NULL);
	String(const String &rhs);
	String& operator=(const String &rhs);
	~String(void){
		delete[] m_data;
	}
 
private:
	char *m_data;
};
 
//构造函数
String::String(const char* str)
{
	if (NULL == str)
	{
		m_data = new char[1];
		*m_data = '\0';
	}
	else
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	}
}
 
//拷贝构造函数,无需检验参数的有效性
String::String(const String &rhs)
{
	m_data = new char[strlen(rhs.m_data) + 1];
	strcpy(m_data, rhs.m_data);
}
 
//赋值函数
String& String::operator=(const String &rhs)
{
	if (this == &rhs)
		return *this;
 
	delete[] m_data; m_data = NULL;
	m_data = new char[strlen(rhs.m_data) + 1];
	strcpy(m_data, rhs.m_data);
 
	return *this;

La différence entre le constructeur de copie de type chaîne et le constructeur ordinaire est qu'il n'est pas nécessaire de comparer avec NULL à l'entrée de la fonction, car "référence" ne peut pas être NULL et "pointeur" peut être NULL. (Il s'agit d'une différence importante entre les références et les pointeurs). Ensuite, il faut faire attention à la copie profonde.
En comparaison, la fonction d'affectation de la classe String est beaucoup plus compliquée:

  1. Tout d'abord, vous devez effectuer l'auto-affectation de vérification
    afin d'empêcher l'auto-copie et la copie indirecte, telles que b = a; c = b; a = c; L'opération de copie se passera également mal, il s'agit donc d'une étape critique. Il convient également de noter que l'autotest vérifie l'adresse, et non le contenu, et l'adresse mémoire est unique. Doit être if (this == & rhs)

  2. Pour libérer les ressources de mémoire d'origine,
    vous devez utiliser supprimer pour libérer les ressources de mémoire d'origine. Si vous ne la libérez pas pour l'instant, l'adresse mémoire pointée par cette variable ne sera plus l'adresse mémoire d'origine et vous ne pourrez pas libérer la mémoire, ce qui entraînera des fuites de mémoire.

  3. Allouez de nouvelles ressources mémoire et copiez les ressources de
    sorte que l'adresse mémoire pointée par la variable ait changé, mais les ressources à l'intérieur sont les mêmes

  4. Retour d'une référence à cet objet Le
    but de ceci est d'obtenir une expression en chaîne comme a = b = c;, faites attention à retourner * this.

Mais réfléchissez bien, le programme ci-dessus ne prend pas en compte la sécurité des exceptions , nous utilisons delete pour libérer la mémoire de l'instance d'origine avant d'allouer de la mémoire, s'il n'y a pas assez de mémoire plus tard pour lever une exception, alors les m_data de la suppression précédente seront un pointeur nul , Il est très facile de faire planter le programme, nous pouvons donc changer l'ordre, c'est-à-dire d'abord une mémoire d'instance, puis utiliser delete pour libérer l'espace mémoire d'origine, et enfin utiliser m_data pour affecter le nouveau pointeur.

Ensuite, parlez de la différence entre le constructeur de copie et la fonction d'affectation.

Le constructeur de copie et la fonction d'affectation sont très faciles à confondre, entraînent souvent une mauvaise écriture et une mauvaise utilisation. Le constructeur de copie est appelé lorsque l'objet est créé, tandis que la fonction d'affectation ne peut être appelée que sur un objet existant. Regardez le code ci-dessous:

String a("hello");
	String b("world");
 
	String c = a;//这里c对象被创建调用的是拷贝构造函数
	             //一般是写成 c(a);这里是与后面比较
	c = b;//前面c对象已经创建,所以这里是赋值函数

Ce qui précède montre que l'endroit où "=" n'est pas nécessairement appelé la fonction d'affectation (fonction arithmétique surchargée), il est également possible de copier le constructeur, alors quand le constructeur de copie est-il appelé et quand la fonction d'affectation est-elle appelée? La norme de jugement est en fait très simple: si la variable temporaire apparaît pour la première fois, alors le constructeur de copie ne peut être appelé, sinon si la variable existe déjà, alors la fonction d'affectation est appelée

Référence:
Transfert depuis selfimpr1991

Le blog d'un grand dieu sur la différence et la mise en œuvre du constructeur, du constructeur de copie et de la fonction d'affectation

Introduction détaillée

Publié 13 articles originaux · loué 5 · visites 459

Je suppose que tu aimes

Origine blog.csdn.net/why18767183086/article/details/104164117
conseillé
Classement