Vidéo: Learn Java Programming with Beginners Tutorial 2024
Par Stephen R. Davis
C ++ n'est pas un langage de programmation facile à maîtriser. Ce n'est que par l'expérience que la myriade de combinaisons de symboles vous semblera naturelle. Cette feuille de triche, cependant, vous donne quelques conseils solides pour faciliter cette transition de C ++ débutant au gourou C ++: Savoir comment lire des expressions C ++ complexes; apprendre à éviter les problèmes de pointeur; et comprendre comment et quand faire des copies en profondeur.
Comment lire une expression C ++ complexe
C ++ est plein de petits symboles, chacun d'entre eux ajoutant à la signification des expressions. Les règles de la grammaire C ++ sont si flexibles que ces symboles peuvent être combinés en combinaisons complexes presque impénétrables. Les expressions dans le langage C plus simple peuvent devenir si obtuses qu'il y avait un concours annuel pour qui pourrait écrire le programme le plus obscur et qui pourrait le comprendre.
Ce n'est jamais une bonne idée d'essayer d'écrire du code complexe, mais vous allez parfois parcourir des expressions en C ++ qui sont un peu déroutantes à première vue. Utilisez simplement les étapes suivantes pour les comprendre:
-
Commencez par les parenthèses les plus intégrées.
Commencez à rechercher les parenthèses les plus externes. Dans ceux-ci, recherchez les parenthèses incorporées. Répétez le processus jusqu'à ce que vous ayez travaillé jusqu'à la paire de parenthèses la plus profonde. Commencez par évaluer cette sous-expression en utilisant les règles suivantes. Une fois que vous avez compris cette expression, revenez au niveau suivant et répétez le processus.
-
Dans la paire de parenthèses, évaluez chaque opération dans l'ordre de priorité.
L'ordre d'évaluation des opérateurs est déterminé par la priorité de l'opérateur indiquée dans le tableau. L'indirection vient avant la multiplication qui vient avant l'addition donc le suivant ajoute 1 plus 2 fois la valeur pointée par * ptr.
int i = 1 + 2 * * ptr;
Préséance | Opérateur | Signification |
---|---|---|
1 | () (unaire) | Appeler une fonction |
2 | * et -> (unaire) | Déréférencer un pointeur |
2 | - (unaire) | Renvoie le négatif de son argument |
3 | ++ (unaire) | Incrément |
3 > - (unaire) | Décrément | 4 |
* (binaire) | Multiplication | 4 |
/ (binaire) | Division | 4 |
% (binaire) | Modulo | 5 |
+ (binaire) | Ajout | 5 |
- (binaire) | Soustraction | 6 |
&& (binaire) | Logique ET | 6 |
! ! | OR logique | 7 |
=, * =,% =, + =, - = (spécial) | Types d'affectation | Évaluer les opérations de même priorité de gauche à droite (sauf affectation, ce qui va dans l'autre sens). |
-
La plupart des opérateurs de même priorité évaluent de gauche à droite. Ainsi le suivant ajoute 1 à 2 et ajoute le résultat à 3:
int i = 1 + 2 + 3;
L'ordre d'évaluation de certains opérateurs n'a pas d'importance. Par exemple, l'addition fonctionne de la même manière de gauche à droite que de droite à gauche. L'ordre d'évaluation fait une grande différence pour certaines opérations comme la division. Ce qui suit divise 8 par 4 et divise le résultat par 2:
int i = 8/4/2;
La principale exception à cette règle est l'affectation, qui est évaluée de droite à gauche:
a = b = c;
Affecte c à b et le résultat à a.
Évaluer les sous-expressions sans ordre particulier.
-
Considérons l'expression suivante:
int i = f () + g () * h ();
La multiplication a une priorité plus élevée, donc vous pouvez supposer que les fonctions g () et h () sont appelées avant f (), cependant, ce n'est pas le cas. L'appel de fonction a la préséance la plus élevée de toutes, donc les trois fonctions sont appelées avant que la multiplication ou l'addition soit effectuée. (Les résultats renvoyés par g () et h () sont multipliés puis ajoutés aux résultats renvoyés par f ().
La seule fois que l'ordre auquel les fonctions sont appelées fait une différence lorsque la fonction a des effets secondaires comme ouvrir un fichier ou changer la valeur d'une variable globale. Vous ne devriez certainement pas écrire vos programmes de sorte qu'ils dépendent de ce type d'effets secondaires.
N'effectuez des conversions de type que lorsque cela est nécessaire.
-
Vous ne devriez pas faire plus de conversions de types que ce qui est absolument nécessaire. Par exemple, l'expression suivante a au moins trois et peut-être quatre conversions de type:
float f = 'a' + 1;
Le caractère 'a' doit être promu dans un int pour effectuer l'addition. L'int est ensuite converti en un double puis réduit à un flotteur de précision simple. Rappelez-vous que toute l'arithmétique est effectuée en int ou en double. Vous devriez généralement éviter d'effectuer de l'arithmétique sur les types de caractères et éviter complètement le flottement de précision simple.
5 façons d'éviter les problèmes de pointeur en C ++
En C ++, un pointeur
est une variable qui contient l'adresse d'un objet dans la mémoire interne de l'ordinateur. Utilisez ces étapes pour éviter les problèmes avec les pointeurs en C ++: Initialisez les pointeurs lorsqu'ils sont déclarés.
-
Ne laissez jamais les variables pointeur non initialisées - les choses ne seraient pas trop mauvaises si les pointeurs non initialisés contenaient toujours des valeurs aléatoires - la grande majorité des valeurs aléatoires sont des valeurs de pointeurs illégales et provoqueront le crash du programme dès qu'elles seront utilisées. Le problème est que les variables non initialisées tendent à prendre la valeur d'autres variables de pointeur précédemment utilisées. Ces problèmes sont très difficiles à déboguer.
Si vous ne savez pas à quoi initialiser un pointeur, initialisez-le à nullptr. nullptr est garanti pour être une adresse illégale.
Remettez les pointeurs à zéro après les avoir utilisés.
-
De même, mettez toujours à zéro une variable pointeur une fois que le pointeur n'est plus valide en lui affectant la valeur nullptr. C'est particulièrement le cas lorsque vous renvoyez un bloc de mémoire au tas en utilisant delete; toujours zéro le pointeur après avoir renvoyé la mémoire de tas.
Allouez la mémoire du tas et retournez-la dans le tas au même "niveau" pour éviter les fuites de mémoire.
-
Essayez toujours de renvoyer un bloc de mémoire au tas au même niveau d'abstraction que vous l'avez affecté. Cela signifie généralement essayer de supprimer la mémoire au même niveau d'appels de fonction.
Attrape une exception pour effacer la mémoire si nécessaire.
-
N'oubliez pas qu'une exception peut survenir presque n'importe quand. Si vous avez l'intention d'intercepter l'exception et de continuer à fonctionner (plutôt que de laisser le programme tomber en panne), assurez-vous d'attraper l'exception et de renvoyer les blocs de mémoire au tas avant que les pointeurs ne soient hors de portée. perdu.
Assurez-vous que les types correspondent exactement.
-
Assurez-vous toujours que les types de pointeurs correspondent au type requis. Ne pas refondre un pointeur sans raison particulière. Considérez ce qui suit:
void fn (int * p); void myFunc () {char c = 'a'; char * pC = & c; fn ((int *) pC);}
La fonction ci-dessus se compile sans se plaindre puisque le pointeur de caractère pC a été retransmis en un int * pour correspondre à la déclaration de fn (int *); Cependant, ce programme ne fonctionnera presque certainement pas. La fonction fn () attend un pointeur sur un entier de 32 bits et non sur un caractère rinky-dink de 8 bits. Ces types de problèmes sont très difficiles à régler.
Comment et quand effectuer des copies profondes en C ++
Les classes qui allouent des ressources dans leur constructeur devraient normalement inclure un constructeur de copie pour créer des copies de ces ressources. L'attribution d'un nouveau bloc de mémoire et la copie du contenu de l'original dans ce nouveau bloc est appelée création d'une
copie en profondeur (par opposition à la copie superficielle par défaut). Utilisez les étapes suivantes pour déterminer comment et quand effectuer des copies profondes en C ++: Toujours effectuer une copie complète si le constructeur alloue des ressources.
-
Par défaut, C ++ réalise des copies membres-par-membres dites «superficielles» des objets lors de leur transmission à des fonctions ou à la suite d'une affectation. Vous devez remplacer les opérateurs de copie superficielle par défaut par leur équivalent en copie profonde pour toute classe qui alloue des ressources dans le constructeur. La ressource la plus commune qui est allouée est la mémoire de segment retournée par le nouvel opérateur.
Incluez toujours un destructeur pour une classe qui alloue des ressources.
-
Si vous créez un constructeur qui alloue des ressources, vous devez créer un destructeur qui les restaure. Aucune exception.
Toujours déclarer le destructeur virtuel.
-
Une erreur courante de débutant consiste à oublier de déclarer votre destructeur virtuel. Le programme fonctionnera bien jusqu'à ce qu'un programmeur sans méfiance arrive et hérite de votre classe. Le programme semble toujours fonctionner, mais comme le destructeur de la classe de base ne peut pas être appelé correctement, la mémoire fuit de votre programme comme un tamis jusqu'à ce qu'il se bloque. Ce problème est difficile à trouver.
Incluez toujours un constructeur de copie pour une classe qui alloue des ressources.
-
Le constructeur de copie crée une copie correcte de l'objet actuel en allouant de la mémoire hors du tas et en copiant le contenu de l'objet source.
Toujours remplacer l'opérateur d'affectation pour une classe qui alloue des ressources.
-
Les programmeurs devraient être découragés de surcharger les opérateurs, mais l'opérateur d'affectation est une exception. Vous devez remplacer l'opérateur d'affectation pour toute classe qui alloue des ressources dans le constructeur.
L'opérateur d'affectation doit faire trois choses:
Assurez-vous que les objets main gauche et droite ne sont pas le même objet. En d'autres termes, assurez-vous que le programmeur de l'application n'a pas écrit quelque chose comme (a = a). Si c'est le cas, ne faites rien.
-
Appelez le même code que le destructeur de l'objet de gauche pour renvoyer ses ressources.
-
Appelez le même code qu'un constructeur de copie pour faire une copie profonde de l'objet de la main droite dans l'objet de gauche.
-
Si vous ne pouvez pas le faire, supprimez le constructeur de copie et l'opérateur d'affectation afin que le programme ne puisse pas faire de copies de votre objet.
-
-
Si vous ne pouvez même pas le faire parce que votre compilateur ne prend pas en charge la fonction de constructeur de suppression C ++ 2011, créez un constructeur de copie et un opérateur d'affectation vides et déclarez-les protégés pour empêcher les autres classes de les utiliser.