这是一个相当长的答案,请随意跳到代码示例的末尾。
首先,通过初始化具有未指定长度的char 数组,您使该数组的长度为 1(它只包含空字符串)。这里的关键问题是 C 中的数组是固定大小的,所以名字不会变大。
其次,格式说明符%c 导致scanf 只读取一个字节。这意味着即使你创建了一个更大的数组,你也只会读取一个字节。
你给scanf 的参数是错误的,但意外地起作用了——当它需要一个指向char 的指针时,你传递了一个指向数组的指针。它之所以有效,是因为指向数组的指针指向数组的第一个元素。幸运的是,这是一个简单的解决方法,一个类型的数组可以传递给一个期望指向该类型的指针的函数——据说它“衰减”到一个指针。所以你可以直接传递name。
作为这两个操作的结果,您现在遇到了name 的长度为 1 的情况,并且您已经读取了其中的一个字节。下一个问题是sizeof(name)/sizeof(char) - 在您的程序中这将始终等于 1。 sizeof char 被定义为始终等于 1,因此将其用作除数不会产生任何影响,而且我们已经知道 sizeof name 等于 1。这意味着您的 for 循环只会从数组中读取一个字节。出于完全相同的原因,n 等于 1。这本身并没有错,只是可能不是您所期望的。
可以通过多种方式解决此问题,但我将展示其中一种。首先,你不想像你一样初始化name,因为它总是创建一个大小为 1 的数组。相反,你想手动为数组指定一个更大的大小,例如 100 个字节(其中最后一个一个将专用于终止空字节)。
char name[100];
/* You might want to zero out the array too by eg. using memset. It's not
necessary in this case, but arrays are allowed to contain anything unless
and until you replace their contents.
Parameters are target, byte to fill it with, and amount of bytes to fill */
memset(name, 0, sizeof(name));
其次,如果您只是从标准输入中读取字节字符串而不是更复杂的格式化字符串,那么您根本不需要使用scanf。你可以例如。使用fgets 从标准输入中读取整行,尽管其中还包括换行符,我们必须去掉它。
/* The parameters are target to write to, bytes to write, and file to read from.
fgets writes a null terminator automatically after the string, so we will
read at most sizeof(name) - 1 bytes.
*/
fgets(name, sizeof(name), stdin);
现在您已经记住了这个名字。但是name 数组的大小没有改变,所以如果您按原样使用其余代码,您会收到很多消息说The ASCII value of the letter is : 0。要获得有意义的字符串长度,我们将使用strlen。
注意:strlen 通常不安全地用于可能未正确以空值结尾的任意字符串,因为它会一直读取直到找到零字节,但我们只能得到一个可移植的边界检查版本 strnlen_s in C11。在这种情况下,我们也知道字符串是以 null 结尾的,因为 fgets 处理它。
/* size_t is a large, unsigned integer type big enough to contain the
theoretical maximum size of an object, so size functions often return
size_t.
strlen counts the amount of bytes before the first null (0) byte */
size_t n = strlen(name);
现在我们有了字符串的长度,我们可以检查最后一个字节是否是换行符,如果是则删除它。
/* Assuming every line ends with a newline, we can simply zero out the last
byte if it's '\n' */
if (name[n - 1] == '\n') {
name[n - 1] = '\0';
/* The string is now 1 byte shorter, because we removed the newline.
We don't need to calculate strlen again, we can just do it manually. */
--n;
}
循环看起来非常相似,因为它一开始就很好。大多数情况下,我们希望避免比较有符号的int 和无符号的size_t 可能出现的问题,因此我们还将i 设为size_t。
for (size_t i = 0; i < n; i++) {
int e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
把它们放在一起,我们得到
#include <stdio.h>
#include <string.h>
int main() {
char name[100];
memset(name, 0, sizeof(name));
printf("Enter a name : \n");
fgets(name, sizeof(name), stdin);
size_t n = strlen(name);
if (n > 0 && name[n - 1] == '\n') {
name[n - 1] = '\0';
--n;
}
for (size_t i = 0; i < n; i++){
int e = name[i];
printf("The ASCII value of the letter %c is : %d \n", name[i], e);
}
/* To correctly print a size_t, use %zu */
printf("%zu\n", n);
/* In C99 main implicitly returns 0 if you don't add a return value
yourself, but it's a good habit to remember to return from functions. */
return 0;
}
这应该可以正常工作。
补充说明:
此代码应该是有效的 C99,但我认为它不是有效的 C89。如果您需要写入较旧的标准,您需要做一些不同的事情。幸运的是,如果你告诉编译器你想使用哪个标准,你的编译器应该会警告你这些问题。 C99 可能是这些天的默认设置,但旧代码仍然存在。
像这样将字符串读入固定大小的缓冲区有点不灵活,因此在实际情况下,您可能希望有一种方法可以根据需要动态增加缓冲区的大小。这可能需要您使用 C 的手动内存管理功能,例如 malloc 和 realloc,这并不是特别困难,但要更加小心以避免出现内存泄漏等问题。
不能保证您正在读取的字符串采用任何特定编码,并且 C 字符串对于处理未以单字节编码编码的文本并不理想。支持“宽字符串”,但您可能更经常处理包含 UTF-8 的 char 字符串,其中单个代码点可能是多个字节,甚至可能不代表单个字母。在更通用的程序中,您应该牢记这一点。