[C++] Citation C++ (Vous ne pouvez pas citer ? Résolvez les nombreux détails de la citation en détail !)

Répertoire de référence C++ :

1. La notion de citation

2. Caractéristiques des citations

2.1 Les références doivent être initialisées lorsqu'elles sont définies

2.2 Une variable peut avoir plusieurs références

2.3 Une fois qu'une référence fait référence à une entité, elle ne peut pas faire référence à d'autres entités.

3. Références constantes (références avec const)

3.1 Les variables temporaires sont permanentes et ne peuvent pas être modifiées (retour par valeur, généré lors de la conversion de type implicite/forcée)

3.2 Lors de l'affectation, les autorisations des pointeurs/références peuvent être réduites, mais ne peuvent pas être élargies.

3.3 Références constantes comme paramètres

3.4 Comment référencer les paramètres par défaut ?

4. Scénarios d'utilisation de référence

4.1 Créer des paramètres (réduire la copie et améliorer l'efficacité, intégrer des formes réelles et physiques)

4.2 Créer des valeurs de retour (réduire les copies pour améliorer l'efficacité, modifier les valeurs de retour)

4.2.1 Que signifie la destruction de l'espace mémoire ? & Que se passe-t-il lors de l'accès à l'espace mémoire détruit ?

4.2.2 L'utilisation de références comme valeurs de retour améliore l'efficacité

4.2.3 Les références utilisées comme valeurs de retour peuvent être modifiées

5. La différence entre les références et les pointeurs


Conseils : Affichez le code assembleur avec vscode :

À l'emplacement du contrôleur de débogage, entrez :

-exec disassemble /m

1. La notion de citation

La forme de base est la suivante : type et nom de la variable de référence (nom de l'objet) = entité de référence

Une référence consiste à donner un alias à une variable existante : une référence ne consiste pas à définir une nouvelle variable, mais à donner un alias à une variable existante. Le compilateur n'ouvrira pas d'espace mémoire pour la variable de référence. Elle partage la même mémoire. avec la variable à laquelle elle fait référence.

Le symbole de référence est le même que le symbole de prise d'adresses dans notre langage C, à savoir & ; l'ajout du symbole de référence (&) après un certain nom de type devient un type de référence . Le but de la conception des références est de simplifier l'utilisation des pointeurs, mais les références ne peuvent pas remplacer les pointeurs (en fait, la couche inférieure des références est implémentée à l'aide de pointeurs)

La difficulté est que les symboles sont les mêmes que les symboles d'adresse. Par exemple, lorsque vous voyez *&, vous serez confus. Et vous devez vous rappeler que ce qui suit le type n'est pas une adresse, mais un alias.

void TestRef()
{
	int a = 10;
	int& ra = a;//<====定义引用类型
	printf("%p\n", &a);
	printf("%p\n", &ra);
}

a et ra pointent tous deux vers cet espace

Remarque : Le type de référence doit être du même type que l'entité référencée (la raison en sera expliquée dans l'élargissement et la réduction des autorisations pour les références fréquentes)


2. Caractéristiques des citations

2.1 Les références doivent être initialisées lorsqu'elles sont définies

Remarque : const int&b=10 ; cela peut également être transmis, et ceci est également considéré comme une initialisation (pour savoir pourquoi const est ajouté, veuillez consulter les connaissances communément citées ci-dessous)

2.2 Une variable peut avoir plusieurs références

int a = 10;
int& b = a;
int& c = a;
int& d = a;

À l’heure actuelle, b, c et d sont tous des références à la variable a.

2.3 Une fois qu'une référence fait référence à une entité, elle ne peut pas faire référence à d'autres entités.

Le sens de cette phrase montre aussi indirectement que les références C++ ne peuvent pas remplacer les pointeurs, car les pointeurs peuvent modifier l'adresse du lien.

(En Java et Python, les références peuvent être modifiées)

	int a = 10;
	int& b = a;

À ce stade, b est déjà une référence à a et b ne peut plus faire référence à d'autres entités. Si vous écrivez le code suivant, vous souhaitez que b fasse référence à une autre variable c :

	int a = 10;
	int& b = a;
	int c = 20;
	b = c;//你的想法:让b转而引用c

Mais le code ne suit pas vos souhaits. La signification du code est : affecter l'entité référencée par b à c, c'est-à-dire changer le contenu de la variable a en 20


3. Références constantes (références avec const)

Une référence constante consiste à modifier le type de référence avec const ; tout comme la variable modifiée const, la référence modifiée par cosnt ne peut être que lue, mais pas écrite, c'est-à-dire que la valeur de la variable de référence ne peut pas être modifiée.

3.1 Les variables temporaires sont permanentes et ne peuvent pas être modifiées (retour par valeur, généré lors de la conversion de type implicite/forcée)

Les variables temporaires en C++ font référence à des variables sans nom qui sont générées sur la pile par le compilateur selon les besoins. Il existe deux principaux types d'utilisations : Les variables temporaires sont permanentes.

1) La valeur de retour de la fonction

Cette photo est très importante et doit être vue :

 Donnons un autre exemple de réception sans référence :

En C++, si une fonction renvoie une variable temporaire (renvoyée par valeur), alors la variable temporaire est une rvalue et ne peut pas être modifiée. `Add(a,b)` renvoie une variable temporaire, elle ne peut donc pas être affectée directement . Si vous souhaitez modifier le résultat, vous pouvez le stocker dans une variable puis opérer sur la variable.

int Add(int a, int b) {
    return a + b;
}

int main() {
    int a = 0;
    int b = 3;
    int result = Add(a, b);
    result += 1;
    return 0;
}

J'ai encore une petite question, pourquoi le type const peut-il être passé au type int ? ? (Attribuez simplement des autorisations sans parler de les agrandir ou de les réduire)

Seules les références et les pointeurs doivent être pris en compte, tout le reste va bien, voici donc le plus simple :

int donne une erreur à const (car const ne peut être initialisé que lorsqu'il est défini)

const vers int paire (vous pouvez attribuer une valeur de type const à une variable de type non-const, mais vous ne pouvez pas attribuer une valeur de type non-const à une variable de type const, car cela détruirait la nature en lecture seule de const)

Mais lorsque vous attribuez des valeurs par référence ci-dessous, attention à la confusion !

int& a=10 C'est const donnant int, ce qui est une erreur d'élargir les autorisations (à l'origine, const est en lecture seule, mais les diables int ont envahi, bien sûr, cela ne fonctionnera pas)

int b=2;const int& c=b; Ceci est int donné à const. Il est raisonnable de réduire les autorisations (avant, j'étais négligent, mais ensuite ma femme me contrôlait strictement)

2) Variables intermédiaires lors de la conversion de type

int main()
{
	int d=10;

	int i=(int)d;//强制类型转换,并不是改变了变量d,而是产生临时变量
	int i=d;//隐式类型转换,也是产生了临时变量

	double d=12.34;
	const int& ri=d;//这里引用的实体其实就是从double d 到int类型转换中间的临时变量
	return 0;
}

Après avoir bien compris cette image, le problème des variables temporaires const peut prendre fin. Fin de la digression, revenons au sujet principal

Bien entendu, ce code ne fonctionnera pas car il y a beaucoup de redéfinitions

3.2 Lors de l'affectation, les autorisations des pointeurs/références peuvent être réduites, mais ne peuvent pas être élargies.

int main()
{
	const int a = 10;
	//int& ra = a;    //该语句编译时会出错,a为常量,权限放大
	const int& ra = a;//正确,权限平移
	
	//int& b = 10;    //该语句编译时会出错,10为常量,权限的放大
	const int& b = 10;//正确,权限平移

    int c=10;
    const int& rc=c;  //正确,权限缩小
	return 0;
}

Les autorisations peuvent uniquement être réduites et panoramiques, pas agrandies

Remarque : Les autorisations ici font référence aux autorisations de lecture et d'écriture et s'appliquent uniquement aux pointeurs et aux références.

3.3 Références constantes comme paramètres

a. Généralement, les références constantes sont utilisées comme paramètres , c'est-à-dire const+références. Si const n'est pas utilisé, il peut y avoir un problème d'amplification des autorisations, et les références constantes peuvent recevoir à la fois des autorisations en lecture seule et des autorisations en lecture et en écriture.

B. Les références constantes ne sont pas utilisées pour modifier les paramètres, mais pour réduire la copie et améliorer l'efficacité.

Bien sûr, tous les paramètres ne peuvent pas être ajoutés avec const. Par exemple, si nous ajoutons const à notre fonction swap, cela sera contre-productif.

3.4 Comment référencer les paramètres par défaut ?

Si vous souhaitez utiliser le paramètre par défaut comme référence, vous devez utiliser une référence constante, car le paramètre par défaut est une constante et ne peut pas être modifié. Il peut uniquement être lu.

void func(const int& N = 10)
{

}

4. Scénarios d'utilisation de référence

4.1 Créer des paramètres (réduire la copie et améliorer l'efficacité, intégrer des formes réelles et physiques)

Lors de l'appel d'une fonction, les paramètres formels doivent être copiés dans le cadre de la pile de fonctions où ils se trouvent, donc si vous appelez par valeur, une copie temporaire des paramètres réels sera effectuée lors de l'appel de la fonction. Si vous appelez par adresse et les variables de pointeur doivent également ouvrir leur propre espace, il s'agit donc d'une consommation de performances du programme.

Les références peuvent modifier directement les paramètres réels. En tant que paramètres d'entrée et paramètres de sortie, les pointeurs n'ont plus besoin d'être passés ; par exemple, dans la fonction Swap ci-dessous, ce que nous échangeons à l'intérieur de la fonction Swap sont en fait les valeurs des deux paramètres réels, et il n'est pas nécessaire de les transmettre comme avant.

Mais si nous utilisons des références comme paramètres, nous n'aurons pas ces problèmes, car le système d'exploitation n'alloue pas d'espace séparé pour les variables de référence et les variables de référence n'ont pas besoin de copier les paramètres réels, ce qui peut réduire la copie et améliorer l'efficacité.
Et comme la référence est essentiellement le paramètre réel lui-même, elle peut également être utilisée comme paramètre de sortie pour modifier les paramètres réels en dehors de la fonction.

//单链表
typedef int SLTDataType;  //数据类型重命名
typedef struct SListNode   //链表的一个节点
{
	SLTDataType data;
	struct SListNode* next;  //存放下一个节点的地址
}SLTNode;

//在头部插入数据
void SListPushFront(SLTNode*& rphead, SLTDataType x)  //引用做形参,直接操作plist
{
	SLTNode* newNode = BuySLTNode(x);  //开辟新节点
	newNode->next = *rphead;
	*rphead = newNode;
}

Dans des opérations telles que l'insertion de l'en-tête d'une liste à chaînage unique, puisque la liste à chaînage unique que nous avons implémentée auparavant n'a pas d'en-tête, nous devons passer le pointeur de plist pour faciliter le changement de plist lors de l'insertion des premières données. utilisez directement la référence de plsit.Cependant, il n'est pas nécessaire de passer un pointeur secondaire, ce qui rend le code plus facile à comprendre. Bien sûr, nous ne pouvons pas concevoir le pointeur suivant dans SListNode comme référence, car le prochain pointeur du nœud de queue change constamment et une fois qu'une référence fait référence à une entité, elle ne peut pas faire référence à d'autres entités. Cela montre également que les références ne peuvent pas être remplacées. . Les pointeurs ne peuvent que simplifier les pointeurs

4.2 Créer des valeurs de retour (réduire les copies pour améliorer l'efficacité, modifier les valeurs de retour)

Discutons d'abord de la question de la destruction de l'espace variable local

4.2.1 Que signifie la destruction de l'espace mémoire ? & Que se passe-t-il lors de l'accès à l'espace mémoire détruit ?

Détruire l'espace mémoire ne signifie pas déchirer l'espace mémoire. Cela détruit définitivement cet espace. L'espace mémoire ne disparaîtra pas. Il y restera intact. Cependant, une fois l'espace mémoire détruit, son droit d'utilisation n'appartient plus . Les données que nous y avons enregistrées ne sont plus protégées et peuvent avoir changé.

Après la destruction, nous pouvons toujours accéder à cet espace, mais le comportement d'accès est incertain et notre comportement de lecture et d'écriture de données dans l'espace est imprévisible.

Traitez l'espace comme un hôtel et les données comme des pommes :
a. La pomme n'est pas perdue b. La chambre n'est pas perdue, mais elle devient un fruit aléatoire c. La pomme est couverte comme un fruit fixe

Premier exemple :

int& Func()
{
	static int n = 0;
	n++;
	printf("&n:%p\n", &n);
	return n;
}

int main()
{
	int ret = Func();
	cout << ret << endl;
	printf("&ret:%p\n", &ret);

	int& rret = Func();
	printf("&rret:%p\n", &rret);
	return 0;
}

L'adresse de n est la même que rret, ce qui signifie que la référence renvoie la variable n elle-même, pas seulement la valeur de n.

Dans la fonction Func, n est une variable statique, et les variables statiques ouvrent de l'espace dans la zone statique et non dans la zone de pile, donc n ne sera pas détruit lorsque le cadre de pile de la fonction Func est détruit ;

En même temps, lorsque nous utilisons une référence comme valeur de retour, cela équivaut à renvoyer directement la variable n à l'appelant de la fonction. Par conséquent, lorsque nous utilisons une référence pour la recevoir, cela équivaut à donner à n un autre alias ;

Exemple 2 : (Remarque : certaines des façons suivantes d'utiliser les citations sont des démonstrations incorrectes, vous devez donc suivre le blogueur pour les comprendre lentement)

Analyse : Pour le code de gauche , après avoir quitté le cadre de pile de la fonction Count, la variable n sera détruite. L'alias renvoyé pointe vers un nom d'espace variable détruit. Si vous utilisez l'alias renvoyé à ce moment-là, les résultats seront imprévisibles. (L'exemple Apple cité plus haut), puisque la variable n est détruite, elle ne doit pas être renvoyée par référence (mais il n'y aura pas d'erreur de compilation de syntaxe, donc soyez prudent)

Pour le code de droite , la variable n ne sera pas détruite et est stockée dans la zone statique, elle pourra donc être renvoyée par référence.

Analyse : Les fonctions ont été détruites, pourquoi la valeur de retour peut-elle encore être ressortie ? En effet, avant d'exécuter l'instruction return, le système créera automatiquement une variable temporaire temp. L'adresse de stockage de cette variable est allouée en fonction de la taille du contenu renvoyé. Il peut s'agir d'un registre ou d'un tas. Nous ne pouvons pas le prédire. Ensuite, le système d'exploitation La valeur de la variable à libérer est attribuée à cette variable temporaire et la valeur de retour est extraite via la variable temporaire.

#include<iostream>
using namespace std;

int& Count()
{
	int n = 0;//n存放在栈区
	n++;
	//...
	return n;
}
void Func()
{
	int x=100;
}
int main()
{
	int &ret = Count();
	cout << ret << endl;

	cout << ret << endl;

	Func();
	cout << ret << endl;
	return 0;
}

analyser:

1. La variable n dans la fonction Func est une variable locale et est détruite une fois l'appel de fonction terminé. Cependant, nous utilisons ici une référence comme valeur de retour, donc l'espace de pile de n est renvoyé ;

2. ret est une référence à la valeur de retour de la fonction Func, et la valeur de retour de la fonction est une référence à la variable locale n, donc la première fois que nous imprimons est la valeur de n, 1 ;

3. Nous avons appelé la fonction printf lors de l'impression de ret. Cette fonction a utilisé l'espace où Count a été détruit, donc les données originales dans l'espace n ont été écrasées, et ret est une référence à n, donc imprimer ret signifie imprimer les données originales dans l'espace n. Fonction printf.Les données dans l'espace mémoire de n, donc ce qui est imprimé est une valeur aléatoire.

4. Nous avons appelé la fonction Func, puis avons constaté que l'adresse de la variable x dans Func est la même que l'adresse d'origine de n, ce qui signifie que la fonction Cover utilise à nouveau l'espace de la fonction printf et l'espace de x se trouve être situé à la position précédente de n, donc l'impression ret Le résultat est 100 ;

Une fois le problème de destruction d'espace résolu, continuez à donner un code d'erreur qui fait référence à la valeur de retour, et continuez à comprendre et à analyser :

4.2.2 L'utilisation de références comme valeurs de retour améliore l'efficacité

D'après le retour par valeur ci-dessus, on sait que des variables temporaires seront créées, il est donc évident que le retour par référence améliore l'efficacité.

4.2.3 Les références utilisées comme valeurs de retour peuvent être modifiées

Le code suivant montre l'utilisation de références comme valeurs de retour en C++ pour modifier la valeur de retour .
Développez tous les nombres pairs du tableau de deux fois

int& change(int* arr,int i)
{
	return arr[i];
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	for (int i = 0; i < 10; i++)
	{
		if (arr[i] % 2 == 0)
		{
			change(arr, i) *= 2;
		}
	}
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << " ";
	}
}

Résumer:

La variable de retour n'existe pas après avoir quitté la portée de la fonction, et elle ne peut pas être renvoyée par référence car le résultat renvoyé par la référence n'est pas défini. La
variable de retour en dehors de la portée de la fonction existe et ne peut être renvoyée que par référence.


5. La différence entre les références et les pointeurs

Concept grammatical : une référence est un alias, n'a pas d'espace indépendant et partage le même espace avec son entité référencée.

En termes d'implémentation sous-jacente : la référence a en fait de l'espace, car la référence est implémentée sous la forme d'un pointeur.

Lorsque nous débogueons le code puis passons au désassemblage, nous pouvons constater que le code assembleur de la référence et le pointeur sont exactement les mêmes, c'est-à-dire que la couche inférieure de la référence est en fait le pointeur.

La différence entre les références et les pointeurs (9 points majeurs)

  • Une référence définit conceptuellement un alias d'une variable et un pointeur stocke une adresse de variable ;
  • Les références doivent être initialisées lorsqu'elles sont définies , mais les pointeurs ne sont pas requis ;
  • Une fois qu'une référence fait référence à une entité lors de l'initialisation, elle ne peut plus faire référence à d'autres entités, tandis qu'un pointeur peut pointer vers n'importe quelle entité du même type à tout moment ;
  • Il n’y a pas de référence NULL, mais il existe un pointeur NULL ;
  • La signification de sizeof est différente : le résultat de la référence est la taille du type de référence, mais le pointeur est toujours le nombre d'octets occupés par l'espace d'adressage (4 octets sur une plateforme 32 bits) ;
  • L'auto-incrémentation de référence signifie que l'entité référencée est augmentée de 1, et l'auto-incrémentation du pointeur signifie que le pointeur est décalé vers l'arrière de la taille du type ;
  • Il existe des pointeurs multi-niveaux, mais il n'y a pas de références multi-niveaux ;
  • La manière d'accéder aux entités est différente, le pointeur doit être explicitement déréférencé et le compilateur de référence gère lui-même le traitement sous-jacent ;
  • Les références sont relativement plus sûres à utiliser que les pointeurs.

J'espère que ce texte intégral minutieux de citation C++ pourra vous être utile ! ! ! allez! !

Je suppose que tu aimes

Origine blog.csdn.net/weixin_62985813/article/details/132724155
conseillé
Classement