B站学习-4小时彻底掌握C指针 - 顶尖程序员图文讲解指针(mycoderschool-Points)-学习笔记分享

前言

这是之前学习过的B站的mycodeSchool的指针教学视频,这篇文章是边学习边记录的一篇文章,个人感觉讲的超级棒,对指针的教学真的是深入浅出,超级推荐大家学习,下方是视频链接

学习视频链接:https://www.bilibili.com/video/BV1bo4y1Z7xf/?spm_id_from=333.337.search-card.all.click&vd_source=33fb97de2a9fae8bce351df45c7d3074


文章介绍

我是以每P来记笔记,进行学习的,大家可以参考B站的每P,对照我的笔记进行观看,比如学习第1p,那么就查看01p

注意了
我记笔记是在语雀进行记录的,上传到CSDN,有些格式错误,如果大家想要PDF版本,可以留下邮箱,那个要好看一些,格式也是正确的


01p 指针的基本介绍

Pour comprendre les pointeurs, nous devons d'abord comprendre comment différents types de données ou différentes variables sont stockées dans la mémoire de l'ordinateur ?

Le segment ou la zone de mémoire de l'ordinateur (mémoire RAM à accès aléatoire)
, chacun représenté par un octet en mémoire, comme un système de mémoire typique, chaque octet a une adresse

Lorsqu'une variable est déclarée dans le programme, comme int a, définissez une variable entière a

Lorsque ce programme est exécuté, l'ordinateur alloue de l'espace mémoire pour cette variable spécifique. La quantité d'espace mémoire allouée dépend du type de données et dépend également de l'éditeur.

L'ordinateur aura une structure interne, une table de recherche, qui stocke les informations de la variable a

int a;
char c;
a++;

image.png
image.png

Un pointeur est une variable qui contient l'adresse d'une autre variable

Par exemple, il y a une mémoire de 4 octets, partant de l'adresse 204 pour stocker la variable a, et une autre variable p, dont le type est un pointeur sur une variable entière, cette variable p peut stocker l'adresse de la variable a

int a;
int *p;  //一个指针变量,指向一个整型,换句话说是指向整型变量的地址的变量

Pour stocker l'adresse de a dans p, il faut

p = &a; 
//取a的地址  把&放在变量的前面就得到了这个变量的地址 
//实际上返回一个指针,指向哪个指定的变量

image.png

int a;		//a所分配的地址为204
int *p;		//p所分配的地址为64
p = &a;		//取a的地址
a = 5;		
printf p;	//204
printf &a;	//204
printf &p;	//64

image.png

Si vous mettez un * devant la variable pointeur, vous obtiendrez la valeur de l'adresse pointée par le pointeur. Appelez cela le déréférencement.

printf *p;   //5  = a的值
//在p中存放着一个地址,使用解引用操作来获得这个地址中的值
*p = 8;  //相当于修改了变量a的值,因为修改了p指向的值,而p又是指向a的地址
printf *p;  //8
printf a;	//8

Lorsque l'on parle de variables pointeur, la valeur de la variable p fait référence à l'adresse de la variable p

Donc * p est une adresse, p est une valeur , et cette valeur est la valeur de la mémoire pointée par l'adresse stockée dans p.
image.png

Résumer

  1. définir une variable de pointeur

int p; // mettre devant le nom de la variable

  1. déréférencement de pointeur

int p= &a; //Mettez & devant le nom de la variable pour obtenir l'adresse d'un

  1. Lors de l'impression de variables de pointeur

N'utilisez pas *, ou n'utilisez pas * pour opérer dessus, toutes les opérations doivent opérer sur son adresse

Lorsque vous utilisez *, lorsque vous travaillez dessus, il manipule la valeur (contenu) de l'adresse pointée par le pointeur


Exemple de code de pointeur 02p

Un pointeur est une variable utilisée pour stocker l'adresse d'autres variables.
Le pointeur peut également pointer vers une structure définie par l'utilisateur ou vers une classe définie par l'utilisateur
image.png

Exemple 1
Le pointeur p n'est pas initialisé, et le pointeur p est utilisé, une erreur se produit, et un problème survient dans la phase de compilation.
le problème du pointeur sauvage
image.png

Exemple 2
Le pointeur p est initialisé, pointant sur la variable a
. A chaque réexécution du programme, l'adresse de p sera différente, et une nouvelle adresse lui sera assignée.
Raison pour laquelle *p est une valeur négative est parce que la variable a pointée par p n'est pas initialisée, * une valeur aléatoire pour p
image.png

Exemple 3
Après initialisation de a, la valeur de *p est 10
image.png

Exemple 4
image.png

Exemple 5
Utiliser un pointeur pour modifier la valeur d'un
image.png

Exemple 6

Lorsque la valeur de b est affectée à *p, le pointeur p pointe-t-il vers b ?

image.png

On peut voir que le pointeur ne pointe pas sur b, seule la valeur change, c'est-à-dire que la valeur de b est payée à *p, et la valeur de l'adresse pointée par le pointeur sur la variable a est modifiée

image.png

L'exemple 7
peut initialiser des variables tout en définissant des variables

image.png

Exemple 8

Pointeur p, addition et soustraction de pointeurs, addition ou soustraction est sizeof (variable), qui obtiendra l'adresse de la prochaine variable entière
Par exemple : dans la figure ci-dessous, la taille d'un entier est de 4 octets, afin d'obtenir la prochaine entier Pour l'adresse de type numéro, 4 octets seront sautés. Donc p+1 ajoutera 4 octets.

image.png

Exemple 9
p+1 pointe vers une valeur aléatoire, qui est en fait une valeur poubelle

Nous n'avons pas attribué de variable entière à cette adresse mémoire particulière

Cette opération est donc très dangereuse et peut accéder

image.png


Type de pointeur 03p, opération arithmétique, pointeur vide

Les pointeurs sont fortement typés , ce qui signifie qu'une variable de pointeur d'un type spécifique est nécessaire pour stocker l'adresse d'une variable d'un type spécifique

int * --> int
car --> car *

Un pointeur vers un entier pour stocker l'adresse des données entières
Pointeur vers un type de caractère pour stocker l'adresse des données caractère

Si vous avez une structure ou un pointeur défini par l'utilisateur, vous avez besoin de ce type spécifique de pointeur

Nous utilisons des pointeurs non seulement pour stocker des adresses mémoire, mais également pour déréférencer le contenu de ces adresses

De cette façon, les valeurs correspondant à ces adresses peuvent être consultées et modifiées.

Chaque octet peut être adressé en mémoire, et les 4 octets d'une variable entière sont généralement disposés consécutivement

Le premier bit est le bit de signe et les 31 bits restants sont utilisés pour stocker la valeur.
Le type flottant fait référence à IEE-754. Il est différent des autres types de données stockées et la valeur imprimée est également différente.
image.png

Exemple 1
image.png

Exemple 2
p0 est un pointeur sur le type caractère et p est un pointeur sur le type entier

Convertissez le type forcé de p en un pointeur vers un caractère et stockez l'adresse de p dans p0, puis l'adresse de l'octet le plus à droite (c'est-à-dire le premier octet) sera stockée dans p0

Vous pouvez voir le résultat d'exécution du programme dans la figure ci-dessous. Puisque le type char est 1 octet, p0 stocke le contenu du premier octet du type int.
image.png
Lors du déréférencement
de p0, la machine pensera qu'il s'agit d'un pointeur vers un caractère type, et le type de caractère Il n'y a qu'un seul octet, donc la machine ne voit qu'un seul octet
image.png

Exemple 3
p+1, saute à la position de la prochaine variable entière, c'est-à-dire l'adresse plus sizeof (type), la taille de l'entier est de 4 octets, et p est un pointeur vers l'entier, et *(p+ Le valeur de 1) est une valeur aléatoire (garbage). Nous n'avons rien écrit à cette adresse.
image.png
p0+1 --> sauter au deuxième octet de type int, obtenir *(p0+1)=4 via binaire
image.png


L'adresse du pointeur p0 du type void est la même que l'adresse de p, mais il n'est pas mappé sur un type de données spécifique, et son adresse peut seulement être imprimée, mais les opérations arithmétiques ne peuvent pas être effectuées dessus, et un une erreur de compilation se produira
image.png

Résumé :

type de pointeur, conversion de type, arithmétique de pointeur

  1. Que se passe-t-il en mémoire lorsqu'une variable de pointeur est déréférencée ?

p=&a; p=(char ) p0;
est lié à l'adresse de la variable pointée par le pointeur et au type (taille en octet) de la variable.

  1. Que se passe-t-il lorsque certains types d'opérations arithmétiques sont effectuées sur des pointeurs ?

p+1; p0+1;
*(p+1); *(p0+1);

  1. 通用指针类型,它不针对某个特定的数据类型,成为void类型的指针

p0=p; 不会有编译错误
*p0; 没有映射到特定的数据类型,不能对它进行解引用


04p 指向指针的指针

是否可以创建一个指针指向变量p(p是一个指向int型的指针)?
创建一个指针变量q,它用来存放p的地址; int **q;
这样q就是一个指向指针的指针

这样q里面存放了p的地址,q指向p,q的类型是**

这里面的关系就是
为了得到x的地址,需要一个int类型的指针p
为了得到p的地址,需要一个指向int
类型的指针,所以加*,即int **

/******  地址对应内容
	x 地址-->225  内容 5
	p 地址-->215  内容 225
	q 地址-->205  内容 215
	r 地址-->230  内容 205
*********/

int x=5;		
int *p;			//p指向x  p的地址和x相同	
p = &x;
*p = 6;
int **q;  		//创建了一个指针变量q  指向p
q = &p;			//得到了p的地址 
int ***r;		//r的类型是int ***,因此可以用来存放int **类型的地址
r = &q;  

/******  解引用
	*p		6
	*q		225
	*(*q)   6
	*r		215
	*(*r)	225
	*(*(*r)) 6
*********/

image.png

总结

指针,二级指针,三级指针

int x;
int *p=&x;
int **q=&p;
int ***r=&q;

***p=**q=*r=x //解引用相同,都等于x

image.png


05p 函数传值 vs 值引用

问题引入值传参,并不能改变a的值

原因,InCrement里的a是局部变量,main函数里的a也是局部变量,main函数中调用InCrement(a)传入的a只是对a=10,做了一个拷贝。

InCrement里的a改变了,main函数里的a并没有改变。
image.png
可以看到a在InCrement函数和在main函数里的地址不同

Celui de la fonction est sur la pile et disparaît après l'exécution. Celui
de la fonction principale existe dans le tas. L'auto-incrémentation de la fonction n'a aucun effet sur a dans le tas.

image.png

Que se passe-t-il exactement en mémoire lorsque le programme démarre

Lorsqu'un programme d'application démarre, l'ordinateur configure de la mémoire pour utiliser
le segment de code, le segment de variable statique/globale et le segment de pile pour ce programme. Ces trois éléments sont fixes. Lorsque le programme démarre, il a été déterminé. , mais lorsque l'application est en cours d'exécution, elle peut demander de lui allouer plus de mémoire dans la zone de tas

La mémoire utilisée par le programme d'application est généralement divisée en quatre parties .
La première partie, le segment de code
sert à stocker les instructions du programme. est une partie en mémoire)
la deuxième partie, le segment de variable statique/globale alloue des variables statiques ou globales. variables globales
(si nous ne déclarons pas la variable dans une fonction, c'est une variable globale, et la variable globale peut être consultée et modifiée n'importe où dans le programme) la troisième partie, pile (pile
)
des variables locales très importantes sont toutes placées dans cet endroit
, tandis que les variables locales ne sont accessibles et modifiées que dans des fonctions spécifiques ou des blocs de code spécifiques.
La quatrième partie, tas (heap)
image.png
avant l'exécution du programme : zone de code, zone de variable statique/globale, zone de constante littérale
Après l'exécution du programme : pile zone (attribuée automatiquement par le système) et zone de tas (appliquée par le programmeur)
image.png
image.png

Le processus détaillé de l'exécution d'un programme :
image.png
pile d'appels ou pile d'appels de fonction
Si une fonction appelle une autre fonction à l'infini, comme une récursivité infinie, alors la pile débordera et le programme plantera

Comprendre les concepts ou les impressions, ce qui se passe lorsqu'une fonction appelle une autre fonction Lorsqu'une fonction est appelée
image.png
, elle mappe essentiellement une variable à une autre variable et copie la valeur d'une variable à une autre variable. Connu sous le nom d'appel par valeur
image.png

Solution :
Passer par adresse, vous pouvez vous référer à cette variable, déréférencer et effectuer certaines opérations, c'est un appel par référence (appel par référence).
Passer par référence peut économiser beaucoup d'espace mémoire.
Éviter la copie de types de données complexes peut économiser nous souvenir
image.png


06p Pointeurs et tableaux

La méthode de stockage du tableau dans la mémoire est
le stockage linéaire, les adresses des éléments du tableau augmentent linéairement La valeur de la variable est inconnue et une erreur se produit
image.png


image.png

Solution
Si le pointeur p pointe sur l'adresse du premier élément du tableau
, puis exécute p+1, p+2..., le déréférencement peut réussir. Comme le tableau est stocké linéairement en mémoire, nous savons ce qui est à l'adresse adjacente

int  A[5];
int *p;
p = &A[0];
print  P;   //200
print *p;   //2
print p+1;	//204
print *(p+1) //4

image.png

Si nous utilisons le tableau A pour être égal au pointeur p
, nous obtenons toujours un pointeur vers le premier élément du tableau

int A[5];
int *p;
p=A;
print  A;   //200
print  p;	//200
print *A;  //2;
print A+1; //204
print *(A+1;  //4

Donc, pour l'élément dont l'index est i dans le tableau, afin d'obtenir l'adresse ou la valeur de cet élément particulier

Vous pouvez utiliser
7
pour obtenir l'adresse
&A[i] ou A+i
pour obtenir la valeur
A[i] ou *(A+i)
7

image.png

La première adresse d'un élément de tableau peut également être appelée l'adresse de base du tableau, qui
peut être utilisée

A ou &A[0] indique la première adresse du tableau

image.png
image.png

Remarque
Lorsque le pointeur pointe sur l'adresse de base du tableau, A++ ne peut pas être exécuté
car A est essentiellement un tableau et la première adresse du tableau doit rester inchangée et ne peut pas être modifiée. Au lieu de cela,
p++ peut être exécuté car p est un pointeur variable de type entier
image.png

Résumer:

  1. La forme de stockage du tableau en mémoire

L'adresse des éléments dans le réseau de stockage linéaire augmente de manière linéaire

  1. comment obtenir l'adresse du tableau

数组名A 或者 &A[0]得到数组的首地址
或者使用int *p=A; p也表示的是数组的首地址

  1. 如何使用指针访问数组

int *p =A;
*(A) //数组第一个值 *(A+i) //数组的第i个值
*p *(p+i)


07p 数组作为函数参数

数组作为函数参数传入

获取数组的个数

int A[] = {1,2,3,4,5};
int ArraySize = sizeof(A)/sizeof(A[0]); //数组中元素的个数

数组作为函数参数传入
image.png

但是当传入参数,只传数组
结果就会出现error
image.png
可以看出在sum函数和main函数里sizeof(A)的数组大小不同
image.png

为什么呢?
在sum函数中数组A是一个局部变量,并不是main函数里的数组A
当调用函数时,main函数里的数组A会被拷贝到被调用的函数中
同时在栈中main函数栈帧中的数组A会占据20个字节,拷贝到sum函数的数组A同样也会占据20个字节,其中的元素和main函数里的数组A相同,但事实不是这样
image.png

当编译器看到整个数组A作为函数参数的时候,它不会拷贝整个数组
实际上编译器所做的事情是在SOE栈帧中创建一个同名的指针A而不是创建整个数组
编译器只是拷贝主调函数的数组首元素的地址
所以SOE的数组参数不是被解释成数组,而是一个整型的指针
这就是为什么在SOE函数里sizeof(A)等于4的原因了 而在main函数里是一个数组

Nous ne copions pas la valeur de la variable, mais simplement l'adresse de la variable , donc voici une référence, pas une valeur

Les tableaux sont toujours passés aux fonctions en tant que références

Si le tableau entier est copié à chaque fois, beaucoup de mémoire sera gaspillée

image.png

Par conséquent, lorsqu'un tableau est utilisé comme paramètre de fonction, il s'agit essentiellement d'un pointeur
et le résultat est le même.
Lorsqu'un tableau est utilisé comme paramètre de fonction, ce qui est renvoyé est essentiellement un pointeur de tableau et la méthode est transmise par référence.
image.png
image.png

Étant donné que le tableau est un paramètre de fonction, il s'agit essentiellement d'un pointeur, de sorte que la valeur du tableau peut être modifiée à l'intérieur de la fonction
Nom du tableau --> Constante du pointeur
image.png

Résumer

  1. Lorsque la fonction principale appelle la fonction, le tableau est utilisé comme paramètre de la fonction et le processus en mémoire
  2. En tant que paramètre de fonction, un tableau est essentiellement équivalent à un pointeur, mais ce n'est pas un pointeur. En fait, la première adresse du tableau est transmise au lieu d'une copie du tableau entier.
  3. Comment trouver le nombre d'éléments dans un tableau

taille = taillede(A)/taillede(A[0])

  1. En tant que paramètre de fonction, le tableau est passé par référence , de sorte que la valeur du tableau peut être modifiée à l'intérieur de la fonction

Pointeur 08p et tableau de caractères (activé)

La différence entre une chaîne et un tableau de caractères
dépend de la présence ou non d'un \0 à la fin
"hello" tableau de caractères chaîne "hello\0"

La chaîne doit se terminer par '\0' '\0' signifie NULL

La raison pour laquelle les tableaux de caractères sont importants est que nous les utilisons pour stocker des chaînes , puis effectuons certaines opérations, telles que la modification, la copie de chaînes, la concaténation de deux chaînes ou la recherche de propriétés de chaînes (connaître la longueur d'une chaîne)

Commencez par comprendre
comment passer une chaîne dans un tableau de caractères. Déclarez et initialisez un tableau de caractères. La première exigence est que le tableau de caractères doit être suffisamment grand. Quelle est la taille du tableau de caractères ?

Un tableau de caractères suffisamment grand dont la taille est supérieure ou égale au nombre de caractères + 1

La taille du tableau de caractères >= le nombre de caractères + 1

image.png

La taille du tableau de caractères est trop petite,
s'il ne se termine pas par \0, une erreur apparaîtra
image.png

  1. Lorsque \0 est ajouté au dernier chiffre du tableau, la sortie est réussie

La fonction printf termine le dernier caractère NUL par défaut, c'est-à-dire \0, le symbole de fin de chaîne
image.png

Dans la bibliothèque string.h, toutes les fonctions supposent que la chaîne se termine par \0.
Par exemple, pour trouver la longueur d'un tableau de caractères, la longueur calculée par défaut est \0.
image.png

  1. Vous pouvez attribuer des valeurs aux éléments du tableau de caractères individuellement ou dans leur ensemble

Par exemple:

char c[20]="JOHN";

Cela initialise le tableau de caractères
c à une chaîne et ajoute \0 qui est implicite pour les littéraux de chaîne et ajoute toujours un caractère NUL à la fin de la mémoire

  1. Vous ne pouvez pas non plus spécifier la longueur du tableau de caractères

Dans ce cas, la taille de c sera de 5, 5 octets, un octet par caractère

char c[] = "JOHN";

Mais la longueur du tableau de caractères est de 5
mais la longueur est de 4 car la fonction strlen ne compte pas \0 (caractère NUL)
image.png

  1. Vous pouvez également déclarer explicitement le tableau de caractères, mais le dernier caractère \0 doit être déclaré explicitement

La taille du tableau doit être supérieure ou égale au nombre de caractères dans tous
image.png

Notez qu'il ne peut pas être déclaré dans la ligne précédente et assigné dans la ligne suivante, ce qui est illégal
image.png

Les tableaux et les pointeurs se ressemblent, mais ne sont pas du même type

char c1[6]="hello";
char *c2;
c2=c1;   //c1代表c1字符数组的首地址

print  c2[1]//数组的第二个元素  e
c2[0] = 'A';    //修改后c1就是 "AELLO"


c2[i]  -- > *(c2+i)   //c2是指向了c1的首地址  c2[i]就相当于c2+i的偏移
c1[i]  -- > *(c1+i) 

image.png

Avis

c1 = c2;   		//invallid
c1 = c1 + 1     //invalid
//invalid  因为c1是数组的首地址  数组的首地址不允许更改  会产生编译错误

c2 = c1;   //valid   
c2 = c2+1;   //c2指向下一个元素

Vous devez comprendre quand c'est un tableau et quand c'est un pointeur, la différence entre les deux et ce que nous pouvons faire respectivement.

Exemple
Tableau de caractères Lorsque le paramètre de la fonction est passé dans l'adresse
%c, les caractères d'impression
La chaîne se termine par \0
c[i] et *(c+i) sont équivalents
image.png

La figure ci-dessous peut également imprimer des tableaux de caractères.
Pourquoi ?
Le char C dans la fonction d'impression est comme un pointeur, pointant vers la première adresse du tableau de caractères C[20] dans la fonction principale,
donc le C dans la fonction d'impression
est égal au c[0] C++ dans la fonction principale
, et l'adresse est incrémentée, lorsque le dernier caractère est \0, la boucle termine
image.png
le résumé

  1. La différence entre les chaînes et les tableaux de caractères

La chaîne se termine par \0 et
le tableau de caractères est utilisé pour stocker la chaîne

  1. Quatre méthodes d'initialisation du tableau de chaînes

char c[] = "bonjour" ;
char c[20] = "bonjour"
char c[20] ; c1='h' ; c2='e' ; c3='l' ; c4='l' ; c5='o' ; c6='\0';
char c[] = {'h','e','l','l','o','\0'} ;

  1. La différence entre %s et %c

%s chaîne d'impression %c caractère d'impression

  1. Les pointeurs et les tableaux de caractères fonctionnent sur des tableaux de caractères à l'aide de pointeurs

Lorsqu'un tableau est utilisé comme paramètre de fonction, la première adresse du tableau est passée par référence

  1. La différence entre la longueur et la taille

char c[20] = "bonjour" ;
taille sizeof© -->6
longueur strlen© -->5


Pointeurs 09p et tableaux de caractères (ci-dessous)

Constantes de chaîne et pointeurs constants

Lors de l'écriture d'un programme, lors de l'exécution d'un programme, nous devrions toujours être en mesure de déterminer où les variables sont placées, ou où les données sont placées et où la
portée de la variable ou des données est placée

La pile est une mémoire continue,
la zone de pile sert à stocker les informations lors de l'exécution de la fonction et à stocker toutes les variables locales.

Lorsque l'on discute des pointeurs, faut-il savoir ce qui se passe en mémoire ?
Lorsqu'une fonction est appelée, elle ouvre un espace dans la zone de pile pour exécuter cette fonction et devenir un cadre de pile.
Les variables locales de la
fonction de pile seront allouées au cadre de la pile. En plus des variables locales, le cadre de la pile contient d'autres informations

Lorsqu'une fonction est appelée, la fonction appelée allouera un cadre de pile correspondant, qui sera au-dessus de la fonction appelante principale, la fonction en haut de la pile sera exécutée en premier et la fonction principale suspendra son exécution (cela peut être considéré comme interruption).

Variables de pointeur, généralement, la taille occupe quatre octets.
image.png
Lorsque la fonction appelée finit de s'exécuter, le cadre de pile de la fonction appelée sera effacé.
La fonction principale reprendra son exécution jusqu'à la fin.
image.png

Modifiez le code comme suit.
Ce qui est défini n'est pas un tableau de caractères, mais un pointeur de caractère

Lorsqu'un tableau de caractères est utilisé pour l'initialisation, la chaîne existera dans l'espace mémoire alloué par le système au tableau

Dans ce cas, il sera alloué sur la pile

image.png

Lorsqu'un pointeur est défini pour pointer vers une chaîne , la chaîne
est stockée dans
la zone de code de l'application

char *p = "helloworld" 1. Appliquer un espace (aire constante), 2. Stocker une chaîne 3. Ajouter \0 après la chaîne L'adresse renvoyée est affectée au pointeur p

Ceci est stocké dans la zone de code et la modification n'est pas autorisée, ce qui peut entraîner le blocage du programme
:::
image.png

Si modifié, une erreur se produit
image.png

Mais lorsqu'il est défini comme un tableau de caractères, vous pouvez modifier
char *C pour pointer vers la première adresse du tableau de caractères c
, afin qu'il puisse être modifié
image.png

Lorsque nous définissons une fonction qui permet la lecture mais pas l'écriture,
nous pouvons définir un pointeur vers un caractère constant (en lecture seule)

caractère const *c;

Lors de la modification, il y aura une erreur
image.png
en lecture seule, et cela peut fonctionner normalement
image.png

Résumer

  1. Lors de l'écriture d'un programme, lors de l'exécution d'un programme, nous devrions toujours être en mesure de déterminer où mettre les variables, ou où mettre les données et où mettre la plage de variables ou de données
  2. Lorsque l'on discute des pointeurs, faut-il savoir ce qui se passe en mémoire ?

Variables de pointeur, généralement, la taille occupe quatre octets

  1. Le tableau de caractères est initialisé et sera alloué sur la pile

Le pointeur de caractère est initialisé et sera affecté à la zone de code (non inscriptible)

  1. Si vous avez besoin d'une fonction en lecture seule , vous pouvez définir un pointeur vers une constante de caractère

* caractère const c ;

  1. Comprendre la relation entre les pointeurs et les tableaux Que deviennent les pointeurs en mémoire

La signification profonde des pointeurs en tant que paramètres de fonction


Pointeurs 10p et tableaux à deux dimensions

  1. Pointeurs pour manipuler des tableaux unidimensionnels

int A[5] ;
int *p=A ;
Utilisez simplement le nom du tableau A, puis dans l'expression, un pointeur vers le premier élément du tableau sera renvoyé
** Vous pouvez utiliser le nom du tableau comme pointeur **
Vous peut également utiliser le déréférencement de tableau et les opérations arithmétiques sur le nom
, mais il est différent de la variable de pointeur (par exemple: p++ peut être utilisé mais pas A++, car le nom du tableau A est l'adresse de base du tableau)

*(A+i) --> A[i] ;
A+i --> &A[i] ;

  1. Utiliser des pointeurs pour manipuler des tableaux à deux dimensions

L'allocation des tableaux à deux dimensions en mémoire
B[0] et B[1] ont chacun trois éléments ,
B[0] et B[1] ne sont pas une donnée entière, mais un tableau à une dimension avec 3 entiers

int B[2][3];
B[0]   B[1]   //每个都有三个整型数据

int *p = B;    //编译错误
//B返回的是一个指向一位数组(其中包含三个整型数据)的指针

image.png

Le type de pointeur est très important, lors du déréférencement et de l'exécution d'opérations arithmétiques dessus , les résultats des différents types d'opérations de pointeur sont différents

Définir un pointeur vers un tableau unidimensionnel (où le tableau unidimensionnel contient 3 entiers)

int (*p)[3] = B;   //这样赋值是可以的

print  B;   //和B[0]地址相同  B  --> &B[0]
print  *B;   //和B[0]的值相同,返回三个整型数据  也和B[0][0]的地址相同

B   -->  &B[0]   
*B  -->  B[0] (包含三个整型数据)   &B[0][0]
B[0]是存放三个整形数据的一维数组的名字  所以等同于 B[0][0]的首地址

image.png

print  B;      //400   B返回一个一维数组 包含三个整型
print  *B;	   //400
print  B+1;		//412
print *(B+1);	//412

image.png

image.png

image.png

Prémisse :
int B[2][3] ;
* int ( p)[3] = B ; //B --> &B[0]

Conclusion : (pensez par vous-même)
B = &B[0]
*B = B[0] = &B[0][0]
B+1 = &B[1]
* (B+1) = B[1] = &B[ 1][0] ;
*(B+1)+2 = B[1]+2 = &B[1][2] ;
*( B+1) = (B[0]+1) = *(&B[ 0][0]) = *(&B[0][1])

B ici est un pointeur vers un tableau à une dimension (avec trois éléments)
B --> int (*)[3] ;
**B[0] --> int ***

Résumer:

  1. Comment les pointeurs bidimensionnels sont stockés en mémoire
  2. Utiliser des pointeurs pour manipuler des tableaux à deux dimensions

int B[2][3] ; int (*p)[3]=B ;
Le type du pointeur est très important. Lorsque le déréférencement et les opérations arithmétiques sont effectués sur le pointeur, les résultats obtenus par différents types de pointeurs sont différents

  1. B[i][j] = *(B[i]+j)= (B+i))

Si vous avez le temps, vous pouvez regarder plus de 10P


Pointeurs 11P et tableaux multidimensionnels

Apprentissage de l'utilisation de pointeurs pour opérer sur des tableaux bidimensionnels,
de l'utilisation de tableaux multidimensionnels
et de la transmission de tableaux multidimensionnels en tant que paramètres de fonctions

Il est important que les tableaux multidimensionnels soient essentiellement des tableaux de tableaux

Un tableau peut être compris comme une collection de choses du même type

Un tableau multidimensionnel peut être compris comme une collection de tableaux

B[2][3] est une collection de tableaux à une dimension.
Nous avons deux tableaux à une dimension
, dont chacun a trois éléments entiers
et ils sont alloués dans une mémoire contiguë.

B est un tableau 2D est un tableau de tableaux 1D (de taille 3)
donc * B renvoie un pointeur vers un tableau 1D de 3 éléments int( p) = B;

image.png

*B est identique à B[0], obtenir un tableau unidimensionnel complet B[0]
B[0] renverra un pointeur entier, pointant vers le premier élément B[0][0] de B[0], Autrement dit, l'adresse de B[0] est égale à B[0][0]
image.png

Le rôle du type de pointeur
fonctionne lors du déréférencement et de l'exécution de l'arithmétique de pointeur

B[i][j] = *(B[i]+j) = ( (B+i)+j)
image.png
Qu'est-ce qu'un pointeur vers un pointeur et qu'est-ce qu'un tableau vers un tableau ? laisser du suspens

  1. S'il existe un tableau tridimensionnel C

int C[3][2][2] ;
int (*p)[2][2] = C ;
est un tableau de tableaux à deux dimensions

image.png

Le déréférencement
C renvoie un pointeur vers un tableau à deux dimensions mais n'est pas essentiellement un pointeur C lui-même est un tableau Les deux types sont différents
image.png

Un lot de petites conclusions
image.png
sympa
image.png

Cas
Pensez-y, poupées matriochka, étape par étape, tableaux de tableaux
image.png
, chaque fois que vous exécutez, la mémoire allouée à la pile changera à chaque fois que vous exécutez
image.png

  1. Tableaux multidimensionnels passés aux fonctions en tant qu'arguments de fonction

Tableau 1D comme argument de fonction
image.png

Tableau à deux dimensions en tant que paramètre de fonction
La première méthode de définition
image.png
La deuxième méthode de définition
image.png
**error **
image.png

Tableau tridimensionnel en tant que paramètre de fonction
La première méthode de définition
image.png
La deuxième méthode de définition
image.png

Il en va de même pour un tableau de n'importe quelle dimension en tant que paramètre de fonction, à l'exception de la première dimension, le reste des dimensions doit obligatoirement être donné


Pointeurs 12p et pile de mémoire dynamique vs tas

Comprendre
l'architecture de la mémoire,
comment le système d'exploitation gère la mémoire
et comment gérer la mémoire en tant que programmeur

Mémoire dynamique en c\c++

Zone de code (zone de code, stocke les instructions à exécuter)
Zone de variables statiques/globales (static/Global area) , stocke des variables statiques ou globales (variables globales non déclarées dans les fonctions), le cycle de vie parcourt tout le programme application, dans le application Elles sont accessibles pendant l'exécution du programme.
La pile (stack) sert à stocker toutes les informations de l'appel de la fonction (fonctions Call) et toutes les variables locales (variables locales). Survivent pendant l'exécution. Stack last in first La taille de ces trois zones n'augmentera
pas pendant la durée du programme

Comment ces trois zones sont-elles utilisées lors de l'exécution du programme ?

la pile

**La taille du cadre de pile d'une fonction est déterminée lors de la compilation**Pendant l'
exécution du programme, la fonction en haut de la pile est exécutée à tout moment et les autres fonctions seront suspendues (interrompues), en attendant ce qui précède fonction pour retourner certains Reprendre l'exécution de l'appel de la fonction imbriquée après quelque chose
(interruption imbriquée)
image.png


Lorsque la fonction appelée se termine, nous reviendrons à l'endroit interrompu (c'est-à-dire l'endroit où la fonction est appelée), le cadre de pile de la fonction précédemment appelée sera éliminé (détruit) et les autres fonctions reprendront l'exécution .
Jusqu'à la fin de la fonction principale, le programme se termine.
Enfin, les variables globales sont également détruites.

Habituellement, uniquement lorsqu'une variable doit être appelée par de nombreuses fonctions, elle doit exister dans le cycle de vie de l'ensemble du programme, sinon définir une variable globale est un gaspillage.

Lorsque le programme commence à s'exécuter, le système d'exploitation alloue de la mémoire.
Supposons que le système d'exploitation alloue 1 Mo de mémoire sous forme de pile, mais que l'allocation réelle des cadres de pile et des variables locales se fait au moment de l'exécution.
Si notre pile dépasse la taille de la mémoire réservée .
Cela va générer un débordement de pile (stack overflow).
Dans ce cas, le programme va se tromper
par exemple : récursivité infinie causée par l'écriture d'une fonction récursive problématique.
image.png
Par conséquent, la pile est défectueuse

L'espace réservé à la pile en mémoire n'augmentera pas pendant l'exécution et l'application ne peut pas demander plus de piles pendant l'exécution

En supposant que 1 Mo est réservé, lorsque la taille de la pile allouée aux variables et aux fonctions dépasse 1 Mo, le programme plantera

Il existe certaines règles pour l'allocation et la destruction de la mémoire sur la pile.
Lorsqu'une fonction est appelée, elle est poussée sur la pile. Lorsque l'appel se termine, la pile est dépilée.
Si la variable est allouée sur la pile, la portée de la variable ne peut pas être manipulée.

Autre limitation, si nous voulons déclarer un type de données volumineux, tel qu'un grand tableau en tant que variable locale, nous devons connaître leur taille lors de la compilation

Si nous devons déterminer la taille du tableau en fonction des paramètres lors de l'exécution du programme, il y aura des problèmes d'utilisation de la pile

tas

Solution :
Par exemple, allouer une grande quantité de mémoire ou réserver des variables en mémoire jusqu'à ce que nous voulions l'utiliser. À
ce stade, nous devons utiliser le tas (heap)

Le tas de l'application n'est pas fixe, sa taille est variable tout au long de la vie de l'application , et il n'y a pas de règles particulières pour allouer et détruire la mémoire correspondante , et le programmeur peut le contrôler complètement . Par exemple, la quantité de mémoire allouée sur le tas, et la mémoire sur le tas peut être utilisée presque arbitrairement, tant qu'elle ne dépasse pas la limite de mémoire du système lui-même.
Mais utiliser le tas sans discernement est aussi très dangereux

Parfois, le tas est utilisé comme un pool de mémoire libre (stockage libre) ou une zone de mémoire libre
Nous pouvons obtenir la mémoire que nous voulons à partir du tas

La façon dont le système d'exploitation implémente le tas peut être très différente. C'est une question d'architecture système.
Mais du point de vue du programmeur, c'est juste un morceau de mémoire qui peut être utilisé librement, et le tas peut être utilisé de manière flexible selon Il est
également appelé mémoire dynamique, l'utilisation du tas signifie une allocation de mémoire dynamique

Le tas est également une structure de données, qui n'a rien à voir avec le tas dans la structure de données. Le tas ici n'est utilisé que pour représenter le pool de mémoire libre

Pour utiliser la mémoire dynamique dans le tas, nous avons besoin de quatre fonctions

/*-------------C语言:-------------------*/
malloc();
calloc();
realloc();
free();

/*-------------C++语言:-------------------*/

new();
delete();

image.png

Exemple 1, utiliser le langage C pour allouer dynamiquement de la mémoire
Allouer un entier dans le tas, réserver ou obtenir de l'espace sur le tas et utiliser la fonction malloc
pour allouer quatre octets de mémoire sur le tas
sous la fonction malloc.

int *p;
p = (int*)malloc(sizeof(int));

Lorsque la fonction malloc est appelée et que la valeur transmise est un entier size
, malloc s'appliquera et allouera 4 octets de mémoire sur le tas

Renvoie un pointeur sur l'adresse de départ de cette mémoire
malloc renvoie un pointeur de type void

Par exemple, l'adresse de départ du retour de 4 octets est 200
malloc renverra 200

p est une variable locale de la fonction principale et sera allouée dans le cadre de pile de la fonction principale.
Le cast est utilisé car malloc renvoie un pointeur de type void

Maintenant, il y a un morceau de mémoire sur le tas, qui peut être utilisé pour stocker un entier,
mais je ne sais pas ce qui est stocké dans la mémoire du tas.Vous pouvez utiliser *p pour déréférencer cette adresse, puis écrire le valeur

La seule façon d'utiliser la mémoire sur le tas est de se référer à
la fonction malloc. La seule chose qu'elle fait est de trouver de la mémoire libre sur le tas, de vous réserver de l'espace, puis de la renvoyer via un pointeur.
La façon d'accéder à cette mémoire est de définir soi-même un pointeur, en déréférencant la manière d'accéder à cette mémoire
image.png

L'exemple 2
appelle à nouveau malloc. Lorsque malloc est réutilisé,
il alloue une autre mémoire sur le tas, occupant quatre octets.
Une autre adresse est allouée, puis laisse p pointer vers
la mémoire allouée avant cette mémoire, qui est toujours dans le tas. Allumé, la mémoire du tas est consommée et ne sera pas récupérée automatiquement
image.png

Si nous utilisons malloc pour allouer et utiliser un morceau de mémoire sur le tas, il doit être libéré lorsqu'il est épuisé, sinon il y aura une fuite de mémoire (mémoire gaspillée).Par conséquent, une fois la mémoire avec l'adresse de 200
dans le heap est utilisé, il doit s'agir de la fonction free() pour libérer la mémoire

Chaque fois que la mémoire allouée par la fonction malloc sera éventuellement libérée
en appelant la fonction free Utilisez la fonction free pour passer l'adresse de début de la mémoire à free free
(p pointer);

Ainsi de cette manière, le premier morceau de mémoire sera libéré, puis pointera vers un autre morceau de mémoire
. Si la mémoire est allouée et n'a plus besoin d'être utilisée, il faut la libérer manuellement (responsabilité du programmeur)
image.png

Ainsi, toute mémoire allouée sur le tas ne sera pas automatiquement libérée comme la pile après la fin de l'appel de fonction.La
mémoire allouée dynamiquement doit être libérée manuellement.

La mémoire allouée dynamiquement n'existe pas dans tout le cycle de vie du programme comme les variables globales, et le programmeur peut choisir quand libérer manuellement la mémoire sur le tas.

Exemple 3
Si nous voulons allouer un tableau sur le tas
, alors comme suit, nous
n'avons qu'à passer la taille totale du tableau en octets

int *p = (int*)malloc(20*sizeof(int));   
//在堆上分配一个长度为20的整型数组空间
//堆上会分配一个连续的并且足够存放20个整型的内存
//会得到这块内存的首地址
//这样就可以使用了    p[1]   p1[2]
//*p = p[0]    //p  -->  &p[0]
//*(p+1) = p[1]

image.png

Si malloc ne trouve pas de bloc de mémoire libre, il ne peut pas correctement allouer de la mémoire dans le tas et renvoie NULL

Exemple 4 : Utilisation de C++ pour allouer dynamiquement de la mémoire
En C++, aucune conversion de type n'est utilisée, tandis qu'en C, malloc renvoie un pointeur vide. En
C++, new et delete sont sûrs.
Cela signifie qu'ils transportent des types et renvoient des pointeurs vers un type de pointeur spécifique
image.png

Résumer:

  1. La différence entre pile et tas

La pile est défectueuse . Un fonctionnement incorrect de la pile débordera de la portée des variables dans la pile et ne pourra pas être modifié.

  1. Comment allouer de la mémoire dans le tas

La taille de la mémoire allouée dynamiquement est variable tout au long de la vie de l'application, et il n'y a pas de règles spéciales pour allouer et détruire la mémoire correspondante, que le programmeur peut entièrement contrôler.

  1. Plusieurs fonctions sur l'allocation dynamique de mémoire

malloc() calloc() realloc() free() langage c
nouveau supprimer c++


Pointeur 13P et mémoire dynamique malloc calloc realloc gratuit

Le concept d'allocation dynamique de mémoire
La signification et la différence entre tas et pile dans le programme d'application

Diverses fonctions de bibliothèque en langage C prenant en charge l'allocation dynamique de mémoire
image.png

malloc

La fonction malloc détaillée est très détaillée

void* malloc(size_t  size)

Le type size_t est similaire à (unsigned int)
size La taille en octets du bloc mémoire est un entier positif
La taille ne peut pas être négative, elle peut être 0 et un nombre positif

Renvoie un pointeur vide, qui pointe vers l'adresse (première adresse) du premier octet alloué à notre bloc de mémoire dans le tas.
En utilisant malloc, vous pouvez allouer de la mémoire, réserver de la mémoire en mémoire et
enregistrer des données en mémoire

En fait, lors de l'allocation de mémoire, nous allons d'abord calculer la quantité de mémoire dont nous avons besoin.
Généralement, nous allons calculer la taille renvoyée par sizeof() * le nombre d'unités requises. Le
nombre total d'octets requis est : le nombre d'unités * le nombre d'octets par unité

int *p = (int *)malloc(sizeof(int))  //在堆上分配一个整型数据的内存空间
free(p);

p = malloc(int *)malloc(sizeof(int)*20) //分配连续的20个整型数据的空间

La taille de la variable dépend du compilateur, donc malloc doit être utilisé pour calculer la taille du type
image.png

Comment remplir des données dans la mémoire allouée au tas
image.png
image.png

Le fonctionnement de la mémoire dynamique est basé sur des pointeurs, qui renverront une adresse de base pointant vers la mémoire allouée dans le tas

calloc

void* calloc(size_t num,size_t size)

Le premier paramètre est le nombre d'éléments d'un type particulier et le second paramètre est la taille du type

Si vous souhaitez allouer de l'espace pour trois données entières dans le tas

int *p = (int *)calloc(3,sizeof(int))

image.png

malloc et calloc peuvent ouvrir de l'espace dans la zone de tas
La différence entre malloc et calloc

Mallo n'initialisera pas la mémoire après l'avoir allouée, donc si la mémoire demandée n'est pas remplie de données, certaines valeurs aléatoires seront utilisées pour
utiliser calloc, qui s'initialisera et attribuera la valeur initiale, et la valeur de remplissage est 0

réaffecter


Explication de l'utilisation de la fonction realloc_Luv Lines Blog-CSDN Blog

void* realloc(void* Ptr,size_t size)

:::info
Le premier paramètre Ptr fait référence au pointeur vers l'adresse de début de la mémoire allouée Le
deuxième paramètre size fait référence à la taille du nouveau bloc de mémoire
:::

Le scénario d'utilisation de realloc

  1. Le nouveau bloc de mémoire que nous voulons peut être plus grand que celui d'origine. Dans ce cas,

La machine créera un nouveau morceau de mémoire et copiera la valeur d'origine

  1. S'il y a de la mémoire contiguë disponible dans la partie adjacente de la mémoire précédente

Ensuite, il peut directement étendre la mémoire précédente

Exemple 1

声明一个数组,但是这个数组是用户想要的数组
如果不首先声明数组的大小,就会出现error

enter n;

int A[n];   //会编译错误  必须实现知道数组的大小 否则会erro 括号里的值不能是一个变量

可以使用动态内存分配
于是拥有了一个大小是n的数组
使用malloc
image.png

向数组中写入数据和打印数组
image.png
打印输出
image.png

使用calloc函数
image.png

calloc和malloc的区别

如果不初始化
使用calloc函数生成的数组元素将会被初始化为0

image.png
但是如果使用malloc,数组元素里的就不会被初始化,数组里面是一些随机值
image.png

示例2

任何分配了的动态内存在程序结束之前会一直存在(占据内存)
除非显示的释放
使用malloc,calloc,realloc分配的内存,要使用free函数来释放内存
image.png

当free(A),A中的数据会被清除也可能不会被清除,取决于编译器或者机器
但是free()之后,那块内存就可以被另一个malloc来分配和使用
随机值出现
image.png
如果没有被free,打印初始化了的1,2,3,4,5
image.png

尽管使用free释放了内存,我们之后还是可以访问那块内存,这是使用指针的一个危险的地方

如果知道地址,可以查看地址中存放的值,但是你只应该去读写系统分配或者自己分配的内存

如果这个地址不是分配给你的,你不知道你读写的地址上是什么,不知道它的行为是什么,这完全取决于编译器和机器 — 这算是野指针?

free后 , 再向堆中内存访问并且赋值会出现什么?
可以看到内存地址中的值还是发生了改变
这取决于编译器,可能在其他机器中会崩溃
image.png

只用分配的内存,其他不属于你的内存不要使用。

示例3

Supposons qu'il existe un morceau de mémoire stockant n données entières, puis nous voulons étendre ce morceau de mémoire,
par exemple en doublant ou en réduisant de moitié la taille

Exigences : Doublez ou divisez par deux la taille de la mémoire
et utilisez la fonction realloc

int *A = (int*)malloc(sizeof(int)*n);
free(A);

int B = (int*)realloc(A,2*nsizeof(int));   //内存大小翻倍
free(B);
    
int C = (int*)realloc(B,(n/2)*sizeof(int));  //内存减半
free(C);

Doublez la mémoire
et copiez-y le contenu du bloc mémoire précédent
Comment ça marche : Si le nouveau bloc demandé est plus grand que le bloc précédent, si le bloc précédent peut être étendu
Si la mémoire continue peut être trouvée sur la base du bloc précédent , puis développez le bloc précédent.
Sinon, allouez une nouvelle mémoire, copiez le contenu du bloc précédent, puis libérez la mémoire précédente
image.png

Après avoir utilisé realloc pour modifier la taille de la mémoire,
vous pouvez voir que B pointe toujours sur la première adresse de la mémoire A

image.png
Entrée, sortie d'impression, l'adresse mémoire actuelle est la même que l'adresse interne précédente, il suffit d'étendre la mémoire.Les
cinq premiers éléments copient la valeur de A
et les cinq derniers éléments sont des valeurs aléatoires.
image.png

Si vous divisez par deux la taille du tableau
, le bloc précédent sera réduit.
image.png
Les deux premiers éléments sont copiés, pas réellement copiés (ils sont déjà là), et les trois autres sont libérés.
image.png

si

int *A = (int*)malloc(sizeof(int)*n);
free(A);

int A = (int*)realloc(A,0);   /
free(B);

Cela équivaut à l'ensemble du tableau A sera publié
image.png

La plupart du temps, nous assignerons l'adresse renvoyée par realloc au même pointeur entier. Si vous voulez que realloc ait le même effet que malloc, realloc n'initialise pas la valeur, c'est-à-dire que vous devez
initialiser le tableau à 0.

int *A = (int*)malloc(sizeof(int)*n);
free(A);

int B = (int*)realloc(NULL,n*sizeof(int));   
free(B);

image.png
Cela créera un nouveau bloc de mémoire sans copier aucune donnée du bloc de mémoire précédent
afin de transmettre les paramètres appropriés, la fonction realloc peut être utilisée en remplacement des fonctions free et malloc

int B = (int*)realloc(NULL,n*sizeof(int)); -->malloc a le même effet

int B = (int*)realloc(A,0); -->free(A); libérer la mémoire du tableau A

Résumer:

  1. Plusieurs fonctions liées à l'allocation dynamique de mémoire

malloc() calloc() realloc() free()

new delete

  1. La différence entre malloc() et calloc()

Lorsque malloc() s'applique dynamiquement pour la mémoire, il ne sera pas initialisé
et calloc() sera initialisé à 0

  1. Plusieurs formes de transformation de réallocation

(int*)realloc(B,n sizeof(int))
A =(int
)realloc(A,0) —> free(A);
(int*)realloc(NULL,n*sizeof(int));


Pointeurs 14p et fuites de mémoire dynamique

Le concept d'allocation dynamique de mémoire
Qu'est-ce qu'une pile
Qu'est-ce qu'un tas

La mémoire allouée pour une application est généralement divisée en 4 segments
zone de code (code/test), zone de variable statique/globale (statique/Global), pile (stack), tas (heap)

Lorsque la mémoire dynamique est utilisée de manière incorrecte, cela peut entraîner des fuites de mémoire.
Les fuites de mémoire font référence au fait que nous appliquons dynamiquement de la mémoire, mais ne la libérons pas après utilisation. Cela
sera causé par une utilisation incorrecte de la mémoire dynamique (tas)

La valeur de retour de la fonction time() est passée en paramètre à la
valeur de départ srand() srand(time(NULL))
puis la fonction rand() génère une série de nombres aléatoires basés sur la valeur de départ

Un jeu de hasard implémenté en langage C

La valeur devinée par la fonction play()
est un **tableau de caractères**
char C[3] = {"J", "Q", "K"} ;
stocké dans la pile,
image.png
la fonction principale ne changera pas pendant la déroulement du programme

Le résultat de l'exécution de
game.exe consomme la mémoire du système.
Continuez à jouer et vous pouvez voir que la mémoire n'a pas changé.image.png

Lors de la modification de la fonction play()
, modifiez la valeur supposée en un type de pointeur
char C = (char )malloc(3*sizeof(char));
C[0] = 'J'; C[1] = 'Q'; C[ 2] = 'K' ;
Cela appartient à l'ouverture de l'espace mémoire sur le tas et à l'application dynamique de la mémoire
image.png

Exécutez-le à nouveau,
vous constaterez que la mémoire occupée par game.exe change avec le pari que nous entrons
image.png
image.png

Voyons ce qui se passe pendant l'exécution du programme

Dans le premier cas , le tableau de caractères
est l'espace mémoire ouvert sur la pile
image.png

Lorsqu'un appel de fonction se termine, la mémoire précédemment allouée sera également récupérée. Chaque appel de fonction correspond à un cadre de pile. Une fois l'appel terminé, la mémoire allouée sera récupérée. La
libération de quoi que ce soit sur la pile ne nécessite pas que le programmeur prenne la libération de l'initiative**
tout se passe automatiquement à la fin de l'appel de la fonction

Dans le second cas , utilisez malloc pour allouer de la mémoire sur le tas
char C = (char )malloc(3*sizeof(char));
C[0] = 'J'; C[1] = 'Q'; C[2 ] = 'K';
Crée un tableau sur le tas, mais ne définit qu'un pointeur de caractère sur la pile

Un pointeur de caractère en tant que variable locale pointe vers un bloc de mémoire spécifique sur le tas
Toute mémoire sur le tas est accessible via une variable de pointeur
image.png

Lorsque l'appel se termine, la mémoire allouée sur la pile sera libérée, mais les données dans la mémoire sur le tas ont été dans un état non référencé et ne seront pas libérées.
La
mémoire sur le tas doit être libérée manuellement via free ou via delete .
image.png

Si vous jouez plusieurs fois, vous demanderez de l'espace sur le tas plusieurs fois. Le tas n'a pas une taille fixe.
Si vous ne libérez pas la mémoire qui n'est plus utilisée sur le tas, vous gaspillez de précieuses ressources de mémoire. la consommation de mémoire de l'application augmentera avec le temps,
donc les fuites de mémoire sont dangereuses
Toute mémoire non référencée et inutilisée sur le tas est une ordure
En c/c++, en tant que programmeur, nous devons nous assurer qu'aucune ordure n'est générée sur le tas, les fuites de mémoire sont des ordure sur le tas
mais d'autres langages, tels que java et c#, les ordures sur le tas seront automatiquement récupérées (mécanisme de récupération automatique)

Si la mémoire demandée sur le tas est importante
et que seules les premières sont utilisées, si la mémoire n'est pas libérée manuellement, elle
occupera un espace mémoire important
, appelez donc la fonction free() pour
image.pngexécuter à nouveau, entrez et vérifiez la tâche gestionnaire, quelle que soit l'entrée Combien de fois, le fichier game.exe ne changera pas.
Étant donné que free est utilisé, la mémoire sur le tas est libérée manuellement.
image.png
Résumé

La mémoire sur la pile est automatiquement récupérée, la taille de la pile est fixe et au plus seul un débordement de pile se produira

  1. Une fuite de mémoire est l'utilisation inappropriée de la mémoire dynamique ou de la zone de tas dans la mémoire qui continue de croître sur une période de temps
  2. Les fuites de mémoire se produisent toujours en raison de blocs de mémoire inutilisés et non référencés dans le tas
  3. Une fois la mémoire appliquée dans le tas, elle doit être libérée manuellement si elle n'est pas utilisée ou référencée

Pointeur de retour de fonction 15p

Un pointeur est juste un autre type de données.
Un pointeur stocke l'adresse d'une autre donnée.
Par conséquent, il est permis à une fonction de renvoyer un pointeur.

Dans quel scénario voulons-nous qu'une fonction renvoie un pointeur

On implémente l'addition de deux nombres

Transfert de valeur Le transfert de valeur réalise l'addition de deux nombres
image.png
x, y, z sont des variables locales de la fonction principale
a, b, c sont des variables locales de la fonction d'addition

Modifiez les variables locales dans main et ajoutez le même nom.
On peut voir que la valeur de a dans la fonction main est copiée dans la fonction add,
mais le a et le b des deux sont différents.
image.png
Imprimez les adresses de a correspondant à ces deux fonctions, et vous constaterez que les deux Le ou n'est pas le même a, l'adresse est différente
, c'est-à-dire que le nom de la variable est local à une fonction
image.png

fonction appelante et fonction appelée
image.png

adresse par référenceimage.png

image.png
image.png

La fonction renvoie un pointeur (adresse) La fonction renvoie un pointeur entier
image.png
image.png

Lors de l'impression de helloworld avant l'affichage du résultat,
le résultat sera erroné
image.png

Pourquoi pincez-vous ainsi ?
Lorsque l'adresse de c est renvoyée, qui est 144, ajoutez les extrémités
*ptr = 144 ;
image.png

A la fin de la fonction d'ajout,
le pointeur ptr pointe sur la mémoire à l'adresse 144, mais sa valeur ne peut être garantie, car cette mémoire (le cadre de pile alloué par la fonction d'ajout) a été libérée.

Ensuite, appelez la fonction helloworld().
L'exécution de l'appel de fonction nécessite toujours de l'espace d'allocation de pile.
La valeur de l'adresse 144 dans la pile est écrasée par la fonction helloworld(), donc la valeur de l'adresse 144 n'est pas 6,
donc des ordures les valeurs sont obtenues.

Pourquoi n'y a-t-il pas de problème lorsque helloworld() n'est pas appelé, de la chance haha, peut-être qu'aucune autre fonction n'est appelée après avoir appelé add, donc la machine n'a pas réécrit la valeur à l'adresse 144

Il n'y a aucun problème à transmettre l'adresse, car la fonction appelée est toujours au-dessus de la fonction appelante dans la pile.
Chaque fois que la fonction appelée est exécutée, la fonction appelante existe toujours dans la mémoire de la pile . Lorsque add est exécuté, la fonction principale la fonction peut toujours être garantie d'être dans la pile, de sorte que l'adresse de la variable dans la fonction principale est accessible à la fonction d'ajout

Mais si vous essayez de renvoyer une variable locale de la fonction appelée à la fonction appelante, cela revient à renvoyer une variable locale de la fonction add à la fonction principale. Lorsque la fonction appelée se termine et revient à la fonction appelante, la mémoire de la fonction appelée a été libérée.

Il est donc possible de passer des paramètres depuis le bas de la pile, et il est également possible de passer une variable locale ou l'adresse d'une variable locale,
mais il n'est pas autorisé de passer du haut de la pile

Quand voudrions-nous renvoyer un pointeur à partir d'une fonction ?
Par exemple, s'il y a une adresse mémoire sur le tas ou une variable dans la zone globale , alors nous pouvons retourner en toute sécurité leurs adresses
image.png

Par conséquent, la mémoire allouée sur le tas doit être libérée explicitement, et nous contrôlons leur libération (pas automatiquement libérée comme la pile).Tout
ce qui se trouve dans la zone globale, comme une variable globale, a un cycle de vie de l'ensemble du programme

Nous pouvons donc utiliser malloc ou le nouvel opérateur de C++ pour ouvrir de la mémoire sur le tas

Ceci est correct et sûr, car le pointeur renvoyé sur le tas
est créé manuellement et publié par le programmeur.
image.png

Les procédures de mémoire
image.png
renvoient donc la valeur de l'adresse allouée dans le tas.
Toute mémoire dans le tas doit être explicitement libérée.
Par conséquent, lors du retour de pointeurs à partir de fonctions, sachez que leur portée
doit garantir que l'adresse n'est pas réutilisée (utilisée pour stocker d'autres choses), et cette adresse n'est pas effacée
image.png
en utilisant : liste liée

Résumer

  1. Paramètres de fonction, passage par valeur et passage par adresse (passage par référence)
  2. Zone de tas et zone de pile Utilisez malloc ou new pour ouvrir de la mémoire dans la zone de tas. Si la fonction veut renvoyer un pointeur, il faut garantir que le pointeur est ouvert dans la zone de tas, de sorte qu'il n'y aura pas de problème d'erreur ( valeur anormale)
  3. Dans quelles circonstances sera-t-il nécessaire de retourner un pointeur ? Lorsque la variable renvoyée est une zone globale ou une variable globale, un pointeur peut être renvoyé.

Pointeur de fonction 16p

Les pointeurs de fonction sont utilisés pour stocker les adresses des fonctions.
Les pointeurs sont utilisés pour stocker les adresses d'autres variables. Fondamentalement, les pointeurs sont un tel type de données.

指针指向或者引用内存中的数据(这里的数据也不一定是指变量,也可以是常量)

我们不仅使用指针来存放地址,来可以解引用

我们也可以使用指针来存放函数的地址

函数的地址是什么?
image.png

一个程序基本上就是一组顺序的计算机的指令的集合,任何需要被执行的程序都要编码为二进制格式

源码 -->机器代码 (可执行代码)
编译器会把源文件作为它的输入,生成一个包含机器码的可执行文件
可执行文件存储在磁盘上或者其他的存储设备中

当说内存的时候,指的是程序运行的上下文,即随机存储器(RAM),称之为主存
一般在讨论应用程序的内存的时候

程序开始运行的时候,会得到一块内存
程序运行结束的时候,他得到的内存将会被回收

实际上,当我们运行一个程序,,或者说程序运行开始的时候,会给他分配一些内存
image.png

代码段,是用来存放可执行文件拷贝过来的机器码或者机器指令的,指令不是在第二存储介质上(比如磁盘)直接运行的,首先要先拷贝到主存才能够执行。

不仅使用内存来存储指令,还要用来存放运行期间的很多数据
其他区段主要就是用来存储和管理数据的

一个程序的指令顺序是顺序执行的
唯一的意外是函数调用
image.png

个函数就是一组存储在连续内存块中的指令
基本上一个函数是一组指令用来执行一个子任务
在内存中,一个函数就是一块连续的内存(里面是指令)
函数的地址,也称之为函数的入口点他是函数的第一条指令的地址
image.png

Un appel de fonction en langage machine est essentiellement une instruction de saut
Sauter au point d'entrée de la fonction, sauter à la première instruction de la fonction

Le pointeur de fonction stocke l'adresse de la fonction et le pointeur de fonction stocke l'adresse de début ou le point d'entrée de la fonction en mémoire.

Comment créer un pointeur de fonction en c/c++

entier (*p)(entier,entier)

Le type de paramètre déclaré dans le pointeur de fonction doit être le même que le paramètre de la fonction pointée
image.png
Initialiser le pointeur de fonction et renseigner l'adresse de la fonction

p = &Add; //Renvoyer l'adresse de Add à p, renseigner l'adresse du pointeur de la fonction d'initialisation

Passer par valeur
image.png
utilise un pointeur de fonction pour faire référence à une fonction
Utiliser le déréférencement * pour obtenir la fonction

c = (*p)(2,3);   // *p 就相当于 Add函数

image.png

si oui écris

int *p(int,int);   //声明了一个函数  这个函数返回一个整型的指针

La différence entre renvoyer un pointeur et renvoyer une fonction
Renvoyer un pointeur
image.png
Renvoyer un pointeur de fonction
image.png

Le pointeur de fonction peut être initialisé sans utiliser le symbole d'adresse et l'adresse de la fonction ne peut être renvoyée qu'en utilisant le nom de la fonction
image.png

"" peut être compris comme un pointeur, qui est une constante de pointeur, pointant vers une chaîne dans la zone constante, et la valeur est égale à l'adresse de la chaîne
image.png

Résumer

  1. pointeur de fonction
  2. Deux façons de référencer et de déréférencer
  3. Pour pointer vers une fonction, le pointeur de fonction doit être du bon type
  4. Le pointeur de fonction stocke l'adresse de la fonction et stocke l'adresse de début (point d'entrée) de la fonction en mémoire

17p Cas d'utilisation de la fonction de rappel de pointeur de fonction

Les pointeurs de fonction peuvent être utilisés comme paramètres de fonctions.
La fonction qui reçoit le pointeur de fonction peut rappeler la fonction pointée par le pointeur de fonction.
image.png

Il peut également être écrit comme ceci
parce que la fonction A, le nom de la fonction A lui-même renvoie un pointeur.
Lorsqu'une référence de fonction est passée à une autre fonction, cette fonction est appelée
une fonction de rappel.
image.png

Exemple 1
Trier le tableau par ordre croissant et trier
image.png
par ordre décroissant
image.png

Utilisez la fonction de rappel du pointeur,
image.png
c'est quelque chose,
image.png
changez la fonction de comparaison
image.png

Les éléments du tableau sont triés par ordre croissant de valeur absolue
image.png

数组元素升序排列
image.png
image.png
数组元素降序排列
image.png
image.png

数组绝对值升序排列
image.png
在compare比较函数中,带比较的函数是通过引用来传递的,他们的地址通过指针来传递

qsort函数能够对任何数组进行排序,不仅仅是整型数组,需要我们自己给出比较逻辑
回调的思想
事件处理


18p 指针以及应用 – 基于ARM Cortex-M

计算机内存视为字节数组,每个字节都有一个唯一的地址
计算机的内存是字节可寻址的
可寻址的最小数据对象是一个字节

ARM Cortex-M微处理器,每个内存地址都有32位,可以总共寻址4GB
image.png

数据对象可能占用内存中的多个字节
例如 一个字在内存中占据4个字节
有两种不同的格式可以存储一个字
最小端,最大端

最小端格式存储一个字时,最高有效字节存储在高位地址,最低有效字节存储在低地址
以最大端格式存储一个字时,最高有效字节存储在低位地址,最低有效字节存储在高位地址
image.png

指针的值仅仅是计算机中存储的某些变量的内存地址

如果变量占用内存中的多个字节,则变量的地址定义为它占用的所有字节的最低地址
image.png

引用运算符和解引用运算符
引用运算符&x,返回x变量的地址,&称为引用运算符或者地址运算符
解引用运算符*p返回指针p指向的变量的值
image.png

image.png

总结
引用运算符对变量起作用,可以读取变量的地址,&var即是var的地址
解引用运算符可以处理指针,可以读取指针指向变量的值
image.png

Le pointeur vers le tableau de caractères effectue l'opération mathématique
ptr++, en ajoutant sizeof (type)
image.png

Les pointeurs entiers effectuent des opérations mathématiques
ptr = ptr + sizeof(int);
image.png

Exemple
de tableaux et de pointeurs
ArrayName est un pointeur vers le premier des éléments du tableau
image.png

Opérateurs
Opérateurs d'incrémentation et de décrémentation, rang supérieur à l'opérateur de déréférencement*
image.png

Comment créer un pointeur vers une adresse mémoire spécifique donnée

Par exemple, l'adresse mémoire du registre de données de sortie du GPIOA est déterminée par le concepteur du microcontrôleur au moment de la conception afin d'
établir un pointeur vers celui-ci, de sorte que le logiciel puisse facilement accéder au registre de données de sortie
image.png
pour convertir l'adresse constante en un pointeur, pointant vers un entier 32 bits non signé, puis utilisez l'opérateur de déréférencement pour accéder à la valeur pointée

Par exemple, définissez GPIOA5 sur le niveau haut
et utilisez une macro pour définir un pointeur. Cette macro forcera l'adresse mémoire dans un pointeur, afin que vous puissiez directement déréférencer la valeur de cette adresse. Mettez l'opérateur de
image.png
déréférencement directement dans la macro, de sorte que le logiciel peut utiliser directement le déréférencement Les pointeurs référencés
sont le plus souvent utilisés pour accéder à la mémoire
image.png

Si vous forcez le compilateur à lire une nouvelle valeur à chaque fois, l'ajout du mot-clé volatile
peut empêcher le compilateur de faire de mauvaises optimisations lors de la compilation
image.png

Dans le fichier d'en-tête du périphérique du processeur STM32 Cortex-M , ** L'adresse mémoire du périphérique est forcée de pointer vers une structure **
Utilisez une macro pour convertir cette adresse mémoire en un pointeur, qui peut être une structure de type GPIO , afin que les registres de sortie de données puissent être modifiés de cette manière
Grâce à la structure, le logiciel peut accéder plus facilement à tous les registres du périphérique
image.png

matériel
image.png

C'est fini, saupoudrez de fleurs, Ollie ! ! ! Cela a pris 5 jours, et l'apprentissage de linux s'y est mélangé !!!


3. Notes du programmeur Pointer Heima

Il s'agit de la vidéo d'enseignement du pointeur apprise par le programmeur du cheval noir. Elle est relativement simple, alors résumons-la ensemble.

Les pointeurs peuvent accéder indirectement à la mémoire

1. Définition et utilisation des pointeurs

  • Les numéros de mémoire sont tous enregistrés à partir de 0, généralement exprimés en hexadécimal
  • Les variables de pointeur peuvent être utilisées pour contenir des adresses
  1. Définir le type de données du pointeur * le nom de la variable du pointeur
  2. Vous pouvez ajouter * avant d'utiliser le pointeur pointeur pour déréférencer et trouver les données pointées par le pointeur en mémoire
#include <iostream>

using namespace std;

int main()
{
    
    
    // 1. 定义指针
    int a = 10;
    int *p = &a;
    cout << "a的地址为:" << &a << endl;
    cout << "指针p为:  " << p << endl;

    // 2. 使用指针
    // 可以通过解引用的方式来找到指针指向的内存  可以修改也可以访问
    // 指针前 可以加 * 表示解引用 找到指针指向内存中的数据
    *p = 1000; //指针p通过解引用找到了a的地址 修改a的值为1000
    cout << "a的值为:" << a << endl;
    cout << "*p的值为:" << *p << endl;
    system("pause");
    return 0;
}

2. L'espace mémoire occupé par le pointeur

Notez que sous le même système d'exploitation, les octets occupés par des pointeurs de tout type de données sont les mêmes

比如,在64位操作系统中,int * 占8字节,float * 占8字节,double * 占8字节

int *p
occupe un espace mémoire de 4 bits sous un système 32 bits
et occupe un espace mémoire de 8 bits sous un système 64 bits

#include <iostream>

using namespace std;

int main()
{
    
    
    // 指针所占内存空间
    int a = 10;
    int *p = &a;
    // 64位 占8字节
    cout << "sizeof(int *) = " << sizeof(int *) << endl;
    cout << "sizeof(p) = " << sizeof(p) << endl;

    // 在相同的操作系统下,不同数据类型的指针所占字节都是相同的
    cout << "sizeof(char) = " << sizeof(char *) << endl;
    cout << "sizeof(flaot) = " << sizeof(float *) << endl;
    cout << "sizeof(double) = " << sizeof(double *) << endl;
    cout << "sizeof(long) = " << sizeof(long *) << endl;
    system("pause");
    return 0;
}

3. Pointeurs nuls et pointeurs sauvages

  • Pointeur nul : la variable de pointeur pointe vers l'espace 0 dans la mémoire
    pour initialiser la variable de pointeur.

La mémoire pointée par le pointeur nul est inaccessible

#include <iostream>

using namespace std;

int main()
{
    
    
    // 1. 空指针用于给指针变量初始化
    int *p = NULL;

    // 2. 空指针不可以进行访问
    // 0-255 之间的内存由系统占用 因此不可以访问
    *p = 100;
    cout<<*p<<endl;  //没有权限
    system("pause");
    return 0;
}
  • Pointeur sauvage
    La variable pointeur pointe vers un espace mémoire illégal
#include <iostream>

using namespace std;

int main()
{
    
    
    //在程序中,尽量避免野指针出现 即指针指向非法的内存空间
    int *p = (int *)0x1100;
    cout << *p << endl;  //访问野指针报错
    system("pause");
    return 0;
}

4. pointeur modifié const

const suivi d'une variable est appelé une constante.

Trois cas de pointeur modifié const

  1. const Pointeur modifié pointeur constant
    Le pointeur peut être modifié, mais la valeur pointée ne peut pas être modifiée

const int *p = &a;

#include <iostream>

using namespace std;

int main()
{
    
    
    int a = 10;
    int b = 20;
    const int *p = &a;
    cout << "为修改指向前*p的值为:" << *p << endl;
    p = &b; //常量指针可以修改指向
    cout << "修改指向后*p的值为:" << *p << endl;
    // *p = 30;  报错 常量指针 不可修改值

    system("pause");
    return 0;
}
  1. const Constante modifiée pointeur constant
    Le point pointé par le pointeur ne peut pas être modifié, mais la valeur pointée peut être modifiée

int * const p=&a;

#include <iostream>

using namespace std;

int main()
{
    
    
    int a = 10;
    int b = 20;
    int *const p = &a;

    //指针常量可以修改值,不可以修改指针的指向
    *p = 100;
    cout << "*p = " << *p << endl;
    // p = &b;  报错 不可以修改指针的指向
    system("pause");
    return 0;
}
  1. const modifie les pointeurs et modifie les constantes.
    Ni le pointeur ni la valeur pointée ne peuvent être modifiés.

const int _ const _p=&a;

#include <iostream>

using namespace std;

int main()
{
    
    
    int a = 10;
    int b = 20;
    const int *const p = &a;

    // *p = 100; 报错 不可以修改指针的值
    cout << "*p = " << *p << endl;
    // p = &b; 报错 不可以修改指针的指向
    system("pause");
    return 0;
}

5. Pointeurs et tableaux

Accéder aux éléments du tableau à l'aide de pointeurs

#include <iostream>

using namespace std;

int main()
{
    
    
    // 利用指针访问数组中的元素
    int arr[10] = {
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    cout << "数组的第一个元素的值为:" << arr[0] << endl;

    int *p = arr; //指针p指向数组的首地址
    cout << "访问指针访问的第一个元素:" << *p << endl;

    p++; //指针偏移了int类型变量的地址 即偏移地址4位
    cout << "利用指针访问的第二个元素:" << *p << endl;

    cout << "访问第三个元素的值:" << *(p + 1) << endl;

    // 利用指针遍历数组
    int *p1 = arr;
    for (int i = 0; i < 10; i++)
    {
    
    
        cout << *(p1 + i) << endl;
    }

    system("pause");
    return 0;
}

6. Pointeurs et fonctions

En utilisant des pointeurs comme paramètres de fonctions, les valeurs des paramètres réels peuvent être modifiées.

Le paramètre formel de la fonction est un pointeur, qui peut réduire l'espace mémoire.

#include <iostream>

using namespace std;

void swap1(int a, int b)
{
    
    
    int temp = a;
    a = b;
    b = temp;
    cout << "a的值为:" << a << endl;
    cout << "b的值为:" << b << endl;
}

void swap2(int *p1, int *p2)
{
    
    
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
int main()
{
    
    
    // 指针和函数
    // 1. 值传递
    // 值传递不会修改实参
    int a = 10, b = 20;
    swap1(a, b);
    cout << "swap1 a的值为:" << a << endl;
    cout << "swap1 b的值为:" << b << endl;

    // 2. 地址传递
    // 地址传递 可以修改实参的值 
    swap2(&a, &b);
    cout << "swap2 a的值为:" << a << endl;
    cout << "swap2 b的值为:" << b << endl;
    system("pause");
    return 0;
}

7. Le cas de la fonction tableau de pointeurs

Encapsuler une fonction et utiliser le tri à bulles pour trier les tableaux d'entiers par ordre croissant

Par exemple les tableaux,

#include <iostream>

using namespace std;

void arraySort(int *arr, int length)
{
    
    
    //冒泡排序
    for (int i = 0; i < length - 1; i++)
    {
    
    
        for (int j = 0; j < length - 1 - i; j++)
        {
    
    
            if (arr[j] > arr[j + 1])
            {
    
    
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}
int main()
{
    
    
    int arr[10] = {
    
    4, 3, 6, 9, 1, 2, 10, 8, 7, 5};
    int length = sizeof(arr) / sizeof(arr[0]);
    // 排序前
    cout << "未排序前:" << endl;
    for (int i = 0; i < length; i++)
    {
    
    
        cout << arr[i] << " ";
    }
    cout << endl;
    //排序
    arraySort(arr, length);
    //排序后
    cout << "排序后:" << endl;
    for (int i = 0; i < length; i++)
    {
    
    
        cout << arr[i] << " ";
    }
    cout << endl;
    system("pause");
    return 0;
}

Je suppose que tu aimes

Origine blog.csdn.net/cyaya6/article/details/132245789
conseillé
Classement