SWITCH DE STRINGS (C++)

Signaler
Messages postés
2023
Date d'inscription
mardi 24 septembre 2002
Statut
Membre
Dernière intervention
28 juillet 2008
-
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019
-
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/39135-switch-de-strings-c

verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

Parler de C# ici est impropre. On est justement dans le site/forum pour le C++. Le C-sharap est un autre langage, très différent dans sa structure comme son fonctionnement et ses principes (il l'est tout autant éloigné de C++ que Java). Ce que voulait l'auteur c'est "alléger" l'écriture de switches sur des valeurs de chaines, une chose quene prend pas en charge nativement le C++ (même en utilisant des classes, car le switch du C++ n'est pas utilisable pour les classes, car il ne permet pas la surcharge de son opérateur d'égalité, et se contente de comparer les pointeurs d'instances, à condition de les promouvoir en entiers et ce qui n'est pas portable, et n'autorise pas le switch sur les références non plus).
Personnellement je n'utilise jamais ce genre de code proposé, mais j'utilise des fonctions de hachage (qui donne un switch bien plus efficace): c'est d'ailleurs cette méthode qu'utilise aussi Java qui permet de convertir les Strings, immuables, en atomes uniques, et le fait automatiquement pour toutes les chaines constantes présentes dans le source des classes : dans ce cas l'égalité stricte des références seulement devient possible.

Le défaut cependant des atomes (soluble) est la capacité de son dictionnaire de stockage, cette méthode de conversion en atomes pouvant être couteuse en mémoire si elles y restent ; mais Java, comme aussi C#, résoud ce problème en utilisant un marquage des références d'instances, et son rammsse-miettes automatique quand l'instance unique n'est plus utilisée ni dans les sources d'une classe active, ni dans les objets qui l'ont référencée ou créée temporairement (par exemple si les chaines viennent d'une entrée-sortie temporaire pendant un traitement de données massives et ne correspondent à aucune des chaines constantes présentes dans les sources de la classe de traitement).

Dans ce cas, le hachage des chaines et leur recherche dans un dictionnaire est une méthode très efficace et permet des traitements très rapides et efficaces, plus rapides même que ce que ferait un switch effectuant des tas de comparaisons de chaines. Dans certains cas, la méthode de hachage utilisée permet d'obtenir aussi un entier représentant exactement toute la valeur de la chaine parmi les valeurs constantes attendues (possible si les valeurs possibles ont une syntaxe restreinte avec peu de variantes, de sorte que toute l'information peut tenir dans un même entier): il n'est alors pas nécessaire d'utiliser un dictionnaire, et on peut se contenter de faire un switch directement sur la valeur entière de hachage.

Si les valeurs de hachage attendues sont contenues dans un intervalle petit, on peut aussi faire une simple indirection via un tableau, mais le switch du C++ choisit la bonne méthode en fonction de la taille du tableau: s'il y a peu de valeurs, il ne stocke aucun tableau et génère une suite de if/else imbriqués de façon dichotomiques, sinon il utilise stocke un tableau de valeurs et une table d'indirection associée, et utilise une fonttion de recherche dichotomique dans le tableau de valeur pour minimiser le nombre de if/else imbriqués et réduire la taille du code généré (une recherche dichomomique dans une boucle très petite est plus efficace en terme de cache/mémoire qu'une trop longue suite de if/else, car la boucle utilise la localité).

Si le nombre d'éléments dans le tableau est vraiment très faible (3 ou moins), il se contente de comparer les éléments un par un (pour minimiser le nombre de branchements conditionnels, et meˆme le nombre de branchements inconditionnels).

Divers paramètres de performance permettent d'indiquer au compilateur C++ quelle méthode utiliser pour générer des switches efficaces, mais si on n'utilise pas le switch du C/C++, c'est à nous de faire le code simulant le même effet. Ils varients selon les compialteurs, mais presque tous aujourd'hui ont de telles options.

Mais le but du script donné plus haut n'est pas là: il ne s'agit pas de performance mais uniquement de lisibilité du code ou de facilité d'écriture (ce dont je ne suis pas du tout convaincu car le code est encore trop verbeux et pas pratique à taper, ni stable au plan du typage, un défaut des macros, ni forcément plus lisible que des ifs correctement indentés, et je ne voit pas l'intérêt de mettre les accolades dans les macros (puisque cela casse les outils d'indentation automatique des éditeurs de code, très pratiques et très rapides dès qu'on touche au code pour le restructurer, ou encore quand on utilise des éditeurs effectuant de la génération de code automatique, par exemple sous Eclipse avec les fonctions dites de "refactoring").

Les accolades étant reconnues automatiquement et faisant déjà l'essentiel de la structure du code, je ne vois pas ce qu'on gagne à les cacher (sauf si elles sont totalement ouvertes et refermées dans la MÊME macro, ce qui n'est pas le cas ici).
geek1983
Messages postés
16
Date d'inscription
mardi 18 mars 2008
Statut
Membre
Dernière intervention
11 septembre 2009

Eh... Pardon, je ne sais pas en C++ mais en C# c'est possible de faire un switch sur des char en utilisant des single quote:

char option = 'abc';

switch (option)
{
case 'abc':
printf("abc");
break;
case 'def':
printf("def");
break;
default:
printf("Error");
break;
}
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

L'introduction des goto est ici nécessaire pour que les instruction des branches du swith se fase comme prévu.

Ce n'est pas plus idiot que ce que fait REELLEMENT un switch (qui est effectivement une forme compliquée de "goto" multiples à l'intérieur d'un bloc, où chaque "case...:" est effectivement un label). (si tu n'en es pas convaincu compile un switch en source assembleur; la seule différence est qu'un switch peut aussi éventuellement se compiler en un goto indexé (en utilisant une table d'addresses) si les valeurs des cases sans le switch entier sont un peu nombreuses, mais à peu près groupées dans un intervalle petit (mais même dans ce cas la compilation doit tester les bornes avant de faire une indirection).

Ce que je voulais montrer c'est que tes macros restent toujours aussi dangereuses: tu les utilises pour mettre 1 seule instruction apès chaque CASE, sinon il te faut mettre des accolades pour former un bloc. Oublie une accolade, ou le fait que d'autres instructions sont générées (de façon masquée par des macros comme ici) en plusieurs, et tu te retrouves avec un bogue inattendu car seule la première instruction du bloc sera condionnelle, les suivante seront exécutées de façon inconditionnelle alors que le cas d'égalité du switch n'a pas encore été atteint et vérifié.

Des gotos n'ont rien de mauvais en soi, même si c'est vrai que c'est moins lisible que les structures de contrôle usuelles qui devraient être utilisées (if...else, while..., do...while, for..., switch...) chaque fois que possible. On peut faire du code incompréhensible, c'est vrai avec des gotos, mais on peut faire encore pire avec des macros aux effets de bords inattendus. Le bon usage reste encore de ne pas en abuser pour aller dans tous les sens dans un bloc, et de limiter leur portée à un seul bloc. L'usage des goto en aval uniquement respecte cette contrainte de lisibilité (pour les sauts en amont, il y a normalement unboucle qui devait être explicite avec do..while, while.. ou for.., et les sauts amonts doivent revenir à un seul point et non plusieurs.

Les goto sont principalement utilisés en C/C++ pour justement faire des automates d'état finis pour lesquels l'état suivant de chaque étape est déterminé par l'évaluation de toute une série de conditions (en principe exclusives, comme dans un switch C/C++, mais ce n'est pas toujours le cas quand les conditions testent d'abord des exceptions puis traitent différement des cas généraux). Le switch du C/C++ est très pauvre non pas par le fait fait qu'il effectue bien des gotos, mais par le fait que les conditions qu'il sait évaluer sont trop limitées à la seul égalité binaire stricte (il lui manque les conditions d'intervalles, les listes de priorité, la comparaison des objets ne serait-ce que pour leur égalité relative. c'est là justement que la combinaison "if() goto" prend son sens (à condition effectivement de limiter le sens et la portée des gotos). dernier point: les gotos sont très efficaces à l'exécution; La source d'inefficacité ce n'est pas les branchements inconditionnels mais bien les branchements conditionnels (c'est-à-dire if, for, while...), mais encore pire, l'utilisation de tests basés sur les valeurs de variables temporaires (comme ici: l'affectation et le test de la variable génère des temps d'attente, et monopolise des registres et réduit l'eficacité du cache, à moins que le compilateur soit très bon et détermine le cycle de vie de ces varaibles et décide de les éliminer totalement de façon prédictive)

Là on discute justement d'un cas où le switch est insuffisant. Et je ne vois pas du coup quel risque il y a à utiliser un "if...else if...else if..." directement sans utiliser de macros, ni de variables bouléennes supplémentaires juste pour tester si un des cas a déjà été traité.

Et je reste sérieux, sans offenser qui que ce soit personnellement.

C.Q.F.N.D.
cs_schlebe
Messages postés
4
Date d'inscription
vendredi 3 février 2006
Statut
Membre
Dernière intervention
13 octobre 2008

A force de vouloir raison vous en perdez la raison !

Petites explications pour les lecteurs qui viendraient à tomber sur cet amassis de monologue sans possiblité d'intervenir et de répondre point pas point.

Indentation du premier if

Il est évident que j'aurais pû écrire.
if (s = = "Bernard")
else if (s == "Chantal")
etc ..

Mais comme dans votre remarque vous preniez comme argument que vous utilisez un indentateur automatique, j'ai fait comme font tous les indentateurs automatiques et j'ai placé le premier if sur la première colonne.
Si vous argumentez en utilisant l'indentation ayez au moins la courtoisie de respectez votre choix et de ne pas donner un exemple contraire quelques lignes plus bas.

fonction de comparaison sur chaines de caractères

Vous écrivez qu'il faut d'abort transtyper la chaîne de caractères en un type string car la chaîne de caractères n'a pas d'opérateur de comparaison.
C'est vrai, mais c'est ce que j'ai fait, pour info, voici la macro EVALUATE

#define EVALUATE(s) \
{\
string sEvaluate = s;\
if (false);

Vous indiquez que c'est impossible en C.
C'est vrai.

Introduction de GOTO

La solution avec des gotos est totalement burlesque. Je ne vois pas pourquoi vous faites des exemples compliqués. Serait-il possible de garder les pieds sur terre et de traiter calmement du problème.

Nom des variables simplistes

Je suis totalement d'accord avec vous, les variables locales (à l'intérieur de la macro EVALUATE aurait pû être mieux choisie.
Mea culpa.

Le code de la macro SELECT ne fonctionne pas

L'erreur étant humaine, j'ai copié mon code sous Visual C++ .Net 2007 et il a fonctionné correctement.
J'ai porté mon code sous C++ 3.1B sous BS2000, il a également fonctionné correctement.

Pourriez-vous être plus correct et ne pas déclarer que mes affirmations sont erronées sans avoir pris vous même le temps de les tester ?

Le code C++ sous Visual C++ est le suivant :

// TestMacro.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
#include <string>

using namespace std;

#define SELECT(s) \
do {\
string sEvaluate = s;\
bool bFind = false;\

#define CASE(s) \
if (bFind (bFind || (sEvaluate s)))\

#define CaseOTHER \

#define EndSELECT \
} while(false);

static void DisplaySexe(string sNom)
{
char cSexe;

SELECT(sNom)
CASE("Lucien");
CASE("Bernard");
CASE("Marc")
{
cSexe = 'M';
break;
}
CASE("Chantal");
CASE("Marie");
CASE("Colette")
{
cSexe = 'F';
break;
}
CaseOTHER
{
cSexe = '?';
}
EndSELECT
cout << ">> Sexe [" << sNom << "] = " << cSexe << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
DisplaySexe("Lucien");
DisplaySexe("Marc");
DisplaySexe("Bernard");
DisplaySexe("Chantal");
DisplaySexe("Marie");
DisplaySexe("Colette");
DisplaySexe("Philippe");

return 0;
}

C.Q.F.D.
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

Note encore: le premier code donné soit-disant pour C++ est bogué! Je cite:

// Fonction pour C++
unsigned int s_(char* str, ...)
{
unsigned int i = 0;
while (*(++i+&str) != 0)
if (strcmp(str, (char*)*(&str+i)) == 0)
return i;
return 0;
}

En fait ce n'est pas spécifique à C++ (car les varargs existent aussi en C) mais il y a un sérieux problème de compatibilité. En effet rien ne dit que les paramètres passés en varargs seront rangés en mémoire dans l'ordre croissant et de façon contigue. Si vous portez ça sur un processeur 64 bits vous aurez des surprises car justement c'est faux :
* Les premiers paramètres sont passés via des registres,
* Les autres paramètres sont empilés (mais ni le C ni le C++ n'indique dans quel ordre (de droite à gauche ou de gauche à droite: cela dépend des conventions d'appel, la convention cdecl empile de gauche à droite les paramètres déclarés, mais il y a d'autres conventions comme pascal, fastcall, stdcall...), ni non plus l'orientation de la pile (qui est toutefois descendante sur les architectures Intel x86 ou x64 ou AMD64, et PowerPC sur Mac, mais cela dépend des systèmes d'exploitation: c'est vrai pour DOS, Windows, MacOS, OSX, Linux, mais pas partout)
* A l'entrée dans la fonction, les paramètres passés dans des registres seront éventuellement copiés dans un bloc au sommet de la pile, mais après les autres paramètres, les pointeurs de framing et de retour. Il y aura donc un décalage entre les premiers paramètres (copiés des registres vers le bloc DANS la fonction elle-même, ce qui n'est sur car la fonction compilée peut se passer de cette sauvegarde dans certaines cnoditions, et les paramètres suivants)

Pour que ca marche et que ce soit portable il faut inclure <stdarg.h> et utiliser les macros de varargs documentés (déclarer un pointeur "p" de type "va_list", l'initialiser avec "va_start(p)", lire les paramètres avec "va_next(p, type)" et à la fin utiliser "va_end(p)" pour libérer certaines ressources éventuellement allouées par "va_start(p)" pour créer un bloc descripteur linéaire et ordonné, compatible avec une lecture séquentielle par "va_next(p, type)";

Ne croyez pas que "va_next()" est très simple: si vous portez pour la plateforme .Net par exemple, (où le code C/C++ est compilé d'abord en language MSIL, qui sera compilé en code natif sur la machine cible lors de l'installation du programme ou de sa première exécution; pour assurer l'isolation stricte, .Net imposera une vérification des paramètres, et les variables empilées sont décrites en fait à l'aide d'un bloc descripteur généré lors de la compilation, les pramètres ne pouvant pas s'analyser sans ce descripteur de sécurité qui indique quels traitement sont autorisés dessus, et empêchera donc de lire les paramètres au delà des limites des véritables arguments passés à la fonction).

Il ne faut pas essayer de lire l'adresse d'un paramètre soi-même pour en déduire la position d'un autre paramètre dans la pile. Et c'est valable aussi bien en C qu'en C++ (en C++ la déclaration de fonction DOIT utiliser "...", alors qu'en C ce n'est pas obligatoire, mais c'est possible aussi en C ANSI; en C de type K&R, on peut aussi uiliser les varargs en incluant <vararg.h> mais la syntaxe est légèrement différente).
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

évidemment, pour ceux qui préfèrent indenter les accolades au même niveau, il vaut mieux les indenter avec leur contenu, cela donne:
if (test1)
{
...
}
else if (test2)
{
...
}
else if (test 3)
instruction;
else if (test 4)
{
instruction1;
instruction2;
}
else
{
...
}
Dans tous les cas, je laisse "else if" groupé sur la même ligne, et je n'indente jamais le if après le else, surtout quand les conditions sont mutuellement exclusives comme ici (leur ordre d'évaluation est indifférent, une seule branche est prise, ce qui est justement le sujet ici avec un "switch" de chaines différentes) !

Les indenteurs automatiques de nombre d'ateliers de développement ou d'éditeurs de texte pour programmeurs reconnaissent tous la séquence "else if" (et certains langages en font un seul mot-clé "elseif" ou "elsif" ou "elif", y compris le préprocesseur C/C++ avec "#elif")
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

Autre problème cette fois, montrant combien tu t'es perdu dans l'exemple tu donnes:
EVALUATE(sNom)
CASE("Bernard")
CASE("Lucien")
masculin:
{
cSexe = 'F';
break;
}
CASE("Chantal");
CASE("Marie");
CASE("Colette")
{
cSexe = 'F';
break;
}
WhenOTHER
{
cSexe = "?";
}
EndEVALUATE
et tu supposes que les trois prénoms féminins provoqueront l'évaluation de "csexe = 'F'". C'est entièrement faux ici: les deux premiers CASE génèrent chacun un if dans le contenu est vide (ces deux tests sont réalisés pour rien on passe à la suite le troisième CASE avec Colette sera alors faux pour Chantal et Marie, donc ne fera rien du tout non (non! cSexe ne sera pas modifié!!)

Pour que ça marche il faudrait écrire ça:
EVALUATE(sNom)
CASE("Bernard") goto _cas1;
CASE("Lucien") goto _cas1;
CASE("Chantal") goto _cas2;
CASE("Marie") goto _cas2;
CASE("Colette") goto _cas2;
WhenOTHER goto _cas0;
_cas1:
{
cSexe = 'F';
break;
}
_cas2:
{
cSexe = 'F';
break;
}
_cas0:
{
cSexe = "?";
}
EndEVALUATE
Ce qui n'est pas très propre il faut l'avouer, et encore moins clair! (note que pour que des cases successifs conduisent à la même branche de code, il faut simuler un "ou", ce qui se fait par des goto vers une même étiquette. On est obligé aussi de déplacer le code des branches après TOUS les CASE, c'est à dire après tous les "goto" conditionnés par un "if". Note que sous cette forme on peut tout à fait se passer des accolades autour des branches Ceci marche aussi:
EVALUATE(sNom)
CASE("Bernard") goto _cas1;
CASE("Lucien") goto _cas1;
CASE("Chantal") goto _cas2;
CASE("Marie") goto _cas2;
CASE("Colette") goto _cas2;
WhenOTHER goto _cas0;
_cas1:
cSexe = 'F';
break;
_cas2:
cSexe = 'F';
break;
_cas0:
cSexe = "?";
EndEVALUATE
(l'instruction break; marche de même que la continuation, comme dans un switch C/C++ normal car le bloc "EVALUATE...EndEVALUATE" est une boucle "do{...}while(0);"). En effet l'expansion des macros devient (j'ai simplifié ici les parenthèses que tu as mis dans tes "if"):
do{ string sEvaluate = sNom; bool bFind=false; if ((bFind bFind || sEvaluate.compare("Bernard") 0) goto _cas1; if ((bFind bFind || sEvaluate.compare("Lucien") 0) goto _cas1; if ((bFind bFind || sEvaluate.compare("Chantal") 0) goto _cas2; if ((bFind bFind || sEvaluate.compare("Marie") 0) goto _cas2; if ((bFind bFind || sEvaluate.compare("Colette") 0) goto _cas2;
/*other*/ goto _cas0;
_cas1:
cSexe = 'F';
break;
_cas2:
cSexe = 'F';
break;
_cas0:
cSexe = "?";
} while (0);
Sous cette forme, il parait évident qu'on peut se passer totalement de la variable temporaire bFind et écrire simplement (et plus efficacement aussi à l'exécution, avec même la même efficacité que si on avait utilisé des ou "||" dans un if):
do{ string sEvaluate = sNom;
if (sEvaluate.compare("Bernard") == 0) goto _cas1;
if (sEvaluate.compare("Lucien") == 0) goto _cas1;
if (sEvaluate.compare("Chantal") == 0) goto _cas2;
if (sEvaluate.compare("Marie") == 0) goto _cas2;
if (sEvaluate.compare("Colette") == 0) goto _cas2;
/*other*/ goto _cas0;
_cas1:
cSexe = 'M';
break;
_cas2:
cSexe = 'F';
break;
_cas0:
cSexe = "?";
} while (0);
Personnellement j'en reste à la solution simple, sans macros:
do{
string sEvaluate = sNom;
if (sEvaluate.compare("Bernard") == 0 ||
sEvaluate.compare("Lucien" ) == 0) goto _cas1;
if (sEvaluate.compare("Chantal") == 0 ||
sEvaluate.compare("Marie" ) == 0 ||
sEvaluate.compare("Colette") == 0) goto _cas2;
goto _cas0;
_cas1:
cSexe = 'M';
break;
_cas2:
cSexe = 'F';
break;
_cas0:
cSexe = "?";
} while (0);
Mais ces goto ici, quel gachis, quand les clauses sont mutuellement exclusives et toutes terminées par un break; autant écrire directement:
{
string sEvaluate = sNom;
if (sEvaluate.compare("Bernard") == 0 ||
sEvaluate.compare("Lucien" ) == 0)
cSexe = 'M';
else
if (sEvaluate.compare("Chantal") == 0 ||
sEvaluate.compare("Marie" ) == 0 ||
sEvaluate.compare("Colette") == 0)
cSexe = 'F';
else
cSexe = "?";
}
Ici le else joue le rôle des "break" dans le switch. Pas besoin d'être sorcier et savoir comment sont faites les macros externes pour deviner ce que fait ce code.
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

Comment veux-tu que "EVALUATE" fonctionne en C++? Il faudrait définir ce qui définit une égalité entre deux types ou objets en C++. Hors les chaines ne sont pas des objets, juste des pointeurs vers peut-être un seul caractère, et pas forcément une chaine.
Au mieux, cet EVALUATE ne pourra que comparer les pointeurs, et il n'y a aucune chance que ça marche puisque ce que tu veux implicitement c'est comparer non pas les pointeurs (qui sont différents) ni le caractère qu'ils pointent chacun, mais la suite de caractères terminée par des NULs, ce que le type "char *" (ou "const char *" pour les constantes chaines) n'indique pas du tout.
Il faudrait donc d'abord transtyper le paramètre de EVALUATE() pour le passer d'un type "char*" en un type objet "string", lequel dispose de la surcharge de l'opérateur == qui accepte un paramètre "char*". Si on est en C (et pas en C++) c'est impossible.
L'essayer chez moi, c'est tout SAUF l'adaopter.
De toute façon ta "technique" ne marche pas avec les indenteurs automatiques car cela ne respecte pas la syntaxe de base des instructions.
Ton code:
EVALUATE(this->GetParamString("INFO"))
WHEN ("*STD") iInfo = MODIFY_DATE;
WHEN ("*ALL") iInfo = ALL;
WHENx("N[AME]") iInfo = NAME;
WHENx("S[IZE]") iInfo = SIZE;
WHENx("NUM[BER]") iInfo = NUMBER;
WHENx("C[REATE]-D[ATE]") iInfo = CREATE_DATE;
WHENx("M[ODIFY]-D[ATE]") iInfo = MODIFY_DATE;
WHENx("A[CCESS]-D[ATE]") iInfo = ACCESS_DATE;
EndSWITCH
suite du code;
suite du code;
s'indentera alors:
EVALUATE(this->GetParamString("INFO")) WHEN ("*STD") iInfo = MODIFY_DATE;
WHEN ("*ALL") iInfo = ALL;
WHENx("N[AME]") iInfo = NAME;
WHENx("S[IZE]") iInfo = SIZE;
WHENx("NUM[BER]") iInfo = NUMBER;
WHENx("C[REATE]-D[ATE]") iInfo = CREATE_DATE;
WHENx("M[ODIFY]-D[ATE]") iInfo = MODIFY_DATE;
WHENx("A[CCESS]-D[ATE]") iInfo = ACCESS_DATE;
EndSWITCH suite du code;
suite du code;
(la première et la dernière ligne sont mal indentées et fusionnées en une seule ligne, l'indenteur croyant y voir une seule instruction). Si l'indenteur veut faire des renvois à la ligne on obtient:
EVALUATE(this->GetParamString("INFO"))
WHEN ("*STD") iInfo = MODIFY_DATE;
WHEN ("*ALL") iInfo = ALL;
WHENx("N[AME]") iInfo = NAME;
WHENx("S[IZE]") iInfo = SIZE;
WHENx("NUM[BER]") iInfo = NUMBER;
WHENx("C[REATE]-D[ATE]") iInfo = CREATE_DATE;
WHENx("M[ODIFY]-D[ATE]") iInfo = MODIFY_DATE;
WHENx("A[CCESS]-D[ATE]") iInfo = ACCESS_DATE;
EndSWITCH
suite du code;
suite du code;
Ce qui est totalement illogique (comment veux-tu que l'indenteur y reconnaisse quelquechose à tes macros ? D'autant que les macros sont connues pour leurs effets de bord désastreux. Ici il y a bien un effet de bord, puisque tu y introduit un bloc contenant deux variables locales: "sEvaluate" et "bFind", qui entreront tôt ou tard en conflit avec le code que tu voulais mettre après chaque WHENx(...), puisque tu casses la portée de variables homonymes déclarées avant le bloc EVALUATE.
Tu n'as même pas pris la peine de nommer ces variables déclarées par une macro avec un double-underscore en préfixe, pour éviter les collisions avec les variables du reste du code.

Autre danger: WHENx() ou CASE suppose qu'il est suivi d'une seule instruction (sinon le "if" qu'il contient ne rendra conditionnelle que la première instruction. Il serait plus logique alors d'écrire non pas:
CASE("Marc")
{
cSexe = 'M';
break;
}
Mais simplement:
CASE("Marc")
cSexe = 'M';
break;
Hors si on l'écrit comme ça, cela ne marche pas puisque le" break;" n'est plus conditonnel, et le reste des CASE ne sera jamais évalué. Voilà comment on peut se perdre avec des macros dangereuses.
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

évidemment, pour ceux qui préfèrent indenter les accolades au même niveau, il vaut mieux les indenter avec leur contenu, cela donne:
if (test1)
{
...
}
else if (test2)
{
...
}
else if (test 3)
instruction;
else if (test 4)
{
instruction1;
instruction2;
}
else
{
...
}
Dans tous les cas, je laisse "else if" groupé sur la même ligne, et je n'indente jamais le if après le else, surtout quand les conditions sont mutuellement exclusives comme ici (leur ordre d'évaluation est indifférent, une seule branche est prise, ce qui est justement le sujet ici avec un "switch" de chaines différentes) !

Les indenteurs automatiques de nombre d'ateliers de développement ou d'éditeurs de texte pour programmeurs reconnaissent tous la séquence "else if" (et certains langages en font un seul mot-clé "elseif" ou "elsif" ou "elif", y compris le préprocesseur C/C++ avec "#elif")
cs_schlebe
Messages postés
4
Date d'inscription
vendredi 3 février 2006
Statut
Membre
Dernière intervention
13 octobre 2008

Vous avez certainement raison sur certains points.
Pour la macro SWITCH il s'agit bien d'une construction pour empêcher l'indentation.

En ce qui concerne la macro EVALUATE je la préfère à la construction else-if et je regrette que cette Macro ne soit pas implémentée en standard dans le langage C++.

Je pense qu'il est plus facile d'écrire:

EVALUATE(sNom)
WHEN("Bernard") cSexe = "M";
WHEN("Chantal") cSexe = "F";
WHEN("Lucien") cSexe = "M";
WhenOTHER
{
cSexe = "?";
}
EndEVALUATE

que

if (sNom "Bernard") cSexe "M";
else if (sNom "Chantal") cSexe "F";
else if (sNom "Lucien") cSexe "M";
else
{
cSexe = "?";
}

Pour ma part je dois encore ajouter que la macro EVALUATE simplifie le code en évitant au programmeur de commettre une faute dans les égalités et tout en lui évitant d'écrire les égalités autant de fois qu'il n'y a de IF.

D'expérience je sais qu'il est extrêment facile de commettres des erreurs comme ci-dessous.

if (sNom "Bernard") cSexe "M";
else if (sNon "Chantal") cSexe "F";
else if (sNom "Lucien") cSexe "M";
else
{
cSexe = "?";
}

L'utilisation de la macro EVALUATE évite ce genre de problème.

Je vais même plus loin dans l'utilisation de la macro WHEN.
Je défini une Macro WHENx pour comparer selon un algorithme particulier.

Voici un exemple et son interprétation avec des ELSE-IF

EVALUATE(this->GetParamString("INFO"))
WHEN ("*STD") iInfo = MODIFY_DATE;
WHEN ("*ALL") iInfo = ALL;
WHENx("N[AME]") iInfo = NAME;
WHENx("S[IZE]") iInfo = SIZE;
WHENx("NUM[BER]") iInfo = NUMBER;
WHENx("C[REATE]-D[ATE]") iInfo = CREATE_DATE;
WHENx("M[ODIFY]-D[ATE]") iInfo = MODIFY_DATE;
WHENx("A[CCESS]-D[ATE]") iInfo = ACCESS_DATE;
EndSWITCH

ou avec IF-ELSE

string s = this->GetParamString("INFO");

if (s "*STD") { iInfo MODIFY_DATE;
} else if (s "*ALL") { iInfo ALL;
} else if (Comparex(s,"N[AME]")) iInfo = NAME;
} else if (Comparex(s,"S[IZE]")) iInfo = SIZE;
} else if (Comparex(s,"NUM[BER]") iInfo = NUMBER;
} else if (Comparex(s,"C[REATE]-D[ATE]") iInfo = CREATE_DATE;
} else if (Comparex(s,"M[ODIFY]-D[ATE]") iInfo = MODIFY_DATE;
} else if (Comparex(s,"A[CCESS]-D[ATE]") iInfo = ACCESS_DATE;
}

Il s'agit de code pour interpréter une valeur de paramètre dans une commande en mode ligne sur une machine BS2000 (Mainframe Fujitsu-Siemens) mais qui fonctionne également sous Windows. La fonction Comparex compare le premier paramètre avec le second, les caractères entre [ ] indiquent que ces caractères ne sont pas obligatoires.
Dans la commande l'utilisateur peut écrire INFO=ACC-DATE ou A-D mais pas ACC.

Petite information pour le lecteur. Je programme en C depuis 1986 et en C++ depuis 1990.
Au début j'ai utilisé le if-else, puis j'en ai eu marre de cette technique alors que les autres langages que je pratique couramment tel le COBOL, Java et VisualBasic 6.0 et .Net avait tous des fonctions de SWITCH sur les String.

En fait EVALUATE est l'instruction d'évaluation en COBOL !

Je peux également ajouter que j'aime également beaucoup le code aéré dont la structure et les finesses sautent tout de suite aux yeux de tous les programmeurs.

Essayer cette technique, c'est l'adopter !
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

"J'utilise la même technique pour l'instruction SWITCH afin d'éviter un décalage superflu dans l'intruction "case" liée à l'instruction "switch" standard du C/C++."

Voila le genre de macros complètement inutiles qui ne résolvent rien du tout! Si c'est pour une raison d'indentation des accolades, c'est une mauvaise raison! Change d'éditeur de texte contre un qui accepte tes préférences d'indentation (dans mon cas, les "case" sont indentés au même niveau que le "switch" et l'accolade de fermeture par défaut dans tous mes projets (C, C++, C#, PHP, ou Java) mais je n'utilise pas ce genre de macros qui ont de gros inconvénients:
- ces macros sont encore plus longues à taper que la syntaxe originale
- elles dissimulent la syntaxe et introduit un niveau de complexité supplémentaire au code. Une macro ne doit se justifier que si cela simplifie à la fois la lecture et la compréhension du code, et facilite son écriture en évitant de commettre des erreurs subtiles ou des oublis de cas.
- elles cassent les outils de gestion de code, bloquent les replis de blocs dans les bons éditeurs.
On ne règle pas les questions d'indentation de code, qui sont largement des questions de préférences personnelles, avec des macros qui justement n'interagissent pas bien du tout avec la syntaxe du langage (passez un indenteur automatique sur votre code, il ressortira illisible!)
Certains n'aiment pas les indenteurs automatiques, pourtant c'est un gain de temps précieux pour modifier ou restructurer le code tout en assurant de maintenir sa lisibilité (l'indentation à la main est souvent pleine d'erreurs!) et sa maintenabilité.
Bref, gardez les accolades et point-virgules qui sont essentiels à la structure même du langage, hors des macros. Les macros sont fortement déconseillées dans les langages objets car elles sont totalement stupides en terme de typage, et généricité, et en terme d'espace de nommage (rique de collisions). En C++, ce qui les remplace avantageusement chaque fois que possible sont:
- les déclarateurs "enum", et "static const"
- les méthodes et fonctions inline
- les templates
Les macros cela reste de la vieille artillerie pour vieux code en C difficile à maintenir. Gardez quelques macros uniquement pour la compilation conditionnelle (code de débogage, ou code spécifique pour un système lors du portage et l'intégration, à masquer pour les autres plateformes): ces macros devraient n'être que des constantes. (Pas étonnant qu'en C# ou Java, les macros ont été proscrites: il n'y a plus de portage à faire, la machine étant totalement virtualisée par le langage lui-même et ses librairies standards; le reste de la compilation conditionnelle est pris en charge par le chargeur de code et l'éditeur de lien à l'exécution, ainsi que le compilateur/optimiseur JIT, le code n'a plus à être masqué et peut être vérifié avec un seul modèle pour toutes les plateformes, le code "superflu" n'affectant alors pas les performances puisqu'il est éliminé automatiquement si inutilisé).

Dernier point: "l'imbrication de if" dont tu parles est un faux problème. Si on s'en tient à la syntaxe brute du C ou C++, ces "if" successifs sont en fait articulés par des clauses "else" finales. Les séquences de "else if" sont si fréquentes qu'il suffit de considérer que "else if" forme un seul mot clé qui n'interromp pas l'instruction "if" en cours. J'indente toujours ces séquences ainsi:
if (test1) {
...
} else if (test2) {
...
} else if (test 3) {
...
} else if (test 4) {
...
}
Pas besoin de rajouter des indentations à l'intérieur d'une clauses "else" pour indenter tout le "if" qui le suit et ses sous-clauses "else" internes.

Enfin dans ton code ci-dessus, je ne vois pas l'intérêt des accolades que tu ajoutes dans certains "case" (ce n'est utile que si tu veux déclarer des variables locales à chaque case sans qu'elles se télescopent mutuellement en terme de type déclaré, d'instanciation, d'initialisation ou de fermeture, mais en général les "switch" qui incluent de telles déclarations locales dans les case deviennent trop longs et les cases devraient faire plutôt appel à des sous-fonctions dans lesquelles ces variables locales seront déclarées et utilisées)
cs_schlebe
Messages postés
4
Date d'inscription
vendredi 3 février 2006
Statut
Membre
Dernière intervention
13 octobre 2008

voici une autre MACRO qui permet l'instruction BREAK

//******************************************************
//* SELECT
//******************************************************

#define SELECT(s) \
do {\
string sEvaluate = s;
bool bFind = false;\

#define CASE(s) \
if (bFind (bFind || (sEvaluate.compare(s) 0)))\

#define CaseOTHER \

#define EndSELECT \
} while(false);

string sNom = "Bernard";
char cSexe;

SELECT(sNom)
CASE("Lucien");
CASE("Bernard");
CASE("Marc")
{
cSexe = 'M';
break;
}
CASE("Chantal");
CASE("Marie");
CASE("Colette")
{
cSexe = 'F';
break;
}
CaseOTHER
{
cSexe = '?';
}
EndSELECT

Attention, lorsqu'une comparaison est bonne mais que le code est exécuté dans un autre bloc, il faut suffixer l'instruction CASE() du caractère ';'.

Remarque: la méthode "compare" est utilisée pour comparer un objet string et un string literal; ce qui n'était pas le cas dans le message précédent.
La même comparaison avec l'opérateur == en Visual C++ 2008 semble ne pas fonctionner.

A+
cs_schlebe
Messages postés
4
Date d'inscription
vendredi 3 février 2006
Statut
Membre
Dernière intervention
13 octobre 2008

Bonjour.

j'utilise une autre technique qui me semble plus efficace, surtout pour la lisibilité du code basée sur l'utilisation de MACRO (#define)

exemple:

#define EVALUATE(s) \
{\
string sEvaluate = s;\
if (false);

#define WHEN(s) \
else\
if (sEvaluate == s)

#define WhenOTHER \
else

#define EndEVALUATE \
}

EVALUATE(sNom)
WHEN("Bernard") cSexe = "M";
WHEN("Chantal") cSexe = "F";
WHEN("Lucien") cSexe = "M";
WhenOTHER
{
cSexe = "?";
}
EndEVALUATE

Cette technique rend le code plus lisible que l'imbrication de if.
L'instruction EndEVALUATE est également plus explicite que le caractère }.
Il faut cependant remarquer que les blocs WHEN sont totalement séparés et que l'instruction EVALUATE ne peut pas contenir d'instruction "break".
Le nom de la variable locale créée à l'intérieur du bloc annonyme ne peut pas être passé
comme paramètre au risque de perturber l'ensemble.

J'utilise la même technique pour l'instruction SWITCH afin d'éviter un décalage superflu dans l'intruction "case" liée à l'instruction "switch" standard du C/C++.

#define SWITCH(s) \
switch (s)\
{

#define EndSWITCH \
}

exemple:

SWITCH(sText[0])
case ',':
{
iCode = OP_COMMA;
bFirstEqual = true;
break;
}
case '(':
{
iCode = OP_PAR_OPEN;
bInFunction = false;
break;
}
case ';': iCode = OP_POINT_VIRGULE; break;
case ':': iCode = OP_DOUBLE_POINT; break;
case ')': iCode = OP_PAR_CLOSE; break;
case '{': iCode = OP_ACCOLADE_OPEN; break;
case '}': iCode = OP_ACCOLADE_CLOSE; break;
case '[': iCode = OP_SEGMENT_OPEN; break;
case ']': iCode = OP_SEGMENT_CLOSE; break;
case '.': iCode = OP_POINT; break;
EndSWITCH

J'aime également bien la solution de hachage mais elle requiert une organisation plus complexe.
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

Juste en passant, c'est une très mauvaise idée de déclarer une variable avec le nom générique "String" (qui ressemble à un nom de classe si on fait du C++)
les variables locales devraient commencer par une minuscule, mais "string" est aussi un type standard dans les librairies C++.

Bref utilise "chaine" ou "str" ou "s", ou "nomUtilisateur" avec un nom parlant associé à ce que représente la variable si la fonction manipule beaucoup de variables...
verdy_p
Messages postés
203
Date d'inscription
vendredi 27 janvier 2006
Statut
Membre
Dernière intervention
29 janvier 2019

une autre façon de faire est de convertir les strings non modifiables avec une classe de hachage, qui permet ensuite de les comparer directement par leur seul pointeur ou par un entier numérique associé. Dans Windows, on a une API pour ça: CreateAtom() pour utiliser une table de hachage globale pour le système (ou pour l'aplication, le système prenant en charge les transferts d'atomes d'une table de hachage à l'autre). Voir aussi la fonction qui crée une classe de fenêtre selon le même principe, ou les nombreuses autres tables de hachage du système pour les noms de modules (en retour de la conversion de chaine, on obtient un HANDLE entier.
Evidemment on peut avoir des entrées de hachage créées dynamiquement pour lesquelles on ne sait pas quelle sera la valeur de Handle retournée, mais rien n'interdit de créer une table de hachage dont les premières entrées sont prérenseignées, et dont on connait à l'avance la valeur de handle sous forme d'un entier utilisable dans un switch, et on traite les autres valeurs de hachage dans le switch avec "default:" et en les comparant avec ==
NitRic
Messages postés
402
Date d'inscription
mardi 1 mai 2001
Statut
Membre
Dernière intervention
15 août 2011

« J'ai réussi à adapter la fonction en C mais elle ne supporte pas les arguments illimités (il faut un tableau comme 2ème argument) »

google.com stdarg.h

faut apprendre le langage avant de l'utiliser ...
luhtor
Messages postés
2023
Date d'inscription
mardi 24 septembre 2002
Statut
Membre
Dernière intervention
28 juillet 2008
5
En C++, on peut faire ca plus proprement (à mon gout). En fait, on créer une classe qui encapsule un type énuméré (du style de ca: http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4001/) mais qui permet au passage la conversion indice<=>string.

Toute facon, en C++, je vois pas pk on irait utiliser des (char*).