字符串,字符数组,字符指针

字符, 字符数组, 字符指针和字符串.

上回说到, 有一种很常见的情境下我们一直在使用字符数组, 但调用函数的时候并没有传入数组的大小. 这个情景就是字符串.

实际上, c语言标准中并没有所谓的"字符串"类型, 所谓的字符串都是一定字符数组.

调用字符串相关函数的时候, 传入的都只是数组名字, 正如上篇推送所说的, 对于接受数组为参数的函数, 参数实际上是指针. 字符串相关函数都只是读取指针指向的值, 让指针+1, 等到指针指向的值为'\0'为止. 因此, 字符串必须以\0结尾.

也正因为如此, 一个字符指针可以作为参数传入如puts等相关函数. 如, 对于一个字符串数组(char的二维数组)char data[100][100], data[0]是这个二维数组的第0个元素, 也就是一个一维数组 (char [100]). data[0]就是指向这个一维数组首元素的指针. 比如, 求这个字符串数组中最长的一个字符串可以这么做:

1
2
3
4
5
6
7
8
9
10
11
char * getLongest(char strs[][200], int n){
注释: 还记得为啥要写第二维的大小吗?
char * ret = strs[0];
int i = 0;
for(i = 0; i < n; ++ i){
if(strlen(s[i]) > strlen(ret)){
ret = s[i];
}
}
return ret;
}

值得注意的是, 返回的ret是一个指向字符串数组(char的二维数组)中的某个字符串的首元素的地址, 并不是复制了一份字符串返回. 如果修改了原先字符串数组中的值, 从ret指针读取到的值也会一样改变.

在字符串数组中, 漏写\0会引发更加奇怪的问题. 对于以下代码:

1
2
3
4
5
6
7
8
9
char strs[5][5] = {'\0'};
int i,j;
for(i = 0; i < 5; ++ i)
for(j = 0; j < 5; ++ j){
strs[i][j] = '*';
}
for(i = 0; i < 5; ++ i){
puts(strs[i]);
}

他会输出什么内容呢? 大概率是类似下面这样的东西:

1
2
3
4
5
*************************乱码乱码乱码
********************乱码乱码乱码
***************乱码乱码乱码
**********乱码乱码乱码
*****乱码乱码乱码

为什么会出现这种现象呢?

1
2
3
4
5
6
7
8
9
10
左滑查看答案------------​--------------> puts函数会从传入的指针 
左滑查看答案------------​--------------> 开始往后遍历输出, 遇到\0
左滑查看答案------------------​--------> 才停止输出. 因此, 当他从strs[0][0]
左滑查看答案------------------------​--> 打印到strs[0][4]时,
左滑查看答案---------​-----------------> 下一个字节实际上是strs[1][0],
左滑查看答案---------------​-----------> 仍然不是\0. 后面的几个字符串被
左滑查看答案---------------------​-----> 打印了好几次. 当打印完全部字符串后,
左滑查看答案--------​------------------> puts会接着往后打印, 由于后面是
左滑查看答案--------------​------------> 未初始化的内存, 因此打印出来乱码,
左滑查看答案--------------------​------> 直到某个地方恰巧为\0才停止.

还有一个有意思的问题, 那就是以下代码是什么意思:

1
2
3
4
5
6
7
8
9
char str[100];
gets(str);
char * p = str;
while (*p){
...
do something...
...
++p;
}

我们来逐层分析这一句: p是一个指向char*的指针, *p是这个char变量所存储的值. while(*p) 就是当这个char变量存储的值不为0的时候继续循环. (非0就是真). 又由于'\0的ascii码是0, 因此, 这句话的全部意思是当p指向的char为\0时退出循环.

下面是附录: 增强可读性后的几个字符串相关函数的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int strlen(char *org)
{
char *s = org;
while (*s){
s++;
}
s = s - 1;
return s - org;
}
char *strcpy(char *ret, char *s2)
{
char *s1 = ret;
while ((*s1 = *s2) != '\0'){
s1++;
s2++;
}
return ret;
}
char *strcat(char *ret, char *s2)
{
char *s1 = ret;
while (*s1 != '\0'){
s1++;
}
s1--;
while (*s1 = *s2){
s1++;
s2++;
}
return ret;
}