Correction 9
Tableaux de taille fixe et chaînes de caractères
Par ailleurs, les solutions proposées correspondent à l'état de vos connaissances au moment où la série est abordée. D'autres solutions pourront être envisagées une fois de nouveaux concepts acquis.
| Exercice 1 | Exercice 2 | Exercice 3 | Exercice 4 |
Exercice 1 : Générateur automatique de lettres (fonctions et chaînes de caractères, niveau 1)
(pages 55 et 217 dans la 2e édition).
Première version du code :
#include <iostream>
using namespace std;
void genereLettre()
{
cout
<< "Bonjour chère Mireille," << endl
<< "Je vous écris à propos de votre cours." << endl
<< "Il faudrait que nous nous voyons le 18/12 pour en discuter." << endl
<< "Donnez-moi vite de vos nouvelles !" << endl
<< "Amicalement, John." << endl;
}
int main()
{
genereLettre();
return 0;
}
Seconde version du code :
#include <iostream>
#include <string>
using namespace std;
// Note : depuis C++17, il est préférable d'utiliser une string_view
// en guise et place d'une référence constante à une string
void genereLettre(bool masculin, const string& destinataire, string sujet,
unsigned int jour, unsigned int mois,
const string& politesse, const string& auteur)
{
cout << "Bonjour ";
if (masculin) cout << "cher";
else cout << "chère";
cout << " " << destinataire << "," << endl;
cout
<< "Je vous écris à propos de " << sujet << endl
<< "Il faudrait que nous nous voyons le " << jour << "/" << mois
<< " pour en discuter." << endl
<< "Donnez-moi vite de vos nouvelles !" << endl
<< politesse << ", " << auteur << endl;
}
int main()
{
genereLettre(false, "Mireille", "votre cours" , 18, 12,
"Amicalement", "John");
cout << endl;
genereLettre(true, "John", "votre demande de rendez-vous", 16, 12,
"Sincèrement", "Mireille");
return 0;
}
Exercice 2 : Segmentation en mots (string, niveau 2)
(pages 56 et 220 dans la 2e édition).
#include <iostream>
#include <string>
using namespace std;
bool nextToken(const string& str, int& from, int& len);
bool issep (char c); // teste si le caractère est un separateur
int main()
{
string phrase;
cout << "Entrez une chaîne : ";
getline(cin, phrase);
cout << "Les mots de \"" << phrase << "\" sont :" << endl;
int debut(0);
int longueur(0);
while (nextToken(phrase, debut, longueur)) {
cout << "'" << phrase.substr(debut, longueur) << "'" << endl;
debut += longueur;
}
return 0;
}
/* La fonction suivante teste si le caractère est un séparateur.
*
* Ecrire une fonction présente l'avantage de pouvoir redéfinir facilement
* la notion de séparateur (et éventuellement d'en définir plusieurs).
*/
bool issep (char c)
{
return (c == ' ');
}
/* Il y a de multiples façons d'écrire cette fonction.
* Nous trouvons celle-ci est assez élégante.
*/
bool nextToken(const string& str, int& from, int& len)
{
const int taille(str.size());
// saute tous les separateurs à partir de from
while ((from < taille) and issep(str[from])) ++from;
// avance jusqu'au prochain séparateur ou la fin de str
len = 0;
for (int i(from); ((i < taille) and not issep(str[i])); ++len, ++i);
return (len != 0);
}
Commentaires
Le corrigé proposé pour nextToken est assez compact et nécessite d'avoir bien compris les structures de contrôle (while, for). Il peut être, de ce fait, un peu plus difficile à comprendre que d'autres solutions.
Comme indiqué, vous pouvez le faire de plein de façons différentes ; mais examinons justement celle proposée :
// Note : depuis C++17, string const& peut avantageusement être remplacé
// par string_view
bool nextToken (string const&; str, int& from, int& len)
{
int const taille(str.size());
Bon, jusque là probablement que "ça va"... ; juste peut être préciser les arguments :
- string const& str : la chaîne à traiter. Le passage par « const ref » est, bien sûr, une optimisation ; et l'on pourra dans un premier temps se contenter d'un simple passage par valeur : string str ;
- int& from : contient au départ la première position à partir de laquelle il faut chercher un nouveau « token », et devra contenir à la sortie de nextToken la position du début du token suivant ; il va donc être modifié par nextToken, et c'est pour cela qu'il est passé par référence ;
- int& len : va également être modifié par nextToken pour contenir la longueur du nouveau « token » (éventuellement) trouvé.
Continuons avec :
while ((from < taille) && issep(str[from])) ++from;
from est ici incrémenté(/augmenté) du nombre de séparateurs rencontrés. En effet, on ne peut pas commencer le nouveau « token » par un séparateur.
Comment fait-on pour sauter tous ces séparateurs ?
-> tant que le caractère courant est un séparateur, on avance.
Ça, c'est le « while (issep(str[from])) ».
Mais il faut penser aussi à ne pas déborder de la chaîne (imaginez le cas où la chaîne ne contient que des séparateurs). Ça, c'est le « (from < taille) » dans le test du while.
Passons au bloc suivant. Son but est de trouver la fin du « token » (puisqu'on vient de trouver le début avec la boucle while précédente).
Cette fin de « token » est en fait indiquée par la longueur, len, du « token ». Au départ le « token » est vide (on a très bien pu sortir du while précédent par la condition « from ≥ taille ». Repensez encore une fois au cas d'une chaîne ne contenant aucun « token », mais uniquement des séparateurs). On a donc :
len = 0;
Puis on cherche la fin du « token » ; c'est-à-dire le prochain séparateur. C'est-à-dire que tant que l'on n'a pas de séparateur (« !issep(str[i]) »), on avance.
Ca veut dire quoi « on avance » ?
-> on
passe au caractère
suivant, ça c'est le « ++i »,
et on met à jour la taille du
« token » en la faisant augmenter de 1, ça c'est le
« ++len ».
D'où part-on ?
-> du caractère « from » précédemment trouvé.
Il ne reste plus qu'à ne pas oublier de ne pas déborder de la chaîne (« i < taille »), et le tour est joué :
for (int i(from); ((i < taille) && !issep(str[i])); ++len, ++i);
Pour essayer d'être encore plus clair, ceci peut aussi s'écrire de la façon moins compacte suivante (rappel : « from » représente le début de « token ») :
bool continuer(true);
int position_courante(from);
do {
// si on déborde de la chaine il faut s'arreter
if (position_courante >= taille) {
continuer = false ;
}
// si on rencontre un séparateur, il faut s'arreter
// (c'est la fin du token)
else if (issep(str[position_courante])) {
continuer = false ;
}
// sinon, tout va bien, ...
else {
// ...on augmente la longueur du token de 1...
len = len + 1;
// ...et on passe au caractère suivant.
++position_courante;
}
} while (continuer);
Voilà pour cette partie.
Et pour finir, on renvoie « true » si on a trouvé un « token », c.-à-d. si len n'est pas nulle, et « false » sinon. Soit :
return (len != 0);
//Tournure à éviter
if (len != 0) {
return true;
} else {
return false;
}
Exercice 3 : placements sans recouvrements
#include <iostream>
#include <array>
using namespace std;
constexpr size_t DIM(10);
// --------------------------------------------------------------------
bool remplitGrille(array<array<bool, DIM>, DIM>& grille,
unsigned int ligne, unsigned int colonne,
char direction, unsigned int longueur)
{
int dx, dy;
switch (direction) {
case 'N': dx = -1; dy = 0; break;
case 'S': dx = 1; dy = 0; break;
case 'E': dx = 0; dy = 1; break;
case 'O': dx = 0; dy = -1; break;
}
unsigned int i(ligne); // les coordonnées de la case...
unsigned int j(colonne); // ...à modifier
unsigned int l; // la longueur modifiée
bool possible(true); // est-ce possible de mettre tout l'élément ?
// avant de modifier la grille, il faut vérifier si c'est possible de
// mettre tout l'objet.
for (// Initialisation
l = 0;
/* Condition de continuation. Vu que i et j sont des "unsigned" *
* pas besoin de tester ici les condition (i >= 0) et (j >= 0), *
* lesquelles sont forcément vraies */
(possible) and (i < grille.size()) and (j < grille[0].size())
and (l < longueur);
// Incréments
++l, i += dx, j += dy) {
if (grille[i][j]) // c'est-à-dire la grille est déjà occupée
{
possible = false; // on ne peut donc pas mettre tout l'objet voulu
}
}
/* Si l == longueur c'est qu'on a pu tout placer.
* Il se pourrait en effet qu'on soit sorti de la boucle ci-dessus
* parce que i >= DIM ou j >= DIM... ...ce qui n'a pas modifié
* "possible" jusqu'ici.
*/
possible = possible and (l == longueur);
if (possible) {
// on met effectivement l'objet, plus besoin de test ici
for (l = 0, i = ligne, j = colonne; l < longueur;
++l, i += dx, j += dy) {
grille[i][j] = true;
}
}
return possible;
}
// --------------------------------------------------------------------
void initGrille(array<array<bool, DIM>, DIM>& grille)
{
for (auto& ligne : grille)
for (auto& cell : ligne)
cell = false;
}
// --------------------------------------------------------------------
void ajouterElements(array<array<bool, DIM>, DIM>& grille)
{
int x, y;
do {
cout << "Entrez coord. x : ";
cin >> x;
if (x >= 0) {
cout << "Entrez coord. y : ";
cin >> y;
if (y >= 0) {
char dir;
do {
cout << "Entrez direction (N,S,E,O) : ";
cin >> dir;
} while ((dir != 'N') and (dir != 'S') and
(dir != 'E') and (dir != 'O'));
cout << "Entrez taille : ";
unsigned int l;
cin >> l;
cout << "Placement en (" << x << "," << y << ") direction "
<< dir << " longueur " << l << " -> ";
if (remplitGrille(grille, x, y, dir, l))
cout << "succès";
else
cout << "ECHEC";
cout << endl;
}
}
} while ((x >= 0) and (y >= 0));
}
// --------------------------------------------------------------------
void afficheGrille(const array<array<bool, DIM>, DIM>& grille)
{
for (auto ligne : grille) {
for (auto cell : ligne) {
if (cell) cout << '#';
else cout << '.';
}
cout << endl;
}
}
// --------------------------------------------------------------------
int main()
{
array<array<bool, DIM>, DIM> grille;
initGrille(grille);
ajouterElements(grille);
afficheGrille(grille);
return 0;
}
Exercice 4 : LIEN PARTIE THEORIQUE : Conversions binaire <--> décimal (chaînes de caractères, niveau 1)
#include <iostream>
#include <string>
using namespace std;
/* ======================================================================
* 1er algorithme
* conversion d'un décimal positif en binaire
*/
string binary_nonnegative(int n)
{
if (n <= 0) {
return "0";
}
// ici n >= 1
string output;
while (n > 0) {
if (n%2 == 1) {
output = '1' + output;
} else {
output = '0' + output;
}
n /= 2;
}
return output;
}
/* ======================================================================
* 2ème algorithme
* conversion d'un binaire positif (pas de bit de signe) en décimal
*/
unsigned int decimal_nonnegative(string const& binary)
// Note: depuis C++-17, utiliser string_view au lieu de "string const&"
{
unsigned int output(0);
unsigned int power(1);
if (not binary.empty()) {
for (size_t i(binary.size() - 1); i < binary.size(); --i) {
if (binary[i] == '1') {
output += power;
}
power *= 2;
}
}
return output;
}
/* ======================================================================
* 3ème algorithme
* complément à deux d'une chaîne binaire
*/
string twos_complement(string const& binary)
// Note: depuis C++-17, utiliser string_view plutôt que "string const&"
{
string output(binary); // commencer par une copie
if (not binary.empty()) {
// inverser tous les bits à gauche du "least-significant 1" (LS1)
// chercher le LS1
size_t i(output.size() - 1);
/* La condition d'arrêt dans la boucle ci-dessous peut sembler étrange ...
Explication : lorsque le --i arrive en dessous de zéro (-1)
comme c'est un non-signé, en vertu du complément à deux, il devient
une très grande valeur positive
(-1 en non-signé pour un size_t vaut 18446744073709551615)
et sera de toute façon plus grand que la taille de output
ce qui permet de sortir de la boucle !
Alternativement, on aurait pu utiliser un int (entier signé) pour i
et formuler la condition comme while (i >= 0 and ...)
*/
while ((i < output.size()) and (output[i] != '1')) --i;
// chercher le LS1
--i;
// ensuite inverser ceux qui sont avant
while (i < output.size()) {
if (output[i] == '1') {
output[i] = '0';
} else {
output[i] = '1';
}
--i;
}
}
return output;
}
/* ======================================================================
* 4ème algorithme
* conversion d'un décimal (signé) en binaire, en utilisant les algorithmes précédents
*/
string binary(int n)
{
// Ajouter le bit de signe à la représentation "unsigned" correspondante
if (n < 0) {
return "1" + twos_complement(binary_nonnegative(-n));
}
return "0" + binary_nonnegative(n);
}
/* ======================================================================
* 5ème algorithme
* conversion d'un binaire (avec bit de signe) en décimal
*/
int decimal(string const& binary)
// Note: depuis C++-17, string_view au lieu de "string const&"
{
// teste le bit de signe
if (not binary.empty() and (binary[0] == '1')) {
return -decimal_nonnegative(twos_complement(binary));
}
return decimal_nonnegative(binary);
}
/* ======================================================================
* Le reste du code ne constitue pas le sujet principal de l'exercice
* il s'agit d'utilitaires permettant de tester
*/
/* ======================================================================
* Tool function: test an int value and its binary writing
*/
void test(int n)
{
const string result(binary(n));
cout << n << " is " << result << endl;
cout << "and " << result << " is indeed " << decimal(result) << '.' << endl;
}
/* ======================================================================
* Tool function: ask for some integer.
*/
int require_int()
{
int value(0);
cout << "Enter some integer: ";
cin >> value;
return value;
}
/* ======================================================================
* Tool function: ask to continue or not.
*/
bool go_ahead() {
char read('x');
do {
cout << "Shall we continue (y/n)? ";
cin >> read;
} while ((read != 'y') and (read != 'Y') and
(read != 'n') and (read != 'N'));
return (read == 'y') or (read == 'Y');
}
// ======================================================================
int main()
{
const int t(42);
cout << binary_nonnegative(t) << endl;
test(t);
test(-t);
do {
test(require_int());
} while (go_ahead());
return 0;
}
Dernière mise à jour : $Date: 2025/10/31 18:10 $