Les pointeurs¶
Warning
Ce cours a été automatiquement traduit des transparents de M.Noyer par Félix qui continue le travail fait par Lorentzo et Elowan et Mehdi, nous ne nous accordons en aucun cas son travail, ce site à pour seul but d'être plus compréhensible pendant les périodes de révision que des diaporamas.
Crédits
- Ce cours de OpenClassRoom (Renvoie sur une page vide ¯\(ツ)/¯ )
- Un autre de Zeste de savoir
- Un cours d'Anne Canteaut
- Et toujours Wikipedia
Pointeurs¶
Accès à la mémoire¶
- Un entier naturel codé en binaire peut-être vu comme :
- ce qu'il est : un nombre destiné par exemple à faire l'objet d'opérations arithmétiques,
- une adresse mémoire. Dans ce cas l'utilisation arithmétique du nombre n'est pas ce qui nous intéresse.
- Dans les processeurs ont donc été créés des registres d'adresses et dans les langages de programmation, un type dédié.
- Pointeurs : Apparus dans \(\texttt{Aigol 68}\). Le langage \(\texttt{C}\) y a ajouté l'arithmétique des pointeurs : quand on incrémente un tel pointeur, il n'est en fait pas forcément incrémenté de un, mais de la taille du type pointé. Cette arithmétique (hors programme) permet donc de se déplacer dans la mémoire.
- L'utilisation des pointeurs permet d'avoir accès à la mémoire. On peut se déplacer de case mémoire en case mémoire. Avantage : optimisations sur l'utilisation de la mémoire ou la performance.
Syntaxe¶
Déclaration¶
- Syntaxe :
- L'astérisque peut être entourée d'espaces et placée n'importe où entre le type et l'identificateur.
Initialisation¶
- Comme les autres variables, un pointeur ne possède pas de valeur par défaut. pour lui en attribuer une, on utilise l'opérateur d'adressage (ou de référencement)
&
. - Déclaration puis affectation
- Déclaration et affectation dans la foulée
- Savoir faire : déclaration et initialisation d'un pointeur dans un type donné ♥
Affichage des adresses¶
Avec :
On obtient :
nom | adresse | valeur |
---|---|---|
\(p\) | \(\text{0x7ffc7aaa6130}\) | \(\text{0x7ffc7aaa612c}\) |
... | ||
\(a\) | \(\text{0x7ffc7aaa612c}\) | \(10\) |
Indirection ♥¶
L'opérateur d'indirection (ou de déréférencement) *
prend un pointeur comme opérande et se place juste avant celui-ci. On accède ainsi à la valeur de l'objet référencé par le pointeur, aussi bien pour la lire que pour la modifier.
On obtient
Les différentes casquettes de *
¶
L'opérateur *
sert au moins à trois choses distinctes :
- à la déclaration de pointeurs :
int *p = &i
; - Au déréférencement :
*p=20; // i prend la valeur 20
- à la multiplication
3*5
;
Pointeurs et constantes¶
Pour info :
- La règle du mot-clé const est qu'il s'applique sur ce qui est immédiatement à sa gauche, et que s'il n'y a rien à gauche alors ça s'applique sur l'élément immédiatement à droite
- Un pointeur peut être déclaré constant :
L'étoile est entre le type et le mot clé const
- Un pointeur peut pointer sur une constante
L'étoile est entre le mot clé const
et le nom du pointeur
- Pointeur constant sur objet constant.
Piège à la déclaration¶
- On l'a vu
int* p;
etint *p;
sont compris de la même façon par le compilateur. int *p;
permet au lecteur de savoir que*p
est un entier. La forme avec l'étoile collée au nom est préférable.int* x,y
; ne déclare pas deux pointeurs sur entier mais un pointeur sur entier et un entier. On le comprend mieux en écrivantint *x,y;
Utilité¶
Utilité♥¶
- On peut utiliser les pointeurs pour faire des effets de bords en les passant en paramètres de fonctions. Ex : passer un pointeur sur une variable x en paramètre de f , permet d'avoir accès au contenu de x et donc de le modifier (voir \(2\) slides suivants).
- Les pointeurs sont une des façons de contourner l'obligation qu'une fonction \(\texttt{C}\) ne renvoie qu'une seule valeur. L'autre façon courante étant l'utilisation de structure (ou enregistrement).
- On peut les utiliser aussi pour stocker des adresses mémoires allouées dynamiquement (i.e. au run time) par l'application.
Conséquence du passage par valeur¶
- Le contenu de la variable y de main n'a pas été modifié par l'appel de incremente. On dit que le passage de l'argument se fait par valeur, ce qui signifie que c'est une copie de y qui est passée en paramètre de la fonction. Pour changer y on utilisera son adresse, c'est à dire un pointeur.
Après compilation/exécution :
Pointeurs dans d'autres langages¶
- Dans les langages de plus haut niveau (ex : \(\texttt{OCAML}\)), l'utilisation des pointeurs est supprimée, au profit des références et des tableaux dynamiques gérés par le compilateur.
- On y gagne en simplicité, on évite de nombreux bugs mais on perd certaines possibilités d'optimisation.
« Retourner » plusieurs valeurs¶
Passage de pointeurs en paramètres¶
- On contourne l'obligation faite aux fonctions C de ne retourner qu'une seule valeur.
On veut appliquer à un point une translation de vecteur donné et récupérer le résultat.
- On ne peut pas retourner un tuple de coordonées comme en \(\texttt{Python}\) au \(\texttt{OCaml}\).
- Solution : utiliser des pointeurs sur les coordonnées du résultat.
applique une translation de vecteur \(V (v_x,v_y)\) à un point \(A(x ,y )\) et met les coordonnées du résultat \(R(r_x,r_y)\) dans deux variables. Les coordonnées de \(A\) et \(V\) sont passées en paramètres. Pour le résultat, ce sont adresses de ses coordonnées qui sont passées en paramètres.
On obtient
Pointeur \(\text{NULL}\), mot clé \(\text{void}\)¶
Pointeur nul¶
- Un pointeur nul est un pointeur contenant une adresse invalide. Cette adresse invalide dépend du système d'exploitation, mais elle est la même pour tous les pointeurs nuls. Ainsi, deux pointeurs nuls ont une valeur égale.
- La constante
NULL
est définie dans l'en-tête<stddef.h>
- Le pointeur
NULL
est souvent utilisé comme marqueur de fin dans une structure définie récursivement (exemple : listes chaînées). - Il sert aussi à indiquer que quelque chose s'est mal passé (ex
malloc
)
Le mot clé void
¶
void
n'est pas un type, c'est un mot clé. Plus précisément, c'est un type dit « incomplet » , c'est à dire que sa taille n'est pas calculable et qu'il n'est pas utilisable dans des expressions.- On l'utilise pour déclarer qu'une fonction ne renvoie pas de valeur :
void f(int x)
- Pour les fonctions sans argument, préferer
int f(void)
(qui signifie explicitement quef
ne prend pas d'argument) àint f()
qui indique que le nombre d'arguments def
n'est pas connu. void *
désigne un pointeur vers une valeur dont on ne connaît pas le type (c'est un pointeur générique). Un pointeur survoid
est considéré comme un pointeur générique : il peut référencer n'importe quel type.
- Les règles de typage du \(\texttt{C}\) permettent d'utiliser
void*
là où une valeur d'un certain type de pointeur est attendu. On peut donc écrire
- Enfin
void *
permet le polymorphisme. Exemple : une fonction qui agit sur un tableau dont les éléments sont de type quelconque. Nous n'utilisons pas cette fonctionnalité. - Le spécifieur de format
%p
deprintf
attend un pointeur sur n'importe quel type et affiche sa valeur (donc une adresse), que le pointeur pointe sur un objet de typeint, double, char...
Valeur de retour¶
Pointeur comme valeur retournée¶
- Une fonction peut retourner un pointeur.
- Cependant : l'objet référencé par le pointeur retourné doit toujours exister au moment de son utilisation.
- Il faut se poser la question :
- l'adresse retournée est-elle celle d'une variable locale à la fonction (donc gérée automatiquement dans la pile d'exécution) ? Si c'est le cas, cette adresse peut servir dans d'autres contextes d'exécution et son contenu risque d'échapper au programmeur distrait.
- l'adresse retournée désigne-t-elle une zone mémoire allouée sur le tas ? Si c'est le cas, il faut bien penser à la libérer par la suite.
On obtient
- Le problème vient de la ligne
- Les variables locales comme
n
deptr
se voient allouer un emplacement en mémoire de façon dynamique lors de l'exécution du programme. Le segment de mémoire dans lequel sont stockées les variables temporaires est appelé segment de pile - Leur emplacement en mémoire est libéré à la fin de l'exécution d'une fonction secondaire comme
ptr
. - Donc la fonction ptr renvoie une adresse qui n'est plus pertinente et qui sera utilisée par d'autres appels à d'autres fonctions. On dit que c'est un pointeur fantôme
Mot clé static¶
- On modifie juste la ligne \(3\) du code précédent :
On obtient
- Une variable locale déclarée
static
occupe toujours la même adresse en mémoire à chaque appel de la fonction. On verra plus tard que la partie de la mémoire (allouée au programme) qui contient les variables statiques (comme d'ailleurs les variables globales) est appelée segment de données. - Cette solution n'est pas d'un grand intérêt sauf si on a absolument besoin que l'adresse retournée soit toujours la même ! On préfère l'allocation dynamique (voir plus loin).
Pointeur sur pointeur¶
- Un pointeur p a une adresse qu'on récupère avec
&p
- Dans le
main
, écrivons :
- Avec
*pp
on récupère l'adresse de a et avec**pp
, la valeur de a. - Après compilation/exécution :
Dangers¶
Complexification du code¶
- Très puissante, l'utilisation de pointeurs rend plus difficile le travail du développeur.
- ♥ Si on ne fait pas attention avec les pointeurs, le programme peut accéder à une zone mémoire qui ne lui est pas allouée ou dans laquelle il ne peut pas écrire. Le processeur via le système d'exploitation engendre alors une erreur de segmentation (segmentation fault) qui provoque une exception voire plante l'application.
- ♥ Si c'est le programmeur qui gère les allocations en mémoire, il ne doit pas oublier de libérer après usage la zone allouée sous peine de fuite de mémoire : Il s'agit d'un bug qui résulte d'une occupation croissante non contrôlés ou non désirée de la mémoire. DANGER : saturation possible de la RAM.
Un exemple de segmentation fault¶
- Explication : le pointeur
variable_entiere
n'est pas initialisé donc contient n'importe quoi (possiblement, une adresse interdite en écriture). Quandscanf
veut accèder à cette adresse l'OS le lui refuse.
Allocation dynamique¶
Allouer de la mémoire ♥¶
Trois manières pour l'allocation de mémoire dans un programme :
- Statiquement, au cours de la compilation lorsque le compilateur lit des mots clés comme
const
oustatic
dans le code source. Adresses mémoires connues à l'initialisation de la mémoire du programme. Ces adresses correspondent à ce qu'on appelle le segment de données du programme. - Dynamiquement, au cours de l'exécution :
- soit de façon automatique sur la pile d'exécution : variables locales déclarées dans un bloc d'instructions,
- soit à la demande sur le tas : en utilisant des fonctions d'allocation de la mémoire.
- Il y a donc trois zones de données distinctes pour le programme. On affinera leur description dans un cours à venir.
segment de données | var. globales ou var. locales statiques |
pile d'exécution | var. locales |
tas | allocation dynamique par malloc |
Allouer de l'espace¶
- Un pointeur non initialisé est égal à la constante symbolique
NULL
de<stddef.h>
. Le testp == NULL
permet de savoir si le pointeur \(p\) pointe vers un objet. - On a vu comment initialiser un pointeur en lui affectant l'adresse d'une autre variable
int * p =&a
; - Pour affecter directement une valeur à
*p
, il faut d'abord réserver à*p
un espace-mémoire de taille adéquate avecmalloc
de<stdlib.h>
. - L'adresse de cet espace-mémoire est la valeur de
p
- Cette opération est appelée allocation dynamique
La fonction malloc
¶
- Syntaxe
- La valeur retournée est l'adresse du premier octet de la zone mémoire allouée. Si l'allocation n'a pu se réaliser (par manque de mémoire libre), la valeur de retour est la constante NULL.
- Toute utilisation de
malloc
doit être suivie plus tard d'une libération de l'espace réservé
Le seul paramètre à passer est l'adresse du premier octet de la zone allouée et aucune valeur n'est retournée une fois cette opération réalisée.
Exemple ♥
- Réserver \(32\) octets et les libérer immédiatement.
Allocation dynamique vs automatique ♥¶
p
ne pointe pas sur i
mais r
si. Modifier *p
ne modifie pas i
, mais modifier *r
modifie i
Assertions (rappel)¶
Pour diverses raisons on peut souhaiter interrompre un programme si une condition n'est pas réalisée (ex : impossibilité d'allouer de la mémoire). Les assertions sont alors utiles.
Exemple d'assertion
Trace d'exécution :
- On la vu que les assertions servent à se protéger d'un comportement aberrant en arrêtant le programmme.
- Elles servent aussi en phase de conception d'un programme :
- Il faut en effet compiler régulièrement (dès qu'on ajoute un bloc d'instructions ?)
- Si l'écriture du programme n'est pas encore terminée : ajouter des assertions dans les branches non écrites des instructions conditionnelles. Cela permet de compiler (donc de vérifier la syntaxe) et de tester les branches complétées.
Assertion et bon fonctionnement d'une allocation¶
Exemple d'assertion¶
- Pour diverses raisons, l'allocation dynamique peut échouer. Le pointeur est alors égal au pointeur
NULL
. On devrait toujours tester si l'allocation s'est bien déroulée. - Il est plus prudent d'arrêter l'exécution du programme en cas d'échec d'allocation. On peut utiliser une assertion.
(Hors programme)¶
Arithmétique des pointeurs¶
Les opérations valides sur les pointeurs sont :
- l'addition d'un entier à un pointeur. Le résultat est un pointeur de même type que le pointeur de départ;
- la soustraction d'un entier à un pointeur. Le résultat est un pointeur de même type que le pointeur de départ;
- la différence de deux pointeurs pointant tous deux vers des objets de même type. Le résultat est un entier.
On obtient
La différence entre les adresses est \(4\), c'est à dire la taille d'un int
. l'expression p2 - p1
désigne en fait un entier dont la valeur est égale à (p2 - p1)/sizeof(int)
.
On obtient
La différence entre les adresses est
\(e0_{16} −d8_{16} = 14_{10} ×16_{10} + 0 −(13_{10} ×16_{10} + 8_{10}) = 8_{10}\)
C'est la taille d'un double
Pointeur et tableaux¶
Tableaux ♥¶
(Rappel)
- Déclaration de tableaux :
Déclaration et initialisation :
Accès aux éléments comme en Python avec tab[i]
Accès aux éléments d'un tableau¶
(Rappel) Considérons int tab[2]
;
- Un nom de tableau comme tab désigne, après déclaration, par convention un pointeur constant sur le premier bloc de son premier élément.
- On peut utiliser un pointeur initialisé à
tab
pour parcourir les éléments du tableau par arithmétique des pointeurs (hors programme en CPGE). - Pour accéder à
tab[2]
, le système ajoute à l'adresse du (premier bloc) detab[0]
le produit \(2*4\)- le nombre \(2\) pour la position dans le tableau,
- le nombre \(4\) pour la taille d'un
int
(\(4\) octets /int
).
Passage d'un tableau en paramètre d'une fonction ♥¶
(Rappel)
- La fonction ci-dessous affiche les éléments d'un tableau
- Les tableaux sont en fait passés en paramètre comme des copies de pointeur sur leur \(1\)er élément.
Exemple de main
Notion de lvalue¶
- Une lvalue (left value) est une expression désignant un objet modifiable.
- Les expressions qui sont des lvalue :
- identificateur de variable :
- N'ayant pas reçu le qualifieur
const
- Autre qu'un nom de tableau
- dans le cas d'une variable de type structure ou union, celle-ci ne doit pas comporter de champs constant.
- N'ayant pas reçu le qualifieur
- Expression de la forme
*p
ou*(adr)
:p
(resp.adr
) étant une variable (resp.expression) de type pointeur sur un objet non constant. - élément de tableau :
- autre que tableau (cas des tableaux multi-indice)
- autre que structure ou union avec champ constant
- Champ de structure ou d'union : même précisions que pour les tableaux
- identificateur de variable :
Un tableau n'est pas une lvalue¶
- Considérons :
int tab[4];
Un nom de tableau commetab
utilisé plus loin dans le programme désigne en fait un pointeur constant (non modifiable) dont la valeur est l'adresse du (premier bloc du)tab[0]
. Après déclaration,tab
a pour valeur&tab[0]
. tab=tab2
soulève une erreur à la compilation (les noms de tableau ne sont pas des lvalue).
Les fonctions d'allocation en CPGE¶
(D'après Koors)
malloc
de <stdlib.h>
- Cette fonction permet d'allouer un bloc de mémoire dans le tas (heap en anglais).
- Attention : la mémoire allouée dynamiquement n'est pas automatiquement relachée. Il faudra donc, après utilisation, libérer ce bloc de mémoire via un appel à la fonction
free
(D'après Koors)
calloc
de <stdlib.h>
Cette fonction alloue un bloc de mémoire en initialisant tous ces octets à la valeur \(0\). Bien que relativement proche de la fonction malloc
, deux aspects les différencient :
- L'initialisation :
calloc
met tous les octets du bloc à la valeur \(0\) alors que malloc ne modifie pas la zone de mémoire. - Les paramètres d'appels :
calloc
requière deux paramètres (le nombre d'éléments consécutifs à allouer et la taille d'un élément) alors quemalloc
attend la taille totale du bloc à allouer.
La fonction de libération¶
- free de stdlib.h
- La fonction
free
libère un bloc de mémoire alloué dynamiquement dans le tas (le heap, en anglais), via un appel à la fonction malloc (ou tout autre fonction d'allocation mémoire) - ne jamais désallouer avec la fonction
free
un bloc de mémoire obtenu autrement que par une fonction d'allocation commemalloc
,calloc
Squelette d'allocation ♥¶
- Puisqu'un nom de tableau désigne un pointeur constant :
- On ne peut pas créer de tableaux dont la taille est une variable du programme (sauf à utiliser des VLA),
- On ne peut pas créer de tableaux bidimensionnels dont les lignes n'ont pas toutes le même nombre d'éléments.
- Plutôt que de déclarer les tableaux comme des pointeurs constants, on peut manipuler des pointeurs alloués dynamiquement
Avec calloc
, le principe est le même mais en plus les cases du tableau sont initialisées à \(0\) :
Dans tous les cas, les éléments de tab
sont manipulés avec l'opérateur d'indexation []
, exactement comme pour les tableaux.
Commentaire
- Dans le transparent précédent,
tab
n'est pas un tableau statique, c'est un pointeur. - Mais on l'utilise comme un tableau : en particulier
tab[2]
désigne l'élément situé à l'adresse&tab[0]+2*4
- de plus,
tab
n'est pas un pointeur constant : c'est une lvalue. On peut écrire par exempletab++
outab = &n
Tableau VS pointeur ♥¶
Les deux différences principales entre un tableau et un pointeur sont
- un pointeur devrait toujours être initialisé, soit par une allocation dynamique, soit par affectation d'une expression adresse, par exemple
p = &i
; - un tableau n'est pas une
lvalue
; il ne peut donc pas figurer à gauche d'un opérateur d'affectation. En particulier, un tableau ne supporte pas l'arithmétique (on ne peut pas écriretab++;
).
Tableaux à plusieurs dimensions ♥¶
- Un tableau à deux dimensions est, par définition, un tableau de tableaux. Après la déclaration, le nom du tableau est utilisé comme un pointeur constant vers un pointeur constant.
- Déclaration, initialisation et usage d'un tableau bi-dimensionnel :
-
Attention : les éléments de mat (\(6\) ici) sont rangés consécutivement en mémoire. Il n'y a pas de « grille » dans la mémoire.
-
Avec l'exemple précédent, on obtient
- Avec
-
mat
est utilisé comme un pointeur (constant) qui pointe vers un pointeur (constant) de type pointeur d'entiers. Le tableaumat
est une adresse invariante égale à celle du premier élément :&mat[0][0]
.- De même
mat[i]
, pouri
entre \(0\) etN-1
, est un pointeur constant vers un objet de type entier, qui est le premier élément de la « ligne » d'indicei
.mat[i]
a donc une valeur constante qui est égale à&mat[i][0]
. mat, mat[0]
et&mat[0][0]
désignent la même adresse.
Exercice
(Hors programme) Utiliser la propriété de contigu ̈ıté en mémoire pour écrire une fonction void display(int n, int m, int t[n][m]) qui affiche sur une ligne tous les coefficients du tableau bi-dimensionnel t. Une seule boucle est autorisée.
Dans main, écrivons :
Après compilation/exécution :
Allocation dynamique de « matrice » ♥¶
- On déclare un pointeur qui pointe sur un objet de type
type *
(deux dimensions) de la même manière qu'un pointeur avec
Le squelette du programme devient :
Tableaux statiques 2D VS pointeur de pointeur¶
- Considérons le pointeur de pointeur utilisé comme un tableau à deux dimensions dans le transparent précédent :
int ** tab = malloc(2 * sizeof(int *))
. Chaque « ligne » est déclarée partab[i] = (int*)malloc(3 * sizeof(int));
. - Considérons un tableau statique à deux dimensions :
int t[2][3]
. - Dans
tab
, les éléments sont rangés par « lignes » , deux « lignes » n'étant pas nécessairement consécutives en mémoire. - Dans
t
, les éléments sont tous contigus en mémoire. Le dernier octet du dernier élément de la « ligne \(i\) » est voisin du premier octet du premier élément de la « ligne \(i\) + 1 » .
Passage en paramètre¶
Les deux déclarations suivantes ne sont pas équivalentes :
- Pour accéder à
t[i][j]
,moo
calcule*(t + i * m + j)
. Rappel : le nom d'un tableau est un alias vers la localisation en mémoire de ce tableau : c.a.d.&t[0][0]
. - Pour accéder à
tab[i][j]
,foo
calcule*(*(tab + i) + j)
(observer le double déréférencement). - Les appels
foo(2,3,t)
etmoo(2,3,tab)
risquent tous deux de mener à une erreur de segmentation (seg fault).- Dans
foo(2,3,t)
, la fonction traitet+i
comme une adresse à déréférencer ; ce qu'elle n'est pas. - Dans
moo(2,3,tab)
, la fonction considère que les élémentstab[i][m-1]
ettab[i+1][0]
sont contigus; ce qu'ils ne sont pas.
- Dans
Allocation dynamique de matrice (suite)¶
Pour créer un tableau à 3 dimensions, utiliser un pointeur vers un objet de type type **
:
Chaîne de caractères¶
- Une chaîne de caractères est un tableau à une dimension d'objets de type char, se terminant par le caractère nul
'\0'
. - On peut donc manipuler toute chaîne de caractères à l'aide d'un pointeur sur un objet de type
char
. - Les constantes chaînes sont notées entre doubles quotes comme
"hello"
mais le caractère de fin de chaîne n'apparaît pas explicitement. - Les constantes chaînes sont toujours placées en mémoire statique. Elles sont immuables.
- Le caractère immuable permet au compilateur d'allouer une seule occurrence de chaque chaîne distincte. En clair, dans
les deux occurrences de hello sont stockées au même endroit en mémoire.
Les chaînes de caractères comme tableaux de char
♥¶
Déclaration comme pointeur (constant):
- Le mot
hello
est déclaré et initialisé avec
- Le caractère
'\0'
représente la fin du mot. Sa présence est \(\underline{\text{impérative}}\). Ainsi « hello » nécessite \(6\) caractères. Les guillemets sont simples. "h"
représente la chaîne de caractères{'h',\0}
et'h'
ben... le caractère «h
» !- Initialisation plus rapide :
Accès aux éléments ♥¶
Comme en \(\texttt{Python}\), l'accès aux constituants de la chaîne se fait avec l'opérateur []
.
- Lecture :
Affiche « hello » .
- Modification :
La chaîne est devenue ifmmp
Affichage et saisie au clavier ♥¶
Le spécifieur de format %s
vaut pour printf
et scanf
:
On obtient :
Longueur¶
On dispose d'un moyen pour calculer la longueur d'une chaîne de caractères : on itère jusqu'à tomber sur le caractère nul '\0'
.
- Avec cette méthode, la complexité est \(\underline{\text{linéaire}}\) : il y a \(n\) étapes pour calculer la longueur (si \(n = |\textbf{chaine}|\)).
- Un autre moyen de calculer la longueur d'une chaîne de caractères est d'utiliser la fonction
strlen
de<string.h>
. - Son protototype est le suivant
- Elle prend donc en paramètre un pointeur sur un
char
constant (on ne modifie pas la chaîne). - On l'utilise ainsi
- Mais la complexité reste linéaire !
Comparer¶
int strcmp(const char *first, const char *second);
destring.h
- Compare les deux chaînes
first
,second
- Si la valeur de retour est nulle, les chaînes sont égales
- Si la valeur renvoyée est négative, le premier caractère qui ne correspond pas a une valeur inférieure dans
first
que danssecond
- Si la valeur renvoyée est positive, le premier caractère qui ne correspond pas a une valeur supérieure dans
first
que danssecond
. - La variante
strncmp(first,second,n)
ne compare que les \(n\) premiers caractères des chaînes. first==second
ne doit être utilisé que pour des chaînes de caractères littérales (dans le doute, on évite !)
Convertir en entier¶
int atoi(const char *theString);
de strlib- Cette fonction permet de transformer une chaîne de caractères, représentant une valeur entière comme
"12356"
, en une valeur numérique de type int . Le terme d'« atoi » est un acronyme signifiant : ASCII to integer. - Retourne la valeur \(0\) si la chaîne de caractères ne contient pas une représentation de valeur numérique. Il n'est donc pas possible de distinguer la chaîne
"0"
d'une chaîne ne contenant pas un nombre entier. L'utilisation de la fonctionstrtol
permet bien de distinguer les deux cas.
Modifier une constante chaîne¶
- La norme n'indique pas si les constantes chaînes comme
"hello"
sont modifiables ou non. Juste qu'elles sont placées en mémoire statique. - Pour imposer un caractère immuable indépendamment des implémentations, préférer :
Concaténation ♥¶
- Pour concaténer deux chaînes de caractères, allouer à une troisième chaîne autant de place que la somme des longueurs.
- Parcourir l'une après l'autre les deux chaînes et entrer leurs caractères dans le résultat.
Tableau de constantes chaînes¶
- Une constante chaîne est convertie par le compilateur en un pointeur sur son premier caractère. On peut donc facilement constituer un tableau de constantes chaînes.
- On peut bien sûr toujours initialiser individuellement :
- Mais on peut aussi profiter de la syntaxe d'initialisation des tableaux en écrivant directement :
C lineums="1"
char *jour [7] = {"lundi","mardi" ,...}