哈喽!这里是一只派大鑫,不是派大星。本着基础不牢,地动山摇的学习态度,从基础的C语言语法讲到算法再到更高级的语法及框架的学习。更好地让同样热爱编程(或是应付期末考试 狗头.jpg)的大家能够在学习阶段找到好的方法、路线,让天下没有难学的程序(只有秃头的程序员 2333),学会程序和算法,走遍天下都不怕!
目录
引言
本文从最基础的什么是串开始,依次讲解串的定义、比较、存储结构,以及常用算法,并给出参考代码,相信哪怕是小白的你,在看完文章后也能豁然开朗。
妈妈再也不担心我不会手写数据结构了
一、串的定义
学习一样知识首先就得知道它是什么
那么什么是串呢?
串 定义为:由零个或多个字符组成的有限序列,又称为字符串
可以记作:S = 'a1a2...an' (n >= 0) 其中n称为串的长度
空串:零个字符(不包含任何字符)的串
子串:串中任意个连续的字符组成的子序列
空格串:只包含空格的串称(注意:空格串非空串,长度为串中空格个数)
如:"abc123"是字符串 长度为6,"abc"是其子串," "是空格串,""是空串
二、串的存储结构
2.1串的逻辑结构
串的逻辑结构和线性表很相似,不同之处在于串针对的是字符集,也就是串中的元素都是字符,哪怕串中的字符是由"123"这样的数字组成,或者是由"2022-12-31"这样的日期组成,它们都只能理解为长度为3和长度为10的字符串,每个元素都是字符而已!
2.2串的顺序存储(静态)
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。
按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般用定长数组来定义。
因此我们就可以得到如下串的静态存储结构:
#define MaxSize 20 //串的最大长度为20
typedef struct String{
char s[MaxSize]; //保存每一个字符
int length; //记录字符串的长度
}String;
使用字符串之前先把它初始化一下
这里我把函数定义为指针类型,因为结构体作为函数参数也是值传递
//初始化串
void Init(String *str){ //结构体是值传递,所以传递指针才能改变串
for(int i = 0; i < MaxSize; i++){
str->s[i] = '\0';
}
str->length = 0;
}
初始化之后此时字符串是空串('\0'表示字符空)
我们可以先给它赋上初值
这里我只赋了10个位置,0-9,通过'0'+i的方式来实现(这里就是ASCII的简单应用,不清楚的可以再翻翻看C语言课本)
//赋初始值
void Create(String *str){
for(int i = 0; i < 10; i++){
str->s[i] = '0' + i;
str->length++;
}
}
怎么知道我们的字符串是否真的有内容了?
没错,直接“遍历”一下就知道了~~~
//遍历字符串
void Print(String str){
printf("遍历字符串:\n");
for(int i = 0; i < str.length; i++){
printf("%c ",str.s[i]);
}
printf("\n");
}
这里因为我们使用了结构体,所以清楚的知道字符串长度,如果没有使用结构体时,我们可以将循环判断条件改成这样。
注意 我判断条件写的是 !=0 因为ASCII值为0就是空字符,也可以改成 !='\0' 结果和上面都是一样的。
//遍历字符串
void Print(String str){
printf("遍历字符串:\n");
for(int i = 0; str.s[i] != 0 ; i++){
printf("%c ",str.s[i]);
}
printf("\n");
}
2.3串的顺序存储(动态)
既然是动态存储,所以就得牵扯到动态分配了,仍然以一组地址连续的存储单元存放串值的字符序列,但它们的存储空间是在程序执行过程中动态分配得到的。
于是得到串的动态存储结构如下:
//串的动态存储结构
typedef struct String{
char *s;
int length;
}String;
初始化字符串,和静态的不同,动态的存储我们是在Init函数中进行动态分配内存的
//初始化串
void Init(String *str,int len){
str->s = (char *)malloc(len * sizeof(char));
for(int i = 0; i < len; i++){
str->s[i] = '\0';
}
str->length = 0;
}
同样,我们赋上初值来看看效果
其实和静态的方式差不多,只是在分配内存的先后和方式上有所不同
//赋初始值
void Create(String *str,int len){
for(int i = 0; i < len; i++){
str->s[i] = '0' + i;
str->length++;
}
}
再实现Print函数看看是否成功
//遍历字符串
void Print(String str){
printf("遍历字符串:\n");
for(int i = 0; i < str.length; i++){
printf("%c ",str.s[i]);
}
printf("\n");
}
串的顺序存储就先讲到这里,其他字符串的操作见文章后半部分,下面我们来看看串的链式存储。
2.4串的链式存储
顺序存储方式其实是有问题的,因为字符串的操作例如拼接、插入、替换,都有可能使得串序列的长度超过了数组的长度MaxSize
对于串的链式存储结构,和线性表也相似,但是由于串结构的特殊性,结构中的每个元素数据都是一个字符。
但串的链式存储结构除了在连接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。
于是得到串的链式存储结构如下:
//串的链式存储结构
typedef struct Node{
char c;
struct Node *next;
}Node,*String;
同样初始化字符串~~
//初始化串
void Init(String str){
str = (Node *)malloc(sizeof(Node));
str->c = '\0';
str->next = NULL;
}
这里我们对串的链式存储只做了解即可,重要的是思想,以及后面串的常用方法的实现。
三、C语言中字符串的注意事项
C语言中是没有专门表示字符串类型的,用char型数组来表示字符串。
在使用过程中有些小的注意事项,我在学习的过程中遇到的疑问也进行说明。
定义方式一
char c[10]; 此时定义了c为字符数组,但是并未给字符数组进程初始化,数组中各元素的值是不可预料的!
可以进行如下的初始化操作:
char c1[10] = {'c','s','d','n'};
如果花括号中提供的初值个数大于数组长度,则出现语法错误
如果初值个数小于数组长度,则其余位置元素自动为空'\0'
定义方式二
如果在定义时就进行初始化,那么数组长度是可以省略的,系统会自动根据初值个数确定数组长度。
char c2[] = {'c','s','d','n'};
数组的长度自动定义为4
定义方式三
也可以用字符串常量来初始化字符数组,C系统在用字符数组存储字符串常量时,会自动加一个'\0'作为结束符。
char c3[] = {"csdn"}; //花括号可以省略,和下面等价 char c3[] = "csdn";
所以此时数组的长度应该是5 而不是4!!!
因此该方式定义如果用单个字符来初始化应该是如下方式:
char c3[] = {'c','s','d','n','\0'};
定义方式四
用字符串常量来进行初始化,除了不声明数组长度的方式,当然也可以声明数组的长度
char c4[10] = "CSDN";
此时从下标为4的位置开始到9结束 都是自动为'\0'空字符
不同方式的对比
上面我们提到,C语言定义并初始化字符数组(字符串)的方式有4种,那么它们都一样吗?
显然是不会都一样,它们使用起来也有不同的问题。
下面我们进行对比总结。
我们定义一个长度为4的数组 “csdn”
通过在四种不同方式定义时,发现方式四 既然报错了!
但是我们将其长度改为5之后就神奇的可以了~
为什么会呢?
因为:C语言会自动在字符串常量的最后一个字符后面加上一个'\0',所以在用字符串常量为给定长度的字符数组赋值时,字符数组的长度至少是字符串长度+1
使用sizeof来测量其所占字节数,也能证明'\0'真的存在且占用空间,其运行结果如下:
四、串的常用方法
平时使用字符串多数还是通过数组方式来定义的,所以这里我们以数组定义字符串为例,来讲解字符串常用方法设计思想及实现过程。
4.1字符数组的输入输出
字符数组的输入输出有两种方法,
(1)逐个字符输入输出。用%c
(2)将整个字符串一次输入或输出。用%s
例如如下代码:
char str[10];
scanf("%s",str);
printf("输出字符串:%s\n",str);
其运行结果为:
4.2字符串拼接
把字符串2拼接到字符串1的后面,先遍历到字符串1的末尾,然后从字符串2的起始位置开始赋值给字符串1,最后给字符串1相应位置赋值空字符。
注意,需要满足两个字符串的长度之和不超过字符串1的长度。
//字符串拼接
void StrCat(char s1[],char s2[]){
int i = 0;
while(s1[i] != '\0'){
i++;
}
int j = 0;
while(s2[j] != '\0'){
s1[i+j] = s2[j];
j++;
}
s1[i+j] = '\0';
}
4.3字符串复制
把字符串2复制给字符串1,只需要遍历字符串2,依次复制给字符串1即可,最后别忘了在字符串1的末尾加上空字符。
注意,需要满足字符串1的长度大于等于字符串2的长度。
//字符串复制
void Copy(char s1[],char s2[]){
int i = 0;
while(s2[i] != '\0'){
s1[i] = s2[i];
i++;
}
s1[i] = '\0';
}
4.4字符串比较
字符串比较是通过比较ASCII的值来实现的,思路很简单,就是对应位置两两进行比较,如果从头至尾都相等,那么就说明两字符串相等;否则必有一个位置的值不等,返回其差值即可。
//字符串比较
int Compare(char s1[],char s2[]){
int i = 0;
while(s1[i] == s2[i] && s1[i] != '\0'){
i++;
}
if(s1[i] > s2[i]) return 1;
if(s1[i] < s2[i]) return -1;
else return 0;
}
4.5字符串长度
字符串求长度就是一个遍历的过程,遍历到空字符结束,统计其出现字符的个数并返回。
//字符串长度
int Length(char s[]){
int i = 0;
while(s[i] != '\0'){
i++;
}
return i;
}
完整测试代码如下:
//字符串拼接
void StrCat(char s1[],char s2[]){
int i = 0;
while(s1[i] != '\0'){
i++;
}
int j = 0;
while(s2[j] != '\0'){
s1[i+j] = s2[j];
j++;
}
s1[i+j] = '\0';
}
//字符串复制
void Copy(char s1[],char s2[]){
int i = 0;
while(s2[i] != '\0'){
s1[i] = s2[i];
i++;
}
s1[i] = '\0';
}
//字符串比较
int Compare(char s1[],char s2[]){
int i = 0;
while(s1[i] == s2[i] && s1[i] != '\0'){
i++;
}
if(s1[i] > s2[i]) return 1;
if(s1[i] < s2[i]) return -1;
else return 0;
}
//字符串长度
int Length(char s[]){
int i = 0;
while(s[i] != '\0'){
i++;
}
return i;
}
int main(){
char s1[20] = "Hello";
char s2[10] = "World";
printf("字符串拼接:");
StrCat(s1,s2);
printf("%s\n",s1);
printf("字符串复制:");
Copy(s1,s2);
printf("%s\n",s1);
printf("字符串比较:%d\n",Compare(s1,s2));
printf("字符串长度:%d\n",Length(s1));
return 0;
}