字符串函数
- 使用前
#include <string.h>
strlen
- 返回字符串长度,不包括字符串结尾的’\0’;
int myStrlen(const char *p) {
int cnt = 0; //变量,尤其是这种实现计数目的的变量使用前一定要初始化;
while ( *p++ != '\0' ) // 条件甚至可以写为*p++,但是可读性不好
cnt++;
return cnt;
}
- 这是自己写的函数,实际上的函数返回类型,执行语句都和这有区别,而且没有输入不正确数据时的处理;下面的myStrCmp函数等也是。
strcmp
- 比较两个字符串:
- 0 : s1 == s2;
- <0 : s1 < s2;
- >0 : s1 > s2;
int mycmp(const char *s1, const char *s2) {
int idx;
whlie ( true ) {
if ( s1[idx] != s2[idx] ) {
break;
} else if ( s1[idx] == '\0' ) {
break;
}
idx++;
}
return s1[idx] - s2[idx];
}
作为老师的分析,这里的while循环尽管并不“简洁”,但是他反映出当写一个循环时我们可能并不清楚循环的条件,这时候就可以采取这样的方法,逐层分析跳出的情况。 (true == 1,bool)
- 我这里调用strcmp函数返回值是1/-1/0;并不是老师所说返回差值,有点奇怪;
修改过的循环
while ( s1[idx] == s2[idx] && s1[idx] != '\0' ) {
idx++;
}
while ( *s1 == *s2 && *s1) {
s1++;
s2++;
}
- 上面是两种处理字符串的方法:
- 当成数组,定义一个整数作为数组下标来遍历这个字符串
- 指针
- idx,cnt, ret。相较于i, j有一定实际含义的变量名,分别用作数组下标,计数,返回/运算结果。
strcpy
char* strcpy(char *restrict dst, const char *restrict src);
- 把src的字符串拷贝到dst里;
- 注意dst在前,src在后。
- 关于这个原型声明现在需要注意的是返回dst
- 为了让strcpy的结果在参与其他的运算,能链起代码来
小套路(复制一个字符串)
char *dst = (char*)malloc(strlen(src)+1);//sizeof(char) == 1;
//+1是因为strlen返回的长度不包括'\0',需要注意。
strcpy(dst, src);
myStrcpy
char* myStrcpy(char *s1, const char *s2) {
int idx;
while ( s2[idx] != '\0' ) {
s1[idx] = s2[idx];
idx++;
}
s1[idx] = '\0';
return s1;
}
注意点:
- 当循环结束时,
s2[idx] == '\0'
,但是并没有给s1字符串结尾赋’\0’,需要在循环外再写一条赋值语句 - 当用指针实现这个函数,需要注意因为最后要返回s1,所以应当定义指针保存s1字符串的首地址,或是另外定义工作指针。
- 使用指针时可以写成
while ( *s1++ = *s2++) ;
,但并不代表实际写时一定要追求这种写法
代码写的简单可读性高一点,就现代的编辑器来说,最后会做出一样的编译效果。
'\0’的问题
‘\0’代表着字符串的结束,没有就会出现问题
在上面的myStrcpy函数中如果最后没有给字符串的结尾赋’\0’,也许侥幸执行结果没有报错,但不能保证每次执行都不出错。
c变量存取地址规律
先定义的变量会存取在较高的地址上,而后定义的变量会在较低的低地址上。
问题
char s1[6] = "Hello";
char s2[5] = {'H', 'e', 'l', 'l', 'o',};
printf("%s\n", s1);
printf("%s\n", s2);
- 输出s2会打印出HelloHello,如果s2先定义,然后输出s2则会打印奇怪的字符。
strcat
- 将第二个字符串连接到第一个上,返回连接后字符串的首地址。
char* myStrcat(const char* s1, const char* s2) {
char *p;
p = (char*)malloc(strlen(s1)+strlen(s2)+1);
strcpy(p, s1);
char *q = p;
while ( *q != '\0' ) {
q++;
}
strcpy(q, s2);
return p;
}
- 这个函数写得应该是和原本函数差异最大的了。因为第一个功能都实现不了,这个只能返回连接后字符串的地址,
- 主要考虑到目标串指向的空间不一定能容下衔接后的整个字符串,这也是strcat函数使用前需要注意的。看下面这个例子
char s[6] = "Hello";
printf("%d\n", strlen(s));
printf("%d\n", sizeof(s));
strcat(s, "*");
printf("%s\n", s);
printf("%d\n", strlen(s));
printf("%d\n", sizeof(s));
- 输出为5,6,Hello*,6,6,我的理解是s指向的空间大小没有变,但是确实将超出s范围的地方赋值了,反映在strlen长度的变化,应该是已经产生错误了,但也许他会修改不能修改的地方的值,那是就会报错了。
其他字符串函数
查找指定字符
char* strchr(const char *s, int c);
//从左边寻找c第一次出现的位置char* strrchr(const char *s, int c);
//从右边找- 找不到返回NULL;找到则返回一个指针,指向字符串中c的位置。
- 类似于strcpy,让strchr返回的结果+1后再次作为函数参数调用函数即可找第二个c
char s[] = "Hello";
char *p = strchr(s, 'l');
//p = strchr(p+1, 'l');找到第二个
char *q = (char*)malloc(strlen(p)+1);
strcpy(q, p);
printf("%s\n", q);//输出l之后的内容
// char c = *p; //临时存*p的值
// *p = '\0';
// char *q = (char*)malloc(strlen(s)+1);
// strcpy(q, s);
// *p = c; //将内容复制后,可以将*p的值恢复;
// printf("%s\n", q);输出l之前的内容
free(q);
查找字符串
char* strstr(const char *s1, const char *s2);
char* strcasestr(const char *s1, const char *s2);
//无视大小写
关于字符串
- c语言中没有专门的字符串类型,只能用字符数组,和指针来描述它,所以有时候会混淆一些东西,但主要原因其实是指针和数组的一些概念没有理解透彻
- 比如:
char *p = "asdf";
**“asdf”**作为一个字符串常量,这个指针应当是指向常量的一个指针,是不能通过指针来修改这个字符串。但是指针本身是变量,可以改变,指向另一个字符串。char s[] = "asdf";
这里的实际操作是将这个"asdf"这个字符串赋值给s数组里的元素,所以是可以通过下标改变其中某个字符。但是s是一个常指针,类似于s = "qwer";
或者是s = p
这样的操作都是不可以做的。
- 还有一些注意点以后遇到在补充
- 因为处理字符串的函数一般都要把字符串首地址传进去,所以在你没有想修改字符串时,有可能不小心修改了其中的值,这时候const修饰可以用来保护这个字符串,包括之后结构体中也会有这样的操作(对于定义较大的结构,传地址进函数是个更好的方法K&R);