赞
踩
用双引号括起来的内容称为字符串字面量,也叫做字符串常量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。
我们已经知道C语言将字符串当成字符数组来存储,具体而言:
长度为n的字符串字面值,会在虚拟内存的只读数据段中,分配n+1长度的字符数组,用来存储字符序列及一个终止的空字符’\0’。
比如一个字符串字面值"abc"是这样存储的:
字符串字面值被存储在只读数据段中,这意味着字符串字面值有以下特点:
总之,字符串字面值本质上就是一个存储在只读数据段当中的字符数组。注意:
所有的字符串都是以终止字符’\0’结尾的字符数组,但并不是所有的字符数组都可以视为有效的字符串。
于是:
字符串字面值在代码中的大多数场景下,可以直接视为一个字符数组的数组名使用,也就是视为字符数组的首元素指针去使用。比如你可以写出下面的代码:
// 以下场景中,"hello"在代码中会被视为数组名,也即首元素指针 // 指针p指向数据段中的只读字符串字面值数组,此时指针p无法修改指向内容 char* p = "hello"; // &"hello"在标准C语言中是不允许的,因为"hello"本身就是地址,无需再取地址。不要这么做! // char *p2 = &"hello"; // 既然被视为数组名,就支持索引运算。字符c此时是'o' char c1 = "hello"[4]; // 指针p_element指向字符'o' char* p_element = "hello" + 4; // 索引运算就是算术运算和解引用运算的语法糖。字符c此时是'o' char c2 = *("hello" + 4);
同样的,在以下场景中,字符串字面值无法视为视为首元素指针:
sizeof运算符。sizeof("hello")
是计算此字符数组的大小,也就是字符串的长度 + 1,这里的结果是6。
字符串字面值既不能指向一个新的字符数组,指向的内容也不可修改,
之所以有这样的特点,原因在于:
除此之外,我们还将演示两种字符串字面值操作的场景:
这两种场景中,字符串字面值在代码中都会被视为数组名,充当"首元素指针"。
C语言并没有专门的、独立的字符串类型,只是规定任何一维的字符数组都可以用来存储字符串,只要保证以空字符 \0 结尾即可。
这种设计看起来很简单,但为字符串的实际使用带来了很大的困难。主要体现在以下几点:
数组长度可不可以小于字符串长度 + 1呢?
可以这么做,但此时的字符数组由于没有存储’\0’,这样的字符数组是不能作为字符串使用的。所以一定要确保字符数组的长度大于字符串的长度,至少大1个长度。
为了加深大家对字符串变量的理解,将它完全和字符串字面值区分开。我们简单比较一下两个非常相似的初始化语法——字符串变量和字符指针的初始化。
// main:
char str[] = "Showmaker";
char *p = "Showmaker";
它们的区别在于:
str[4] = 'M'; // 该代码是可行的,修改后的字符串变为"ShowMaker"
p[4] = 'M'; // 这样的代码会导致未定义行为,字符串字面值是只读的
在字符串处理的场景下:
字符串字面值是不可修改的,所以它们只能进行写的操作,无法实现读。
所谓写字符串,也就是将整个字符串遍历,逐个输出字符或者整个输出。字符串变量和字面值都可以进行写的操作。
如果是逐个输出,一般需要循环遍历,
当然更常见的,我们会整个输出字符串
如果希望输出字符串的一部分,可以使用转换说明中的**%.ps**来控制,其中p是一个正整数,用于确定要显示的字符。
为了更方便快捷的打印字符串,通过包含头文件stdio.h,我们就可以使用一个C标准库函数——puts函数。
注意:
注意:
scanf函数传参时,转换说明后面的参数需要传地址(指针变量),但str数组名作为参数传递时会退化为指针。切记不要写成"&str"!!
scanf函数读键盘录入字符串的特点是:
跳过前面的所有空白字符,从第一个非空字符开始读取,遇到第一个空白字符(空格,制表等)结束,中间的内容都是录入的内容。
如果键盘录入内容:
abcd ef
那么录入的字符串内容实际是:
abcd
scanf函数在表示录入字符串时,会在录入结束后,在字符信息的后面自动存储一个空字符。
比如上面得到的"abcd"字符串的字符数组,内容是:
‘a’, ‘b’, ‘c’, ‘d’, ‘\0’
利用scanf录入的字符串,永远不可能包含空白字符(空格,制表等),因为遇到空白字符它就会结束录入。
scanf函数是不安全的,采取以下做法让它更安全
scanf函数是不安全的,在录入字符串时它会自动在字符数组后面存储一个空字符。
这就意味着,最多只能录入数组长度-1的字符数量,若超出则会出现数组下标越界问题。你可以使用转换说明"%长度s"来限制最大读取的字符数量,其中长度可设定为数组长度-1的字面值。
例如上面代码中,字符数组长度是10,采用下列转换说明限制最多读取9个字符:
scanf("%9s", str); // 限制最多读取9个字符
这种做法避免了将键盘输入读入字符数组时,发生越界访问非法内存区域的情况,是推荐的做法!!
scanf函数无法录入空白字符,且它无法确保录入一整行的数据。为了实现这两个目标,我们可以使用标准库函数——gets(),使用它同样需要包含头文件stdio.h。
此函数的行为是:
注意事项:
推荐使用更安全的fgets函数替代gets函数
假如需要从标准输入流(stdin,也就是键盘录入)中读取一行字符数据,并存储在一个字符串变量中,建议使用fgets这个函数:
fgets(str, sizeof(str), stdin); // str是一个存储数据的字符数组
该函数的行为是:
fgets函数会将换行符存入数组的特点,在有些时候是比较讨厌的,比如:
总之,如果你不需要这个额外存入的换行符’\n’,可以用下列手段去掉它:
// str是fgets函数录入数据的一个字符串,空字符前可能存在一个换行符
int len = strlen(str); // strlen获取当前字符串的长度,也就是空字符前面字符的个数
// 换行符如果有,那肯定是字符串最后一个有效字符
if (len > 0 && str[len - 1] == '\n') {
str[len - 1] = '\0';
}
C 语言中的字符串处理与其他高级编程语言有所不同。在许多编程语言中,字符串被当作一个独立的类型,并提供了一套丰富的运算符来操作这些字符串,如复制、比较和拼接。
但在C语言中,字符串实际上就是以空字符’\0’结束的字符数组。这意味着常规的C运算符不能直接用于字符串操作。
但幸运的是,C 语言为我们提供了丰富的字符串处理相关的库函数,来帮助我们处理字符串相关的任务。
这些函数基本上,都声明在头文件string.h当中,所以使用它们需要包含这个头文件。在这里,我们不可能将所有的函数都逐一介绍,这里我们只介绍几种最基本的和最常用的。
作用是:返回当前字符串的长度,也就是字符数组中空字符’\0’前面的字符数量。
int len;
char str[] = "abcd";
char str2[10] = "12345";
char str3[5] = { 'a','\0','c' };
len = strlen("abc"); /* len is now 3 */
len = strlen(""); /* len is now 0 */
len = strlen(str); /* len is now 4 */
len = strlen(str2); /* len is now 5 */
len = strlen(str3); /* len is now 1 */
char str4[4] = "1234";
len = strlen(str4); // str4并不能表示一个字符串,该函数调用会引发未定义行为
注意:
char *strcpy(char *dest, const char *src);
函数作用: strcpy函数会将 src(source,源数组) 中存储的以空字符 ‘\0’ 结束的字符串复制到 dest(destination,目的数组) 所指向的数组中。
也就是说,会从首字符开始逐个字符地从 src 复制字符,包括空字符在内,都会从dest首位置开始,复制到dest当中。
这个过程中,src数组是不会被修改的,所以它被const修饰。
总之,该函数调用后,dest 将包含 src 的完整副本。
函数返回值:
该函数会返回指向目标数组 dest 的指针。一般来说,该函数的返回值没有什么意义,推荐忽略它。但某些场景下,这个返回值允许strcpy 函数的调用,可以被嵌套使用或者用于更复杂的表达式中。
char src[] = "hello";
char dest[10];
char dest2[10];
// 可以直接利用printf、puts函数打印复制后的目标数组
puts(strcpy(dest, src));
// 更加复杂的函数调用
// 将src复制到dest,再将dest复制给dest2
strcpy(dest2, strcpy(dest, src));
注意:
strncpy函数,全名是string copy (n characters),即复制字符串的(n个字符)。该函数的声明如下:
char *strncpy(char *dest, const char *src, size_t n);
该函数会将最多n的字符从src中复制到dest数组中:
所以,在实际开发中建议将n的值设定为dest长度-1,并主动将dest的最后一个元素设置为空字符,这样总能安全地得到一个字符串。
也就是以下操作代码:
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
实际的代码案例如下:
char src[] = "hello";
char dest[10];
// 这个复制是没有问题的
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
char dest2[5];
// 这个复制虽然没有真的将"hello"都复制到dest2,但是一个安全的操作。最终dest2存放字符串"hell"
strncpy(dest2, src, sizeof(dest2) - 1);
dest2[sizeof(dest2) - 1] = '\0';
strcat 函数的全名是 string_concat,顾名思义,它用于将一个字符串拼接到另一个字符串的末尾。
该函数的声明为:
char *strcat(char *dest, const char *src);
函数作用: strcat 函数会将 src(source,源字符串)中存储的以空字符 ‘\0’ 结束的字符串拼接到 dest(destination,目标字符串)的末尾。
具体来说:
会从 dest 字符串的空字符 ‘\0’ 开始,替换这个空字符,然后将src中表示字符串的字符数据从开头到空字符,全部拼接到dest末尾。
这个过程中,src 字符串不会被修改,所以它被 const 修饰。
总之,该函数调用后,dest 将包含 dest 和 src 的字符串拼接后的副本。
函数返回值:
该函数会返回指向目标数组 dest 的指针。一般来说这个返回值可以忽略,但有些场景中可以利用该返回值,对函数调用进行嵌套处理。
char dest[20] = "Hello, ";
char src[] = "World!";
char src2[] = " Have a nice day!";
// 嵌套,将src和src2都拼接到dest上
strcat(strcat(dest, src), src2);
// 输出最终的拼接字符串
printf("%s", dest); // 输出 "Hello, World! Have a nice day!"
注意:
strncat函数,它的声明如下:
char *strncat(char *dest, const char *src, size_t n);
该函数的行为是:
为了安全的使用此函数,基于函数的特点,我们就可以得出n的计算公式:
int n = sizeof(dest) - strlen(dest) - 1; // dest数组的长度 - 已存储字符串的长度 - 1(留给存空字符)
表达式中-1
是必要的,否则会导致数组越界情况发生。
给strncat函数传入这样的一个n
参数,就能够保证一定安全的得到一个拼接后的结果字符串。
当然若dest容量不够大,拼接只会截取src的一部分字符数据。但安全是更重要的事情,这样的结果是我们可以接受的。
实际开发中,建议采取以下方式来调用此函数,避免dest空间不足导致溢出:
char src[] = “world”;
char dest[10] = "hello, ";
// 确保dest是一个数组类型,而不是一个指针类型才能这样操作
int n = sizeof(dest) - strlen(dest) - 1;
strncat(dest, src, n);
也就是说,dest最多再拼接**(自身数组长度 - 字符串长度 - 1)**个字符,最后留一个字节,strncat函数会自动拼接一个空字符表示字符串结束。
strcmp函数的全名是 string_compare,它用于比较两个字符串的大小。(当然这个大小肯定不是一般的数值大小)
该函数的声明为:
int strcmp(const char *str1, const char *str2);
函数作用: **strcmp函数按照字典顺序比较两个字符串的大小。**当你在 C 程序中调用 strcmp函数时,它会逐个字符地比较两个字符串 str1 和 str2:
注意:
在操作字符串的过程中,经常需要对单个字符做出判断,比如:
C 标准函数库在头文件 <ctype.h> 中定义了大量此类功能函数。比如:
islower 和 isupper 函数是 C 标准库中声明在 <ctype.h> 头文件中的函数,它们用于检查给定的字符是否为小写字母或大写字母。
除此之外,<ctype.h> 头文件中还包括了以下类似的库函数:
isalpha(int c)
:检查传入的字符是否为字母(包括大写和小写)。isdigit(int c)
:检查传入的字符是否为十进制数字(0到9)。isalnum(int c)
:检查传入的字符是否为字母或数字。isspace(int c)
:检查传入的字符是否为空白字符,比如空格、制表符、换行符等。isblank(int c)
:检查传入的字符是否为空格或制表符。总之,我们建议大家在操作字符串时,碰到某个需求可以优先查找一下是否有现成的库函数实现,若有则不需要重复造轮子。
在实际应用中,我们经常需要处理一组字符串。因此,我们就需要思考这样一个问题:如何存储一组字符串集合呢?
在C语言中,处理字符串集合时,我们通常有两种主要的存储方式:
用字符二维数组来存储字符串集合
char week_days[][10] =
{ "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday" };
用字符指针数组来存储字符串集合
char* week_days[] =
{ "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday" };
这两种方式都可以称之为"字符串数组"
char char_arr[10];
这是一个字符数组的声明。它分配了10个连续的char类型的存储空间。你可以使用这个数组来存储最多9个字符的字符串(第10个字符用来存储字符串结束符’\0’),或者任何其他的字符数据。
它可以执行的操作有:
利用索引运算符取、改元素。如:
char_arr[0] = 'A';
当此字符表示存储字符串时,可以直接作为参数传递给字符串处理函数。
char *char_p;
这是一个指向char类型的指针变量的声明。它可以存储一个字符变量的地址,或者更常见的是指向字符数组(字符串)的首地址。
它可以执行的操作是:
指向一个字符(如char c; p = &c;
)。
指向一个字符串或字符数组的首字符(如char str[] = "Hello"; p = str;
)。
指向字符串的首字符后,它可以用于各种遍历数组的场合。比如:
while (*char_p){ // 字符串字符数组会以'\0'结尾,这个循环条件就可以遍历完整个字符串
printf("%c\n", *char_p++); // 逐个遍历打印字符串的字符
}
char *p_arr[10];
这是一个字符指针数组的声明,数组里有10个元素,每个元素都是指向char类型的指针。通常用来存储多个字符串的地址,也就是存储一组字符串的集合。
字符指针数组是字符串数组最常见的形式之一。
char (*arr_p)[10];
这是一个指针的声明,它指向一个具有10个char的数组。通常用于指向二维字符数组中的一行,或者一个固定大小字符数组的指针。
它可用于字符二维护数组的遍历,但一般比较少见。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。