web-dev-qa-db-fra.com

Compensation de chaîne insensible à la casse en C

J'ai deux codes postaux char* que je souhaite comparer, en ignorant le cas . Y a-t-il une fonction pour faire cela

Ou dois-je parcourir en boucle chaque utilisation de la fonction tolower et ensuite faire la comparaison?

Toute idée de la manière dont cette fonction réagira avec les nombres dans la chaîne

Merci

52
bond425

Il n'y a pas de fonction qui fait cela dans le standard C. Les systèmes Unix conformes à POSIX doivent obligatoirement avoir strcasecmp dans l'en-tête strings.h; Les systèmes Microsoft ont stricmp. Pour être sur le côté portable, écrivez votre propre:

int strcicmp(char const *a, char const *b)
{
    for (;; a++, b++) {
        int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
        if (d != 0 || !*a)
            return d;
    }
}

Notez toutefois qu'aucune de ces solutions ne fonctionnera avec les chaînes UTF-8, mais uniquement ASCII.

54
Fred Foo

Jetez un coup d'œil à strcasecmp () in strings.h.

28
Mihran Hovsepyan

Je voudrais utiliser stricmp(). Il compare deux chaînes sans tenir compte de la casse.

Notez que, dans certains cas, la conversion de la chaîne en minuscules peut être plus rapide.

4
Jonathan Wood

J'ai trouvé la méthode intégrée nommée à partir de laquelle contient des fonctions de chaîne supplémentaires à l'en-tête standard. 

Voici les signatures pertinentes: 

int  strcasecmp(const char *, const char *);
int  strncasecmp(const char *, const char *, size_t);

J'ai également trouvé son synonyme dans le noyau xnu (osfmk/device/subrs.c) et qu'il est implémenté dans le code suivant, vous ne devriez donc pas avoir de changement de comportement en nombre par rapport à la fonction strcmp d'origine. 

tolower(unsigned char ch) {
    if (ch >= 'A' && ch <= 'Z')
        ch = 'a' + (ch - 'A');
    return ch;
 }

int strcasecmp(const char *s1, const char *s2) {
    const unsigned char *us1 = (const u_char *)s1,
                        *us2 = (const u_char *)s2;

    while (tolower(*us1) == tolower(*us2++))
        if (*us1++ == '\0')
            return (0);
    return (tolower(*us1) - tolower(*--us2));
}
4
Zohar81

Des pièges supplémentaires à surveiller lorsque vous faites de la casse, comparez:


Comparaison en minuscule ou en majuscule? (problème assez commun)

Les deux éléments ci-dessous renverront 0 avec strcicmpL("A", "a") et strcicmpU("A", "a").
Pourtant, strcicmpL("A", "_") et strcicmpU("A", "_") peuvent renvoyer différents résultats signés car '_' est souvent compris entre les majuscules et les minuscules. 

Cela affecte l'ordre de tri lorsqu'il est utilisé avec qsort(..., ..., ..., strcicmp). Les fonctions de bibliothèque C non standard, telles que les fonctions stricmp() ou strcasecmp() couramment disponibles, ont tendance à être bien définies et favorisent la comparaison via des minuscules. Pourtant, des variations existent.

int strcicmpL(char const *a, char const *b) {
  while (*a) {
    int d = tolower(*a) - tolower(*b);
    if (d) {
        return d;
    } 
    a++;
    b++;
  } 
  return 0;
}

int strcicmpU(char const *a, char const *b) {
  while (*a) {
    int d = toupper(*a) - toupper(*b);
    if (d) {
        return d;
    } 
    a++;
    b++;
  } 
  return 0;
}

char peut avoir une valeur négative. (pas rare)

touppper(int) et tolower(int) sont spécifiés pour les valeurs unsigned char et la valeur négative EOF. De plus, strcmp() renvoie les résultats comme si chaque char était convertie en unsigned char, que char soit signé ou non signé.

tolower(*a); // Potential UB
tolower((unsigned char) *a); // Correct

Locale (moins commun)

Bien que les jeux de caractères utilisant le code ASCII (0-127) soient omniprésents, les codes restants ont tendance à avoir des problèmes spécifiques à locale. Donc, strcasecmp("\xE4", "a") peut renvoyer un 0 sur un système et un zéro sur un autre.


Unicode (la voie du futur)

Si une solution doit gérer plus de ASCII, envisagez une unicode_strcicmp(). C lib ne fournissant pas une telle fonction, une fonction pré-codée à partir d'une autre bibliothèque est recommandée. Écrire votre propre unicode_strcicmp() est une tâche ardue.


Est-ce que toutes les lettres mappent un bas sur un haut? (pédant)

[A-Z] mappe un-à-un avec [a-z], mais divers locales mappent divers caractères minuscules en un supérieur et inversement. De plus, certains caractères majuscules peuvent ne pas avoir d’équivalent minuscule et inversement.

Cela oblige le code à se convertir à la fois par tolower() et tolower().

int d = tolower(toupper(*a)) - tolower(toupper(*b));

Encore une fois, les résultats possibles pour le tri sont différents si le code avait tolower(toupper(*a)) contre toupper(tolower(*a)).


Portabilité

@B. Nadolson recommande d'éviter de lancer votre propre strcicmp() et ceci est raisonnable, sauf lorsque le code nécessite une fonctionnalité portable équivalente élevée.

Vous trouverez ci-dessous une approche qui a même fonctionné plus rapidement que certaines fonctions fournies par le système. Il effectue une comparaison simple par boucle plutôt que deux en utilisant 2 tables différentes qui diffèrent par '\0'. Vos résultats peuvent varier.

static unsigned char low1[UCHAR_MAX + 1] = {
  0, 1, 2, 3, ...
  '@', 'a', 'b', 'c', ... 'z', `[`, ...  // @ABC... Z[...
  '`', 'a', 'b', 'c', ... 'z', `{`, ...  // `abc... z{...
}
static unsigned char low2[UCHAR_MAX + 1] = {
// v--- Not zero, but A which matches none in `low1[]`
  'A', 1, 2, 3, ...
  '@', 'a', 'b', 'c', ... 'z', `[`, ...
  '`', 'a', 'b', 'c', ... 'z', `{`, ...
}

int strcicmp_ch(char const *a, char const *b) {
  // compare using tables that differ slightly.
  while (low1[(unsigned char)*a] == low2[(unsigned char)*b]) {
    a++;
    b++;
  }
  // Either strings differ or null character detected.
  // Perform subtraction using same table.
  return (low1[(unsigned char)*a] - low1[(unsigned char)*b]);
}
1
chux
static int ignoreCaseComp (const char *str1, const char *str2, int length)
{
    int k;
    for (k = 0; k < length; k++)
    {

        if ((str1[k] | 32) != (str2[k] | 32))
            break;
    }

    if (k != length)
        return 1;
    return 0;
}

Référence

1
S. M. AMRAN

Vous pouvez avoir une idée, comment en implémenter une efficace, si vous n'en avez pas dans la bibliothèque, depuis ici

Il utilise une table pour tous les 256 caractères. 

  • dans cette table pour tous les caractères, sauf les lettres - utilisé ses codes ASCII. 
  • pour les codes de lettres majuscules - les codes de liste de tableaux des symboles minuscules. 

alors nous avons juste besoin de parcourir une chaîne de caractères et de comparer nos cellules de tableau pour un caractère donné:

const char *cm = charmap,
        *us1 = (const char *)s1,
        *us2 = (const char *)s2;
while (cm[*us1] == cm[*us2++])
    if (*us1++ == '\0')
        return (0);
return (cm[*us1] - cm[*--us2]);
0
Andrey Suvorov

Je ne suis pas vraiment fan de la réponse la plus votée ici } (en partie parce que ce n'est pas correct, car elle devrait continuer si elle lit un terminateur nul dans l'une des chaînes - mais pas les deux chaînes en même temps --et ça ne fait pas ça), alors j'ai écrit le mien.

Ceci est un remplacement immédiat pour strncmp() , et a été entièrement testé avec de nombreux cas de test, comme indiqué ci-dessous:

Le code uniquement:

#include <ctype.h> // for `tolower()`
#include <limits.h> // for `INT_MIN`

// Case-insensitive `strncmp()`
static inline int strncmpci(const char * str1, const char * str2, size_t num)
{
    int ret_code = INT_MIN;
    size_t chars_compared = 0;

    if (!str1 || !str2)
    {
        goto done;
    }

    while ((*str1 || *str2) && (chars_compared < num))
    {
        ret_code = tolower((int)(*str1)) - tolower((int)(*str2));
        if (ret_code != 0)
        {
            break;
        }
        chars_compared++;
        str1++;
        str2++;
    }

done:
    return ret_code;
}

Version entièrement commentée:

#include <ctype.h> // for `tolower()`
#include <limits.h> // for `INT_MIN`

/*

Case-insensitive string compare (strncmp case-insensitive)
- Identical to strncmp except case-insensitive. See: http://www.cplusplus.com/reference/cstring/strncmp/
- Aided/inspired, in part, by: https://stackoverflow.com/a/5820991/4561887

str1    C string 1 to be compared
str2    C string 2 to be compared
num     max number of chars to compare

return:
(essentially identical to strncmp)
INT_MIN  invalid arguments (one or both of the input strings is a NULL pointer)
<0       the first character that does not match has a lower value in str1 than in str2
 0       the contents of both strings are equal
>0       the first character that does not match has a greater value in str1 than in str2

*/
static inline int strncmpci(const char * str1, const char * str2, size_t num)
{
    int ret_code = INT_MIN;

    size_t chars_compared = 0;

    // Check for NULL pointers
    if (!str1 || !str2)
    {
        goto done;
    }

    // Continue doing case-insensitive comparisons, one-character-at-a-time, of str1 to str2, 
    // as long as at least one of the strings still has more characters in it, and we have
    // not yet compared num chars.
    while ((*str1 || *str2) && (chars_compared < num))
    {
        ret_code = tolower((int)(*str1)) - tolower((int)(*str2));
        if (ret_code != 0)
        {
            // The 2 chars just compared don't match
            break;
        }
        chars_compared++;
        str1++;
        str2++;
    }

done:
    return ret_code;
}

Code de test: (exécutez-le en ligne ici): https://onlinegdb.com/B1Qoj0W_N

int main()
{
    printf("Hello World\n\n");

    const char * str1;
    const char * str2;
    size_t n;

    str1 = "hey";
    str2 = "HEY";
    n = 3;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    str1 = "heY";
    str2 = "HeY";
    n = 3;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    str1 = "hey";
    str2 = "HEdY";
    n = 3;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    str1 = "heY";
    str2 = "HeYd";
    n = 3;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));   
    printf("\n");

    str1 = "heY";
    str2 = "HeYd";
    n = 6;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    str1 = "hey";
    str2 = "hey";
    n = 6;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    str1 = "hey";
    str2 = "heyd";
    n = 6;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    str1 = "hey";
    str2 = "heyd";
    n = 3;
    printf("strncmpci(%s, %s, %u) = %i\n", str1, str2, n, strncmpci(str1, str2, n));
    printf("strncmp(%s, %s, %u) = %i\n", str1, str2, n, strncmp(str1, str2, n));
    printf("\n");

    return 0;
}

Exemple de sortie:

Bonjour le monde

strncmpci (hey, HEY, 3) = 0
strncmp (hé, HEY, 3) = 32

strncmpci (heY, heY, 3) = 0
strncmp (hé, hé, 3) = 32

strncmpci (hey, HEdY, 3) = 21
strncmp (hé, HEdY, 3) = 32

strncmpci (heY, HeYd, 3) = 0
strncmp (hé, hé, 3) = 32

strncmpci (heY, HeYd, 6) = -100
strncmp (hé, hé, 6) = 32

strncmpci (hé, hé, 6) = 0
strncmp (hé, hé, 6) = 0

strncmpci (hey, heyd, 6) = -100
strncmp (hé, hé, 6) = -100

strncmpci (hé, hé, 3) = 0
strncmp (hé, hé, 3) = 0

Références:

  1. Cette question et d’autres réponses ici ont servi d’inspiration et d’éclairages ( composition de chaînes insensible à la casse en C )
  2. _ { http://www.cplusplus.com/reference/cstring/strncmp/
  3. https://en.wikipedia.org/wiki/ASCII
  4. _ { https://en.cppreference.com/w/c/language/operator_precedence } _
0
Gabriel Staples

Comme d'autres l'ont dit, aucune fonction portable ne fonctionne sur tous les systèmes. Vous pouvez partiellement contourner ceci avec un simple ifdef:

#include <stdio.h>

#ifdef _WIN32
#include <string.h>
#define strcasecmp _stricmp
#else // assuming POSIX or BSD compliant system
#include <strings.h>
#endif

int main() {
    printf("%d", strcasecmp("teSt", "TEst"));
}
0
Miljen Mikic