11.字符串和字符串函数
一、表示字符串和字符串I/O
字符串是以空字符( \0)结尾的char类型数组。
1.1 在程序中定义字符串
1.1.1. 字符串字面量(字符串常量)
-
用双引号括起来的内容称为字符串字面量,也叫作字符串常量。
-
双引号中的字符和编译器自动加入末尾的\0字 符,都作为字符串储存在内存中。
-
从ANSI C标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C 会将其视为串联起来的字符串字面量。
如,char greeting[50] = "Hello, and" " how are" " you" " today!";
等价于: char greeting[50] = "Hello, and how are you today!";
-
如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠 ( \)。
-
字符串常量属于静态存储类别,这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。
-
用双引号括起来的内容被视为指向该字符串储存位置的指针。这类似于把数组名作为指向该数组位置的指针。
如,printf("%p", "are"); ,根据%p 转换说明打印一个地址,printf()将打印该字符串首字符的地址。
1.1.2. 字符串数组和初始化
-
定义字符串数组时,必须让编译器知道需要多少空间。
-
一种方法是用足够空间的数组储存字符串。
如,const char m1[40] = "Limit.";
等价于:const char m1[40] = { 'L','i', 'm', 'i', 't', '.', '\0' };
-
注意最后的空字符。没有这个空字符,这就不是一个字符串,而是一个字符数组。
-
在指定数组大小时,要确保数组的元素个数至少比字符串长度多1(为了容纳空字符)。
-
所有未被使用的元素都被自动初始化为0(这里的0指的是 char形式的空字符,不是数字字符0)。
-
另一种方法是让编译器确定数组的大小。
-
如果创建一个稍后再填充的数组,就必须在声明时指定大小。声明数组时,数组大小必须是可求值的整数。
-
字符数组名和其他数组名一样,是该数组首元素的地址。
如,当char car[10] = "Tata";
以下表达式都为真: car == &car[0]、car == 'T'、*(car+1) == car[1] == 'a'。
-
还可以使用指针表示法创建字符串。
如,const char * pt1 = "Something.";
几乎等同于:const char ar1[] = "Something.";
pt1和ar1都是该字符串的地址。
1.1.3. 数组和指针
-
数组形式(ar1[]):
- 数组形式在计算机的内存中分配为,每个元素对应一个字符,还加上一个末尾的空字符'\0'。
- 通常,字符串都作为可执行文件的一部分储存在数据段中。当把程序载入内存时,也载入了程序中的字符串。
- 字符串储存在静态存储区中。但是,程序在开始运行时才会为该数组分配内存。此时才将字符串拷贝到数组中。此时的字符串有两个副本,一个是在静态内存中的字符串字面量,另一个是储存在数组中的字符串。
- 在数组形式中,ar1是地址常量。不能更改ar1,如果 改变了ar1,则意味着改变了数组的存储位置(即地址)。
-
指针形式(* pt1 ):
-
指针形式也使得编译器为字符串在静态储存区为空字符预留个元素空间。
-
一旦开始执行程序,它会为指针变量留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变,可以使用递增运算符来改变。
如,++pt1将指向第 2 个字符。 -
字符串字面量被视为 const 数据。如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为 const。
-
-
初始化数组把静态储存区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。
1.1.4.数组和指针的区别
-
指向字符串”的意思是指向字符串的首字符。假设有下面两个声明:
char heart[] = "I love Tillie!";
const char *head = "I love Millie!";
两者主要的区别是:数组名 ar 是常量,指针名 pt 是变量。
-
两者都可以使用数组表示法:heart[i],head[i]
-
两者都能进行指针加法操作:*(heart + i),*(head + i)
-
但是,只有指针表示法可以进行递增操作:head++
-
假设想让head和heart统一,可以这样做: head = heart; /* head现在指向数组heart */
但是,不能这样做: heart = head; /* 非法构造,不能这样写 */
因为赋值运算符的左侧必须是变量(或概括 地说是可修改的左值)。
但,head = heart; 不会导致head指向的字符串消失,这样做只是改变了储存在head中的地址。
-
数组的元素是变量(除非数组被声明为const),但是数组名是变量。
-
编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量。
char * p1 = "Klingon"; p1[0] = 'F'; printf("Klingon"); printf(": Beware the %ss!\n", "Klingon"); //如果编译器使用这种单次副本表示法,并允许 p1[0] 修改 'F',那将影响所有使用该字符串的代码. //建议在把指针初始化为字符串字面量时使用 const 限定符:const char p1 = "Klingon"; //把非 const 数组初始化为字符串字面量却不会导致类似的问题,因为数组获得的是原始字符串的副本 //如果打算修改字符串,就不要用指针指向字符串字面量
1.1.5.字符串数组
const char* mytalents[LIM] = { "Adding numbers swiftly",
"Multiplying accurately",
"Stashing data",
"Following instructions to the letter",
"Understanding the C language" };
char yourtalents[LIM][SLEN] = { "Walking in a straight line",
"Sleeping",
"Watching television",
"Mailing letters",
"Reading email" };
- 从某些方面来看,mytalents和yourtalents非常相似。两者都代表5个字符串。
- 但,mytalents数组是一个内含5个指针的数组,在我们的系统中共占用40字节。而yourtalents是一个内含5个数组的数组,每个数组内含40个char类型的值,共占用200字节。
- 且,mytalents中的指针指向初始化时所用的字符串字面量的位置,这些字符 串字面量被储存在静态内存中;而 yourtalents 中的数组则储存着字符串字面量的副本,所以每个字符串都被储存了两次。
- 即,字符串数组分配内存 的使用率较低。yourtalents 中的每个元素的大小必须相同,而且必须是能储 存最长字符串的大小。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构