Aller au contenu


Photo
* - - - - 1 note(s)

Introduction au C++


  • Veuillez vous connecter pour répondre
9 réponses à ce sujet

#1 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 24 décembre 2006 - 12:49

Avant Propos : A qui s'adresse ce cours !?

Ce modeste chapitre est destiné à tous ceux qui souhaitent affiner leur compréhension du langage C++, boucher les zones troubles, les appelations et définitions incomprises et aussi un bref récapitulatif des fonctions de base du langage C.

Ce premier cours insistera sur « Quelques définitions du langage C++ »
Il va de soi que vous devez avoir une certaine connaissance de base du langage C au préalable (pointeurs, fonctions ect.), pour pouvoir suivre ce cours de façon aisée.

Si ce n'est pas le cas, je vous renvoie illico-presto sur l'excellent tutorial : Apprenez à programmer en C / C++ !

En 3 semaines chrono vous serez opérationnel.

Revenons à nos moutons ...

Sommaire 1ere partie.
  • Généralités du C++.
    • Norme C++.
    • Un peu d'histore.
    • Qu'est ce que la programmation objet (quelques définitions)
      • L'objet
      • L'encapsulation
      • Les classes
      • L'héritage
    • Questionnaire.
  • Incompatibilité entre C++ / C.
    • Incompatibilité sur définitions de fonctions.
    • Incompatibilité sur les prototypes de fonctions.
    • Incompatibilité sur les arguments et valeur de retour.
      • Points communs.
      • Différences.
    • Compatibilité entre le type void * et les autres pointeurs
  • Input-Ouput Conversationnelles du C++
    • Généralité.
    • L'Affichage.
    • Les nuances de l'affichage et son code.
    • Lecture au clavier.
    • Le synchronisme.
I - Généralités sur le C++

a) La norme de C++

Ce cours est fondé sur la norme ANSI/ISO du langage C++, laquelle est dorénavant reconnue par la plupart des compilateurs récents ... En gros, vous pouvez suivre ce cours sur quasiment toutes les IDEs, avec tous les compilateurs, il n'y aura à priori pas de problème.

b) Un peu d'histoire ?

Le C++ est conçu en 1982 par Bjarne Stroustrup pour les laboratoires Bell, son objectif principal été d'ajouter au langage C des classes analogues à celles du langage Simula. Soit en plus clair :
Intégrer à un langage classique des possibilités de "Programmation Orientées Objets".

c) Quelques définitions

"Programmation Orienté Objet", mais qu'est ce que c'est ?

1 - L'Objet.
La POO est fondé sur le concept de l'objet. A savoir une association des données et de procédures(méthodes) agissant 'encore' sur des données.
Donc la POO peut se traduire par :

Objets = Méthodes + Données

2 - L'Encapsulation.
L'encapsulation, l'encapsulation ... imaginé donc, quelques carrés de sucre posés sur une table, et un verre recouvrant ces sucres. Est-il possible pour une personne extérieur de toucher directement les sucres ?

Bien sûr que NON. Voilà le concept de l'encapsulation !

De manière un peu plus 'Programmatique' :), cela signifie qu'il n'est pas possible d'agir directement sur les données(rappelez-vous les petits sucres) d'un objet. Il sera donc nécessaire de passer par l'intermédiaire de ses méthodes, qui joueront alors le rôle d'interface obligatoire.
Mais à quoi ça sert ce truc ? :)

Bien oui, à quoi ça sert ? nous allons y venir..

Question : Par quoi se caractérise un objet ?

Allez allez ... vous le savez, creusez dans cette mémoire !

Par quoi se caractérise un objet ?

...

Nous savons tous qu'un objet se caractérise uniquement par les spécifications de ces méthodes, à savoir :
  • Noms
  • Arguments
  • Rôles
Et la manière d'implantation des données est sans importance.

L'encapsulation des données donc, à pour but de faciliter considérablement entre autre la maintenance logiciel. Rappelez-vous que l'implantation des données est sans importance donc la modification éventuelle de la structure des données d'un objet n'aura d'incidence que sur l'objet lui-même et non sur les données.

Magique n'est ce pas !

Lisez bien ce qui suit, vous allez comprendre :

Les utilisateurs de l'objet ne seront pas concernés donc par la teneur de cette modification, d'une importance catégorique, ce qui n'est pas du tout le cas pour la programmation structurée.

Et oui, le principal défaut de la programmation structurée hormis sa robustesse, c'est en ce qui concerne sa réutilisabilité, et l'extensibilité ... l'extension quoi !
Il serait difficile donc, de modifier, (pour une quelconque adaptation par exemple) un logiciel sans en modifier les données !

Voila ... Le Gros problème du problème ! bon on ne va pas trop s'attarder la dessus, mais c'est toujours
bon à savoir. ... euh, à comprendre plutôt.

En Bref :

Encapsulation = Protection des données, et si modification d'un objet, pas d'incidence sur les utilisateurs de cette objet.

Voila, plus clair que ca, ca n'existe pas ! :D

3 - Les classes.
Le concept de classe correspond à la généralisation de la notion de type que l'on rencontre dans les langages classiques.

Une classe c'est une description d'un ensemble d'objet, ayant une structure de données communes et disposant des mêmes méthodes :-D.

Une Classe = description de :
  • un ensemble d'objet,
  • une structure de données communes
  • disposant des mêmes méthodes.
(je répète délibérément sous une autre forme. (psychologie oblige)).
Les objets apparaissent donc, comme des variables d'un tel type de classes.

La phrase magique : Un objet est une instance de sa classe !

Ca commence a s'éclaircir là ? ... continuons.

4 – L'héritage.
Un concept tout aussi important que le concept de l'encapsulation, il permet de définir une nouvelle classe à partir d'une classe déjà existant, à laquelle on ajoute de nouvelles données et de nouvelles méthodes (on verra l'importance par la suite).
La conception d'une nouvelle classe permettra d'hériter les propriétés et les aptitudes de l'ancienne, et les spécialiser comme bon vous semblera. Il y aura donc une réutilisation des produits existants, et réitéré autant de fois que l'on désire.

d) Questionnaire

A présent vous devriez être capable de répondre de manière théorique à quelques question.
  • Qu'est ce qu'un objet ?
  • Une classe ?
  • Un héritage ?
  • Quel est l'intérêt de l'encapsulation ?
suite du cours : les incompatibilité en C/C++.
pour toute réclamation envoyé un mail-privé, et non un poste à la suite du cours merci

Modifié par Gen, 02 mars 2007 - 12:29 .

  • 0

PUBLICITÉ

    Annonces Google

#2 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 24 décembre 2006 - 01:15

II â€â€œ LES DIFFERENCES ENTRE LE C++ ET LE C

a) Incompatibilité en C/C++ sur les définitions de fonctions .

Suivant la norme ANSI il existe en C deux façons pour déclarer une fonction. (/!\ : je dis bien déclarer et non définir).

Nous pouvons faire ainsi :

//1er forme
int fct(int x, int y)
 {
	  ... // corps de la fonction
 }

//2ieme forme
int fct(x,y)
int x;
int y;
  {
	  ... // corps de la fonction.
  }


Vous apercevez la différence bien sûr, pas besoin de commentaire rassurez-moi.
Par contre voilà le premier Hick du langage C++, il n'accepte que la première forme, eh eh !

On peut toujours essayer ?

// 1er forme
#include <stdio.h>
int fct(int x,int y);
int main (int argc, char *argv[])
  {
   fct(1,2);
   return 0;
  }
int fct(int x,int y)
	{
	 printf("\nx:%d; y:%d\n",x,y);
	}

// 2ieme forme
#include <stdio.h>
int fct(x,y);
int main (int argc, char *argv[])
  {
   fct(1,2);
   return 0;
  }
int fct(x,y)
int x, y;
	{
	 printf("\n%x:d; y:%d\n",x,y);
	}

Je compile et j'exécute la première forme et la deuxième forme :

[C-Mos@localhost test]$  gcc 1er_forme.c -o test 
[C-Mos@localhost test]$ ./test

x:1; y:2

et le second :

[C-Mos@localhost test]$  gcc 2ieme_forme.c -o test 
test.c:2: warning: parameter names (without types) in function declaration
[C-Mos@localhost test]$ ./test

x:1; y:2

ah ! Le petit warning s'annonce ... mais ca marche quand même.
Jeux d'enfant me diriez-vous ! ... on essaie à présent avec le compilateur C++.

Nous allons donc renommer les deux programmes 1er_forme.c et 2ieme_forme.c en
'.cpp' tout simplement. Et nous allons compiler tout ca :

1er forme :
[C-Mos@localhost test]$ g++ 1er_forme.cpp -o test
[C-Mos@localhost test]$ ./test
1:2
[C-Mos@localhost test]$

2ieme forme :
[C-Mos@localhost test]$ g++ 2ieme_forme.cpp -o test
test.cpp:2: error: 'x' was not declared in this scope
test.cpp:2: error: 'y' was not declared in this scope
test.cpp:2: error: initializer expression list treated as compound expression
test.cpp: In function 'int main(int, char**)':
test.cpp:5: error: 'fct' cannot be used as a function
test.cpp: At global scope:
test.cpp:8: error: redefinition of 'int fct'
test.cpp:2: error: 'int fct' previously defined here
test.cpp:8: error: 'x' was not declared in this scope
test.cpp:8: error: 'y' was not declared in this scope
test.cpp:11: error: expected unqualified-id before '{' token

ah ! Mais qu'est ce que c'est que ca ??

Nous avons dit au début de ce chapitre que, le langage C++ permettait d'intégrer au langage classique C des possibilitées de "Programmation Orientées Objets", et est donc considéré comme une extension du langage C. On constate donc qu'il y a incompatibilité entre les deux langages sur ce point-ci qui est au niveau de la déclaration de fonctions.

b) Incompatibilité en C/C++ sur les prototypes.

Nous avons constaté que sur les définitions des fonctions il y a une certaine incompatibilité entre le C et C++, idem donc, pour les déclarations des fonctions ou plus communément, les prototypes.
En C++, l'appel de fonction ne sera accepté que si le compilateur connait au préalable le type d'argument et aussi le type de sa valeur de retour.

Rappel, définition d'une fonction :

On appelle fonction, un sous-programme qui permet d'effectuer un ensemble d'instructions par simple appel de la fonction dans le corps du programme principal.
Les fonctions permettent d'exécuter dans plusieurs parties du programme une série d'instructions, cela permet une simplicité du code et donc une taille de programme minimale.


A comprendre surtout, n'apprennez pas ca comme des Robots !

Donc voici la syntaxe d'une fonction :

type_de_donnée_retournée Nom_Fonction(type1 argument1, type2 argument2, ...);

...

type_de_donnée_retournée Nom_Fonction(type1 argument1, type2 argument2, ...)
{
... //liste d'instructions
}


Nous avons dit que le compilateur C++ doit connaître au préalable le type de la valeur retournée et aussi les types d'arguments.
un exemple :

int fct (int x, int y, char z); // en gras ce qu'il doit connaître


On oublie pas le ';' car le probleme se porte sur les prototypes.
On ne faiblit pas, c est du gateau.

Conclusion :

En C++, il faut toujours mettre les prototypes des fonctions avec le type de la valeur retournée et aussi les types d'arguments ! Voilà.

(Trop simple, rien de nouveau)

Ah oui ... que faisions-nous en C ?

Et bien lors d'une declaration d'une fonction,
  • si nous n'avions pas mis la valeur de retour, le compilateur considérer alors que sa valeur retour était de type int, ... Pas possible en C++.
  • on pouvait la déclarer en n'indiquant que le type de la valeur de retour par exemple :
    float fct ; // Pas possible en C++
  • ou alors la déclarer à l'aide d'un prototype
    int fct (int, int, char); // en C++, on utilisera que ca.
Mais que se passe-t-il lorsque :
les types d'arguments déclarés dans le « prototype » ne sont pas identiques aux types de variables créer dans le programme qui doivent lui être envoyés ?

8-(, quéqui dit ?

...

Par exemple un exemple :

je déclare une fonction qui reçoit pour argument, un int et un float,
et renvoie une valeur retour de type int.

Traduction :

int fct (int, flaot); // déclaration de fct sous forme d'un prototype

int main (int argc, char argv[][])
 {
  int x; 
  char y;
  ...
  fct(x,y);
 }

On constate normalement une incohérence entre la valeur envoyée y de type char, et la valeur recu de la fonction fct(x,y), y etant de type float ...
Nous savons que quand le compilateur rencontre un appel de fonction, il compare les types des arguments effectifs avec ceux des arguments muets correspondant.
Si différence il y a ... alors il met en place un système de conversion pour que la fonction recoivent les arguments du bon type.

int fct (int, flaot);
...
 fct(a,b); [i]// b est de type char, et est envoyé en tant que float.[/i]

Nous pouvons vérifier cela, sur une petite application :

#include <stdio.h>
int fct(int x, float y);
int main (int argc, char *argv[])
  {
   int x;
   char y;
   printf("taille d\'un char			: %d\n", sizeof(char));
   printf("taille de y avant envoi	  : %d\n", sizeof(y));
   fct(x,y);
   return 0;
  }
int fct(int x,float y)
	{
	 printf("\ntaille d'un float : %d\n", sizeof(float));
	 printf("taille du y recu	:%d\n", sizeof(y));
	}


Voici le résultat :

[C-Mos@localhost test]$ g++ test.cpp  -o test
[C-Mos@localhost test]$ ./test

taille d'un char		: 1
taille de y avant envoi : 1

taille d'un float: 4
taille du y recu : 4
[C-Mos@localhost test]$

Nous constatons donc, qu'il y a bien convertion du type envoyé initialement, de char en float par le compilateur. Si la fonction et sa déclaration (:forme prototype) se trouvent dans le même fichier source, alors le compilateur verifie la cohérence entre l'en-tête de la fonction et le prototype, et si il n'y a oas cohérence alors, alors le compilateur exécute une conversion de type de sorte que la fonction, reçoit les types déclarer prélablement, soit : Conversion autoriée lors d'affectation

En revanche, nous avons vu que, lorsque les arguments d'une fonction recevait un type différent que celui déclarer dans le prototype, le compilateur faisait une conversion de type, n'est ce pas ?

Et si par exemple, les arguments du prototype et celles de la définition ne sont pas identiques dans le même fichier source que se passe t-il ?

et bien tout simplement pas besoin d'exemple, il y a une erreur de compilation !

Allez un petit exemple quand même pour illustrer.
si j'ecris ca, :

int fct(int, int);
...
main ()
{
...
}

int fct(char y, float x); [i]// erreur de compilation.[/i]

A savoir :
Pour que la vérification lors de la conversion des types soit pris en charge, il faut donc que le prototype soit mis dans le meme fichier source que la définition, sinon aucun contrôle ne sera fait !
Pour une plus grande précaution, on déclare souvent quand on doit utiliser une focntion en dehors du fichier ou elle est définie, on place son prototype dans un fichier d'en-tête, ce dernier incorporé , en cas de besoin par la directive #include, on évite ainsi tout rique de faute d'ecriture du prototype.

C'est tout ce qu'il y a d'interressant pour le moment à savoir.
Si, ce n'est pas compris, et bien pas de precipitation, ...

Citation attention :
Il est préférable de lire un Livre 3 fois et de l'avoir compris, que de lire 3 livres et de ne pas les avoir compris
donc, la suite du cours se portera sur : II.c) les arguments et valeur de retour, de fonction
@bientot.

Modifié par C-Mos, 12 mai 2007 - 04:28 .

  • 0

#3 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 25 décembre 2006 - 08:55

II - LES DIFFÉRENCES ENTRE LE C++ ET LE C (Suite).

c) Incompatibilité sur les arguments et valeur de retour.

1 - Point communs entre C et C++.
En C++ tout comme en C ANSI, les arguments d'une fonction ainsi que la valeur retour peuvent :
  • être scalaire, d'un type de base (char, int, float, *pointeur)
  • être une valeur de type structurée.
...
2 - Différences entre C et C++.
Les différences comme nous les avons vu, ne portent que sur la syntaxe des en-têtes & prototypes des fonctions, et ceci sur deux cas bien spécifique :
  • Fonction sans arguments.
    En C ANSI on peut employer void pour définir (en-tête) ou déclarer (prototype) une fonction sans argument, en C++, on ne met rien(liste vide).
    En C :
    float fct (void);
    En C++ :
    float fct ( );
  • Fonction sans valeur retour.
    Alors qu'en C ANSI, on serait amené à employer le mot void, pour le type de retour, en C++, on doit obligatoirement le mettre, sinon, ne mettre qu'une liste vide, amènerait le compilateur à considérer que la fonction renverrait un int.
    void fct ( int ); //en C et C++ renvoie un void.
    Par contre mettre ceci :
    fct ( int ); // renvoie un void en C, et un int en C++, si toute fois, votre compilateur veuille bien le compiler !
On pourrait croire à priori, que ceci est totalement fictif, voir sans importance, que ce soit un int, ou un void, puisque nous ne la réutiliserions pas, après tout, on s'en fiche !

Et bien permettez-moi, de rependre une très belle parole, piochée, je ne sais pas trop où :
En Vertu du principe de l'économie des moyens, un bon programme ne se contente pas que de marcher ; il marche, tout en évitant de gaspiller les ressources du système. L'Abus de variables, ou de variables surdimensionnées peut entrainer un ralentissement notable du système, voir un plantage pur et simple.
(avec une touche personnelle !:°)

Imaginons un instant, un programme en C++, de 200.000 lignes (non, non c'est rien :P de code, et ayant plus de 30.000 fonctions (imaginons ... ), qui sont sensées ne rien renvoyer « à la base. »
Déclarées dans ce genre là :
fct(int, double, double ); // par exemple.

:P ?

En C++, noté ainsi, nous avions dit, que quand le compilateur ne voyait rien (liste vide) pour « type de valeur retour », il considèrerait que cette valeur retour est un int.

Donc dans ce cas-ci, il va réserver un emplacement mémoire de la taille d'un int pour un type retour ... et tout ça, tout seul !!!

Un int, est codé sur combien d'octet ?
(ohhh, la blague eh, c'est quoi cette question ? ).
Vous me dites 4 ( et que cela dépend des systèmes), mais on va supposer 4 octets.
Ok, donc,

30.000 x 4 = 120.000 octets de réservation mémoire qui ne servent à rien !

Vous voyez l'inattention à quoi ca amène !
En C++ lors d'une déclaration, on met tout ! Type de valeur renvoyée, type des arguments, nom de la fonction ... on laisse rien au hazard.
L'assiduité, on ne le dira jamais assez. :P

d) Compatibilité entre le type void* et les autres pointeurs

Le type générique void* est compatible avec tous les autres types de pointeurs en C ANSI, et ceci dans les deux sens. Si je déclare ceci :
void * x;
int * y;
et j'affecte de cette manière :
x = y;
y = x;
elles feront intervenir les conversions implicites a savoir
int * -> void * //pour la première
void * -> int * // pour la seconde

En C++ seul la conversion d'un pointeur quelconque en void peut être implicite. Par conclusion seule l'affectation :
int * -> void *
soit :
x = y;

Autrement il faudra faire intervenir l'opérateur cast, pour forcer la conversion d'un type de pointeur void, en un tout autre type, la conversion sera donc faite explicitement.

Le qualificatif « const »

Le qualificatif const permet de spécifier qu'un symbole correspond à « quelque chose » dont la valeur ne doit pas être changer.
Lorsque la portée s'applique à des variables locales, il n'y pas de différence entre le C et le C++, la fonction étant limitée par au bloc ou à la fonction concernée par la déclaration.
Mais si, le qualificatif s'applique à des variables globales, C++ limitera donc la portée du symbole au fichier, comme si il avait reçu l'attribut « static ». Ainsi il devint beaucoup plus aisé de remplacer certaines instructions #define par des déclarations de constantes.

En C, nous faisions :
fichier1
#define VA 8						
...
fichier2
#define VA 5
...

en C++ on fera :
fichier1
const int VA = 8;
...
fichier2
const int VA = 5;
...

De cette manière, si nous aurions compilé en C, nous aurions eu une erreur au niveau de l'édition de lien, pour éviter cela, il aurait fallu déclarer VA « en static » dans au moins un des fichier ou les deux. De cette manière :

fichier1
static const int VA = 8;
...
fichier2
static const int VA = 5;
...

Voilà. Ce chapitre est terminé ! Pas très long, mais cela suffira à éviter quelques erreurs d'inattention qui vous feront peut être moins perdre de temps à l'avenir.

Que diriez-vous d'un peu d'exercice, histoire de stimuler les neurones !

Voici ce programme qui peut très bien être compilé en C, toute fois avec le compilateur C++ il fournira pas mal d'erreurs, interprétez donc les erreurs possibles du compilateur tout en les expliquant.

main ()
  {
   int x, y, resultat;
   printf("soit (a+b)²\n");
   printf("donnez a ? ");
   scanf("%d", &x);
   printf("donnez b ? ");
   scanf("%d", &y);
   resultat = fct(x,y);
   printf("Le resultat de (%d+%d)² est : %d\n",x,y,resultat);
  }
fct(int x, int y)
	{
	 return (x*x + 2*x*y + y*y);
	}

Correction au prochain cours : " Input-Output conversationnelles ", ... alors, à vos copies !

Modifié par Gen, 02 mars 2007 - 12:32 .

  • 0

#4 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 26 décembre 2006 - 04:55

Correction de l'exercice précédent :
La première erreur que révèlera le compilateur C++, sera pour la fonction fct(). En effet, elle devra faire obligatoirement l'objet d'une déclaration sous forme prototype. que vous placerez n'importe ou avant son appel.
int fct(int, int );
ou encore
int fct(int var1, int var2);
Les noms var1 et var2 sont totalement fictifs : ils n'ont aucun absolument aucun rôle dans la suite du programme, et n'interfèreront nullement sur d'autres variables de même type.
La seconde erreur se portera sur la fonction "printf", qui devra faire l'objet d'un prototype. C++ étant incapable de distinguer les fonctions de l'utilisateur et celles des bibliothèques.
#include <stdio.h>
ou comme nous l'avons dit précédemment, avec le préfixe 'c' :
#include <cstdio>


Voilà, vous avez je pense tous réussi à résoudre ce petit exercice des plus basique. donc, on passe à :


III - Les Entrées-Sorties conversationnelles du C++

a) Généralités

Nous allons voir dans ce chapitre les nouvelles disponibilités offertes du C++ pour les entrées-sorties conversationnelles.
  • La lecture sur l'entrée standard -> Clavier.
  • La lecture sur la sortie standard -> Écran.


Les fonctions et macros de la bibliothèque C ANSI sont réutilisables en C++, en particulier celles des input-ouput, raison pour laquelle nos premiers programmes fait précédemment en C++ n'ont rencontré aucun problème de compilation lors de l'utilisation des flux d'entrées sorties !

Chose nouvelle pour le C++, c'est qu'à présent, nous allons préfixer les fichiers d'en-tête habituels, par la lettre « c », ainsi le fichier : « stdio.h » deviendra : « cstdio » et le « .h» disparaitra.

Les principales caractéristiques du C++ en ce qui concerne les Input-Output sont :
  • Simplicité d'utilisation : Fini le temps des « printf - scanf ».
  • Extensibilité des types que l'ont définira sous forme de classes.

b) Affichage

Habituellement en C, nous écrivions :
printf("Bonjour");

en C++, nous utiliserons :
cout << "Bonjour";

On expliquera plus tard, l'interprétation détaillée de cette instruction, pour le moment il nous sera simplement utile de savoir que "cout" :
  • Désigne un flot de sortie associé à (stdout)
  • << est un opérateur : [opérande gauche] << [opérande droite]
  • l'opérande de gauche est un flot, ici cout !
  • l'opérande de droite est une expression de type quelconque.
On peut donc traduire :
cout << "Bonjour";
par : le flot cout reçoit la valeur "Bonjour".

1. Le fichier en-tête « iostream ».

Nous savons qu'en C, pour utiliser les flots d'entrées-sorties standard, il fallait incorporer le fichier d'en-tête ''stdio.h'', en C++, il faudra le remplacer par : ''iostream'', et pas seulement !

L'utilisation des symboles introduit dans ce fichier fait appel à la notion « d'espace de noms », Cette notion oblige d'introduire dans le programme une instruction de déclaration using, qui se présente ainsi :

using namespace std;
Pour certain compilateur, l'utilisation de iostream sans la déclaration de using namespace std pourrait entrainer une certaine erreur.

2. Lecture au clavier.

Le flot d'entrée C++ associé à l'entrée standard du C (stdin), est cin. De la même manière que l'opérande << qui permet d'envoyer des informations sur le flot de sortie cout, l'opérande >> permet de recevoir de l'information.

Voici un petit programme d'une lecture de suite de caractères, illustrant le défaut majeur de celle ci.

#include <iostream>
using namespace std;
int main (void)
  {
   char car[129];
   int i = 0;

   cout << "Donnez une suite de caractères terminée par un point : \n";
   do
	cin >> car[i];
	while (car[i++] != '.');
   cout << "\n Caractères effectifs lus : \n";
   i=0;
   do
	cout << car[i];
	while (car[i++] != '.');
   return 0;
  }
Voilà, je compile/exécute ce bout de programme, et je met des sauts de ligne délibérés :
[C-Mos@localhost test]$ g++ test.cpp -o test
[C-Mos@localhost test]$ ./test
Donnez une suite de caractères terminée par un point :
Voyez comme
	
C++

pose quelques soucis
lors de la lecture d'une
"suite de caractères"
.

 Caracteres effectifs lus :
VoyezcommeC++posequelquessoucislorsdelalectured'une"suitedecaractères".
[C-Mos@localhost test]$


On constate que C++ à quelques difficultés pour gérer les séparateurs.

Voyons à présent que se passe-t-il si par exemple nous exécuterions une boucle sur un caractère invalide !?

Soit le programme suivant :

[C-Mos@localhost test]$ cat test.cpp
#include <iostream>
using namespace std;
int main (void)
  {
  int nbre;
   do
	{ cout << "Donnez un nombre de type entier : ";
	  cin >> nbre;
	  cout << "voici son cube : " << nbre*nbre*nbre << "\n";
	}
   while (nbre);
  return 0;
  }


On l'exécute, et nous allons voir comment C++ va gérer cela.
[C-Mos@localhost test]$ g++ test.cpp -o test
[C-Mos@localhost test]$ ./test
Donnez un nombre de type entier : 2
voici son cube : 8
Donnez un nombre de type entier : 3
voici son cube : 27
Donnez un nombre de type entier : p   
Donnez un nombre de type entier :voici son cube : 27
Donnez un nombre de type entier :voici son cube : 27
Donnez un nombre de type entier :voici son cube : 27

Voilà, le compilateur s'emballe, et boucle infiniment, vous interromprez donc l'exécution du programme suivant la démarche appropriée à votre IDE, si sur une IDE vous êtes.
Dans mon cas, CTRL+C, qui émettra le signal SIGINT à tous les processus associés au terminal respectif.

3. Le synchronisme.

Nous allons dans ce petit paragraphe illustrer la présence d'un tampon, s'apercevoir qu'une seule lecture peut utiliser une information non exploitée par la précédente.

[C-Mos@localhost test]$ cat test.cpp
#include <iostream>
using namespace std;
int main (void)
  {
  int nbre, nbre2;
   cout << "Donnez un nombre de type entier : ";
   cin >> nbre;
   cout << "valeur : " << nbre << " récupérée"<< "\n";
   cout << "Donnez un nombre de type entier : ";
   cin >> nbre2;
   cout << "valeur : " << nbre2 << " récupérée"<< "\n";
  return 0;
  }




Compilation :
[C-Mos@localhost test]$ ./test
Donnez un nombre de type entier : 3 4
valeur : 3 récupérée
Donnez un nombre de type entier : valeur : 4 récupérée


Voilà, c'est la fin de la première partie Introduction au C++, c'était pas très difficile, c'est juste des révisions.

La suite du chapitre portera donc sur :
  • Les notions de référence
  • Arguments par défaut dans les déclarations des fonctions.
  • Surdéfinition de fonctions
  • Opérateur : new et delete
  • Fonctions inline
  • Le très attendu opérateur cast
  • le type bool
  • Les notions d'espace

Et tout plein d'exercices. La quantité bien sûr sera relative à la difficulté des leçons.

Voilà,
Vous vous attendiez à quoi ? A voir le Calcul Vectoriel, les Algorithmes de Fusion, le conteneur multimap ... ben non ! pour cela, il faudra être très attentif au début des cours, et après nous les aborderons avec facilité.

En attendant, pourquoi ne pas essayer de programmer ces fonctions en C, et ensuite, de les faire évoluer en C++.

la fonction : max(x,y) -> qui retourne la valeur maximum de x et y;
la fonction : min (x,y) -> qui retourne la valeur minimum de x et y;
la fonction : hypot(x,y) -> qui retourne l'hypoténuse d'un triangle rectangle avec x, et y, les cotés de l'angle droit.

Histoire de garder la forme.

@bientôt .

Modifié par Gen, 01 mars 2007 - 08:45 .

  • 0

#5 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 06 janvier 2007 - 07:13

Sommaire 2ème partie.
  • Les spécificités du C++
    • Transmission des arguments en C.
    • Transmission des arguments en C++ par référence.
    • Propriétés
    • Risques indirects
    • Absence de conversion
    • Arguments "effectif constant" & "muet constant"
  • Transmission par référence d'une valeur retour.
    • Le lvalue
    • Valeur de retour et sa constance
    • Notion de référence générale
    • Initialisation de référence
    • Conclusion
I - Les spécificités du C++

a) Transmission des arguments en C.

En C, les arguments et la valeur retour d'une fonction, sont toujours transmis par valeur (on pourrait toute fois établir une "simulation de transmission par adresse" à l'aide de pointeur), à l'encontre C++ offre une nouveauté, qui consiste à envoyer ces valeurs par référence. L'intérêt, est que cette fois-ci, le compilateur prendra en charge le transfert d'adresse.

Voici quelques récapitulatifs :
Transmission des arguments par Valeur en C. (valable en C++)
#include <stdio.h>
int addition(int, int, int);
int main (void)
  {
   int a = 1, b = 2, c = 3, resultat;
   resultat = addition(a, b, c);
   printf("%d + %d + %d = %d\n", a, b , c , resultat);
   return 0;
  }
int addition(int m, int n, int o)
 {
  return (m+n+o);
 }

Résultat :
1 + 2 + 3 = 6


Transmission par adresse en C. (aussi valable en C++)
Nous réutiliserons le même programme que précédemment mais cette fois-ci au lieu d'envoyer les valeurs des arguments, nous enverrons les adresses des arguments, on expliquera les deux cas en détail ultérieurement.
#include <stdio.h>
int addition(int*, int*, int*);
int main (void)
  {
   int a = 4, b = 5, c = 6, resultat;
   resultat = addition(&a, &b, &c);  //envoi d'adresse
   printf("%d + %d + %d = %d\n", a, b , c , resultat);
   return 0;
  }

int addition(int *m, int *n, int *o)
 {
  return (*m+*n+*o);
 }

Résultat :

[cmos@localhost test]$ gcc test.c -o test
[cmos@localhost test]$ ./test
4 + 5 + 6 = 15

Nous constatons donc, que suivant les deux manières d'envoi d'arguments, (soit par valeur soit par adresse), l'utilisateur doit absolument savoir si il transmet une variable ou l'adresse de la variable, et ceci se justifie par ces déclarations et définitions. En revanche, C++ demandera au compilateur de prendre en charge la transmission des arguments par adresse, il utilisera « les bonnes méthodes » qui nous simplifieront « des jonglettes » habituelles de pointeurs que nous faisions d'en-temps.
La transmission par référence que nous allons voir, bien sûr est incompatible en C.

b) Transmission des arguments en C++ par référence.

Voici un exemple :

#include <iostream>
using namespace std;
int addition(int &a, int &b, int &c);
int main (void)
  {
   int a = 1, b = 2, c = 3, resultat;
   resultat = addition(a,b,c);
   cout << a << " + " << b << " + " << c << " = " << resultat << "\n";
   return 0;
  }

int addition(int &m, int &n, int &o)
 {
  return(m+n+o);
 }

Résultat :
[cmos@localhost test]$ g++ test.cpp -o test
[cmos@localhost test]$ ./test
1 + 2 + 3 = 6

Explication :
Dans l'instruction : int addition(int &m, int &n, int &o);, int &m, signifie que 'm' est de type int transmise par référence, on envoie donc l'adresse de 'm' et non sa valeur (attention) !!!
Par contre si nous regardions l'appel de la fonction addition dans le main,
resultat = addition(a,b,c);
nous constatons que ces arguments semblerait être des valeurs ... Détrompez-vous ! Ce sont bien des adresses, et la seul manière pour un programmeur extérieur de savoir de quel type de transmission s'agit-il, serait de regarder le prototype de la fonction, et sa définition. Au final, nous nous préoccuperons plus du mode de transmission des arguments, le compilateur s'en charge tout seul !
Une seconde chose que nous constatons, survient dans le bloc de définition de la fonction :
int addition(int &m, int &n, int &o)
 {
  return(m+n+o);
 }

en particulier :
return(m+n+o);
Effectivement bien que l'on ait reçu les adresses des variables, nous ne feront plus appel à l'opérateur d'indirection *, Oubliez les * et les * ! :P


1. Propriétés.

La transmission par référence d'un argument entraine un certain nombre de conséquences qui n'existait pas dans le cas :
- Transmission par Valeur.
- Transmission par adresse par le biais de pointeur.

2. Risques indirects.

Hormis la simplification d'écriture pour une transmission d'arguments par référence, ce type de transmission peut entrainer le risque "d'effet de bord" non désiré. En effet lorsque l'appel de fonction est établie, l'utilisateur ne sait plus si il transmet la valeur ou l'adresse d'un argument.
Il risque donc de modifier une variable alors qu'il pensait n'avoir transmis qu'une copie de la valeur !

3. Absence de conversion.

Un point essentiel, je dirais même plus ... essentiel !
Rappelez-vous que lorsque nous utilisions une transmission par valeur, le compilateur exerçait une vérification (comparaison) "des types" d'arguments, entre ceux de la fonction et ceux qui lui seront transmis. Si il y a différence, alors le compilateur établissait une conversion de type.

Exemple :
#include <iostream>
using namespace std;

void fct(char );
int main (void)
  {
   int val='x';
   cout << "Taille de val' avant envoi: " << sizeof(val) << "\n";
   fct (val);	
   cout << "Taille de 'val' apres : " << sizeof(val) << "\n";
   return 0;
  }

void fct(char m)
 {
  cout << "valeur recu: " << m << "\n";
  cout << "Taille de 'val' dans fonction: " << sizeof(m) << "\n";
 }

Résultat :

Taille de 'val' avant envoi : 4
valeur recu : x
Taille de 'val' dans fonction : 1
Taille de 'val' apres : 4

Le résultat parle de lui-même.
Faisons à présent un exemple d'une transmission par référence, et des possibilités de conversions correspondant .. si il y en a.

int fct (int &) // la fct reçoit la référence à un entier
double x;
...
fct(x);

Dès lors que la fonction reçoit l'adresse d'un emplacement qu'elle considère comme contenant un entier, qu'elle peut éventuellement modifier,
il va de soi qu'il n'est plus possible d'effectuer une quelconque conversion de la valeur qui s'y trouve.

fct(x); //appel illégal
La transmission par référence impose donc à un argument effectif d'être une Lvalue du type prévu pour l'argument muet.

Mais que se passe-t-il si à la place de :fct(x);, nous mettions : fct(&x);, étant donné que nous envoyons une adresse ?

Et bien tout simplement, cette appel-çi sera rejeté ! car même si au bout du compte nous envoyons une adresse, la référence n'est pas un pointeur, et l'usage qui est fait dans la traduction des instructions de la fonction n'est pas le même.
Dans le cas d'un pointeur on utilise directement sa valeur, quitte à mentionner une indirection avec l'opérateur *; avec une référence, l'indirection est ajouté automatiquement.

C'est clair pour tout le monde ! ... Respirez, ... On continue ! :P

4. Arguments « effectif constant » & « muet constant »
  • Arguments « effectif constant »
Supposons que la fonction ait pour prototype :

void fct(int &);

Le compilateur refusera alors un appel sous cette forme :

fct(3); // incorrect : f ne peut pas modifier une constante.

ou encore :

const int val=123;
...
fct(val); // incorrect : f ne peut pas modifier une constance.

Pour quelle raison ?
Et bien je vous réponds par une autre question : Peut-on modifier une constante au cours d'un programme ?
Bien non, si les appels auraient été acceptés, alors ils fourniraient à la fonction les adresses des constantes 3 ou val, et elle pourrait très bien en modifier les valeurs.
  • Arguments « muet constant »
Par contre nous allons considérer ce prototype :

void fct (const int &); // const int & correspond à une référence à une constante.Les appels suivant seront donc corrects :

const int var = 15;
...
fct(3); //appel correct 
fct(var);
// appel correct

L'acceptation de ces instructions se justifie ici, par le fait que fct a prévu de recevoir une référence à quelque chose de constant. De plus un appel de fonction : fct(expression), sera accepté quelque soit le type de (expression). En effet dans ce cas :
[code ]
void fct(const int &);
float var;
...
fct(var); // correct
[/code]

Il y a ici une création d'une variable temporaire (de type int) qui recevra le résultat de la conversion de expression en int.
En définitif, l'utilisation de const pour argument muet transmis par référence, est lourde de conséquence, car il force le compilateur vérifier la constance de l'argument concerné, au sein de la fonction, et de plus, nous autorisons la création d'une copie de l'argument effectif précédé d'une conversion dés lors que ce dernier est constant et de type différent de celui attendu.

c) Transmission par référence d'une valeur de retour
1. Introduction

Exemple :

int & fct()
 {...
  return n;
 }

Ici, l'appel de fct provoquera un retour non pas de la valeur de 'n', mais de la référence de 'n', il peut être utilisé aussi de façon plus usuelle :

int var;
...
var = f();

Ici, on affectera à 'var', la valeur situé à la référence fournie par 'fct'.

1.c.1 ) On obtient une Lvalue

Dès lors qu'une fonction renvoie une référence, il nous sera possible d'utiliser son appel comme une Lvalue. Par exemple :

int & fct();
int x;
float y;
...
/* à la référence fournie par fct, on range la valeur de l'expression */
f() = 2*x+5; 

/* à la référence fournie par fct, on range la valeur de y, après conversion en int.*/
f() = y;

Pour le moment nous n'y voyons pas grand intérêt, mais nous en verrons le but principal lors de l'étude de la surdéfinition d'opérateur.

2. Valeur de retour et sa constance

Si une focntion prévoit dans son en-tête une retour par référence, elle ne pourra pas mentionner dans l'instruction return une constante. Sinon, on prendrait le rique de modifier la valeur en question :

Illustration :

int x = 2; // variable global
float y = 3.456; // variable global
int & fct(...)
{
...
return 5; // interdit
return x; // ok
return y; // interdit
}

Si par contre l'en-tête mentionne une référence à une constante, il y aura exception, on renverra donc la référence d'une copie de cette constante, précédée d'une éventuelle conversion :
const int & fct_2() 
 {
   return 5; // ok : On renvoie la référence à une copie temporaire
   return x; // ok
   return y; /* ok : On renvoie la référence à un int temporaire obtenue par conversion de la valeur de y */
 }

On notera qu'une telle référence à une constante ne pourra plus être utilisé comme une Lvalue :

const int & fct();
int x;
float y;
...
fct() = 2 * x + 5; // erreur : fct(), n est pas une Lvalue
f() = y; // erreur : fct(), n est pas une Lvalue

3. Notion de référence générale.

De manière générale, on peut déclarer un identificateur comme référence d'une autre variable.

#include <iostream>
using namespace std;
int main (void)
 {
  int x=3;
  int &y = x;
  cout << "adresse x: "<< &x <<" valeur : " << x <<"\n";
  cout << "adresse y: "<< &y <<" valeur : " << y <<"\n";
  return 0;
 }

Dans ce cas, 'y' est une référence à la variable x, ainsi, x et y designerons le même emplacement mémoire. Ce que nous constatons :

Le résultat :

adresse x: 0xbf93d66c valeur : 3
adresse y: 0xbf93d66c valeur : 3


4. Initialisation de référence.

Quand nous ecrivons ceci :

int & x = y;

Nous déclarons une référence 'x', accompagnée d'une initialisation à la référence de 'y'. Attention, il n'est pas possible de déclarer une référence sans l'initialiser :

int & x ; // Incorrect.

Ou encore, d'initialiser une référence par une constante.

int & x = 15 ; // Incorrect

Maintenant je vous pose ceci :

int y = 3;
int & x = y;
y = z; // ou y = 10;

Que se passe-t-il ?

Premièrement, nous avons déclarer une variable de type int de valeur 3, puis une référence 'x' initialiser à la référence y,
et secondo, il s'agit obligatoirement d'une affectation à l'emplacement de référence y, et non une modification à la référence z ... :P vous suivez ???

Une référence une fois déclarée et initialisée, il n'est plus possible de la modifier, PLUS POSSIBLE ! donc le compilateur considérera :

y = z;

comme une affectation, ... automatiquement.

Exemple :
#include <iostream>
using namespace std;
int main (void)
 {
  int x=3;
  int &y=x;
  cout << "adresse x: "<< &x <<" valeur : " << x <<"\n";
  cout << "adresse y: "<< &y <<" valeur : " << y <<"\n";
  y = 5;
  cout << "y = 5" << "\n";
  cout << "adresse y: "<< &y <<" valeur : " << y <<"\n";

  return 0;
 }

Résultat :

adresse x: 0xbfe8b96c valeur : 3
adresse y: 0xbfe8b96c valeur : 3
y = 5
adresse : 0xbfe8b96c valeur : 5

Nous voyons explicitement que le compilateur à effectuer une affectation de valeur 5 à l'emplacement de référence y.

y = 5
adresse y: 0xbfe8b96c valeur : 5


Compris tout ça !

Par contre, il est possible de définir des références constantes qui peuvent alors être initialiser par des constantes :

int & x = 15 ; // Incorrect

const int &x = 15 ; // Correct

Ceci génére une variable temporaire contenant la valeur 15, et place sa référence dans x, nous aurions l'écrire de cette manière :

int temp = 15;
int &x = temp;

mais avec const int &x = 15 ;, on n'aura pas accès explicitement à la variable temporaire.

Et enfin,

float x;
const int &y = x;

Ces déclaration sont corrects, il y aura création d'une variable temporaire contenant le résultat de la conversion de 'x' en int et placement de sa référence dans 'y' .

float x; int temp = x;
const int &y = temp;

5. conclusion
L'Appel d'une fonction conduit donc à une initialisation des arguments muets. Dans le cas d'une référence, ce sont donc les règles que nous avons décrites qui sont tout aussi utilisées. Il en va de même pour une valeur retour. :P

Voilà, ... pour le moment ... la suite et la fin du chapitre portera sur :

  • Surdéfinitions de fonctions
  • Opérateur : new et delete
  • Fonctions inline
  • Le très attendu opérateur cast
  • le type bool
  • Les notions d'espace

Modifié par Gen, 03 mars 2007 - 03:24 .

  • 0

#6 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 20 février 2007 - 11:21

Les arguments par defaut.

Introduction.

Exemples :

Rappelons qu'en C Ansi, il est indispensable que l'appel de fonction contienne autant d'arguments que la fonction en attend.

fct(int, int, int); //prototype
...
fct(val1,val2,val3); // Appel

Il était impossible d'appeler la fonction tout en oubliant un argument, du genre :

fct(int, int int); //prototype
...
fct(val1,val); // Appel

Oui, on avez une erreur, lors de l'appel de la fonction, et ne me dites pas : "ah bon !"

Et bien C++, permet d'appliquer un mécanisme d'attribution de valeurs par defaut à des arguments, 'tit exemple :

#include <iostream>
using namespace std;

void fct(int, int=12); // prototype avec valeur par defaut 'int=12'.

int main (void)
  {
   int x=3, y=4;
   cout << "Appel normal" << "\n";
   fct(x,y);
   cout << "\n" << "Appel un seul argument" << "\n";
   fct(x);
   return 0;
  }

void fct(int val, int val2)
 {
  cout << "premier argument : "<< val << "\n";
  cout << "deuxieme argument : "<< val2 << "\n";
 }

Résultat :

Appel normal
premier argument : 3
deuxieme argument : 4

Appel avec un seul argument
premier argument : 3
deuxieme argument : 12

Explication :
La declaration de fonction fct avant le main est réalisé par le prototype :

void fct(int, int=12);

La déclaration du second argument initialisé par défaut de cette fonction apparaît sous cette forme :

int = 12;

Chose nouvelle, car celle-ci précise au compilateur que si la fonction est appelée sans second argument, la fonction considérera que le deuxième argument sera de valeur 12, (valeur par défaut).
Si nous aurions voulu appeler la fonction par :

fct();

alors il aurait fallu initialiser le premier argument par une quelconque valeur defaut. Par exemple :

#include <iostream>
using namespace std;

void fct(int=-1, int=12);

int main (void)
  {
   int x=3, y=4;
   cout << "Appel normal" << "\n";
   fct(x,y);
   cout << "\n" << "Appel avec un seul argument" << "\n";
   fct(x);
   cout << "\n" << "Appel sans aucun argument" << "\n";
   fct();
   return 0;
  }

void fct(int val, int val2)
 {
  cout << "premier argument : "<< val << "\n";
  cout << "deuxieme argument : "<< val2 << "\n";
 }


Résultat :

Appel normal
premier argument : 3
deuxieme argument : 4

Appel avec un seul arguments
premier argument : 3
deuxieme argument : 12

Appel sans aucun argument
premier argument : -1
deuxieme argument : 12

Fastoche !

Propriétés des arguments par défaut

Si le programmeur décide d'établir une déclaration de prototype avec des valeurs par defaut, alors il est OBLIGTOIRE de les déclarer en dernière liste.

float fct(int=0, long, double=1);

Ceci, est totalement INTERDIT ! Et, en effet, il est évident d'en comprendre pourquoi ?
Supposons que le compilateur ait accépté cette déclaration et qu'ensuite nous avions appelé la fonction par

fct(10,13);

alors le compilateur aurait pû très bien l'interpréter comme étant :

fct(0,10,13);

ou encore,

fct(10,13,1);

On constate bien que ce mécanisme proposé par C++ revient à fixer les valeurs par défaut dans la déclaration de la fonction, et non dans sa définition.
Nous constaterons lorsque nous aborderons le chapitre : " Constructeur d'une classe ", l'intérêt principal des arguments par défaut.


Surdéfinition de fonction

De manière globale, nous parlons de "surdéfinition", lorsqu'un même symbole possède plusieurs définitions différentes, le choix de la définition se fera en focntion du contexte.
En effet, nous connaissons à titre d'exemple, le symbole *, qui peut soit être considéré comme un opérateur d'indirection ou encore comme une multiplication.
L'un des grands atouts du C++ est de surdéfinir la plupart des opérateurs lorsqu'ils sont associés à la notion de classe.
Pour pouvoir employer plusieurs fonctions de même nom, il faut bien sûr un critère particulier permettant au compilateur de choisir judicieusement la bonne fonction. C++, basera sont choix, par l'identification du type d'argument.

Mise en oeuvre de la surdéfinition de fonction

Un peu de pratique ...

Nous alons définir deux focntions de même nom, appelé : sosie , la première fonction possèdera un argument de type char, et la seconde possédera un argument de type int, ce qui les differencie bien l'une de l'autre.
Nous transmetterons les arguments par référence.

#include <iostream>
using namespace std;

void sosie(int &); // Déclaration des prototypes
void sosie(char &);

int main (void)
  {
   int val = 3;
   char lettre = 'a';
   sosie(lettre); // sosie I : Lettre.
   sosie(val); // sosie II : Valeur
   return 0;
  }
void sosie(char &car)
 {
  cout << "sosie I : Lettre : " << car << "\n";
 }

void sosie(int &var)
 {
  cout << "sosie II : Valeur : " << var << "\n";
 }

Résultat :

sosie I : Lettre : a
sosie II : Valeur : 3

On constate que le compilateur à bien mis en place l'appel de la "bonne fonction" sosie au vu des arguments.

Exemple d'une fonction surdéfinie

Cette exemple été très simple, du fait que nous appelions toujours la fonction sosie avec un argument ayant exactement l'un des types prévus dans les prototypes( int ou char). Mais que se passera-t-il si nous appelions la fonction avec un argument de type double, long, ou encore pointeur, ou si nous avions à faire face à des fonctions de plusieurs arguments.
Nous allons donc aborder les règles de détermination d'une fonction surdéfinie.
Examinons avant, quelques exemples intuitifs.

Vous souvenez vous des conversions de type ??? :P
si non, retour au chapitre précédent.

Exemple 1:
void sosie(double); // sosie I
void sosie(int); // sosie II
char a, float b;
...
sosie(a); // appel de sosie II, après conversion de [i]c[/i] en [i]int[/i]
sosie(b); // appel de sosie I, après conversion de [i]b[/i] en [i]double[/i]
sosie('w'); // appel de sosie II, après conversion de [i]w[/i] en [i]int[/i]

Exemple 2:
void affichage(char *); // affichage I
void affichage(void *); // affichage II
char *valeur1;
double *valeur2;
...
affichage(valeur1); // appel de affichage I
affichage(valeur2); // appel de affichage II, après conversion de [i]valeur2[/i] en [i]void *[/i]

Exemple 3:
void program(double, int); // program I
void program(int, double); // program II
int a,z; double e,r; char y;
...
program(a,e); // appel de program II
program(y,r); // appel de program II, après conversion de [i]y[/i] en [i]int[/i]
program(a,z); // erreur de compilation

Dans Exemple 3:, le compilateur génére une erreur, en effet, il subvient ici, une ambiguïté au niveau de la compilation. Deux cas se présente :
Soit, le compilateur décide de convertir la variable 'a'de type int en double et de laisser z en int pour faire appel à program I, ou alors inversement. le compilateur décide de convertir la variable 'z'de type int en double et laisser a en int pour faire appel à program II.

Voilà, si vous avez à peu près compris le principe de surdéfinition de fonction, essayez de quel appel il sera quesion dans les exemples suivant.

Exemple 4:
void test (int n=0, double x=0); // test I
void test (double y=0, double p=0); // test II
int n; double z; 
...		 
test(n,z);
test(z,n);
test(n);  
test(z);  
test();


Correction :

test(n,z); // appel de test I
test(z,n); // appel de test II
test(n); // appel de test I
test(z); // appel de test II
test(); // erreur de compilation, dû à l'ambiguïté


Bun voilà, cela ne demande pas d'énorme effort, vous avez compris le principe !

Voyons un cas particulier :

Exemple 5:
void term (int); // term I
void term (const int); // term II
...
int val;
term(val);

Ici, nous obtiendrons une erreur de compilation, en effet C++ n'a pas prévu de distinguer le int de const int, ceci se justifie par le fait que les deux fonctions term recevront une copie de l'information à traiter, et il n'y aura aucun risque de modification de valeur. A comprendre ici, que l'erreur ne tient qu'à la seul présence des déclarations, indépendamment de la nature des appels.
Par contre, regardons l'exemple n°6.

Exemple 6:
void coco(int *);
void coco(const int *);
int a=4;
const b=5;

Cette fois-çi dans cette exemple, la distinction se justifie entre int * et const *, en effet on peut très bien prévoir que coco I modifie la valeur de la lvalue dont elle reçoit l'adresse, tandis que coco II n'en fait rien.
Ah oui, vous savez tous ce qu'est une lvalue ?
bon bun, je vous ecoute !
...
bizarre, plus personne ne parle !
:P
Donc nous disions, une lvalue est la référence à quelque chose dont on peut modifier. Contraction de left value, qui signifie quelque chose qui peut apparaître à gauche d'un opérateur d'affectation.

Nous pouvons réutiliser l'Exemple 6:, pour ne pas travailler avec les pointeurs, mais avec les références :

Exemple 6 Bis:
void coco(int &); //coco I
void coco(const int &); //coco II
int a=4;
const b=5;
...
coco(a); // appel de coco I
coco(b); // appel de coco II

On notera que le mode de transmission (valeur ou référence), n'intervient pas dans le choix d'une focntion surdéfinie. Par exemple, les déclarations suivantes, engendreront une erreur de compilation, dû à l'ambiguïté :

void coco(int ); 
void coco(int &);

Voyons cette exemple, que je vous propose et décrivez les conséquences de chaque appel :

void coco(int &); //coco I
void coco(const int &); //coco II
int a;
float b;
...
coco(a); 
coco(3);
coco(b);

Donc on constate que coco(a); fait appel à coco I, coco(3); fait appel à coco II, ... mais de quelle manière ?
En faite, le compilateur fera une copie éventuelle de la valeur '3' dans un entier temporaire dont la référence sera transmise coco.
Et coco(b);, fait évidemment appel à coco II, après conversation de la valeur de 'b' en un entier temporaire dont la référence sera transmise à coco.

Que se passe-t-il ?

Le compilateur établie une recherche de correspondance de type. Pour cela il dispose d'un critère d'évaluation prédéfinie, tout ceci en 3 points :

1 - Conversion à correspondance exacte :
Bon, on a compris le principe, on voit tous ce que ca veut dire. le(s) type(s) correspond(ent) aux/à type(s) argument(s) de la fonction.

2 - Conversion à correspondance avec promotions numériques :

char et short -> int.
float -> double.

Rappel :
Un argument transmis par référence ne peut être soumis à aucune conversion, aucune aucune, sauf s'il s'agit d'une référence à une constante, vous vous souvenez ?!

3 - Conversions dites standart :

Il s'agit des conversions légales en C++, c'est à dire, celles qui peuvent être imposées par une affectation sans opérateur cast.

Exception :
L'Exception survient lorsque plusieurs fonctions conviennent au même niveau de correspondance, il y a alors : ambiguïté.
Et si, aucune fonction ne convient à aucun niveau, alors il y a erreur de compilation.
  • Mécanisme de la surdéfinition de fonctions[/liste]

    Jusqu'ici nous avons étudié, comment le compilateur procédé pour faire le choix de la "bonne fonction", en agissant sur un seul fichier, mais il sedrait tout à fait possible de compiler dans un premier temps un fichier source contenant la définition des fonctions, et ensuite utiliser ultérieurement ces fonctions dans un autre fichier source en nous contentant juste d'en finir les prototypes.
    Pour que ceci soit réalisable, l'éditeur de lien doit être en mesure d'effectuer le lien entre le choix opéré par le compilateur et la "bonne fonction" figurant dans un autre module objet.
    Cette reconnaissance est basée sur la modification, par le compilateur, des noms "externes" des fonctions.
    Un problème surviendra lorsque l'on souhaitera utiliser dans un programme C++, une fonction écrite et compilée en C, ou en Fortran, ou en Assembleur, ... (de manière globale, qui possède les mêmes conventions d'appels de fonctions).
    Pour résoudre ce problème, dont on ne verra pas la nature pour le moment, la solution serait de faire précédé son prototype par la mention extern "C".

    Soit la fonction écrite et compilé en C :

    float rad(float a, float b, int c);

    Dans le file C++, il nous suffira de fournir son prototype de cette forme :

    extern "C" float trame(float a, float b, int c);

    [list]La forme collective de la déclaration extern est :
extern "C"
{
void coco(int &);
void sosie(int &var);
void fct(int=-1, int=12);
};


Le problème évoqué pour les fonctions C (Assembleur / Fortran) se pose, pour toutes les fonctions de la bibliothèque standart C que l'on réutilise en C++. On pourrait aussi employer au sein d'un même programme C++, une fonction C, ou Fortran, ou Assembleur et une ou plusieurs fonctions C++ de même nom mais d'arguments différents.
Par exemple :
float rad(float x);
float rad(double c);

Qui deviendra ainsi :

extern "C" void rad(int);
void rad(int *);
void rad(char);


Voili, voilà, la fin de ce paragraphe, bon c'est pas très difficile tout ça, on approfondira plus avec les exos.


Les opérateurs new et delete

Vous vous rappelez quels sont les fonctions du langage C qui s'occupe de la GDM (gestion dynamique de la mémoire) ?

Malloc et free bien sûr, pouvons-nous les utiliser en C++ ? Béh oui, quelle question !
Mais le contexte de la programmation orientée objet, a fait introduire deux nouveaux concepts :
new et delete, pariculièrement adaptés à la GDO ( Gestion dynamique d'objets).
Par soucis d'homogénéité et de simplicité il serait plus judicieux d'utiliser ces nouveaux opérateurs, et de jeter, le plus loin possible le vieux Malloc et son comparse free.

Le new, vous avez dit new ?

Pour allouer de la mémoire :

exemple 1 :

La déclaration,

int * case

et l'instruction :

case = new int;

Voilà, c'est tout, pas plus !
Ceci permet d'allouer un espace mémoire pour un élément de type int et d'affecter à case l'adresse correspondante.
En C, on faisez quoi dejà ? et bien ca :

case = (int*) malloc((int)); // int* étant facultatif.

Ah oui, je ne vous l'avais pas dit au début, mais en C++ les déclarations ont des emplacements libres, genre :

for(int a; a<12; a++) { ... } // En C, c'est un coup de régle sur les doigts.
// mais en C++, c'est possible.

Revenons à notre new, donc on aura :

int * case = new int;

exemple 2 :

La déclaration et instruction :

char * civil = new char[50];

Permet d'allouer un emplacement nécessaire pour un tableau de 50 caractères, et place l'adresse de début dans : civil.
En C, on aura :

civil = (char *) malloc(100);


Le delete

Comme nous l'avons compris, delete, permet de libérer la mémoire, d'un emplacement allouer, bien évidemment. Si l'emplacement est alloué par l'opérateur new, alors on aura pas d'autre choix que de le libérer par delete. Ainsi pour libérer l'emplacement mémoire de nos exemples précédent nous feront :

int * case = new int;
...
delete case

Capiche ?!

REMARQUE :
Nous verrons lorsque nous aborderons les tableaux d'objets, l'autre syntaxe de delete.


L'opérateur new (Nothrow)

L'opérateur new, peut générer une exception bad_alloc en cas d'échec. Dans les versions d'avant la norme, l'opérateur new, comme malloc, fournissait un pointeur nul, mais avec la nouvelle norme, on retrouve aussi se comportement en remplaçant le new habituel, par new(std::nothrow),(std:: etant superflu du moment où nous avons déclaré l'espace de nom par using).
Pour mieux comprendre, regardons cette exemple, qui permet d'allouer des emplacements mémoire pour des tableaux d'entiers dont la taille est fournie en donnée (prenez une réservation de taille assez grande, sinon vous aurez beaucoup de tableaux de petite taille, et l'éxécution sera très longue, enfin ... cela dependra aussi de la taille restante de votre OS)

Voici le code avec new(nothrow) :

#include <iostream> 
#include <cstdlib> // en C :stdlib.h

using namespace std;

int main (int argc, char *argv[])
 {
  long taille; int * adresse; int nbloc;
  cout << "Taille souhaitée ? ";
  cin >> taille;
  for(nbloc =1;; nbloc++)
	{ adresse = new (nothrow)  int [taille];
	  if (adresse == 0) { cout << "*** Manque Memoire ***\n";
						  exit (-1); 
				}
	  cout << "Allocation bloc numéro : " << nbloc << "\n";
	}
  delete adresse;
  return 0;
 }

Exécution :

[cmos@localhost test]$ g++ test.cpp -o test
[cmos@localhost test]./test
Taille souhaitée ? 70000000
Allocation bloc numéro : 1
Allocation bloc numéro : 2
Allocation bloc numéro : 3
Allocation bloc numéro : 4
Allocation bloc numéro : 5
Allocation bloc numéro : 6
Allocation bloc numéro : 7
Allocation bloc numéro : 8
Allocation bloc numéro : 9
Allocation bloc numéro : 10
*** Manque Memoire ***


Voici le même code sans new(nothrow) :

#include <iostream> 
#include <cstdlib> // en C :stdlib.h

using namespace std;

int main (int argc, char *argv[])
 {
  long taille; int * adresse; int nbloc;
  cout << "Taille souhaitée ? ";
  cin >> taille;
  for(nbloc =1;; nbloc++)
	{ adresse = new int [taille];
	  if (adresse == 0) { cout << "*** Manque Memoire ***\n";
						  exit (-1); 
				}
	  cout << "Allocation bloc numéro : " << nbloc << "\n";
	}
  delete adresse;
  return 0;
 }

Exécution :

[cmos@localhost test]$ g++ test.cpp -o test
[cmos@localhost test]./test
Taille souhaitée ? 70000000
Allocation bloc numéro : 1
Allocation bloc numéro : 2
Allocation bloc numéro : 3
Allocation bloc numéro : 4
Allocation bloc numéro : 5
Allocation bloc numéro : 6
Allocation bloc numéro : 7
Allocation bloc numéro : 8
Allocation bloc numéro : 9
Allocation bloc numéro : 10
terminate called after throwing an instance of 'std::bad_alloc'
  what(): St9bad_alloc
Abandon

Que faut-il en comprendre ?

L'operateur new seul permet d'allouer ou faire une réservation mémoire et de lever une exception std::bad_alloc si il échoue, tandis que new (nothrow) permet aussi d'allouer de la mémoire, mais si il échoue il renverra NULL, et ne lévera pas d'exception. Raison pour laquelle nous avons pû gérer nous-même celle-çi par :

[bloc]
if (adresse == 0) { cout << "*** Manque Memoire ***\n";
exit (-1);
}

[/bloc]

Nous aurions pû mettre bien évidemment à la place de : if (adresse == 0) ceci, if (adresse == NULL), ce qui revient au même.

set_new_handler : Gestion des Débordements de mémoire

Comme vu précédemment new déclenche une exception : bad_alloc en cas d'échec de réservation mémoire. Il nous ait permit aussi de définir une fonction de notre choix et de faire appel à celle-çi, en cas de manque mémoire. Pour cela, nous utiliserons la fonction : set_new_handler, en lui fournissant en argument l'adresse de la fonction prévue pour traiter le manque de mémoire.

Exemple :

/*********************************/
/** GESTION DEBORDEMENT MEMOIRE **/
/**	   SET_NEW_HANDLER	   **/
/*********************************/

#include <iostream> 
#include <new> // utilisation de set_new_handler
#include <cstdlib> // en C :stdlib.h pour "exit"

using namespace std;
void deborde (void); // prototype pour gestion de manque mémoire.
void set_new_handler(void(*));
 
int main (int argc, char *argv[])
 {
  set_new_handler(&deborde);
  int nbloc; char c;
  long taille;
  int * adresse;
  
  cout << "taille du bloc souhaité ?";
  cin >> taille;
  cout << "Réservation de : " << taille * sizeof(int) << " octets par bloc \n";

  for(nbloc=1;;nbloc++)
   {
	adresse = new int [taille];
	cout << "Allocation bloc numero : " << nbloc << "\n";
   }
  return 0;
 }

void deborde (void) 
 {
  cout <<"Mémoire Insuffisante !\n";
  cout <<"Abandon de l'éxécution.\n";
  exit (-1);
 }


Résultat

taille du bloc souhaité ? 123456789
Réservation de : 493827156 octets par bloc 
Allocation bloc numero : 1
Allocation bloc numero : 2
Allocation bloc numero : 3
Allocation bloc numero : 4
Allocation bloc numero : 5
Mémoire Insuffisante !
Abandon de l'éxécution.

Vous avez remarquez que dans le programme main, il n'est question d'appeler deborde(), cette fois, on commence par appeler la fonction set_new_handler, à laquelle on precise l'adresse d'une fonction (nommee ici deborde). Cette derniere sera appelée automatiquement dès qu'une allocation mémoire aura echoué, de sorte qu'il n'est plus necessaire de prévoir un test à chaque appel de new.
Interressant n'est ce pas ?


Spécification inline ?

En C, nous connaissions deux notions très proches l'une de l'autre qui sont : les macros et les fonctions. Lorsque nous appelions une macro nous faisions suivre leur noms d'une liste d'argument, n'est ce pas, et il en allait de même pour une fonction, cependant :
  • Les instructions correspondant à une macro étaient incorporées dans notre programme, au niveau du préprocesseur, à chaque fois que nous l'appelions.
    Les instructions correspondant à une fonction étaient générées en une seule fois, par le compilateur sous forme de langage machine, à chaque appel donc, il sera mis en place des instructions nécessaires pour établir la liaison entre le programme et la fonction :
  • Sauvegarde de "l'état courant"
  • Recopie des valeurs des arguments.
  • Branchement avec convervation de l'adresse retour.
  • Recopie de la valeur retour.
  • Restauration de l'état courant.
  • Retour dans le programme.

    Toute ces instructions nécessaires à la mise en oeuvre d'une fonction n'existe pas pour les macros. Ainsi, les fonctions permettent de gagner de la place mémoire par rapport aux macros, mais on perd en contre-partie un temps relativement supérieur.

    Par contre, les macros peuvent entraîner des "effets de bords" non_désirés. En C, lorsque vous avez besoin de faire appel à une fonction, et que le temps d'exécution est primordiale, on fait appel aux macros. En C++, on utilisera la spécification inline.
Exemple :

#include <iostream>
using namespace std;

inline int suite(int nombre)
 {
 int i; int resultat=0; 
 for(i=0; i <=nombre; i++)
	{
	 cout << " " << i; if(i != nombre) cout<< " +"; else cout<< " = ";
	 resultat =  resultat + i;
	}
  return resultat;
 }

int main (int argc, char *argv[])
 {
  int n;
  
  cout << " Donnez n , pour n! : ";
  cin >> n;
  
  cout << suite(n) << "\n";
  return 0;
 }

Résultat :

Donnez n , pour n! : 5
 0 + 1 + 2 + 3 + 4 + 5 = 15

La fonction suite reçoit un argument de type int, pour générer la suite arithmétique de cette valeur. La présence du mot inline, demande au compilateur de traîter la fonction suite différemment d'une fonction ordinaire. A chaque appel de la fonction, le compilateur devra incorporer au sein du programme en langage machine les instructions correspondantes. Le mécanisme de gestion de l'appel et du retour n'existera plus, ce qui permet de gagner du temps. En revanche, vu qu'il n'y aura plus ce mécanisme de sauvegarde, recopies ... il y aura une consommation de quantité mémoire qui accroissera en fonction des appels de la fonction.
Chose très importe, une fonction inline, ne peut pas être compilé séparément, en effet, elle doit être dans le même fichier source.



Autre exemple :

/*************************************/
/* SIMULATION D'UN SIGNAL SINUSOIDAL */
/*************************************/

#include <iostream>
#include <cmath> // utilisation de sinus ligne 18
#define PERIODE 20 // 20 : nombre de point correspondant a une période	 
#define AMPLITUDE 10

using namespace std; // utilisation des fichiers d'en-tête standart.

inline void affiche(int n)
 {
  for(int i=0;i<n;i++)
  cout << " ";
  cout << "*";	
 }

inline int sinus_entier(float x)
 {
  int p = (sin(x)+1) * AMPLITUDE;
  return (p);
 }

/***********************/
/* PROGRAMME PRINCIPAL */
/***********************/

int main(int argc, char **argv) 
{
 float n_p;
 cout << "nombre de période "; // évitez la folie des grandeurs ! 1 pour une T(=période) !
 cin >> n_p;
 for(int i=0;i<(n_p * PERIODE); i++) 
  {
   affiche(sinus_entier(i/3.0));
   cout << "\n";  
  }
 return 0;
}

Resultat

*
			 *
				*
				  *
				   *
				   *
				   *
				 *
			  *
		   *
		*
	*
  *
*
*
*
*
	*
	   *
		  *

Bon, vous devinez bien sûr à quoi sert ce programme ...
On rabache pas.

Et vous allez m'dire, pourquoi se casser la tête quand on peut faire simple :

int main(int argc, char **argv) 
{
 for(int i=0;i<30; i++) 
  {
   int p = (sin(i/3.0)+1) * 10;
   for(int f=0;f<p;f++)
   cout << " ";
   cout << "*\n";	
  }
 return 0;
}

lol, comme disait un certain, Mister.Cloper, qui se reconnaîtra, ... " Programme de "Pac-Man", "des foutaises", on verra quand vous programmerez des petits trucs genre, 500 milles lignes ... on verra l'allure du main.
Non, non, on ne néglige rien, car tout ce que nous voyons sera d'une grande utilité par la suite.
Donc, entraînez-vous à comprendre et à utiliser les fonctions inline.

Avantages :Macro : Economie du temps d'éxécution.
Fonction : Economie d'espace mémoire, compilation séparée possible.
F_Inline : Economie du temps d'exécution.
Inconvéniant :Macro : Perte d'espace, risque effets de bord, pas de compilation séparée
Fonction : Perte de temps d'éxécution.
F_inline : perte de mémoire, pas de compilation séparée.
L'Opérateur de cast

En C++, comme en C, il nous ait possible d'exercer des conversions explicites à l'aide de l'opérateur de cast, ces conversions comportent évidemment les conversions implicites légales (celles que nous avons précédemment vu), auxquelles s'ajoutent quemques autres qui peuvent être dégradantes ou dépendantes de l'implémentation.
  • const_cast, permet l'ajout ou la suppression à un type, l'un de ces modificateurs tel que : const ou volatile
  • reinterpret_cast, permet la conversion dont le résultat dépend de l'implémentation. Conversion d'entiers vers pointeurs par exemple et vis versas.
  • static_cast, permet la conversions indépendantes de l'implémentation. Conversions de pointeurs vers pointeurs.
#include <iostream>
using namespace std;
int main (int argc, char ** argv) 
{
 int nombre = 1;
 const int * ad1 = &nombre;
 int * ad2;

 ad2 = (int *) ad1; // ecrire : ad2 = ad1 serait rejeté.
 ad2 = const_cast<int*>(ad1); // forme conseillée.
 ad1 = ad2; //légal
 ad1 = (const int*) ad2; // ancienne forme
 ad1 = const_cast <const int *> (ad2); // forme ANSI conseillé
 const int p = 12;
 const int * const ad3 = &p;
 int * ad4;
 ad4 = (int *) ad3;
 ad4 = const_cast <int*> (ad3);
}

L'espace de noms.

Lorsque l'on doit utiliser plusieurs bibilothèques dans un programme, on peut être confronté au problème de : " Pollution d'espaces noms", lié à ce qu'un même identificateur peut très bien avoir été utilisé par plusieurs bibliothèques, tout simplement. Ce même problème proviendra lorsque nous developperons des grosses applications, raison pour laquel le C++ ANSI à introduit le concept de l'espace nom, qui s'agit de donner à un "espace" de déclarations, un nom, ... de cette manière :

namespace une_biblio
{
// déclarations usuelles
}


Et pour se définir à des identificateurs définis dans cet "espace de noms", on utilisera l'instruction using.
using namespace une_biblio
// ici, les identificateurs de une_biblio seront donc connus.


On étudiera plus tard, bien en profondeur ce concept d'espace de noms.

Le type Bool

Ce type simple est formé de deux valeurs notées true et false, ou 1 et 0. Ce type sert surtout à apporter de la clarté au programme sans pour autant modifier le code. Et je vous laisse regarder la taille du type bool !


FIN DE CE COURS ! :P



LES NEXERCICES

héhé, dernière étape avant le cours sur les classes / objets,
Ces exercices feront appels bien évidemment aux chapitres précédent :

_________________________________________


Exercice I : Input-Output

Voyez ce code,

#include <stdio.h>
#include <conio.h>
int main(int argc, char **argv)
{
 int age;
 char nom[10], prenom[10];
 printf("Donnez votre nom, votre prenom et ensuite votre age ?\n");
 scanf("%s %s %d", nom, prenom, &age);
 printf(" Vos Informations,\nNom : %s\nPrenom : %s\nAge : %d\n", nom, prenom, age);
 getch ();
 return 0;
}
}

Modifier-le pour ne faire appel qu'aux nouvelles fonctionnalitées input-output, c'est à dire, pas de printf ou de scanf, et bien évidemment, ormis les déclarations dans le main et le return 0; il n' y a que 3 lignes de code, votre nouveau code donc, possédera donc que 3 lignes de code ni plus ni moins.

_________________________________________


Exercice II : Transmission d'argument

Reprendre le programme de l'exercice I, et créer une fonction permettant de transmettre ces informations et de les afficher :
3 mode de transmission :
- Transmission par valeur
- Transmission par adresse
- Transmission par référence
Ces codes seront écrit en C++ !

_________________________________________


Exercice III : Surcharge d'une fonction

Soit le code suivant :

int fonction (int); // fct I
int fonction (float); // fct II
void fonction (int, float); // fct III
void fonction (float, int); // fct IV

int a,b; float c,d ; char e; double z;

Ces appels sont-ils correct, si oui, expliquez déterminer la fonction appelée, et le processus d'appel.

a.fct(a);
b.fct©;
c.fct(c,a);
d.fct(a,c);
e.fct(e);
f.fct(a,b);
g.fct(b,e);
h.fct(a,z);

_________________________________________


Exercice IV : Opérateur new.

#include <iostream>
using namespace std;
int main(int argc, char **argv) 
{
  char *chaine, i;
  
  if((chaine = (char *) malloc(100)) == NULL)
   { printf("Pas assez de Place memoire\nAbandon ! ");
	 system("pause"); exit(-1);
   }
   strcpy(chaine,"Utilisation et gestion de debordement d'allocation mémoire en C\n");
   printf("%s", chaine); 
 return 0;
}

Trouvez, corrigez les erreurs et réécrivez correctement ce code en C++.

_________________________________________


Exercice V : Opérateur new.

Sur un terminal, nous avons ce résultat suivant :

taille du bloc souhaité ? 100000000 
Réservation de : 400000000 octets par bloc
Allocation bloc numero : 1
Allocation bloc numero : 2
Allocation bloc numero : 3
Allocation bloc numero : 4
Allocation bloc numero : 5
Allocation bloc numero : 6
Allocation bloc numero : 7
 Mémoire Insuffisante !
 Abandon de l'éxécution ligne : 31

Vous rédigerez un code en C++ générant ce résultat, tout en gérant la gestion de débordement mémoire, et ceci de deux manière :
- Avec, new (nothrow).
- Avec, set_new_handler.

Exercice VI : Fonction inline.

a) Transformer ce code, pour que la fonction norme devienne une fonction inline.
b) Comment procéder pour que cette fonction soit à présent compilé séparemment.


#include <iostream>
#include <cmath>

using namespace std;
double norme(double *);

int main (int argc, char *argv[])
 {
  double v1[3], v2[3];
  int i;
  
  for(i=0;i<3;i++)
   {	
	v1[i]=i; 
	v2[i]=2*i-1; 
   }
  cout << "Norme de v1 : " <<norme(v1); 
  cout << "\nNorme de v2 : " << norme(v2) << "\n";
  return 0;
 }

double norme(double vec[3])
 {
  int i; double res = 0;
  for(i=0;i<3;i++)
	res+= vec[i] * vec[i];
  return sqrt(res);
 }

Modifié par C-Mos, 22 février 2007 - 12:12 .

  • 0

#7 C-Mos

C-Mos

    Junior Member

  • Membres
  • 17 messages

Posté 20 février 2007 - 11:28

Chapitre : Classes et Objets, a suivre ...
Reprise d'activité :P
donc les prochains postes seront pour ... je sais pas trop. desolé ...
@bientot

Modifié par C-Mos, 21 août 2008 - 09:14 .

  • 0

#8 imeness

imeness

    Junior Member

  • Membres
  • 3 messages

Posté 10 décembre 2007 - 02:14

Grand merci à vous :P
  • 0

#9 Marc07

Marc07

    Junior Member

  • Membres
  • 2 messages

Posté 20 septembre 2013 - 11:07

Franchement un grand merci pour tout se travaille, en attendant la suite je vais étudier cette première partie attentivement.


  • 0

#10 isyfur

isyfur

    Junior Member

  • Membres
  • 5 messages

Posté 28 septembre 2013 - 08:08

Hum... Je sais que c'est beaucoup de boulot, bravo pour cela. Malheureusement, je dirai d'éviter ce cours dans l'état.

 

Les utilisations de delete (sans crochets) suivent des new[]. L'allocation nothrow est vue alors que son intérêt est des plus restreint. Il est bien plus important de former les nouveaux utilisateurs à faire du C++ (et non du C/C++). Ce n'est pas un hasard si le tuto de OC (ex-sdz) fut revu il y a quelques années pour passer au C++ moderne et intégrer std::string et std::vector<> bien avant les pointeurs.

En fait, même si vous connaissez le C, et que vous voulez apprendre le C++, je vous dirais de mettre momentanément de côté cette information et de repartir de 0. Je sais, ça peut paraitre lourd, mais les bonnes pratiques dans ces deux langages n'ont rien à voir. Cet article est probablement la meilleure illustration de cela: c'est une (traduction d'une) réponse à Raymond Chen sur : "comment reconnaitre en quelques secondes un mauvais code" -> http://alexandre-lau...-ou-exceptions/

Sa compréhension explique beaucoup de choses quant aux cours qui se voient dénoncés dans les communeautés C++ en ligne (usenet, SO, dvpz, sdz/oc, ...).


Modifié par isyfur, 28 septembre 2013 - 08:15 .

  • 0