String et stream **************** .. include:: ../BoutonGoogleTrad.rst Bien que beaucoup d'efforts aient été faits pour améliorer le support des caractères internationaux en C++, cela reste encore fastidieux. Nous choisissons donc dans cette unité de se limiter aux caractères de la table `ASCII de 32 à 126 `_, c'est à dire : * Les chiffres et les lettres majuscules/minuscules * L'espace * Les signes de ponctuation : ! ? . ; , * Les signes mathématiques : + - / * % < > = * Les signes de dev : ( ) [ ] {} & # \" ~ * Et quelques autres : $ _ - @ ^ .. warning:: Par exemple, vous ne pouvez donc pas utiliser les accents : é à è ê et le ç. En effet, ces caractères nécessitent un encodage sur 2 octets ou plus. Ainsi, si vous demandez la longueur de la chaîne, la fonction retournera en réalité le nombre d'octets utilisés et ce résultat ne correspondra pas aux nombres de caractères effectivement présents dans la chaîne. Les littéraux ============= Dans vos programmes vous allez écrire des littéraux sous la forme suivante : .. code-block:: cpp "Bonjour" Cette syntaxe, datant du langage C, correspond à la création d'un tableau de caractères char[]. Il est possible d'insérer des caractères spéciaux en utilisant un backslash *\\* : * \\n : retour à la ligne * \\t : tabulation * \\\\ : un backslash (utile pour indique un repertoire sur windows) Le type string ============== La librairie standard du C++ (std) intègre le type **string** pour représenter des objets chaînes de caractères. Pour cela, il faut insérer en début de votre code la ligne suivante : .. code-block:: cpp #include Avantages des strings : * Un objet string peut changer de taille dynamiquement : en insérant ou retirant des caractères. * Les chemins de fichier ne sont plus limités en taille ! * De nombreuses fonctions sont disponibles à travers l'objet *string*. * Des opérateurs sont disponibles sur les *string* comme le + pour fusionner deux chaînes. Syntaxe ======= Si vous cherchez la page de description de la classe *string* sur le site *cppreference*, vous ne la trouverez pas ! En effet, la classe *string* est juste une spécialisation de la classe template **std::basic_string** pour le type *char* : .. image:: def.png :align: center Si vous cherchez les fonctions membres disponibles, il faudra donc consulter `la classe basic_string `_. Voici une liste des commandes intéressantes : .. csv-table:: Opérations sur les strings :header: "Type", "Exemple", "" :widths: 10, 10, 10 Affichage, std::cout << s1 << std::endl; , Initialisation, std::string s1 = \"Exemple 1\"; , Initialisation, std::string s2(\"Exemple 2\"); , Initialisation, std::string t {\"Hello world\"}; , Conversion int->str, std::string s = std::to_string(42); , Conversion str->int, std::stoi(s);, Conversion str->double, std::stod(s);, Conversion str->float, std::stof(s);, Méthode, t.clear(), Efface tous les caractères Méthode, t.erase(i٬nb), Retire *nb* caractères à partir de l'index *i* Méthode, t.insert(i٬\"1-\"), Insère une chaîne dans la chaîne *t* à la position *i* Méthode, t.erase(index٬nb), Retire *nb* caractères à la position *i* Méthode, t.replace(index٬nb٬\"test\"), Remplace les *nb* caractères à la position *i* Méthode, t.append(s1), Insère en fin Méthode const, t.length(), Nombre de caractères Méthode const, t.find(\"Ex\"), Retourne l'index de la première occurrence Méthode const, t.rfind(\"Ex\"), Retourne l'index de la dernière occurrence Méthode const, t.substr(i٬nb), Retourne une sous-chaîne depuis l'index *i* Opérateur +, s1+\"@\"+s2, Concaténation Opérateur +=, s1+= \"fin\", Insère en fin Opérateur [], s1[i], retour le i-ème caractère Opérateur <, s1 < s2, Comparaison lexicographique Opérateur ==, s1 == s2, Comparaison des caractères Test, t.empty(), Indique si la chaîne est vide Test, t.starts_with(\"Ex\"), Teste le début de la chaîne de caractères Test, t.ends_with(\"1\"), Teste la fin de la chaîne de caractères .. quiz:: QuString :title: Le type string Indiquez si les affirmations suivantes sont vraies ou fausses : * :quiz:`{"type":"TF","answer":"T"}` L'écriture \"3\" correspond à un littéral. * :quiz:`{"type":"TF","answer":"F"}` Pour créer un string, une seule syntaxe est possible. * :quiz:`{"type":"TF","answer":"F"}` Le type string est un type fondamental du C++. * :quiz:`{"type":"TF","answer":"F"}` On peut concaténer un string avec un int. * :quiz:`{"type":"TF","answer":"F"}` L'écriture : string s = 42 convertit implicitement un int en string. * :quiz:`{"type":"TF","answer":"F"}` La fonction stod() convertit un double en string. * :quiz:`{"type":"TF","answer":"T"}` La fonction to_string() permet de convertir un numérique en string. .. quiz:: QuString2 :title: Opérations sur les string Les variables s1 et s2 représentent deux string initialisés avec \"BOB\" et \"EVA\". Pour chacune des expressions, donnez le résultat **SANS** guillemets s'il existe ou indiquez ERR : .. csv-table:: :header: Expression, Résultat :widths: 10, 10 :delim: ! \"Hello\" + 4 ! :quiz:`{"type":"FB","answer":"ERR"}` s1 + \"_\" + s2 ! :quiz:`{"type":"FB","answer":"BOB_EVA"}` s1 + 5 ! :quiz:`{"type":"FB","answer":"ERR"}` to_string(50) ! :quiz:`{"type":"FB","answer":"50"}` Affichage ========= iostream -------- Cette librairie propose un objet **cout** permettant d'effectuer des sorties sur la console de manière plus intuitive que la fonction *printf*. Pour cela, l'opérateur **<<** a été redéfini pour traiter tous les types fondamentaux. Pour les booléens cependant, il affiche 0 pour *false* et 1 pour *true*. La librairie inclut aussi des constantes comme **endl** pour effectuer un retour à la ligne. Nous pouvons ainsi écrire : .. code-block:: cpp #include #include int main() { int a = 17; std::string s = "Hello"; std::cout << a << std::endl << s << std::endl; } .. note:: Vous pouvez retirer les std en écrivant : using namespace std; En chaînant plusieurs affichages à la suite, les éléments sont accolés, ainsi *cout << 18 << 13* affiche 1813. Il faut alors penser à insérer des espacements supplémentaires pour séparer les nombres : .. code-block:: cpp cout << "Coordonnées : " << x << " " << y << endl; Formatage des nombres --------------------- La librairie fournit une fonction *setprecision()* permettant de contrôler l'affichage des nombres flottants : .. code-block:: cpp #include #include using namespace std; int main() { cout << setprecision(5) << 3.141519 << endl; // ==>> 3.1415 5 digits cout << setprecision(3) << 3.141519 << endl; // ==>> 3.14 3 digits cout << setfill('-'); cout << setw(5) << 25 << endl; // ---25 cout << setw(5) << 148; // --148 } Surcharger l'opérateur << pour vos objets ----------------------------------------- Pour les structures, on peut surcharger l'opérateur << pour qu'il puisse afficher vos objets. Ainsi, le format d'affichage sera identique pour tous les objets de même type. Voici un exemple dans le code ci-dessous : .. code-block:: cpp #include using namespace std; struct Point { int x, y; Point(int xx, int yy) { x = xx; y = yy; } }; # no const ostream & ostream & operator << (ostream & stream, const Point & p) { stream << "(" << p.x << "," << p.y << ")"; return stream; } int main() { Point p(4,5); cout << p; } >> (4,5) Stream ====== Si l’on considère des entités comme l’écran, le disque ou encore une chaîne de caractères, toutes sont capables de gérer l’affichage ou le stockage de caractères. Elles ont donc naturellement des comportements communs, qu’il est pertinent de regrouper sous une même hiérarchie de classes. La classe mère, définie comme abstraite, représente alors le concept de flux de sortie (output stream). Elle spécifie une interface unique pour toutes ces formes de sorties. Output stream ------------- Dans la hiérarchie des output streams, on trouve : * std::cout : objet global de type std::ostream, associé au flux standard de sortie (l’écran). * std::ostringstream : classe dérivée permettant d’écrire dans une chaîne de caractères. * std::ofstream : classe dérivée permettant d’écrire dans un fichier. .. image:: ostream.png :align: center :scale: 60% Exemple ------- Supposons que nous ayons une fonction qui injecte des informations dans un stream. Comme nous avons cette hiérarchie, nous pouvons lui passer en paramètre *cout*, un stream ouvert sur un fichier ou un stream ouvert sur une string, et tout va fonctionner de manière similaire. Voici un exemple : .. code-block:: cpp #include #include #include void ExPolymorphism(std::ostream & s, int i) { s << "File_" << i << ".txt" << std::endl; } int main() { // output to console ExPolymorphism(std::cout, 7); // output to a string buffer std::ostringstream os; ExPolymorphism(os, 7); std::cout << os.str(); // output to a file std::ofstream ofs("C:\\test\\output.txt"); ExPolymorphism(ofs, 7); ofs.close(); return 0; } >> Console : File_7.txt >> String : File_7.txt .. image:: file.png :align: center :scale: 60% Input stream ------------ La logique est identique pour les inputs streams ; une classe mère *istream* dipose d'une instance *std::cin* ainsi que deux classes dérivées : * *ifstream* spécialisée dans la lecture des fichiers (input file stream) * *istringstream* spécialisée dans la lecture des chaînes de caractères (input string stream) .. image:: in.png :align: center :scale: 60% Voici un programme d'exemple gérant les trois types d'entrée : * Le clavier avec std::cin, l'utilisateur va entrer : 11 22 33 + Enter * Un objet inputstream initialisé avec la chaîne : \"11 22 33\" * Un fichier dont le contenu est affiché ci-dessous .. code-block:: cpp #include #include #include void ExPolymorphism(std::istream & is) { int i; is >> i; std::cout << "word : " << i << std::endl; } int main() { // input from keyboard ExPolymorphism(std::cin); // input from a string std::istringstream iss("11 12 13"); ExPolymorphism(iss); // input from a file std::ifstream ifs("C:\\test\\output.txt"); ExPolymorphism(ifs); ifs.close(); return 0; } .. note:: Le caractère espace sert de séparateur entre les différentes informations. .. warning:: Dans notre exemple, tout semble parfaitement fonctionner, mais certaines situations résistent à une homogénéisation complète. Ainsi, la détection de la fin d’un flux dépend fortement de sa nature : pour un fichier, cette notion est claire et bien définie ; en revanche, pour une saisie au clavier, il n’existe pas de véritable fin. Ce type de particularités doit donc être traité spécifiquement, au cas par cas. Dans les fonctions utiles, on peut citer : * std::getline(streamin,str) : lit une ligne et la charge dans l'objet string str * std::getline(streamin,str, \"#\") : idem mais en utilisant le caractère # comme séparateur