Switch de strings (c++)

Soyez le premier à donner votre avis sur cette source.

Snippet vu 31 790 fois - Téléchargée 28 fois

Contenu du snippet

J'ai eu un jour une idée pour "switcher" les strings, ce qui est impossible en c ou c++. Faire des strcmp à tout bout de champ, ça ne m'intéressait pas pour créer un code bien espacé et compréhensible.

Pour me rapprocher des switchs, j'ai décider de faire une fonction à arguments illimités qui renvoie la position de l'argument qui répond au "switch". La fonction s_() effectue tous les strcmp un par un avant que la valeur renvoyée passe au switch.

Voilà ce que ça donne concrètement :

Source / Exemple :


/********************

  • Switch de string *
  • par ordiman85 *
                                        • /
#include <conio.h> #include <stdio.h> #include <string.h> // char* str est la string à "switcher" // IMPORTANT !! Mettre 0 comme dernier argument ! #ifdef __cplusplus // 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; } #else // Fonction pour le langage C unsigned int s_(char* str, char* cmp[]) { unsigned int i = 0; while (cmp[i] != 0) { if (strcmp(str, cmp[i]) == 0) return i+1; i++; } return 0; } #endif // Fonction principale int main() { printf("Test du switch de strings :\r\n"); // La string à tester est "switch", sa position est 4 char* String = "switch"; // On récupère un entier qui correspond à la position de la string identifiée // Méthode 1 (C++) #ifdef __cplusplus unsigned int id = s_(String, "Hello", "world", "!", "switch", "strings", 0); #else // Méthode 2 (C) char* comp[] = {"Hello", "world", "!", "switch", "strings", 0}; unsigned int id = s_(String, comp); #endif // Le switch switch (id) { case 1: // "Hello" printf("%d. Hello", id); break; case 2: // "world" printf("%d. world", id); break; case 3: // "!" printf("%d. !", id); break; case 4: // "switch" printf("%d. switch", id); break; case 5: // "strings" printf("%d. strings", id); break; default: // Pas identifié, id = 0 printf("Pas dans la liste (%d)", id); break; } // Pause et fin getch(); return 0; }

Conclusion :


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)

IMPORTANT : mettez 0 comme dernier argument !

@+

A voir également

Ajouter un commentaire

Commentaires

Messages postés
202
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).
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;
}
Messages postés
202
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.
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.
Messages postés
202
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).
Afficher les 17 commentaires

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.