Date de première publication : 2013/06/10
Ce premier TP est là pour vous montrer les spécificités de C++ par rapport au C. Nous n'abordons pas encore la notion d'objet.
Nous utilisons un compilateur "récent" qui supporte nativement la norme 2014 (2011 améliorée).
Du C au C++
"Hello world"
Voici votre premier programme C++ :
#include <iostream>
int main(int argc, char ** argv) {
int i;
for(i=0; i< 120; ++i)
std::cout << "Bonjour les ZZ" << 2 << std::endl;
return 0;
}
Si l'on compare avec un programme C :
- les
#include
ne mentionnent plus l'extension.h
(ou autre.hpp
) - l'instruction
printf()
est avantageusement remplacée par un truc qui s'appellestd::cout
dont la syntaxe est un peu bizarre mais qui est relativement pratique : les éléments sont donnés les uns à la suite des autres séparés par l'opérateur<<
(il n'y a plus la chaîne de format) std::endl
est la séquence particulière qui signifie "retour à la ligne" (bon, ok, c'est plus verbeux que'\n'
qui reste utilisable)- Remplacer le
2
par la variablei
, et ensuitei/10.0
Le reste est similaire au C. Effectuez les opérations suivantes :
- Compilez ce petit programme avec g++ (et non gcc, ce qui est une erreur classique)
- Ajoutez maintenant les options
-Wall -Wextra
- Enlever les warnings en utilisant les paramètres muets, c'est-à-dire en omettant les noms des paramètres qui ne sont pas utilisés
Tous les warnings doivent être pris en compte, comme en C.
En C++, il est relativement courant de déclarer les variables de boucle dans la structure elle-même (attention, cela peut aller à l'encontre d'un guide de style) :
for(int i=0; i< 120; ++i)
std::cout << "Bonjour les ZZ" << 2 << std::endl;
Cette manière de faire peut aller à l'encontre d'un guide de style et certains compilateurs (notamment MS C++) peuvent avoir une gestion par défaut de la durée de vie de telles variables)
Entrée standard...
- Compilez et exécutez le programme suivant
- Afficher les réponses
#include <iostream>
#include <string>
int main(int, char **) { // parametres muets
std::string prenom; // type chaines de caracteres"
int age;
std::cout << "Quel est votre prénom ?" << std::endl;
std::cin >> prenom;
std::cout << "Quel est votre age ?" << std::endl;
std::cin >> age ;
std::cout << "Bonjour "<< prenom << std::endl;
return 0;
}
Demander également le nom de la personne et affichez successivement le nom et le prenom, puis la concatenation du nom, du prénom séparé par un :
... et chaînes de caractères
std::string
est un type qui représente la chaîne de caractères en C++. Vous ne devriez plus JAMAIS avoir besoin
(sauf TP à vocation casse-pied) de faire appel au char *
. La fonction main()
vient du C, c'est pour cela que son prototype n'utilise pas std::string
mais char *
Manipuler les "objets" standards avec le préfixe std::
peut être un peu pénible MAIS c'est obligatoire si vous avez TP avec Alexis et Jeremy.
On peut ajouter une instruction qui permet de s'en débarasser :
using namespace std;
(C'est le U word) ou using std::cout;
(c'est mieux).
Petits exercices sur les chaînes
- Demander deux chaînes de caractères à l'utilisateur et afficher la plus petite des deux (ordre lexicographique)
- Afficher la longeur de la chaîne saisie par l'utilisateur
Tableaux et constantes
Vous pouvez compiler ce programme en C et en C++, en mettant de côté les appels à std::cout
.
Si vous souhaitez conserver l'affichage à l'écran, il est possible d'utiliser la fonction printf
car tout programme C++ peut
faire appel aux bibliothèques C, seuls les noms des fichiers entête changent : par exemple, stdlib.h
devient cstdlib
.
#include <iostream>
using std::cout;
using std::endl;
/* on peut utiliser le mot clé const pour définir la taille d'un tableau statique en C++ */
/* Jamais de #define pour cela */
const int TAILLE = 10;
int main(int, char **) {
int tab[TAILLE];
for (int i=0; i < TAILLE; ++i) {
tab[i] = i %2;
cout << tab[i] << " ";
}
cout << endl;
return TAILLE;
}
Surcharge de fonction
Placer ce code dans un fichier d'extension .c
et compiler avec gcc
. Observer les erreurs.
#include <stdio.h>
void afficher(int a) {
printf("%d", a);
}
void afficher(double a) {
printf("%lf", a);
}
int main(int, char **) {
afficher(1);
afficher(2.0);
return 0;
}
Si le fichier est d'extension .cpp
et compilé avec gcc
ou bien si le fichier d'extension .c
est compilé avec g++
, vous n'aurez pas ces messages d'erreurs !
Chaines de caractères
Une fiche sur les chaînes de caractères est disponible.
Exécutez le code suivant et regardez ce que cela donne avec les différentes versions de s
:
#include <iostream>
int main() {
char s[10];
// std::string s;
// char * s;
std::cin >> s;
std::cout << "#" << s << "#" << std::endl;
for (int i = 0; i< 10; ++i)
std::cout << "@" << s[i] << "@" << (int)s[i] << "@" << std::endl;
return 0;
}
Exécutez avec valgrind pour relever les éventuelles erreurs de contexte (vous pouvez également résoudre ces erreurs de contexte).
Pour exécuter avec valgrind, il faut que le code à profiler ait été compilé avec l'option -g
Références
En C, il y a deux manières d'accéder à une variable : par sa valeur ou par son adresse en la déréférençant. Le C++ ajoute une nouvelle manière d'accéder à une variable en la référençant, c'est-à-dire en en faisant un alias.
int main(int, char**){
int a = 5;
int &r = a;
std::cout << a << " " << r << std::endl;
std::cin >> a;
std::cout << a << " " << r << std::endl;
std::cin >> r;
std::cout << a << " " << r << std::endl;
}
Premières manipulations
- Compiler et exécuter le programme précédent.
- Afficher les adresses de
a
et der
- Que se passe-t-il si
r
n'est pas initialisée ? - Compiler et exécuter le programme suivant. A-t-on le comportement attendu ?
void fonction1(int a) {
std::cout << &a << std::endl;
}
void fonction2(int& a) {
std::cout << &a << std::endl;
}
int main(int, char **) {
int age = 45;
std::cout << &age << std::endl;
fonction1(age);
fonction2(age);
return 0;
}
- BONUS : Que se passe-t-il si on essaie de donner le même nom aux fonctions
fonction1()
etfonction2()
?
La référence n'est pas un type différenciant pour le compilateur pour déterminer la surcharge
Échange de variables
Essayer le code suivant :
int a = 3;
int b = a;
int& c = a;
std::cout << a << " " << b << " " << c << std::endl;
b = 4;
std::cout << a << " " << b << " " << c << std::endl;
c = 5;
std::cout << a << " " << b << " " << c << std::endl;
a = 6;
std::cout << a << " " << b << " " << c << std::endl;
Écrire une fonction qui permet l'échange de deux variables de type int
- avec des pointeurs
- avec des références
Comparer la facilité d'écriture et de compréhension avec les références.
Une référence référence ...
int main(int, char**){
int a;
int &r = a;
std::cout << a << " " << r << std::endl;
}
Que se passe-t-il avec ce bout de code ? Comment détecter l'erreur à tous les coups ?
mais pas n'importe comment
int main(int, char**){
int a = 1;
int b = 2;
int &r = a;
std::cout << a << " " << b << " " << r << std::endl;
r = b;
std::cout << a << " " << b << " " << r << std::endl;
b = 4;
std::cout << a << " " << b << " " << r << std::endl;
r = 5;
std::cout << a << " " << b << " " << r << std::endl;
}
Une référence "constante"
Que se passe-t-il avec le code suivant ?
int main(int, char**){
int a = 1;
const int &r = a;
std::cout << a << " " << r << std::endl;
a = 2;
std::cout << a << " " << r << std::endl;
r=3;
std::cout << a << " " << r << std::endl;
}
La référence est dite "constante", ce qui est un abus de langage. En fait, la variable référencée est considérée comme constante : accessible en lecture mais pas en écriture.
Une référence sur une constante
Que se passe-t-il avec le code suivant ?
int main(int, char**){
const int a = 1;
int &r = a;
std::cout << a << " " << r << std::endl;
Il faut une référence "constante" pour référencer la constante
Corriger l'erreur !
Pointeurs et allocation mémoire
En C++, on n'utilise plus les fonctions malloc()
et free()
mais les opérateurs new
et delete
. L'opérateur delete
existe en deux versions suivant que le pointeur est associé à une zone mémoire simple ou une zone mémoire contiguë.
Pour initialiser un pointeur, le C++ introduit une valeur spéciale nullptr
:
int a = 4;
int * p = nullptr;
p = &a;
std::cout << *p << " " << p;
Nous verrons plus tard comment gérer une allocation mémoire impossible
Exécutez valgrind
sur les deux exemples suivants et corrigez-les en décommentant la libération mémoire ! Voici le premier :
int main(int, char**) {
int * p = new int;
*p = 3;
cout << *p << endl;
// delete p;
return 0;
}
Voici le second :
int main(int, char**) {
const int TAILLE = 500;
int * p = new int[TAILLE];
for(auto i = 0; i< TAILLE; ++i ) p[i] = i;
for(auto i = 0; i< TAILLE; ++i ) cout << p[i] << endl;
// delete p;
// delete [] p;
return 0;
}
Que se passe-t-il dans ce dernier exemple si vous oubliez les crochets ?
Notez la présence du mot-clé auto
dont le sens a été modifié depuis le C qui permet de demander au compilateur de déduire le bon type de variable ! Ne surutilisez pas cet opérateur car cela peut nuire à la lisibilité du code.
C++, un langage objet ?
Finalement, on va faire un tout petit peu d'objet... Compiler et exécuter le programme suivant :
#include <iostream>
class Exemple {
public:
void afficher() {
std::cout << "hello" << std::endl;
}
};
int main(int, char **) {
Exemple e;
e.afficher();
return 0;
}
Regarder les erreurs obtenues si vous faites les choses suivantes :
- enlever le mot
public
- enlever le point virgule en fin de classe
- compiler avec
gcc
?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10