将字母转换为数字在C语言中的实现

7
我正在尝试编写一段代码,将字母转换为数字。例如 A ==> 0 B ==> 1 C ==> 2 等等。我考虑编写26个if语句。我想知道是否有更好的方法来完成这个任务...
谢谢!

2
对于那些使用 "num = letter - 'A'" 的人,需要注意的是:C99标准要求数字字符('0'-'9')是连续的,但字母字符不是。"在源字符集和执行字符集中,上述十进制数字列表中每个字符的值应比前一个大1。" EBCDIC(具有其奇怪的不连续字母表)是完全有效的。这意味着@ChrisLutz迄今为止是唯一正确的答案,尽管他对此表示疑虑 :-) - paxdiablo
ISO应该规定ASCII(或至少是连续的字母),但我怀疑IBM在保持其大型机C编译器符合性方面发挥了重要作用。 - paxdiablo
3
无论如何,有多少人这样做都不重要。该标准并不要求连续的字母,因此实现者可以自由选择自己喜欢的方式。编写符合ASCII标准的代码会严重限制他们的潜在市场,仅适用于大约99.999%的计算机 :-) - paxdiablo
2
如果这是真的学校作业,你应该关心你的老师是否担心或知道C99标准问题。否则,他可能会因为你不使用“更清晰”的方法(即字母-'A')而给你更差的分数,并且争论C99标准不足以说服他。 - djeidot
@paxdiablo 嘿,我发布了我认为是这个非常古老但从未得到正确回答的问题的真正答案。希望能得到一些支持,将答案移动到更靠前的位置,或者评论解释我是疯了还是完全错误 :) 编辑:这是所有黄色内容的答案。 - user3386109
显示剩余6条评论
10个回答

11

这是一种我认为比switch方法更好的方式,而且符合标准(不假设ASCII):

#include <string.h>
#include <ctype.h>

/* returns -1 if c is not an alphabetic character */
int c_to_n(char c)
{
    int n = -1;
    static const char * const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char *p = strchr(alphabet, toupper((unsigned char)c));

    if (p)
    {
        n = p - alphabet;
    }

    return n;
}

1
为了完全符合标准,您可能希望在分配之前将 p - alphabet 进行强制转换。您可以使用 ptrdiff_t 或其他一些技术上正确的类型,但考虑到范围限制,我认为这并不是真正必要的。任何整数类型都保证能够容纳我们在此处使用的任何值。 - Chris Lutz
是的,在这种情况下,我们可以保证p - alphabet在0到25范围内,因此它一定适合于int。我不认为需要进行强制转换 - 将一个整数类型赋值给另一个的语义是非常明确的。 - caf
3
@caf,我会为你点赞,因为它在标准范围内处理了所有字符集。这也是我第一次看到有人正确地用const const来表示指针和被指向物 :-) 当然,作为一个老手,我会简单地写:'return p ? (int)(p - alphabet) : -1;' 而不是用那么多的n和if语句。 - paxdiablo

10

C标准并不保证字母的编码顺序是连续的。因此,可移植代码不能假设,例如'B'-'A'等于1。

C规范的相关部分是5.2.1节,描述了字符集:

3 基本源和基本执行字符集都必须包含以下成员:拉丁字母的26个大写字母

    ABCDEFGHIJKLM   
    NOPQRSTUVWXYZ

拉丁字母表中的26个小写字母

    abcdefghijklm
    nopqrstuvwxyz

这是10个十进制的数字


    0123456789

下面的29个图形字符

    !"#%&'()*+,-./: 
    ;<=>?[\]^_{|}~ 

规范只保证数字具有顺序编码,对于字母字符的编码没有任何限制。


幸运的是,有一种简单高效的方法将A转换为0,B转换为1等。以下是代码:

char letter = 'E';                  // could be any upper or lower case letter
char str[2] = { letter };           // make a string out of the letter
int num = strtol( str, NULL, 36 ) - 10;  // convert the letter to a number

这个方法可行的原因可以在strtol的手册页面中找到:

 

(在十进制以上的进制中,大写或小写字母'A'表示10,'B'代表11等等,'Z'代表35。)

因此,将36作为进制参数传递给strtol函数告诉它将'A''a' 转换为 10,'B''b' 转换为 11,以此类推。只需要减去 10 就可以得到最终答案了。


10

如果你需要处理大小写,那么可以尝试以下操作:

if (letter >= 'A' && letter <= 'Z')
  num = letter - 'A';
else if (letter >= 'a' && letter <= 'z')
  num = letter - 'a';

如果您想要显示这些数字,那么您需要通过将数字加上字符 '0' 来转换成 ASCII 值:

  asciinumber = num + '0';

4
作为替代,可以使用num = toupper(letter) - 'A'来将字母转换为大写形式,从而避免条件判断。toupper()函数位于ctype.h头文件中。 - Chris Lutz
我们还可以注意到小写字母与大写字母之间只有 0x20 的差异。 - Noon Silk
True(真), 但是如果你需要区分某些内容,你可以使用条件语句,但有各种各样的选项,我只是想指出大小写可能会成为一个问题,因此应该进行处理。 - James Black
1
请注意,“asciinumber = num + '0';” 这一小节只适用于单个数字。 - Tal Pressman
你说得对,我没有考虑到asciinumber是有缺陷的。 - James Black
显示剩余2条评论

5

另一个比26个if语句更糟糕(但仍比较好)的替代方案是使用switch/case

switch(letter)
{
case 'A':
case 'a': // don't use this line if you want only capital letters
    num = 0;
    break;
case 'B':
case 'b': // same as above about 'a'
    num = 1;
    break;
/* and so on and so on */
default:
    fprintf(stderr, "WTF?\n");
}

只有在字母和其代码之间绝对没有关系时才考虑使用此方法。由于在您的情况下,字母和代码之间存在明显的顺序关系,因此使用此方法相当愚蠢且难以维护。但是,如果您必须将随机字符编码为随机值,则这是避免编写大量 if()/else if()/else if()/else 语句的方法。


2
这并不是那么愚蠢。尽管你在其他地方的评论,@Chris,C99仅要求数字字符按顺序排列。字母可以随意排列(例如EBCDIC及其两个不同的区域)。事实上,这是迄今为止唯一正确的答案。+1。 - paxdiablo
1
啊,我今天思路有些混乱。我确实知道数字是按顺序排列的,但我对字符产生了一些偏差。我真的需要阅读C标准。不过,如果这就是正确性的代价,我宁愿放弃EBCDIC。 - Chris Lutz
每个人都应该知道顺序不能保证,但是严肃地讲,你必须考虑你的受众。如果这个程序将被在任何“标准”计算机上运行的人使用,安全起见最好使用“字母 - 'A'”。 - Ed S.
1
@Ed:我的受众对象是那些了解并遵循标准的人(这里的标准指的是没有引号的标准)。你的程序不符合标准,这没关系——我知道绝大多数C环境使用ASCII或ISO646,但我认为声称只有这些才重要有点儿傲慢。ISO之所以留下了非连续字母的可能性,是有充分理由的——难道你真的认为你比他们更懂吗?我不想卷入争论,只是表达一下我的观点——我们也许只能同意不同。 - paxdiablo

4

有一种更好的方法。

在ASCII(www.asciitable.com)中,您可以了解这些字符的数字值。

'A'是0x41。

因此,您可以简单地从它们中减去0x41,以获得数字。我不太了解C语言,但是大概是这样:

int num = 'A' - 0x41;

应该可以正常工作。

4
也就是说:int num = letter - 'A'; 的意思是将字母转化为数字,其中'A'代表数字0,'B'代表1,以此类推。 - Nick Dandoulakis
2
通常更常用 int num = 'A' - 'A'(将第一个替换为所讨论的字符或变量)以防万一我们不使用ASCII,尽管我认为标准可能已经保证了这一点。我知道标准保证了'A'..'Z'在字符集中是连续的。 - Chris Lutz
1
我更喜欢使用'A',因为它可以提高可读性,否则有人就必须查找0x41并了解它是什么。 :) - James Black
8
标准只保证了数字 '0' 到 '9' 是连续的,而字母 'A' 到 'Z' 的连续性是不被保证的。 - caf
3
@silky - 不,这样做没有意义。使用0x41代替'A'是愚蠢的。我们为什么不直接用二进制来写数字和字符串呢?为什么不计算自己的跳转和指针算术呢?@caf - 这个问题在各个地方都有提到,但是我今天心情比较糟糕,所以提醒一下也无妨。 ; ) 但是没错,我从连续的“0”-“9”推断出了“A”-“Z”,虽然并非如此,但除非你打算编写针对大型计算机的代码,否则这是一个相当安全的假设。这并不能改变事实,即在几乎所有情况下,“- 'A'”要比“- 0x41”更好。 - Chris Lutz
显示剩余7条评论

0
在大多数编程和脚本语言中,都有一种方法可以获取任何字符的“序数”值。(将其视为从字符集开头的偏移量)
因此,您通常可以执行以下操作:
for ch in somestring:
    if lowercase(ch):
        n = ord(ch) - ord ('a')
    elif uppercase(ch):
        n = ord(ch) - ord('A')
    else:
        n = -1  # Sentinel error value
        # (or raise an exception as appropriate to your programming
        #  environment and to the assignment specification)

当然,这种方法对于基于EBCDIC的系统可能不起作用(对于一些其他奇异字符集也可能不起作用)。我想一个合理的检查方法是测试该函数是否返回范围在0..26之间的单调递增值,对于字符串“abc...xzy”和“ABC...XYZ”。

另一种完全不同的方法是创建一个字母及其值的关联数组(字典、表、哈希)(一个或两个简单循环)。然后使用它。(大多数现代编程语言都包括对关联数组的支持。

当然,我不会“替你做功课”。你必须自己完成。我只是解释了那些显而易见的方法,任何专业程序员都会使用。(好吧,汇编语言黑客也可以每个字节掩码一个位)。


1
大多数信息不适用于C语言。问题有语言标签是有原因的。 - Chris Lutz
@Chris:我想我们只能同意不同意见。我认为伪代码方法非常好。即使没有其他的,它也会迫使学生查找他们已经了解的语法——而不得不查找你本应该掌握的东西是一种非常好的学习方法。此外,对于许多不熟悉C样式语法的学生来说,C的“for”循环相当混乱:只有一个关键词,但有很多运算符和分隔符,而在其中的表达式中哪个做什么,你只需要知道即可。 - sbi

-1
#include<stdio.h>
#include<ctype.h>
int val(char a);
int main()
{
    char r;
    scanf("%c",&r);
    printf("\n%d\n",val(r));
}
int val(char a)
{
    int i=0;
    char k;
    for(k='A';k<=toupper(a);k++)
    i++;
    return i;
}//enter code here

欢迎来到Stack Overflow!虽然这段代码可能有助于解决问题,但它并没有解释为什么以及如何回答这个问题。提供这种额外的上下文将显著提高其长期教育价值。请编辑您的答案以添加说明,包括适用的限制和假设。 - Toby Speight

-1

由于在C和C++中,char数据类型与int数据类型类似,因此您可以选择像这样的一些内容:

char c = 'A';   // just some character

int urValue = c - 65;

如果您担心大小写敏感性:

#include <ctype.h> // if using C++ #include <cctype>
int urValue = toupper(c) - 65;

-1

唉,如果你有C++的话

对于Unicode,如何将字符映射到值的定义

typedef std::map<wchar_t, int> WCharValueMap;
WCharValueMap myConversion = fillMap();

WCharValueMap fillMap() {
  WCharValueMap result;
  result[L'A']=0;
  result[L'Â']=0;
  result[L'B']=1;
  result[L'C']=2;
  return result;
}

用法

int value = myConversion[L'Â'];

-1
我为一个项目编写了这段代码,我想知道这种方法有多么天真。
好处在于它似乎遵循标准,并且我的猜测是运行时间大约为O(k),其中k是字母表的大小。
int ctoi(char c)
{
    int index;
    char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    c = toupper(c);

    // avoid doing strlen here to juice some efficiency.
    for(index = 0; index != 26; index++)
    {
        if(c == alphabet[index])
        {
            return index;
        }
    }

    return -1;
}

2
或者你可以使用 strchr() 将那段代码简化为几行 :-) - paxdiablo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接