十一、字符串
🎈C语言中的字符串
在C语言中 字符串是这样的:
char msg[]={'H','e','l','l','o','\0'};
在字符数组的末尾有一个\0
在C语言中 字符串是以整数0结尾的一串字符
0和’\0’相同 标志着字符串的结束 但只是作为一个标记的作用 (’\0’代表的是整数0 和’0’是完全不一样的)
但他并不是字符串的一部分 因此在计算字符串长度的时候也不包括这个0
在C语言中 字符串是以字符数组的形式存在的 (这才叫字符"串" 哈哈)
以数组或指针的形式来访问字符串 但更多以指针访问
🚩字符串变量
有好几种方法可以定义字符串变量
char *str="Hello";
char strArr[]="Hello";
char strArr[10]="Hello";
在以字面量的形式定义字符串变量后 编译器会自动在末尾生成一个0标识符
🚩字符串常量
比如 定义了一个字符串"Hello"
这个"Hello"会被编译器转换成一个字符数组放在内存中
这个数组的长度为6 而不是5 因为在末尾还有一个表示字符串结束的0 只是看不见而已
两个相邻的字符串常量会被自动连接(它们中间不能有任何符号)
比如:
printf("你是一个一个一个一个..."
"啊啊啊啊啊"); // 你是一个一个一个一个...啊啊啊啊啊
当然 也可以使用/
来连接 但此时行的连接处不能有引号 且换行后前面不能有缩进 否则缩进也会被算在字符串内
比如:
printf("你是一个一个一个一个...\
啊啊啊啊啊"); // 你是一个一个一个一个... 啊啊啊啊啊
这样才对:
printf("你是一个一个一个一个...\
啊啊啊啊啊"); // 你是一个一个一个一个...啊啊啊啊啊
C语言中 字符串是以字符数组的形式存在的 因此 不能用传统的运算符来对字符串进行运算
(Java可以用加号+
来连接字符串 但是C语言不可以)
但是可以用字符串形式的字面量来初始化字符数组
比如:
char *str="Hello";
通过数组的方式可以遍历字符串
🎈字符串的创建方式
✨字符串可以使用指针的创建方式:
char* str="Hello";
当创建了两个相同的字符串 它们所指向的位置实际上是同一个地方:
char* s1="Hello";
char* s2="Hello";
printf("%p\n",s1); // 00405064
printf("%p\n",s2); // 00405064
在字符串被创建后 实际上该常量是一个指针 指向了内存中的一块代码段区域 字符数组就存放在这
这块区域是只读的 无法写入 因此当写入时候 程序会崩溃
因此 实际上char *str="Hello"是const char *str=“Hello”
✨若要使用可修改的字符串 应该用数组的创建方式char strArr[]="Hello";
char s[]="Hello";
printf("%c\n",s[0]); // H
s[0]='P';
printf("%c\n",s[0]); // P
🎈字符串的创建、赋值、输入输出
创建方式的选择:
- 若创建为数组 那么字符串的存放位置就在当前位置
而且作为本地变量 空间是会被自动回收的 - 若创建为指针 那么这个字符串的存放位置不知道在哪
通常用于作为函数的指针参数 或者用于malloc的动态分配空间
🚩字符串的赋值
char *s1="Hello";
char *s2;
s2=s1;
由于采用的是指针方式创建的字符串 因此实际上在该赋值过程中 并没有产生新的字符串
只是让指针s2指向了指针s1所指的字符串的位置 即 对s2的任何操作 都是对s2做的
s1和s2是共享内存位置
🚩字符串的输入和输出
对于字符串 在C中 使用 %s
来输入和输出 (相信已经猜到了 s就是string)
在读入字符串的时候 到空格 或 tab 或 回车为止
空格 或 tab 或 回车是作为分隔符 因此并不会被算在读入的字符串中
在读入的时候 有可能会遇到数组越界问题 有可能会导致程序崩溃
char word[8];
scanf("%7s",word); // 最多读取7个 剩下留一个位置用于存放最后面的0
printf("%s##",word);
可以用 %ns
的格式来 限制最多能读取多少个字符 比如%7s为最多读取7个字符 超过的部分就不读了
因此 若同时读入到多个数组 在到达读取上限后 下个数组会从上次读取上限的地方开始读
比如:
char word1[3];
char word2[3];
scanf("%2s %2s",word1,word2); // 输入123456789
printf("%s\n",word1); // 12
printf("%s",word2); // 34
❗注意
在用指针方式创建数组的时候要进行初始化
不要这么用:
char *s;
scanf("%s",&s);
因为 char*并不是字符串类型 而是指针 当指向的位置无法存入 那么程序就会崩溃
因此 在创建指针的时候 要初始化为0
像这样:
char *s=0;
scanf("%s",&s);
🎈字符串数组
用一个数组来表示很多字符串
可以这么写:
char *arr[];
其意义是 有个数组 其中的每一项存放的都不是确切的值 而是一个指针 该指针指向的位置是所要存放的字符串单元
比如 arr[0]
存放的是Hello\0的指针 该指针指向一个位置 这个位置保存着Hello\0这么一个字符串
arr[1]
存放的是World\0的指针 该指针指向一个位置 这个位置保存着World\0这么一个字符串
以此类推
例子:
int i;
char *arr[]={"AA","BB","CC","DD","EE"};
for (i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
printf("%s\n",arr[i]);
}
🎈字符串函数
字符串处理函数放置于string.h中 因此若要使用字符串 则必须先引入#include <string.h>
里面有:
- strlen
- strcmp
- strcpy
- strcat
- strchr
- strstr
🚩getchar & putchar / 读 & 写
【getchar】:
从标准输入读入一个字符
语法:
int getchar(void)
返回值是int 这是因为方便返回EOF(-1)代表输入结束
【putchar】:
向标准输出(即黑窗口)写一个字符
语法:
int putchar(int c)
返回值是 写了几个字符 若返回EOF(-1) 则代表写失败
键盘输入的值是先交给shell 然后shell处理后再交给程序
当未按下回车前 所有输入的值都在shell里
然后一旦按下回车 就会将值存入shell中的缓冲区(回车符也会被存入缓冲区) 程序的getchar从shell的缓冲区中读数据 然后进行处理
因此 当键盘按下Ctrl+V(Unix是Ctrl+D)时 在shell内部会通过一种方式 改变控制程序将要退出的状态码
然后再通过getchar去读取的时候 读到了程序将要退出的状态码 那么便会退出
当键盘按下Ctrl+C 那么直接关闭程序了
int ch;
while((ch=getchar())!=EOF)
{
putchar(ch);
}
printf("EOF\n");
🚩strlen / 获取长度
strlen:string length
返回传入的字符串的长度
语法:
size_t strlen(const char *s)
例:
char c[]="Hello";
printf("%lu\n",strlen(c)); // 5
printf("%lu",sizeof(c)); // 6 因为后面还有一个\0代表字符串的结束
🚩strcmp / 比较
strcmp:string compare
比较两个字符串 不仅能比较是否相等 还能比较大小
语法:
int strcmp(const char *s1,const char *s2)
返回的是两个字符串之间的差值
因此 若相等 则返回 0
char s1[]="Hello";
char s2[]="Hello";
printf("%d",strcmp(s1,s2)); // 0
后面的若大于前面的 则返回 1
后面的若小于前面的 则返回 -1
char s1[]="abc";
char s2[]="CBc";
printf("%d\n",strcmp(s1,s2)); // 1
当然 该函数还有一个版本 可以限定只比较前面多少位字符
int *strncmp(char *restrict s1,const char *restrict s2,size_t n);
🚩strcpy / 复制
strcpy:string copy
将第二个参数的字符串拷贝到第一个参数的字符串空间中
语法:
char *strcpy(char *restrict dst,const char *restrict src)
// 参数是(目的,源),而不是(源,目的)
restrict是C99的关键字 意为dst和src不能是重叠的(即字符空间不能有重叠)
返回值是dst 即拷贝后的值(或者可以说是复制品)
其目的是为了能够使用链式编程 使得代码能够串联 返回值再次作为其它函数的参数
复制一个字符串:
char src[]="Hello";
char *dst=(char*)malloc(strlen(src)+1); // 切记要加一 留给最末尾的0
strcpy(dst,src);
printf("%s",dst); // Hello
🚩strcat / 连接
strcat:string concat
将第二个参数的字符串接到第一个参数的字符串后面
返回拼接后的第一个字符串(前提是第一个字符串必须要具有足够的空间)
语法:
char *strcat(char *restrict s1,const char *restrict s2)
其基本实现思路就是将第一个参数的最后一位(即结尾符0)替换为第二个参数的字符串的第一位
连接也可以看作是一种拷贝
🚧安全问题
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);
安全版本加了个n
其意思为 限制最多能够拷贝/连接多少个字符 若超出限定值 那么会去掉 超出的范围不会被拷贝/连接
🚩str[r]chr / 字符串中查找字符
strchr:string character
strrchr:string rear/right character
搜索函数可以在字符串中查找指定的字符(可以从左找也可以从右找)
语法:
char *strchr(const char *s,int c); // 从左找
和
char *strrchr(const char *s,int c); // 从右找(right)
若返回NULL 则代表未找到
若找到了 则会返回要找的那个字符在该字符串中的指针
char s[]="Hello";
char *p=strchr(s,'e');
printf("%s",p); // ello
🚩str[case]str / 字符串中查找字符串
strstr:string string
strcasestr:string case string
在字符串中查找指定的字符串(可以忽略大小写)
语法:
char *strstr(const char *s1,const char *s2); // 在字符串中查找指定的字符串
char *strcasestr(const char *s1,const char *s2); // 在字符串中忽略大小写查找指定的字符串
若返回NULL 则代表未找到
若找到了 则会返回要找的那个字符在该字符串中的指针
char s1[]="Hello";
char s2[]="ell";
char *p=strstr(s1,s2);
printf("%s",p); // ello