五、字符串补充

基础补充

字符数组和字符串

字符数组和字符串不同,在C语言中二者是不同的

这是一个字符数组:char word[] = {'H','e','l','l','o','!'}
而这是一个字符串:char word[] = {'H','e','l','l','o','!','\0'}
二者的区别在于,我们在初始化该数组的过程中,用一个 \0 结尾
如此一来,字符串的单元总数会比字符数多一,也就是多了一个0

字符串是一个”字符数组”,而因为末尾的”\0”,它变成了C语言中的字符串

字符串以数组的形式存在,以数组或者指针的形式访问(更多时候是以指针的形式)

字符串变量

表达一个变量是字符串,有以下写法:

1
2
3
char *str = "Hello" ; 
char word[] = "Hello" ;
char line[10] = "Hello" ;

用双引号括起来的部分,被称为字符串的字面量
两个相连的字符串常量会被自动连起来

1
2
printf("这里是一个例子,"
"相邻的字符串会被自动连接") ;

字符串变量

地址

当我们定义了两个完全一样的字符串是s1和s2时,它们实际上位于同一地址
事实上,s1和s2指向的地方位于程序的代码段,而且这一部分的内容都是只读的
也就是说,我们是无法在此修改字符串的

char* s = "Hello , world!" ;
此时s是一个指针,其初始化为指向一个字符串常量——而这个常量所在的地方是只读的区域

实际上s是const char* s ,由于历史原因,编译器接受不带const的写法。但是试图对s指向的字符串做写入则会导致严重后果

修改数组

如果想修改字符串,则应该应用数组来定义
char s[] = "Hello , world!" ;
而此时字符串的意义变为”所要的字符串就在此处”,而非char* s 表示的”指向了某个地方的字符串”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void){
int i = 0 ;
char* s = "Hello , world!" ;
char s3 [] = "Hello , world!" ;

printf("&i=%p" , &i);
printf("s = %p \n" , s) ;
printf("s3 = %p \n" , s3);

s3[0] = 'B' ;
printf("This Time, s3[0] = %c \n " , s3[0]);

return 0 ;
}

区别总结

指针: 不知道字符串具体位置,可以处理参数(数组作为参数时是和指针等同的),可以动态分配空间
数组: 字符串就在当前位置,可以动态修改,作为本地变量空间自动被回收

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

字符串可以表达为char*的形式,而char*不一定是字符串
其本意是指向字符串的指针,可能指向的是字符的数组

只有其所指的字符数组有结尾的0,才能认为它指向的是字符串

字符串的输入输出

字符串赋值

字符串的赋值实际上并没有产生新的字符串,只是让被赋值的指针指向了对应的区域

1
2
3
char *t = "title" ; 
char *s ;
s = t ;

此处并没有产生新的字符串,而只是让指针s指向了t所指的字符串,对s的任何操作都是对t做的

输入输出

1
2
3
char string[8] ; 
scanf("%s" , string) ;
printf("%s" , string) ;

scanf会读入一个单词,到空格,tab,回车为止
scanf是不安全的,因为其不知道要读入的内容的长度

我们可以在%s中间限定读取的数量
scanf("%7s" , word) ;
这个数字表示了最多允许读入的字符的数量,这个数字应该比数组大小小1,因为字符串数组的末尾固定为0

另外,在声明字符串的时候,需要初始化为0以免出错

char buffer[100] = “” ; 此时这是一个空字符串,而buffer[0] == ‘\0’
char buffer[] = “” ; 此时这个数组的长度只有1

字符串数组与程序参数

字符串数组

有两种方式表示字符串数组

char[][X]

代表的是一个字符串数组,每一个字符串的大小至多为X(有X-1字符)
可以认为是把一个个“数组型的字符串”放置到了数组里面,比如
a[0] == world\0
a[1] == Hello\0

char* a[]

表示数组a[]里面存放了各个字符串(的地址)
a[0] ->地址1 ->指向”Hello\0”
a[1] ->地址2 ->指向”World\0”

两种方式是不一样的

程序参数

可以作为main函数的参数,比如
int main(int argc , char const *argv[])
其中argv[0]是命令本身

当使用Unix的符号链接时,反映符号链接的名字

字符串函数

putchar

int puchar(int c) ;
向标准输出写一个字符
返回写了几个字符,EOF(-1)表示写失败

getchar

int getchar(void) ;
从标准输入读入一个字符
返回类型是int是为了可以返回EOF(-1)

Window Ctrl-Z Unix Ctrl-D

string.h

在string.h中,有许多帮助处理字符串的函数

常用函数

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

strcpy
char * strcpy(char *restrict dst , const char *restrict src) ;
可以把src的字符串拷贝到dst
在c99下,restrict表名src和dst不重叠
最终返回的是dst
另外,参数中,注意第一个参数是目的地,第二个是源
目的地需要有足够空间

1
2
3
char *dst = (char*) malloc(strlen(src)+1) ;
// +1是因为考虑末尾的0
strcpy(dst,src) ;

自主实现:

1
2
3
4
5
6
7
8
9
10
11
12
char* myCpy(char* dst , const char* src){
char* ret = dst ;
while(*src != '\0'){
*dst = *src ;
dst++ ;
src++ ;
//或者 *dst++ = *src++ ;
}

*dst = '\0' ;
return ret ;
}

strcat
char * strcat(char *restric s1 , const char *restrict s2) ;
把s2拷贝到s1的后面,接触成一个长的字符串
返回s1
s1需要有足够的空间

尽可能不要使用strcpy和strcat因为有安全问题,可以使用下面的安全版本

char * strncpy(char *restrict dst , const char *restrict src , sizr_t n) ;
chat * strcat(char *restrict s1 , const char *restrict s2 , size_t n) ;
int strncmp(const char *s1 , const char *s2 , size_t n );

size_t n 参数表示了最多可以运输多少字符

int strncmp :判断前n个字符是否是xxx

字符串搜索函数

char * strchr(const char *s , int c);
char * strrchr(const char *s , int c);
返回NULL表示没有找到
(前者是从左往右,后者是从右往左)

因为返回的是对应位置的指针,所以也可以尝试打印接下来的部分:

1
2
3
char s[] = "hello" ; 
char *p = strchr(s,'l');
printf("%s \n" , p );

最后结果会是”llo”
因此我们可以利用这个特性寻找第n个字符,比如:

1
2
3
4
char s[] = "hello" ; 
char *p =s strchr(s,'l') ;
p = strchur(p+1,'l');
printf("%s \n" , p );

在字符串中寻找字符串

char * strstr(const char *s1 , const char *s2);
char * strcasestr(const char *s1 , const char *s2); //相较前者,忽略大小写