字符串
廖家龙 用心听,不照做

字符串数据在C中的存储方式:

内存中的五大区域:

  1. 栈:是专门用来存储局部变量的,所有的局部变量都是声明在栈区域中
  2. 堆:允许程序员手动的从堆申请指定字节数的空间来使用
  3. BSS段:是用来存储未初始化的全局变量和静态变量,声明一个全局变量,如果我们没有初始化,在程序运行最开始的时候,这个全局变量是没有初始化的,存储在BSS段【程序运行后系统就自动的初始化为0,并把初始化后的全局变量存储在数据段中】
  4. 数据段/常量区:用来存储已经初始化的全局变量、静态变量和常量数据
  5. 代码段:用来存储程序的代码/指令

字符串数据在C语言中有两种存储方式:

  1. 使用字符数组来存储:将字符串数据的每一个字符存储到字符数组中,并追加一个’\0’代表存储结束
    char name[]=“jack”;

  2. 使用字符指针来存储字符串数据:直接将一个字符串数据初始化给一个字符指针
    char* name =“jack”;

1)当它们都是局部变量的时候:

字符数组是申请在栈区,字符串的每一个字符存储在这个字符数组的每一个元素中;
指针变量是声明在栈区的,字符串数据是以字符数组的形式存储在常量区的,指针变量中存储的是字符串在常量区的地址

2)当它们作为全局变量的时候:

字符数组是存储在常量区的,字符串的每一个字符存储在这个字符数组的每一个元素中;
指针变量也是存储在常量区的,字符串数据是以字符数组的形式存储在常量区的,指针变量中存储的是字符串在常量区的地址

3)
以字符数组的形式存储字符串数据,不管是全局的还是局部的,都可以使用下标去修改字符数组中的每一个元素;
以字符指针的形式存储字符串数据,不管是全局的还是局部的,都不能通过指针去修改指向的字符串数据

当我们以字符指针的形式要将字符串数据存储到常量区的时候,并不是直接将字符串存储到常量区,而是先检查常量区中是否有相同内容的字符串,如果有直接将这个字符串的地址拿过来返回,如果没有,才会将这个字符串数据存储在常量区中

当我们重新为字符指针初始化一个字符串的时候,并不是修改原来的字符串,而是重新的创建了一个字符串,把这个新的字符串的地址赋值给它

几个比较容易混的点:

  1. 这样是可以的,但是不是把“jack”改成了“rose”,而是重新创建了一个“rose”,把“rose”的地址赋值给name
    char *name = "jack";
    name = "rose";

  2. 这样是不行的,name是数组名,代表数组的地址,不能为数组名赋值
    char name[]="jack";
    name = "rose";

  3. 这样做是可以的,直接修改数组的元素
    name[0]='r';
    name[1]='o';
    name[2]='s';
    name[3]='e';
    name[4]='\0';


统计字符串中某一个字符出现的次数:


使用字符指针数组来存储多个字符串数据:

这是一个一维数组,每一个元素的类型是char指针:
char* names[4] = {"jack","rose","lily","lilei"};


在声明字符数组的同时,如果初始化了部分元素,那么其他的字符会被初始化为’\0’,‘\0’是一个字符,是一个不可见的字符,打印出来啥都没有,这个字符的ASCII码是0

在C语言中字符串数据必须要用双引号引起来

C语言中存储字符串数据:将字符串数据的每一个字符存储到字符数组中,并在后面追加一个’\0’代表字符串存储完毕

最根本的方式存储字符串:
char name[5]={‘j’,’a’,’c’,’k’,’\0’};

char name[]={“jack”};
系统会自动的将这个字符串中的每一个字符存储到字符数组中,并自动的追加一个’\0’

最常用的方式:
char name[]=“jack”;

如果我们使用字符数组存储字符串数据的时候,没有指定这个字符数组的长度,那么这个时候这个字符数组的长度为字符串长度+1

我们在使用字符数组存储字符串数据的时候,最好不要指定长度了

如果在声明一个字符数组的同时我们就初始化一个字符数据给这个数组,那么这个时候是可以用中文的【这个字符数组的长度为7,因为一个中文占3个字节】

使用格式控制符%s就可以输出存储在字符数组中的字符串数据【%s从给定的数组的地址开始,一个字节一个字节的输出,直到遇到’\0’为止】

无需&

使用scanf函数:
1)如果用户输入的字符串数据在给定的字符数组中存储不下的时候,就会运行报错
2)用户在输入字符串的时候,如果输入了空格,就会认为输入结束

不能使用sizeof去计算字符数组的长度来得到字符串的长度,因为有可能字符串数据存储在字符数组中只占了一部分空间

解决方法:

字符串常量:
“Hello”会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
两个相邻的字符串常量会被自动连接起来

C语言的字符串是以字符数组的形态存在的,不能用运算符对字符串做运算,通过数组的方式可以遍历字符串,唯一特殊的地方是字符串字面量可以用来初始化字符数组

char *s=“Hello,world!”;
s是一个指针,初始化为指向一个字符串常量,由于这个常量所在的地方,所以实际上s是const char *s,但是由于历史的原因,编译器接受不带const的写法,但是试图对s所指的字符串做写入会导致严重的后果
如果需要修改字符串,应该用数组:char s[]=“Hello,world!”;

数组:这个字符串在这里,作为本地变量空间自动被回收
指针:这个字符串不知道在哪里,处理参数,动态分配空间

如果要构造一个字符串,用数组
如果要处理一个字符串,用指针

在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一



stdio.h文件:

puts()函数:用来输出字符串的【优点是输出完毕之后自动换行,缺点是只能输出字符串,也不能使用占位符】

fputs()函数:将字符串数据输出到指定的流中【标准输出流:控制台;文件流:磁盘上的文件】
输出到标准输出流中:

将字符串存储到文件中:
1)要先声明一个文件指针,指向磁盘上的文件【fopen函数可以创建一个指向文件的指针】


2)使用fputs()函数将字符串写入到指定的文件流中
3)写完之后一定要记得使用fclose()函数将这个文件关闭

gets()函数:从控制台接收用户输入一个字符串数据【优点是当用户输入的数据包含空格的时候,会连空格一起接收;缺点是和scanf函数一样不安全,当用来存储字符串的数据的字符数组的长度不够的时候,程序就会崩溃】

fgets()函数:从指定的流中读取字符串,这个流可以是标准输入流:控制台,也可以是文件流
1)从标准输入流中读取数据



解决方案:


2)从文件流中读取数据

string.h头文件:strlen,strcmp,strcpy,strcat,strchr,strstr

strlen:
size_t strlen(const char *s);
返回s的字符串长度(不包括结尾的0)

strcmp:
int strcmp(const char *s1,const char *s2);
比较两个字符串,返回:
0: s1==s2
1: s1>s2
-1: s1<s2
int strncmp(const char *s1,const char *s2,size_t n);只比较前几个字符

strcpy:【存储字符串1的字符数组长度不够,无法存储字符串2,这个时候运行就会崩溃】
char *strcpy(char *restrict dst,const char *restrict src);
把src的字符串拷贝到dst,restrict表明src和dst不重叠
返回dst,为了能链起代码来

复制一个字符串:
char *dst=(char *)malloc(strlen(src)+1);
strcpy(dst,src);

把name2拷贝到name1:

strcat:【接的时候会把第一个字符串后的’\0’干掉】
char *strcat(char *restrict s1,const char *restrict s2);
把s2拷贝到s1的后面,接成一个长的字符串
返回s1
s1必须具有足够的空间

strcpy,strcat不安全!!

安全版本:
char *strncpy(char *restrict dst,const char *restrict src,size_t n);
char *strncat(char *restrict s1,const char *restrict s2,size_t n);

字符串中找字符:
char *strchr(const char *s,int c);
char *strrchr(const char *s,int c);
返回NULL表示没有找到

字符串中找字符串:
char *strstr(const char *s1,const char *s2);
char *strcasestr(const char *s1,const char *s2);