字符串简介
C语言中的字符串一般就是字符数组(char array terminated with a null character '\0')。而数组和指针又可以操作字符串,同时C标准库(stdio.h和string.h等)提供了很多常用的字符串操作函数。
因此,有必要记录这些重要的函数,以及字符串的函数和指针操作。
字符串的定义方式
最常用的方式为使用字符串常量,使用char类型数组,或使用char指针。
字符串常量
如"I am a symbolic string"等,字符串常量存储在内存中的静态存储区中,意味着一旦在函数中使用字符串常量,字符串常量仅会存储一次,其生命周期在整个程序的运行期,无论函数调用多少次。
整个字符串短语充当指向存储字符串的位置的指针。 此操作类似于充当指向数组位置的指针的数组的名称。如下程序示例。
#include<stdio.h>
int main(void)
{
printf("%s, %p, %c\n", "We", "are", *"space farers");
return 0;
}
输出结果为:
We, 0x104fbafa1, s。可以看出,%s输出整个字符串,%p输出字符串常量的地址,*操作取字符串首地址的值,即为s。
字符串数组的初始化
const char m1[40] = "Limit yourself to one line's worth."; const表示不允许通过m1的操作改变字符串产量。
const char m2[6] = "hello"; 和 const char m2[6] = {'h', 'e', 'l', 'l', 'o', '\0'}的标准数组初始化相同。值得注意的是,若以第二种方式声明字符串数组,必须显式使用'\0‘作为最后一个字符,否则该数组仅仅为字符数组,而不是字符串。
省略数组方括号中的字符长度,编译器将自动决定所需大小。
字符串的数组表示和指针表示的区别
示例:
// addresses.c -- addresses of strings
#define MSG "I'm special."
#include<stdio.h>
int main(void)
{
char ar[] = MSG;
const char *pt = MSG;
printf("address of \"I'm special\": %p \n", "I'm special");
printf(" address ar: %p\n", ar);
printf(" address pt: %p\n", pt);
printf(" address of MSG: %p\n", MSG);
printf("address of \"I'm special\": %p \n", "I'm special");
return 0;
}
输出:
address of "I'm special": 0x10de0ef4c
address ar: 0x7ffee1df1adb
address pt: 0x10de0ef20
address of MSG: 0x10de0ef20
address of "I'm special": 0x10de0ef4c
解读:pt和MSG的地址相同,ar的地址不同。MSG存储在静态存储区,pt指向静态存储区中该字符串的首地址,所以MSG和pt的值相同。而ar是在程序运行时才分配内存的,此时静态存储区中的字符串会复制一份到字符串数组中,但数组的首地址和静态存储区中的地址不为同一个地址。也就是说,此时会有MSG的两份字符串,一份为静态存储区中的, 另一份为存储在ar中。
"I'm special."打印了两次,但编译器选择使用同一个存储位置,却和MSG的地址不同。编译器可以自由的选择"I'm special"存储在一个位置还是不同位置(对于程序中多次出现该字符串常量来说)。
数组和指针的区别
char heart[] = "I love Tillie!";
const char *head = "I love Millie!";
首要的区别是数组名heart是一个常量,但指针head是一个变量。因此,只有指针变量可以使用自增或自减运算符。
heart[7] = 'M'或*(heart + 7) = 'M',数组元素是变量(除非用const声明数组),但数组名是指向字符串首地址的常量。
⚠️:char * word = "frame"; word[1] = '1'; 虽然编译器可以通过,但可能引发内存访问错误。原因,之前提到过,对于"frame"字符串常量的存储,编译器可以只存储"frame"在一个地址。
char * p1 = "Klingon"; p1[0] = 'F'; printf("Klingon"); printf(": Beware the %ss!\n", "Klingon"); 这些语句使用了"Klingon"3次,若运行第二句修改字符串常量,则后面两句的字符串将输出"Flingon",而不是原来的"Klingo"。
当然,各个编译器的处理方式可能不同,比较建议的使用指针表示字符串的方式为:const char * p1 = "Klingon";。
使用非常量数组声明字符串,却没有这种问题,因为使用数组得到的是复制的一份字符串常量,数组的地址和静态存储区中的地址不同。
总结:如果要改变字符串,不要使用指针进行操作。
字符串数组
用一个数组的方式表示多个字符串常量。const char *mytalents[5] = {"Adding numbers swiftly", "Multiplying accurately", "Stashing data", "Following instructions to the letter", "Understanding the C language"}; char yourtalents[5][40] = {"Walking in a straight line", "Sleeping", "Watching television", "Mailing letters", "Reading email"};
mytalents和yourtalents很相似,都表示5个字符串。mytalents[0]和yourtalents[0]都表示第一个字符串。mytalents[1][2]是'l'。yourtalents[1][2]是'e'。
不同的地方在于:mytalents数组是一个包含5个指针的数组,而yourtalents是一个包含5个数组的数组,每个数组占40个字节,总共200字节。mytalents[0]和yourtalents[0]都是字符串,但mytalents指向的是静态存储区中的字符串,而yourtalents,则是从静态存储区中复制了一份字符串,保存在数据的地址处。最后,对于数组的内存分配是低效率的,对于yourtalents的每一个元素,都要确保是相同的大小,其大小至少能够存储最长的那个字符串。
总结:如果要使用数组来表示要显示的一串字符串,则指针数组比字符数组数组更有效。 然而,有一个问题。 因为mytalents中的指针指向字符串,所以不应更改这些字符串。 然而,yourtalents的数据可以改变。 因此,如果要更改字符串或为字符串输入留出空间,请不要使用指向字符串的指针。
示例
#include<stdio.h>
int main(void)
{
const char * mesg = "Don't be a fool!";
const char * copy;
copy = mesg;
printf("%s\n", copy);
printf("mesg = %s; &mesg = %p; value = %p\n", mesg, &mesg, mesg);
printf("copy = %s; © = %p; value = %p\n", copy, ©, copy);
return 0;
输出:
Don't be a fool!
mesg = Don't be a fool!; &mesg = 0x7ffee6c94ae0; value = 0x108f6bf4e
copy = Don't be a fool!; © = 0x7ffee6c94ad8; value = 0x108f6bf4e
通过指针进行字符串复制,复制的仅是字符串的首地址,也就是mesg和copy都指向同一个地址,但mesg和copy变量的地址却不一样。