在众多的数组中,有一个特殊的数组,即字符数组,我们需要对其进行额外的讲解。
在探究字符数组之前,我们来回顾与字符串相关的知识点,看个例子:
#include
int main()
{
printf("sizeof HelloWorld = %d\n", sizeof("HelloWorld"));
return 0;
}
使用 sizeof 操作符测量字符串 "HelloWorld" 占用了 11 字节,执行结果为:
sizeof HelloWorld = 11
内存中的字符串常量由每个字符的 ASCII 码按照顺序排列构成,每个字符仅占 1 字节,并且末尾会附上一个数值 0,指示字符串结尾,如下图所示:
图 1 字符串"HelloWorld"内部存储
字符 '0' 对应的 ASCII 码为十进制 48。为了不与字符 '0' 冲突,将标记字符串结尾的数值 0,使用转义序列 '\0' 表示。
字符数组存储字符串
由于字符串满足数组的类型相同且按顺序排列的特点,元素为 char 的数组可以用于存储字符串。
1) 初始化字符数组
我们可以声明一个 char 类型的数组,并将其初始化为 "HelloWorld",代码如下:
char str[20] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
在上述代码中,由于数组大小为 20,而初始化列表中仅有 10 个元素,数组中剩余的 10 个元素会被自动初始化为 0,即自动添加了字符串结尾标识符 '\0'。
此外,我们还可以使用更简便的字符数组初始化方式,即将初始化列表直接写成一个字符串常量,代码如下:
char str[20] = "HelloWorld";
在上述代码中,字符串常量的末尾会被自动添加 '\0' 作为字符串结尾标识符,因此上述代码等价于:
char str[20] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0'};
2) 省略数组大小
有时候,我们希望一个数组被初始化为某个字符串,但是又不想数清楚到底有多少个字符。此时,我们可以在数组声明时省略数组大小,此时数组的大小就是初始化列表中元素的个数。
代码如下:
char str1[] = "HelloWorld";
char str2[] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0'};
上述两种写法是等价的,数组的大小为 11,即初始化列表中的元素个数。
3) 输出字符数组
接下来,我们可以使用 printf() 函数将字符数组中存储的字符串输出到控制台中,由于 printf() 函数的第一个参数可以接收一串字符串,因此我们可以直接将数组作为 printf() 的第一个参数,如下所示:
printf("HelloWorld");
printf(str); //使用字符数组
在 C语言中,字符数组和字符串常量是存储字符串的两种方式。字符串常量是一种特殊类型的字符数组。当我们在 C语言中使用 printf() 函数时,可以直接将字符数组作为参数传递给该函数,因为 printf() 函数会将字符数组中的字符逐个输出到控制台中,直到遇到空字符。
此外,转换规范 %s 也可以作为字符数组的占位符,代码如下:
printf("%s", str);
下面是一个将字符数组输出到控制台中的示例:
#include
int main()
{
char str[20] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
printf(str);
printf("\n");
printf("%s", str);
printf("\n");
return 0;
}
运行结果为:
HelloWorld
HelloWorld
字符串结尾标记'\0'
在使用字符串常量时,系统会自动为我们在字符串末尾添加 '\0' 标记字符串结束。但是在使用字符数组时,有些情况不能保证字符串末尾有 '\0',需要格外注意。
1) 初始化列表长度小于数组长度
如果使用初始化列表初始化字符数组,并且初始化列表长度小于数组长度,则数组前面的元素将被初始化为字符串,后面的元素将被填充为 0。在这种情况下,字符数组中的字符串正常结尾,因为系统在初始化期间已经为字符串添加了结尾标记 '\0'。
例如:
char str[20] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
上面的代码将数组前 10 个元素初始化为字符串 "HelloWorld",后面的 10 个元素被填充为 0。
2) 初始化列表长度等于数组长度
如果使用初始化列表初始化字符数组,并且初始化列表长度等于数组长度,则数组中的元素都被初始化为字符串。但由于初始化列表已经占用了数组中的所有空间,因此没有空间可以保存结尾标记 '\0'。
在这种情况下,字符数组中的字符串无法结尾,这可能会导致数组越界访问。例如:
char str[10] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
上面的代码将数组中的所有 10 个元素都初始化为字符串 "HelloWorld"。但由于缺少结尾标记 '\0',在使用 printf() 等函数输出该字符数组时,程序将继续输出数组外的元素,直到遇到一个 '\0' 才停止。这可能导致数组被越界访问。
例如,下面的代码将输出字符数组 str 中的所有元素,以及其后面的不确定值。
printf("%s", str);
运行结果为:
HelloWorld烫烫烫烫烫烫烫烫烫
因此,在使用初始化列表初始化字符数组时,必须确保数组中有足够的空间保存结尾标记 '\0'。
3) 初始化列表长度大于数组长度
如果使用初始化列表初始化字符数组,并且初始化列表长度大于数组长度,则将无法通过编译。这是因为初始化列表中的元素数量超过了数组的容量,无法被全部存储。
例如:
char str[5] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
上面的代码将会导致编译错误,因为初始化列表中有 10 个元素,而数组只有 5 个元素的容量。
4) 省略数组大小的情况
在 C语言中,我们有时会省略数组的大小并直接使用字符串常量来初始化数组。
例如,下面的代码:
char str[] = "HelloWorld";
这段代码省略了数组的大小,直接使用字符串常量来初始化数组。由于系统会自动为字符串常量结尾添加 '\0',因此字符串常量大小为 11。使用该字符串初始化数组,数组大小也为 11,并且最后一个字符为 '\0',即字符串正常结尾。
我们还可以使用初始化列表来初始化数组,例如:
char str[] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0'};
在这段代码中,我们使用了初始化列表来初始化数组。该初始化列表包含了 11 个字符常量,其中最后一个字符常量为 '\0',表示字符串的结尾。使用该初始化列表初始化数组,数组大小为 11,并且最后一个字符为 '\0',即字符串正常结尾。
需要注意的是,如果我们使用以下代码初始化数组,则该数组中的字符将不会正常结尾。
char str[] = {'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'};
该初始化列表包含了 10 个字符常量,最后一个字符常量为 'd',因此使用该初始化列表初始化数组,数组大小为 10,并且最后一个字符为 'd'。这意味着该字符数组中的字符串无法正常结尾。
在实际编程中,我们应该避免出现这种情况,以确保程序的正确性和稳定性。
字符数组的大小及长度
在下面的代码中,我们声明一个长度为 20 的字符数组 str,并使用字符串常量"HelloWorld"对它进行初始化。这样,字符数组的前 10 个元素为 HelloWorld,第 11 个元素为 '\0'。
char str[20] = "HelloWorld";
为了测试 str 的大小,我们可以使用 sizeof 关键字,结果为 20。从结果可以看出,str 的大小为 20 字节,和我们声明的数组大小一致。因此,我们无法直接用 sizeof 关键字测量字符串的长度。
接下来,我们将提供两种方法来测量字符串的长度。
1) 使用循环测量字符串长度
一个字符串用 '\0' 标记结尾,只要知道在 '\0' 之前,有多少个字符,就能知道字符数组中的字符串的长度了。具体的代码如下:
#include
int main()
{
char str[20] = "HelloWorld";
int len = 0;
while(str[len] != '\0') // 检查字符串结束符
{
len++;
}
printf("%d", len);
return 0;
}
在程序中,我们声明了一个 len 变量,用于统计字符串长度。while 循环从第一个元素开始,检查元素是否为 '\0',如果不是,则将 len 加 1,直到元素为 '\0'。循环结束时,len 的值即为字符串的长度。运行结果为 10。
2) 使用strlen()测量字符串长度
我们还可以使用 strlen() 函数来计算字符串的长度。strlen() 是由 string(字符串)和 length(长度)两个单词组合而成。
strlen() 函数的使用方法如下:
strlen() 函数可以接收一个字符串作为参数;
strlen() 函数的返回值是这个字符串的长度;
在使用 strlen() 函数之前,需要包含头文件
下面展示了一个使用 strlen() 函数的例子:
#include
#include
int main()
{
char str[20] = "HelloWorld";
int len1;
len1 = strlen(str);
printf("len1 = %d\n", len1);
int len2;
len2 = strlen("HelloWorld");
printf("len2 = %d\n", len2);
printf("sizeof str %d\n", sizeof(str));
printf("sizeof helloworld %d\n", sizeof("HelloWorld"));
return 0;
}
在这个例子中,len1 是由 strlen() 函数测量的字符数组 str 内字符串的长度,len2 是由 strlen() 函数测量的字符串常量 "HelloWorld" 的长度。后续,用 sizeof 分别测量字符数组 str 和字符串常量 "HelloWorld" 占用的空间大小。
这个程序的运行结果为:
len1 = 10
len2 = 10
sizeof str 20
sizeof helloworld 11
因为 HelloWorld 有 10 个字符,所以字符串长度为 10,len1 和 len2 都是 10。字符数组有 20 个元素,所以它占用 20 字节的空间。字符串常量为字符串长度加上结尾标记,所以它占用 11 字节的空间。
总的来说,strlen(str) 测量从第一个元素开始直到元素值为 '\0' 的字符串的长度,而 sizeof(str) 测量数组本身占用的空间大小。
修改字符数组
字符串常量是不可变的,但字符数组却可以被修改。
下面展示了一个修改字符数组的例子:
#include
#include
int main()
{
char str[20] = "abcde";
// 修改前
printf("%s\n", str); // 使用%s格式符来打印字符串
// 每个元素减 32
for(int i = 0; i < strlen(str); i++)
{
str[i] = str[i] - 32;
}
// 修改后
printf("%s\n", str);
return 0;
}
在程序中,数组被初始化为 "abcde",然后我们将其中的小写字符转换为大写字符。根据 ASCII 码,将小写字符的 ASCII 码减去 32 即可得到对应的大写字符。运行结果为:
abcde
ABCDE
从键盘输入字符串到字符数组中
可以使用 scanf() 函数将从键盘中输入的一串字符串存储到字符数组中,其转换规范为 "%s"。例如:
char str[20];
scanf("%s", str); // 将从键盘中输入的一串字符串存储到字符数组str中
下面程序使用 scanf() 函数将一串字符串存储到字符数组中,并将其中的小写字符转换为大写字符后再进行输出。
#include
#include
int main()
{
char str[20];
// 输入一串字符到 str 中
scanf("%s", str);
// 修改前
printf("%s\n", str);
// 每个元素减 32
for(int i = 0; i < strlen(str); i++)
{
str[i] = str[i] - 32;
}
// 修改后
printf("%s\n", str);
return 0;
}
scanf() 函数会自动在输入的字符串后面加上 '\0',因此输入的字符串可以正常结束。运行结果为:
apple
apple
APPLE