Chaînes de caractères en C
Se termine par \0
"chouette"
Chaîne de caractères = pointeur sur char
On peut initialiser
char* s = "chouette";
En mémoire, s
est un pointeur, une adresse mémoire, vers une zone de la mémoire avec 9 octets alloués où on a :
QCM
-
Que pensez-vous de
char[3] s = "abc";
?Il manque une case pour '\0'. -
Que pensez-vous de
char[10] s = "abc";
?Pas de soucis, le compilateur complète avec des \0
. -
Que pensez-vous de
char s[] = "abc";
?Pas de soucis, le compilateur comprend qu'il faut allouer 4 octets, et d'ailleurs il n'alloue que 4 octets. -
Comment accéder à la i-ème lettre ?
a[i]. -
Que pensez-vous du programme suivant ?
char s[] = "abca";
s[2] = 'b';
- Que pensez-vous du programme suivant ?
char* s = "abca";
s[2] = 'b';
- Est-ce que ce programme est correct bien que le tableau ne finissent pas par
\0
?
char A[3];
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
<string.h>
qui gèrent les chaînes de caractères ! En effet, A[3]
(qui sort de la zone allouée) n'est peut-être pas \0
et donc les fonctions vont continuer à lire des parties de la mémoire indéterminée jusqu'à trouver un \0
.
- Est-ce que ce programme est correct ?
char* A = malloc(2);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[2]
n'est pas alloué.
- Est-ce que ce programme est correct ?
char* A = malloc(3);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
char* A = malloc(4);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;
\0
.
char* A = malloc(10);
A[0] = 'a';
A[1] = 'a';
A[2] = 'a';
A[3] = `\0`;
\0
. Il y a des cases non utilisées mais ce n'est pas grave.
Le C c'est vraiment du bas niveau !
Considérons deux chaînes de caractères
char s[10], t[10];
-
Peut-on écrire
s = "abc";
?Non, car les tableaux ne sont pas assignables. -
Peut-on écrire
t = s;
?Pareil, non. -
Peut-on initialiser
char u[10] = "abc";
?Oui, à l'initialisation c'est bon. C'est équivalent à char u[10] = {'a', 'b', 'c', '\0'};
-
Peut-on tester
s == t
?Oui, on peut toujours tester l'égalité d'adresse mémoire de tableaux ou de pointeurs :). -
Que teste
s == t
?Que l'adresse mémoire s
est égale à l'adresse mémoiret
. -
Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char s[50] = "Bonjour";
return s;
}
- Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char* s = "Bonjour";
return s;
}
- Que pensez-vous du programme suivant ?
char* donnerSalutation() {
char* s = malloc(10*sizeof *s);
s[0] = 'h';
s[1] = 'i';
s[2] = '!';
s[3] = '\0';
return s;
}
Longueur d'une chaîne de caractères
size_t strlen(const char *s);
- Ecrire soi-même une fonction
int len(char* s)
qui renvoie la longueur de la chaînes
.
Parcours d'une chaîne de caractères
- Ecrire une fonction qui teste d'appartenance d'un caractère à une chaîne.
- Ecrire une fonction qui transforme une chaîne de caractères en sa version en minuscule.
Considérons :
char* s = "Bonjour tout le monde";
-
Que vaut
strlen(s)
?21 car il y a 21 caractères dans "Bonjour tout le monde"
. -
Que vaut
sizeof(s)
?8 car il faut 8 octets pour stocker une adresse mémoire.
Considérons :
char[] s = "Bonjour tout le monde";
-
Que vaut
strlen(s)
?21 car il y a 21 caractères dans "Bonjour tout le monde"
. -
Que vaut
sizeof(s)
?22 car il faut 22 octets pour mettre la chaîne de caractères "Bonjour tout le monde"
de 21 caractères, puis\0
.
Comparaison de chaînes
int strcmp(const char *s1, const char *s2)
strcmp("abricot", "chat") | strcmp("chat", "chat") | strcmp("chat", "abricot") |
---|---|---|
< 0 | 0 | > 0 |
Copie de chaînes
strdup
char * strdup( const char * source);
Cette fonction renvoie une copie de la chaîne de caractères source
:
- Elle alloue une nouvelle zone mémoire avec un
malloc
(caché dans l'appelstrdup
) de la même taille quesource
- En cas de succès, elle copie
source
vers cette nouvelle zone mémoire et renvoie un pointeur vers cette zone - En cas d'échec (hé oui, le
malloc
peut échouer), elle renvoie un pointeur nul.
⚠ Attention à libérer la mémoire avec free
de la copie créée.
strcpy
strcpy
copie src
dans dst
que l'on a déjà alloué préalablement. Attention, si ce n'est pas alloué suffisamment : .
char * strcpy(char * restrict dest, const char * restrict source);
strncpy
fait la même chose mais dans la limite de len
caractères.
size_t strncpy(char * restrict dst, const char restrict * src, size_t len);
size_t strlcpy(char * restrict dst, const char * restrict src, size_t dsze);
memcpy
etc.
void * memcpy ( void * destination, const void * source, size_t nbOctetsACopier );
La même chose mais pour de la mémoire et pas de vérification de caractère nul ou autre. C'est fait pour copier n'importe quoi.
Concaténer deux chaînes
strcat
de <string.h>
char * strcat(char* restrict dst, const char * restrict src);
Précondition :
- il faut que
dst
soit suffisamment alloué pour contenir le contenu de la concaténation. Sinon .
Effet :
- place
src
à la fin dedst
char[1000] s = "";
strcat(s, "Bienvenue ");
strcat(s, "à ");
strcat(s, "l'ENS de Lyon");
strcat
renvoie :
dst
! C'est pour pouvoir faire des cascades d'appel
On peut donc chaîner les appels à strcat
comme cela :
char[1000] s = "";
strcat(strcat(strcat(s, "Bienvenue "), "à "), "l'ENS de Lyon");
Variante efficace
Malheureusement, la complexité de strcat
est en (O(|s1| + |s2|)).
cf https://www.joelonsoftware.com/2001/12/11/back-to-basics/
On souffre du problème de Shlemiel le peintre.
- Ecrire une version
mystrcat
qui fait la même chose questrcat
mais renvoie un pointeur sur la fin de la chaîne.
char* mystrcat( char* dst, char* src )
{
while (*dst) dst++;
while (*dst++ = *src++);
return --dst;
}
Variante à la Python
- En utilisant
memcpy
, écrire une variantestring_concat
qui renvoie une nouvelle chaîne de caractères qui est la concaténation des1
ets2
.
La signature de memcpy
est
void * memcpy( void * restrict dst, const void * restrict src, size_t nbBytesToCopy );
/*
return a new string that is the concatenation of s1 and s2
(in Python, s1 + s2)
**/
char * string_concat(char* s1, char* s2) {
int l1 = strlen(s1);
int l2 = strlen(s2);
char* result = malloc(l1 + l2 + 1);
if(result == NULL)
return NULL;
memcpy(result, s1, l1);
memcpy(result + l1, s2, l2+1);
return result;
}
Variante avec réallocation
- Ecrire une fonction qui prend une chaîne
s1
et qui lui concatènes2
. Si pas assez de mémoire, on réalloues1
. La fonction renvoie la nouvelle chaîne. Dans tous les cas, la chaînes1
initiale est perdue.
char* strcatrealloc(char* s1, char* s2) {
int l1 = strlen(s1);
int l2 = strlen(s2);
char* result = realloc(s1, l1 + l2 + 1 * sizeof(*result));
if(result == NULL)
return NULL;
memcpy(result + l1, s2, l2 + 1);
return result;
}
Tableaux de chaînes de caractères
- Dessiner un schéma mémoire de
#define NB_MAXCHAR 12
char planets[][NB_MAXCHAR] = {"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto"};
- Même chose pour
char *planets[] = {"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto"};
- Même chose pour
#define NB_PLANETS 9
char ** planets = malloc(NB_PLANETS*sizeof(*planets));
planets[0] = "Mercury";
planets[1] = "Venus";
planets[2] = "Earth";
planets[3] = "Mars";
planets[4] = "Jupiter";
planets[5] = "Saturn";
planets[6] = "Uranus";
planets[7] = "Neptune";
planets[8] = "Pluto";
Arguments en ligne de commande
int main(int argc, char *argv[]) {
...
}
Par exemple si notre programme est programme
et l'utilisateur lance la commande
programme -l arf.txt
alors
argc
vaut3 argv[0]
contient"programme" argv[1]
contient"-l" argv[2]
contient"arf.txt" argv[3]
est le pointeur .NULL
Exercices
- Modifier les projets précédents pour qu'ils prennent en entrée les arguments en ligne de commande
- Ecrire un programme qui prend en entrée un texte et qui renvoie sa version justifiée.
Aller plus loin
- article philosophique sur les chaînes qui finissent par \0 : https://queue.acm.org/detail.cfm?id=2010365