J'ai récemment commencé à apprendre le C++, et comme la plupart des gens (selon ce que j'ai lu), j'ai du mal avec les pointeurs.
Pas dans le sens traditionnel, je comprends ce qu'ils sont, pourquoi ils sont utilisés et comment peuvent-ils être utiles, mais je ne peux pas comprendre comment l'incrémentation des pointeurs serait utile, quelqu'un peut-il expliquer comment l'incrémentation d'un pointeur est un concept utile et idiomatique C++?
Cette question est venue après que j'ai commencé à lire le livre A Tour of C++ par Bjarne Stroustrup, on m'a recommandé ce livre, parce que je connais bien Java, et les gars de Reddit m'ont dit qu'il le ferait être un bon livre de "basculement".
Lorsque vous avez un tableau, vous pouvez configurer un pointeur pour pointer vers un élément du tableau:
int a[10];
int *p = &a[0];
Ici, p
pointe vers le premier élément de a
, qui est a[0]
. Vous pouvez maintenant incrémenter le pointeur pour pointer vers l'élément suivant:
p++;
Maintenant, p
pointe vers le deuxième élément, a[1]
. Vous pouvez accéder à l'élément ici en utilisant *p
. Ceci est différent de Java où vous devez utiliser une variable d'index entier pour accéder aux éléments d'un tableau.
L'incrémentation d'un pointeur en C++ où ce pointeur fait pas pointer vers un élément d'un tableau est comportement non défini.
L'incrémentation des pointeurs est idiomatique C++, car la sémantique des pointeurs reflète un aspect fondamental de la philosophie de conception derrière la bibliothèque standard C++ (basée sur Alexander Stepanov [~ # ~] stl [~ # ~] )
Le concept important ici est que la STL est conçue autour de conteneurs, d'algorithmes et d'itérateurs. Les pointeurs sont simplement itérateurs.
Bien sûr, la possibilité d'incrémenter (ou d'ajouter/de soustraire) des pointeurs remonte à C. De nombreux algorithmes de manipulation de chaîne C peuvent être écrits simplement en utilisant l'arithmétique des pointeurs. Considérez le code suivant:
char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));
Ce code utilise l'arithmétique du pointeur pour copier une chaîne C terminée par un caractère nul. La boucle se termine automatiquement lorsqu'elle rencontre le null.
Avec C++, la sémantique des pointeurs est généralisée au concept de itérateurs. La plupart des conteneurs C++ standard fournissent des itérateurs, accessibles via les fonctions membres begin
et end
. Les itérateurs se comportent comme des pointeurs, en ce sens qu'ils peuvent être incrémentés, déréférencés et parfois décrémentés ou avancés.
Pour itérer sur un std::string
, nous dirions:
std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;
Nous incrémentons l'itérateur comme nous incrémenterions un pointeur sur une chaîne en C simple. La raison pour laquelle ce concept est puissant est que vous pouvez utiliser des modèles pour écrire des fonctions qui fonctionneront pour n'importe quel type d'itérateur qui répond aux exigences de concept nécessaires. Et c'est la puissance de la STL:
std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));
Ce code copie une chaîne dans un vecteur. La fonction copy
est un modèle qui fonctionnera avec l'itérateur any qui prend en charge l'incrémentation (qui inclut des pointeurs simples). Nous pourrions utiliser la même fonction copy
sur une chaîne C simple:
const char* s1 = "abcdef";
std::vector<char> buf;
std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));
Nous pourrions utiliser copy
sur un std::map
ou un std::set
ou any conteneur personnalisé qui prend en charge les itérateurs.
Notez que les pointeurs sont un type spécifique d'itérateur: itérateur d'accès aléatoire, ce qui signifie qu'ils prennent en charge l'incrémentation, la décrémentation et l'avancement avec +
et -
opérateur. D'autres types d'itérateurs ne prennent en charge qu'un sous-ensemble de sémantique de pointeur: a itérateur bidirectionnel prend en charge au moins l'incrémentation et la décrémentation; a itérateur avant supporte au moins l'incrémentation. (Tous les types d'itérateurs prennent en charge le déréférencement.) La fonction copy
nécessite un itérateur qui prend au moins en charge l'incrémentation.
Vous pouvez lire sur les différents concepts d'itérateur ici .
Ainsi, l'incrémentation de pointeurs est une façon idiomatique C++ d'itérer sur un tableau C, ou d'accéder à des éléments/décalages dans un tableau C.
L'arithmétique du pointeur est en C++ parce qu'elle était en C. L'arithmétique du pointeur est en C parce que c'est un idiome normal dans assembleur.
Il existe de nombreux systèmes où "increment register" est plus rapide que "load constant value 1 and add to register". De plus, un certain nombre de systèmes vous permettent de "charger DWORD dans A à partir de l'adresse spécifiée dans le registre B, puis d'ajouter sizeof (DWORD) à B" dans une seule instruction. Ces jours-ci, vous pourriez vous attendre à ce qu'un compilateur d'optimisation trie cela pour vous, mais ce n'était pas vraiment une option en 1973.
C'est essentiellement la même raison pour laquelle les tableaux C ne sont pas vérifiés et que les chaînes C n'ont pas de taille intégrée: le langage a été développé sur un système où chaque octet et chaque instruction comptent.