字符串与字符数组
字符串是由一系列字符组成的字符数组,并以空字符 '\0'
结尾。
字符数组与字符串的概念在 C 语言中密不可分,是计算机处理文本时的基础。
字符数组与字符串数组
字符数组
字符数组:一个存储字符的数组 char str[6] = "hello";
,通常用于表示字符串。
字符数组 str
包含5个字符 'h'
, 'e'
, 'l'
, 'l'
, 'o'
和一个隐含的结尾字符 '\0'
,表示字符串的结束。
因此,字符数组初始化时,大小应比存储的字符串长读至少多一个字节。
char c[10]="1234567890";
printf("%s\n", c);
//输出结果为:123456789烫烫烫
//表示此时赋值没有结束符,使用%s输出会在'\0'处停止
字符串数组
包含多个字符串的数组。例如:
char names[3][10] = {"Alice", "Bob", "Charlie"};
这个 names
数组包含三个字符串,每个字符串最多可以容纳 9 个字符(最后一个字符是 '\0'
)。
字符串的打印
printf
函数用于格式化输出字符串和字符。此时,使用 %s
和 %c
来打印字符串和字符有明显区别:
-
%s
:用于打印字符串,即一系列字符直到遇到'\0'
结束。例如:char str[] = "hello"; printf("%s", str); // 输出: hello
-
%c
:用于打印单个字符。例如:char ch = 'A'; printf("%c", ch); // 输出: A
如果使用 %s
打印一个字符数组,程序会输出从该地址开始的所有字符,直到遇到 '\0'
。而使用 %c
只会打印单个字符。
void print(char c[])
{
int i=0;
while(c[i]!=0)
{
printf("%c", c[i]);
//%c是单字符逐一打印
i++;
}
}
int main()
{
char c[10]="123456789";
printf("%s\n", c);
//%s从起始地址开始打印字符串,在'\0'处结束
//此处"c"是字符数组名,表示字符数组的起始地址
print(c);
}
字符串的初始化
scanf
函数
在使用 scanf
函数读取字符串时,有一些重要的注意事项:
-
不要使用取地址符
&
:读取字符串时,
scanf
不需要使用取地址符&
,因为字符数组的名称本身就代表了该数组的首地址。例如:char name[20]; scanf("%s", name);
-
不能读取带空格的字符串:
scanf
在遇到空白字符(如空格、回车)时会停止读取,因此它不能直接读取带空格的字符串。C 语言会自动在最后一个字符后填入结束符。char c[10]; char d[10]; scanf("%s%s", c, d); //匹配时忽略空格 //输入:hello world printf("%s--%s\n", c, d); //输出:hello--world
如需读取整行字符串,可以使用
gets
函数。
gets
与 puts
函数
gets
和 puts
是处理字符串输入输出的函数:
-
gets
:用于从标准输入读取一行字符串,直到遇到新行(\n
)或到达 EOF,新行字符翻译为一个 null 中断符。与scanf
不同,gets
可以读取包含空格的整行字符串。然而,由于gets
没有边界检查,可能会导致缓冲区溢出,因此在现代 C 编程中,gets
已被fgets
替代。char str[50]; gets(str);
-
puts
:用于向标准输出打印一个字符串,并自动在结尾添加一个换行符。函数成功时返回非负值,失败时返回 EOF。相比printf
,puts
更简单直观,尤其在只需打印字符串时。char str[] = "Hello, World!"; puts(str); // 输出: Hello, World! 并换行
使用gets
与 puts
来输入输出字符串:
int main()
{
char c[20];
while (gets(c) != NULL)
{
puts(c);
}
}
字符数组与指针
字符数组的名称表示该数组的首地址,可以作为指针传入函数。
代码注释与介绍
#include <stdio.h>
// 定义一个函数 change,它接受一个字符指针作为参数
void change(char *p)
{
*p = 'H';
// 将 p 指向的第一个字符修改为 'H'
p[1] = 'E';
// 将 p 指向的第二个字符修改为 'E'
*(p + 2) = 'L';
// 将 p 指向的第三个字符修改为 'L'
}
int main()
{
char c[10] = "hello"; // 定义一个字符数组 c,并初始化为字符串 "hello"
change(c); // 调用 change 函数,并将字符数组 c 的首地址传递给它
puts(c); // 输出修改后的字符串
// 输出:HELlo
return 0;
}
change
函数接受一个字符指针 p
作为参数,这个指针指向一个字符数组的首地址。如,p
将指向数组 c
。
通过指针 p
的偏移,修改数组 c
中的字符:
*p = 'H';
将第一个字符修改为'H'
。p[1] = 'E';
将第二个字符修改为'E'
。*(p + 2) = 'L';
将第三个字符修改为'L'
。
指针与字符数组的初始化
int main()
{
char *p = "hello";
// p 指向存储在常量区中的字符串 "hello"
char c[] = "hello";
// 在栈区中分配内存并存储 "hello"
c[0] = 'H';
// 修改字符数组 c 的第一个元素,合法操作,c 现在变成 "Hello"
// p[0] = 'H'; // 试图修改常量区的内容是非法的,程序会崩溃
p = "world"; // 合法操作,指针 p 现在指向另一个字符串常量 "world"
// c = "world"; // c 是常量,不能对它重新赋值
}
常量区(或称为只读数据区)是内存的一部分,用来存储程序中那些不需要修改的常量数据,比如字符串常量。如,在代码中直接写 "hello"
这样的字符串时,它就被存储在常量区。
特点:常量区的内容是只读的,任何试图修改它的操作都会导致程序错误(通常会崩溃)。
常量区与栈区的区别:常量区的字符串不可修改,而栈区的字符数组可以修改。对常量区的写操作会导致程序崩溃。
指针 p
和字符数组 c[]
的区别:
char *p = "hello";
:p
是一个指针,指向存储在常量区的字符串"hello"
的首地址。("hello"
是一个字符串常量,它存储在常量区,因此p
指向一个只读的内存区域。)char c[] = "hello";
:c[]
是一个字符数组,表示在栈区(或堆区)分配内存,并复制一份字符串"hello"
到这个数组中。数组c
是可以被修改的,因为它是程序运行时在栈或堆中分配的内存空间。
数组名是常量:
c
是字符数组的名字,它代表数组的首地址。虽然c
指向的是数组的第一个元素,但它的值(即首地址)是常量,不能被修改。换句话说,不能对c
重新赋值让它指向其他地方。
指针 p
和字符数组 c[]
:p
可以指向不同的字符串常量(如从 "hello"
到 "world"
),但不能修改它指向的字符串内容(常量区只读)。而 c[]
是字符数组,存储在栈区,可以修改数组的内容(如把 "hello"
改为 "Hello"
),但不能改变 c
的指向。
其他:字符串操作函数练习
mystrlen
字符串长度
#include <stdio.h>
int mystrlen(char *c)
{
int i;
while(*c)
{
i++;
c++;
}
return i;
}
int main()
{
char c[20];
while(gets(c)!=NULL)
{
printf("%d\n",mystrlen(c));
}
}
mystrcpy
复制字符串
#include <stdio.h>
void mystrcpy(char *str1, char *str2)
{
int i=0;
while(*str2)
{
str1[i] = str2[0];
i++;
str2++;
}
str1[i]=0;
}
int main()
{
char c[20];
char d[20];
while(gets(c) != NULL)
{
gets(d);
mystrcpy(c, d);
printf("%s\n", c);
}
}
mystrcmp
比较字符串
#include <stdio.h>
int mystrcmp(char *str1, char *str2)
{
int d;
while(*str1 && *str2)
{
d = *str1 - *str2;
if(d != 0) break;
str1++;
str2++;
}
if(d == 0)
{
d = *str1 - *str2;
}
return d;
}
int main()
{
char c[20];
char d[20];
while(gets(c)!=NULL)
{
gets(d);
printf("%d\n", mystrcmp(c, d));
}
}