文章目录
字符串函数
1.strlen
strlen是用来记字符串的长度的。写一下我知道的三种实现方法
方法1:计数(bf)
暴力算法,不解释
size_t my_strlen1(const char* str)
{
int len = 0;
while (*str++)
len++;
return len;
}
方法2:递归
这个方法很有趣。
有一些面试题会要求你不创建临时变量去写strlen,就只能用递归写了。
size_t my_strlen2(const char* str)
{
if (*str == '\0')
return 0;
return my_strlen(str+1) + 1;
}
方法3:指针减指针
我们知道,在c语言里面,指针减指针得到的结果是它们之间的元素个数。
size_t my_strlen3(const char* str)
{
char* pc = str;
while (*pc)
{
pc++;
}
return pc - str;
}
关于strlen的一些易错点:
- strlen的返回值是无符号整数
给道题就理解了
char* str1 = "hello";
char* str2 = "hello world";
if (strlen(str1) - strlen(str2)>0)
{
printf("str1 is shorter than str2\n");
}
else
{
printf("str1 is longer than str2\n");
}
答案:
原因也很简单,由于strlen的返回值是无符号数,无符号数相减仍然是无符号数。无符号数永远大于等于0.
- strlen若遇不到终止符NUL就会一直越界往后找(它就是这么犟)
2.strcpy和strncpy
strcpy的作用是把一段字符串复制到另一个地方。
有几个很重要的点
- 源字符串必须要有终止符NUL
- 目标空间必须足够大,足够大的意思就是能放的下这一段字符串
- 目标空间必须可以被修改。(不能是char*的常量字符串,也不能是const char* 的具有常属性的字符串)
这里就不具体举例子了。
strcpy的实现也很简单,但是要写的漂亮也不是那么容易的。先贴代码
char* my_strcpy(char* dst, const char* src)
{
assert(dst && src);
char* ret = dst;
while (*dst++ = *src++)
{
;
}
return ret;
}
这段代码里面有几个要强调的点:
- 传入参数的时候,由于源字符串是不可修改的,可以加上const。一旦它被修改了,程序直接报错。方便debug(在长的程序中,这个好处实在是太好了)
- 对目标字符串和来源字符串进行断言。一旦它们两有一个为空字符串,程序直接崩溃。(也是大大方便了debug)
- 解引用和用于迭代的++可以放在一起。当*src等于终止符NUL时,它也将被赋值给了目标字符串。同时整个条件判断也会变成假,跳出循环。
这里给了我们一个启示,以后我们写代码时可以想一想++可以放在条件判断中吗?
讲完了strcpy,现在讲一下strncpy。
strncpy可以操控copy的个数,不一定要把整一个字符串都拷贝过去。可以拷贝你想要的个数。
char str1[20] = "hello";
char str2[] = "world";
printf("%s\n",strncpy(str1, str2, 3));
输出worlo。就是把world的前三个字符拷贝到了str1里面。
3.strcmp和strncmp
这是msdn里面对strcmp的描述(msdn是微软自己做的函数标准库)
这是函数返回值。
注意了:less是更小,greater是更大。这里的比较是按照每一个字符的ASCII码来比的,和长度并没有什么关系。
来个例子说明:
char str1[20] = "hello";
char str2[] = "worl";
printf("%d\n", strcmp(str1, str2));
如果说是按照长度来判断,str1比str2长,输出的值应该大于0才对。实际上输出:
这是因为h的ASCII码是104,w的ASCII码是119.104比119小,所以输出的数是负数。实际上vs2019输出-1也是它的一种微软特色。
我们可以这样实现,返回两个字符之间ASCII码值的差值。不一定要-1或者1
代码:
int my_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1 - *str2;
}
这里要说明一点:为什么*str1等于终止符就停止了。*str2等于终止符不可以吗?
原因是:这个判断是写在条件下面这个条件之后的。此时*str1和*str2是一样的。
*str1 == *str2
讲完了strcmp,再讲一下strncmp。
同样strncmp可以选择比较的个数。
你也可以理解成比较两个字符串之间有没有相同的字串。
4.strcat和strncat(strcat不可以自己追加自己的原因)
strcat是用来拼接两个字符串的。
有几个重要的点:
- strcat的源字符串必须要有终止符
- strcat追加的空间必须要足够大,足够大的意思就是能放得下两段字符串
- 目标字符串必须可以修改。和strcpy一样。不能是char*的常量字符串,也不能是const char* 的具有常属性的字符串
问题来了,自己追加自己可以吗?
答案:不可以。
原因我们可以通过实现一下strcat并画图来解决这个问题。
strcat思路很简单,这里就不解释了。着重讲一下为什么strcat不可以自己追加自己。
char* my_strcat(char* dst, const char* src)
{
char* ret = dst;
while (*dst)
{
dst++;
}
while (*src)
{
*dst++ = *src++;
}
*dst = '\0';
return ret;
}
初始状态:
追加一个字符后,现在其实已经可以看出问题了。src往后走之后并不会按预期的一样找到终止符NUL。如果没看懂我们可以继续往后看。
追加两次后dst和src分别的位置。
经过几次迭代后就会变成这样,很明显越界了。但它们并没有停下来的意思。
因此我们吸取了教训,src和dst不能放在一起操作。
我们只能用strncat来进行追加自己的操作。
char str1[20] = "hello";
printf("%s\n",strncpy(str1, str1, 5));
这样就没有任何问题了。
5.strstr
strstr是用来找substring的。
这里用bf算法实现一下strstr。更优秀的算法请参考kmp算法。
画图来理解一下:
初始状态:(prev是用来记录比较串时的开头)
string和substring的第一个字符不相等,所以string往后走,prev也往后走。直到相等,如图:
substring和string的第一个字符相等,string和substring都向后走一步。(注意prev不要走)
匹配完了所有字符的样子。如图:
当substring走到终止符的位置时,返回prev的位置。
代码如下:
最关键的在于:用一个变量来记住比较串的开头,当这个变量走到终止符的时候结束循环。
char* my_strstr(const char* string, const char* strCharSet)
{
char* cp = string;
char* setHead = strCharSet;
while (*cp)
{
while (*strCharSet && *string && *string == *strCharSet)
{
string++;
strCharSet++;
}
if (*strCharSet == '\0')
{
return cp;
}
if (*string == '\0')
return NULL;
cp++;
string = cp;
strCharSet = setHead;
}
}
6.strtok
strtok是一个分割函数。很奇特也很有趣。
strtok分割的原理是把你指定的sep字符全部变成终止符NUL
sep参数是个字符串,定义了用作分隔符的字符集合。只要字符串里面有这个字符,就会变成终止符。不分顺序。
这个函数的用法也很奇特。
strtok函数的第一个参数不为 NULL时 ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL 时,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
给个例子:
int main ()
{
char str[] =This, a sample string.";
char * pch;
printf ("Splitting string \"%s\" into tokens:\n",str);
pch = strtok (str," ,.-");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, " ,.-");
}
return 0;
}
第一次由于它传入了str,因此重头开始找,strtok把逗号变成终止符
第二次传入了NULL,因此从逗号开始找,strtok把a后面的空格变成终止符
第三次传入NULL,strtok把sample后的空格变成终止符
第四次传入NULL,strtok把string后面的句号变成终止符
最后返回一个NULL,循环结束。
7.strerror
strerror是返回错误信息的一个函数。
在C语言里面,每一个错误信息都有一个对应的错误码。strerror可以根据这个错误码来打印出报错信息。
例如0,1,2,3在c语言里面代表的错误码是:
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
但我们也不能记住每一个错误码。c语言有一个变量叫errno(是一个全局变量),当你的程序出现错误时,errno就会记录此次错误码。
现在没有这个文件但要强行打开,会返回一个NULL指针给pf,即打开失败。errno记录了这个错误码并通过strerror打印了出来。
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
}
还有一个类似的函数perror(print error)
它会自动打印错误。
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
//printf("%s\n", strerror(errno));
perror("test");//可以在报错信息前面加上一些你想打印的信息。
}
return 0;
内存操作函数
前面的那些函数都只能对字符串操作,但下面这些函数可以对任意类型操作。
1.memcpy
memcpy和strcpy效果是一样的,都是拷贝。只不过memcpy可以拷更多类型的内容而已。
需要注意的是:memcpy需要传三个参数。第三个参数是字节数。
(不是你要拷贝的元素个数)
现在我们实现一下memcpy。
我们可以看到传入的参数是void*,很明显就是要求我们对其强制转换成char*类型,然后再对每一个字节进行拷贝了。
代码:
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = src;
while (count--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
return ret;
}
这里多说几句。
如果想拷贝自己的部分元素到自己身上,memcpy正常来说是做不到的。但是在vs2019上,微软实现它时让它有了这个功能。(这其实是memmove的功能)
2.memmove
memmove就具有了拷贝自己的元素到自己身上的能力。
要想实现memmove就要知道为什么memcpy的写法无法自己拷贝自己。
假设有这么一种情况:要把src的内容拷贝到dst里面。
在拷贝了两个元素之后,我们发现会变成下面这个样子。原先的3和4找不到了。
最后会变成这个样子。很明显就是错误的结果。
因此用memcpy的方法是无法解决这个情况的。这时候我们就应该从后往前面拷贝。
那是不是永远从前往后拷贝就可以了呢?
我们再来看一下这个情况
假如现在还从后面往前面拷贝。
3和4就会被5和6覆盖掉。拷贝仍然是错的。
从上面两个情况我们可以看出来,我们知道了,要实现memmove是要分情况的。
从上面四种情况,
我们可以简化成两种。
当dst小于src时用前->后。
当dst大于src时用后->前。
具体代码实现:
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = src;
if (dest >= src)
{
(char*)dest += (count - 1);
(char*)src += (count - 1);
while (count--)
{
*(char*)dest = *(char*)src;
((char*)dest)--;
((char*)src)--;
}
}
else
{
while (count--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
}
return ret;
}
3.memset
可以让你指定的空间变成你指定的内容。
int arr[] = { 1,2,3 };
memset(arr, 0, 12);
前:
后:
4.memcmp
很简单,不多说。
用于比较两段空间。(可以指定比较的个数)
int arr1[] = { 1,2,3 };
int arr2[] = { 1,2,4 };
printf("%d", memcmp(arr1, arr2, 8));//返回0
printf("%d",memcmp(arr1, arr2, 12));//返回-1