Mmh~ je sent que je vais en passer du temps sur ce forum... oups pardon j' était perdu dans mes pensées BREF
Je cherche a créer une class Fraction et j'ai quelque problème lors de la compilation (ça compile pas). Je souhaiterai faire en sorte que mon constructeur, lorsque je ne met aucun argument lors de mon initialisation de variable (ci-dessous), mette ma variable tout seul a 0/1 (donc valeurs d'argument par défaut) et dans le cas contraire qu'il utilise les valeurs entrées par l'utilisateur.
#include <iostream>
#include "Fraction.h"
const int END = 0;
int main()
{
Fraction f1;
Fraction f2(2);
Fraction f3(2, 4);
f1.show();
return (END);
}
On devrait donc avoir f1 = 0/1, f2 = 2/1 et f3 = 2/4 (dans un premier temps...).
Voici ma class:
#pragma once
class Fraction
{
public:
Fraction(int x = 0, int y = 1);
void show() const;
private:
int mX;
int mY;
};
Mes methods:
#include <iostream>
#include "Fraction.h"
using std::cout;
using std::endl;
Fraction::Fraction(int x = 0, int y = 1) : mX(x), mY(y)
{
}
void Fraction::show() const
{
cout << mX << "/" << mY << endl;
}
Et mes erreures de compilation:
Merci d'avance pour votre aide!
PS: Je sais le cours de C++ d'OC est très mauvais, je devrais plutôt suivre celui de Guillaume Belz ce que je fais en parallèles... Je veux voir ce que je devrais faire contre ce que je ne dois surtout pas faire eeeet~ ça fais plus d'exo.
PS: Si vous voyez une critique a faire sur mon code (manière de coder), allez y, j'essaye de coder le plus propre possible~
EDIT--
Au final j'ai trouver avec un peu d'aide extérieure; désole pour le post inutile...
Pour ceux qui passe par le même problème, il suffit juste de laisser les arguments "basiques" dans le prototype du constructeur dans le fichier .cpp comme cela:
class Fraction
{
public:
Fraction(int x = 0, int y = 1);
void show() const;
private:
int mX;
int mY;
};
Fraction::Fraction(int x, int y) : mX(x), mY(y)
{
}
Voila, et ça devrais compiler et marcher comme voulus! Moi ça marche en tout cas...
PS: Vous pouvez comme même m'envoyer vos commentaires de code
- Edité par Rymfire 21 septembre 2018 à 12:30:37
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
Juste une chose, ça ne me semble pas terrible de faire ça c'est ambigu je trouve, d'ailleurs le compilo' le dit aussi si tu ajoutes un constructeur par défaut.
À partir du moment où on est censé pouvoir instancier une classe sans paramètre, il devrait y avoir un constructeur par défaut avec une initialisation directe des variables dans la classe elle-même, ce qui a en plus l'avantage de ne pas être contraint d'initialiser à chaque fois les variables non précisées à la construction dans la liste d'initialisation :
class Fraction
{
public:
Fraction() = default;
Fraction(int x) : mX{x} {}
Fraction(int x, int y) : mX{x}, mY{y} {}
void show() const;
private:
int mX{0};
int mY{1};
};
Et prend l'habitude d'initialiser les variables avec les accolades. C'est plus sûr.
int i;
++i; // 4200348 : Undefined behavior
int j{};
++j; // 1 : Ok
float k{1.5f};
int k2(k); // Le compilateur ne dit rien...
int k3{k}; // Warning du compilateur en indiquant une "narrowing conversion"
class Fraction
{
public:
Fraction() = default;
Fraction(int x) : mX{x} {}
Fraction(int x, int y) : mX{x}, mY{y} {}
void show() const;
private:
int mX{0};
int mY{1};
};
Salut, merci pour ton commentaire d'abord. En ce qui concerne le code, je vais m'y atteler de suite!
Le mot-clé `default;` signifie que le constructeur appelé par défaut est celui sur la même ligne?
Je ne savais pas qu'on pouvais initialiser les valeurs de mX et mY directement dans la class ou alors es-ce les valeurs par défaut qui sont introduite? Cela marche t'il seulement avec les accolades?
Guit0Xx a écrit:
Et prend l'habitude d'initialiser les variables avec les accolades. C'est plus sûr.
int i;
++i; // 4200348 : Undefined behavior
int j{};
++j; // 1 : Ok
float k{1.5f};
int k2(k); // Le compilateur ne dit rien...
int k3{k}; // Warning du compilateur en indiquant une "narrowing conversion"
J'aimerai bien mais mon compilateur ne semble pas vouloir le compiler... je vais peut être essayer avec g++ mais je n'ai pas accès au pc avec celui-ci pour le moment. Je vais voir ton link
- Edité par Rymfire 23 septembre 2018 à 11:42:17
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
Le mot-clé `default;` signifie que le constructeur appelé par défaut est celui sur la même ligne?
Non, il indique qu'il n'y rien a de spécial et que le compilateur peut créer à ta place le constructeur de la ligne (ici c'est le constructeur par défaut.)
Guit0Xx a écrit:
Je ne savais pas qu'on pouvais initialiser les valeurs de mX et mY directement dans la class ou alors es-ce les valeurs par défaut qui sont introduite? Cela marche t'il seulement avec les accolades?
Les accolades sont utilisables depuis 2011 (le C++11 ou C++0x), le égal est aussi possible mais pour un meilleur contrôle il faut préférer les accolades. Cette expression permet d'indiquer ce qui sera fait pour les membres qui ne sont pas explicitement initialisés dans chacun des constructeurs, donc le constructeur par défaut dans ton cas exemple.
Guit0Xx a écrit:
J'aimerai bien mais mon compilateur ne semble pas vouloir le compiler... je vais peut être essayer avec g++ mais je n'ai pas accès au pc avec celui-ci pour le moment. Je vais voir ton link
Indique nous l'erreur que tu reçois. Ton compilateur doit être d'avant 2011 ou sinon c'est que la fonctionnalité C++11 n'est pas activée (et le message d'erreur devrait d'indiquer quoi faire.)
Les valeurs par défaut ne doivent être fournie que dans la déclaration des fonctions, l'implémentation des fonctions, quand elle est séparée (comme c'est le cas ici) ne doit pas contenir ces valeurs.
Pour aller plus loin:
Il faut être particulièrement prudent lorsque l'on décide de préciser une (ou pire, des) valeur(s) par défaut pour les paramètres d'une fonction.
Il y a deux raisons majeures à cette prudence:
La première est que chaque valeur par défaut ajoute un prototype possible pour la fonction, et qu'on peut donc "assez facilement" créer des conflits. Je m'explique:
Mettons que nous déclarions une fonction proche de
void foo(int i = 0, int j = 0, int k = 0);
Les trois valeurs par défaut nous permettent d'appeler foo de quatre manière différentes, à savoir
foo() qui utilise les trois valeurs par défaut (i == 0, j == 0, k == 0)
foo(1) qui associe la valeur 1 à i, et les valeurs par défaut pour j et k
foo(1,2) qui associe la valeur 1 à i, la valeur 2 à j et la valeur par défaut pour k
foo(1,2,3) qui associe la valeur 1 à i, la valeur 2 à j et la valeur 3 à k
Si "je suis distrait", et que j'essaye de créer, à coté de cette fonction, une autre fonction foo "surchargée" proche de
void foo(int i, int j, int k);
Le prototype de cette fonction utilisera le même nombre de paramètres de même type que la quatrième possibilité offerte par les paramètres par défaut, et cela occasionnera un conflit, le compilateur ne sachant pas -- lors de l'appel à foo(1, 2, 3) s'il doit faire appel à la "version" qui dispose de valeurs par défaut ou à la version qui n'en a pas.
En un mot: le nombre et / ou le type des paramètres de chaque surcharge de fonction doit être strictement unique. Par exemple, une fonction proche de
void foo(int i, int j, float k)
serait elle tout à fait acceptée car nécessite également trois paramètre, mais il y a un type qui est différent de tous les autres prototypes
La deuxième raison découle directement de la première, mais est beaucoup plus subtile: il faut s'assurer que chaque prototype offert par la présence des valeurs par défaut soit cohérent et sensé. Je m'explique:
Il faut, pour chaque prototype autorisé par les valeurs par défaut, se poser la question de savoir si il y a du sens à permettre à la fonction avec les paramètres en question.
Comme nous sommes, dans le cas présent, occupés à travailler sur le constructeur, nous devons donc nous poser la question
Y a-t-il du sens à permettre la création d'une donnée de type fraction sans aucune valeur?
Y a-t-il du sens à permettre la création d'une donnée de type fraction avec une seule valeur?
Y a-t-il du sens à permettre la création d'une donnée de type fraction avec deux valeurs?
la réponse au (3) est sans équivoque : bien sur qu'il y a du sens à permettre la création d'une fraction sous la forme de
fraction f{2/3};
La réponse au (2) est déjà "beaucoup moins claire", car, s'il y a effectivement du sens à permettre la création d'une fraction sous la forme de
Fraction f{5}; // f == 5/1
il faut se poser la question de savoir "Mais en aurons nous besoin?". Et la réponse à cette question n'est pas forcément "oui"!!!
Bien sur, dans l'immédiat, tu fais juste des essais pour essayer de comprendre le principe, et tu serais donc sûrement tenté de répondre oui à cette question. Mais, essaye de "voir un peu plus loin" que ton essais actuel, et de réfléchir à un programme qui devrait utiliser ta classe Fraction, et, surtout, à la manière dont ce programme créera les différentes données de type fraction.
S'il s'agit d'une programme qui crée les fractions sous une forme qui pourrait être proche de
int main(){
for(int numerator = 0; numerator<10; ++numerator){
for(denominator = 1; denominator<11; ++denominator{
Fraction f{numerator, denominator};
/* on utilise f ici */
}
}
return 0;
}
La réponse est clairement "non" : on n'a absolument pas besoin de pouvoir créer une fraction en ne fournissant que le numérateur.
Et même si le programme en question venait à demander à l'utilisateur d'introduire les valeurs pour chaque fraction créée ou à lire ces valeurs dans un flux quelconque, on devrait s'attendre à ce qu'il demande (ou extraie du flux) à chaque fois ... la valeur du numérateur et du dénominateur.
Si bien que la réponse resterait clairement "non".
Et les choses sont encore plus subtiles pour le (1), car, le principe de base est de s'assurer que les données que l'on crée soient cohérentes et sensées dés le moment de leur création.
Or, ta classe Fraction représente une valeur. Il faut donc que la valeur représentée par une fraction "créée par défaut" soit ... cohérente et sensée.
Pour répondre à la question de savoir s'il est sensé de permettre la création d'une fraction sans fournir aucune valeur, il faut d'abord et avant tout... déterminer quelle valeur peut être utile comme valeur par défaut cohérente et sensée
Est-ce 0/1 ( == 0) ?
Est-ce 1/1 ( == 1 ) ?
Est-ce 0/0 (valeur indéfinie, qui provoquera une erreur à l'exécution si on essaye de l'évaluer) ?
Est-ce "une autre valeur arbitraire" (et surtout laquelle)?
Et, une fois que nous aurons déterminé cette valeur par défaut (afin de répondre à la question "est-il sensé de permettre la création d'une fraction de cette manière?", il faudra encore répondre à la question "Mais en aurons nous besoin?".
Et, pour répondre à cette question, nous devrons répondre à une autre question beaucoup plus vaste qui est
Quel usage va-t-on faire de nos fractions?
Ou, pour être plus précis encore :
La manière dont on va utiliser nos fractions implique-t-elle -- ne serait-ce que dans quelques situations très particulière -- d'utiliser la valeur par défaut ?
Et, malheureusement, la réponse à cette question est très loin d'être claire: Même le fait d'envisager l'utilisation de collections de fractions comme std::vector<Fraction>, comme std::map<float, Fraction> ou comme std::array<Fraction, NombreDeFractions> ne permet pas de donner de réponse précise.
Car, si tu décide de ne pas fournir la possibilité de créer une fraction avec "une valeur par défaut", tu imposeras "une manière de travailler" avec de telles collections, et cela peut être une excellente chose parce que cette manière de travailler sera "plus sécurisante" ... ou non.
Par contre, si tu décide de fournir cette possibilité, tu autoriseras certaines manière de travailler, et, encore une fois, cela peut être une excellente chose ... ou non.
Car, le problème à ce moment là, c'est que les manières de travailler que tu autoriseras peuvent très facilement provoquer des catastrophes (surtout si tu choisi 0/0 comme valeur par défaut). Or, si je devais abandonner les principes SOLID au profit d'un seul conseil à donner, je choisirais de donner le conseil de Scott Meyers dont la traduction est
Rendez vos interface faciles à utiliser correctement et difficile à utiliser de manière incorrecte
En autorisant l'utilisation de valeurs par défaut, tu rend surtout ta classe ... beaucoup plus facile à utiliser de manière incorrecte.
Personnellement, je choisirais très certainement de rendre ma classe "plus difficile" à utiliser de manière incorrecte. Et toi?
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
À partir du moment où on est censé pouvoir instancier une classe sans paramètre, il devrait y avoir un constructeur par défaut avec une initialisation directe des variables dans la classe elle-même, ce qui a en plus l'avantage de ne pas être contraint d'initialiser à chaque fois les variables non précisées à la construction dans la liste d'initialisation :
Pas d'accord. C'est écrire 3 lignes pour faire la même chose qu'une seule. La version intiale me convient.
class Fraction
{
public:
Fraction(int x = 0, int y = 1) : mX{x}, mY{y} {}
void show() const;
private:
int mX{0};
int mY{1};
};
(Il peut y avoir une discussion sur les valeurs par défaut et où les mettre. Mais cela ne justifie pas d'ecrire 3 constructeurs au lieu d'un).
Et si on a besoin de plusieurs constructeurs, il sera souvent pertinent d'utiliser les delegating constructors (les constructeurs qui vont appeler un autre constructeur) pour avoir moins de code a écrire, tout en garantissant les comportement par defaut.
Par exemple :
class Fraction
{
public:
Fraction() : Fraction(0, 1) {}
Fraction(int x) : Fraction(x, 1) {}
Fraction(int x, int y) : mX{x}, mY{y} { do_someting(); }
void show() const;
private:
int mX{0};
int mY{1};
};
koala01 a écrit:
Si "je suis distrait", et que j'essaye de créer, à coté de cette fonction, une autre fonction foo
Ce n'est pas très pertinent comme "distraction". Le compilateur produit une erreur et tu es obligé de corriger. Il n'y a pas de risque d'ecrire un code invalide.
koala01 a écrit:
En un mot: le nombre et / ou le type des paramètres de chaque surcharge de fonction doit être strictement unique. Par exemple, une fonction proche de
Paradoxalement, cette signature me pose plus de problème. Parce qu'il est facile de se tromper dans le code, d'appeler le mauvais constructeur et d'avoir un comportement qui n'est pas celui qu'on attend, sans que ce problème soit très evident a voir.
(Je parle du cas où le seule changement entre 2 signatures serait entre un int vs un float)
foo(1,2,3.); // facile de louper le point
A mon sens : il ne devrait pas y avoir des telles surcharges. Préférer utiliser des noms différents ou avoir des paramètres assez differents pour ne pas avoir d'ambiguité.
Ce n'est pas très pertinent comme "distraction". Le compilateur produit une erreur et tu es obligé de corriger. Il n'y a pas de risque d'ecrire un code invalide.
Nous sommes bien d'accord là dessus : le compilateur t'insultera parce que tu fais "quelque chose d'invalide".
Mais, reviens dix ans en arrière, à l'époque où tu avais toutes les peines du monde à comprendre ce que le compilateur te disait
Ou penses peut-être à une situation plus récente qui aurait pu t'arriver suite à cette distraction, et dans laquelle tu auras passé "un certain temps" à te "gratter la tête" en te demandant "mais qu'est ce qu'il veut me dire cet emm**eur".
j'admets volontiers qu'une code aussi trivial que
void foo(int i = 0, int j = 0, int k = 0 ){
}
void foo(int i , int j, int k){
}
provoquera une sortie "relativement compréhensible" du genre de
g++ main.cpp
main.cpp:5:6: error: redefinition of ‘void foo(int, int, int)’
void foo(int i , int j, int k){
^~~
main.cpp:2:6: note: ‘void foo(int, int, int)’ previously defined here
void foo(int i = 0, int j = 0, int k = 0 ){
Mais ce n'est pas toujours aussi clair!
Quoi, tu ne t'es jamais gratté la tête face à un tel message???
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Je voulais dire : "rappelle toi le (pas si) bon vieux temps (que cela)"
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
J'aimerai bien mais mon compilateur ne semble pas vouloir le compiler... je vais peut être essayer avec g++ mais je n'ai pas accès au pc avec celui-ci pour le moment. Je vais voir ton link
Indique nous l'erreur que tu reçois. Ton compilateur doit être d'avant 2011 ou sinon c'est que la fonctionnalité C++11 n'est pas activée (et le message d'erreur devrait d'indiquer quoi faire.)
Voici l’erreur de compilation que je reçois:
Mais il faut savoir que je code avec 2 pc parce que l'un est sous windows et l'autre sous linux (l'erreur si dessus viens de windows). Je n'ai jamais essayer sous linux.
koala01 a écrit:
Car, le problème à ce moment là, c'est que les manières de travailler que tu autoriseras peuvent très facilement provoquer des catastrophes (surtout si tu choisi 0/0 comme valeur par défaut). Or, si je devais abandonner les principes SOLID au profit d'un seul conseil à donner, je choisirais de donner le conseil de Scott Meyers dont la traduction est
Rendez vos interface faciles à utiliser correctement et difficile à utiliser de manière incorrecte
En autorisant l'utilisation de valeurs par défaut, tu rend surtout ta classe ... beaucoup plus facile à utiliser de manière incorrecte.
Personnellement, je choisirais très certainement de rendre ma classe "plus difficile" à utiliser de manière incorrecte. Et toi?
Pour ce projet, je m’entraîne donc je vais continuer a le faire de la manière dites au dessus. Car, on le sait bien, en programmation, c'est en codant qu'on apprend a faire. CEPENDANT, je retiens ta réflexion car effectivement, dans un futur projet il serai probablement utile de penser a la manière d'utiliser la classe que je construit. Je choisirai très probablement "Facile a utiliser, difficile a maîtriser" je pense...
gbdivers a écrit:
Pas d'accord. C'est écrire 3 lignes pour faire la même chose qu'une seule. La version intiale me convient.
class Fraction
{
public:
Fraction(int x = 0, int y = 1) : mX{x}, mY{y} {}
void show() const;
private:
int mX{0};
int mY{1};
};
(Il peut y avoir une discussion sur les valeurs par défaut et où les mettre. Mais cela ne justifie pas d'ecrire 3 constructeurs au lieu d'un).
Et si on a besoin de plusieurs constructeurs, il sera souvent pertinent d'utiliser les delegating constructors (les constructeurs qui vont appeler un autre constructeur) pour avoir moins de code a écrire, tout en garantissant les comportement par defaut.
Mais du coup je dois utiliser quelle méthode (pour être le plus propre possible)?
Et aussi je vois que vous faites tous cela:
private:
int mX{0};
int mY{1};
En fesant cela vous fetes quoi? vous initialez la valeur a 0 et 1 ou ce sont les valeurs par defaut... J'avais cru comprendre qu'on ne devrais jamais initialiser des variables dans la declaration de la classe...
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
Ton erreur ne correspond pas aux codes que tu as donné avant. C'est dans la fonction main que tu as une erreur. A priori, tu declares 2 fois la meme variable f4.
Rymfire a écrit:
En fesant cela vous fetes quoi? vous initialez la valeur a 0 et 1 ou ce sont les valeurs par defaut... J'avais cru comprendre qu'on ne devrais jamais initialiser des variables dans la declaration de la classe...
Tu dois toujours declarer tes variables dans la classe. Et oui, c'est une initialisation par defaut.
Rymfire a écrit:
Mais du coup je dois utiliser quelle méthode (pour être le plus propre possible)?
1 seul constructeur.
class Fraction
{
public:
Fraction(int x = 0, int y = 1) : mX{x}, mY{y} {}
void show() const;
private:
int mX{0};
int mY{1};
};
Ton erreur ne correspond pas aux codes que tu as donné avant. C'est dans la fonction main que tu as une erreur. A priori, tu declares 2 fois la meme variable f4.
C'est bon j'ai regle le probleme! Merci
gbdivers a écrit:
Rymfire a écrit:
En fesant cela vous fetes quoi? vous initialez la valeur a 0 et 1 ou ce sont les valeurs par defaut... J'avais cru comprendre qu'on ne devrais jamais initialiser des variables dans la declaration de la classe...
Tu dois toujours declarer tes variables dans la classe. Et oui, c'est une initialisation par defaut.
Rymfire a écrit:
Mais du coup je dois utiliser quelle méthode (pour être le plus propre possible)?
1 seul constructeur.
class Fraction
{
public:
Fraction(int x = 0, int y = 1) : mX{x}, mY{y} {}
void show() const;
private:
int mX{0};
int mY{1};
};
Ok je vois, je prend note!
Mais du coup si le programme sait que mX{0} et mY{1} par defaut, il y a t il vraiment besoin de redefinir ces limites dans la declaration du constructeur?
- Edité par Rymfire 1 octobre 2018 à 10:43:10
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
Oui, sinon aucun constructeur sans paramètre ne peut être utilisé. Le compilateur ne sait pas a priori que les paramètres que tu transmets en entrée du constructeur vont servir à directement instancier les valeur internes de ta classe.
Mais du coup si le programme sait que mX{0} et mY{1} par defaut, il y a t il vraiment besoin de redefinir ces limites dans la declaration du constructeur?
Ce sont les valeurs par défaut qui pourraient servir si il y avait d'autres constructeurs. "Avoir vraiment besoin", dans le cadre d'un exemple bidon, c'est assez relatif.
Déjà, il est assez probable que la Fraction garde son numérateur et son dénominateur sous forme normalisée : dénominateur positif, numérateur et dénominateurs premiers entre eux. C-a-d que si on définit
Fraction f { 2, -4};
ce qui sera stocké c'est -1 et 2.
- Edité par michelbillaud 1 octobre 2018 à 11:05:37
Tien tu viens de me faire penser que j'ai pas gérer ce détails la o.0
Give a man a fish and you feed him for a day; teach a man to fish and you feed him for a lifetime.
Argument par defaut dans un constructeur de class
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
...
En recherche d'emploi.
...
Discord NaN. Mon site.
Discord NaN. Mon site.
Discord NaN. Mon site.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C