求字符串长度
strlen
在MSDN中查找一下strlen函数,我们会发现一个并不熟悉的类型:size_t
我们会发现返回类型为size_t,这种类型是我们不常见的。
size_t
size_t本质上是无符号类型,是专门给sizeof设计的的返回类型。
但使用size_t有不合理的地方:
int main()
{
if(strlen("abc")-strlen("abcdef")>0)
printf(">");
else
printf("<=");
return 0;
}
Q:上面程序最终输出什么?
A:因为strlen函数的返回值为无符号数,所以无符号数与无符号数相减肯定不会得到负数-3,而是会输出一个很大的值。
长度不受限制的字符函数
strcpy
strcpy的作用为拷贝字符串。
strcpy的使用:
strcpy的使用要引用头文件<string.h>
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[20] = { 0 };
strcpy(arr2, arr1);//将目的地的首元素地址放在第一个参数为上,将要拷贝过去的字符串地址传入
printf("%s\n", arr2);//之后就可以直接打印出来,最终输出arr1拷贝过去的内容
return 0;
}
Tip:拷贝是将字符串的'\0'也拷贝进去的
使用strcpy需要注意的情况:
1.可以对字符串拷贝,但记住一定要带上'\0'
例如下面
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = { 'a','b','c','d','e','f' };
char arr2[20] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
由于没有'\0',所以在输出的时候不知道要输出到什么时候截止。所以后面会输出乱码。
2.拷贝的目的地要够大,切忌越界访问。
就如这种情况,虽然arr2的字符串长度大于arr1的字符串长度,但是还是会依照函数copy过去给arr1,虽然有越界访问的情况出现,系统还是报错,但我们会发现还是copy了。
strcpy的仿写:
char* my_strcpy(char* dest, const char* fir)
{
char* str = dest;
assert(dest && fir);
while (*det++=*fir++)
{
;
}
return str;
}
strcat
strcat——字符串链接
strcat的使用:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[30] = "abcd";
char arr2[] = "efg";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
使用strcpy需要注意的情况:
1.初始化一定要加上'\0'
2.目标空间一定要足够大
在链接过程中同样遇上了栈溢出的情况,虽然也还是会链接上去,但是会报错。
3.目标空间要可修改
strcat的仿写:
#include <stdio.h>
char* my_strcat(char* dest, const char* str)
{
while (*dest)//*
{
dest++;//*
}
while (*dest++ = *str++)
{
;
}
return dest;
}
int main()
{
char arr1[30] = "abcdef";
char arr2[] = "ghijk";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
注意!!!若把 //*的位置变为while(*dest++)是不行的,因为会把arr1中的'\0'也拷贝进去。
strcmp
先在MSDN中研究一下strcmp函数:
strcmp的功能是比较两个字符串,比较的非长度,而是对应位置上的字符大小,例如:
char arr1[ ]="abcdef"; char arr2[]="bbq";
在这里对应第一个元素,arr1中的'a'与arr2中的'b',比较,当比出来是b较大的时候,若代码为:strcmp(arr1,arr2); 则输出-1。
strcmp的仿写
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* arr1, const char* arr2)//这里加const是为了防止在传参过程中两个数组被修改
{
assert(arr1 && arr2);//这里是为了防止传入的是空指针,使用当传入空指针时assert函数会报错指出来同时停止程序的运行
while (*arr1 == *arr2)
{
if (*arr1 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
if (*arr1 > *arr2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdfg";
printf("%d\n", (my_strcmp(arr1, arr2)));
return 0;
}
也可以简化一点点:
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* arr1, const char* arr2)//这里加const是为了防止在传参过程中两个数组被修改
{
assert(arr1 && arr2);//这里是为了防止传入的是空指针,使用当传入空指针时assert函数会报错指出来同时停止程序的运行
while (*arr1 == *arr2)
{
if (*arr1 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
return *arr1-*arr2;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdfg";
printf("%d\n", (my_strcmp(arr1, arr2)));
return 0;
}
长度受限制的字符串函数介绍
strncpy
strncpy是选择要拷贝的个数。
strncpy的使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "xxxxxxxxxxxxxx";
char arr2[] = "hello world";
strncpy(arr1, arr2, 5);//第一个参数的要拷贝的目的地地址,第二个位置是要拷贝的字符串地址,第三个是要拷贝的个数
printf("%s\n", arr1);
return 0;
}
strncpy的奇怪用法:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "xxxxxxxxxxxxxx";
char arr2[] = "he";
strncpy(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
如上,当我们能copy的字符串只有两个而硬要copy五个过去时,编译器会在剩余位置上补全'\0',在运行观察中打开监视器我们就可以发现了。
strncpy的模拟实现
//strncpy的仿写
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strncpy(char* dest, const char* ori, int n)
{
assert(dest && ori);
int i = 0;
int sz = strlen(ori);
int width = sz;
if (n > sz)
{
while (n != width)
{
*(dest + width) = '\0';
width++;
}
for (i = 0; i < sz; i++)
{
*(dest + i) = *(ori + i);
}
}
else
{
for (i = 0; i < n; i++)
{
*(dest + i) = *(ori + i);
}
}
return dest;
}
int main()
{
char arr1[20] = "xxxxxxxxxxxxxxx";
char arr2[] = "efg";
printf("%s\n",my_strncpy(arr1, arr2, 5));
return 0;
}
自己的实现代码太长了,看看编译器给的写法:(天秀)
char * __cdecl strncpy (
char * dest,
const char * source,
size_t count
)
{
char *start = dest;
while (count && (*dest++ = *source++) != '\0') /* copy string */
count--;
if (count) /* pad out with zeroes */
while (--count)
*dest++ = '\0';
return(start);
}
strncat
会选择n个大小链接到目的字符串上,并在链接完后加上'\0'。
strcat的使用:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "hello\0xxxxxx";
char arr2[] = "world";
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}
最终输出结果:
strncat的奇葩使用:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[20] = "hello\0xxxxxxxxxxxxx";
char arr2[] = "world";
strncat(arr1, arr2, 7);
printf("%s\n", arr1);
return 0;
}
本来最多arr2只有六个位置,但硬要用七个位置,那么此时编译器在链接'\0'后就不会再链接其他大小过去了,我们可以再程序运行的时候点开监视器观察:
strncat的仿写:(genius)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
char* my_strncat(char* dest, const char* ori, int n)
{
assert(dest && ori);
int i = 0;
char* str = dest;
while (*dest++ != '\0')
{
;
}
dest--;
for (1; *ori;)
{
*dest++ = *ori++;
}
*dest = '\0';
return str;
}
int main()
{
char arr1[20] = "abcdef\0xxxxxxxx";
char arr2[] = "ghij";
printf("%s\n",my_strncat(arr1, arr2, 6));
return 0;
}
编译器的参考写法:(感觉上差不多)
char * __cdecl strncat (
char * front,
const char * back,
size_t count
)
{
char *start = front;
while (*front++)
;
front--;
while (count--)
if ((*front++ = *back++) == 0)
return(start);
*front = '\0';
return(start);
}
strncmp
与strcmp差别不大,就是可以指定要对比大小的个数。
strncmp的仿写
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <assert.h>
int my_strncmp(const char* arr1, const char* arr2, int n)
{
assert(arr1 && arr2);
int i = 0;
for (i = 0; i < n; i++)
{
if (*(arr1+i) > *(arr2+i))
{
return 1;
}
if (*(arr1 + i) < *(arr2 + i))
{
return -1;
}
if (*(arr1 + i) == *(arr2 + i))
{
if (i == (n - 1))
{
return 0;
}
}
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcefg";
printf("%d\n",my_strncmp(arr1, arr2, 3));
return 0;
}
字符串查找
strstr
strstr为字符串查找字符串。当找到后会从找到的位置开始往后打印,为第一次找到的起始位置为准。如果找不到,则会返回空指针。
strstr的使用
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "bcd";
char* str=strstr(arr1, arr2);
if (NULL == str)
{
printf("找不到");
}
else
{
printf("%s\n", str);
}
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "bbcd";
char* str=strstr(arr1, arr2);
if (NULL == str)
{
printf("找不到");
}
else
{
printf("%s\n", str);
}
return 0;
}
strstr的仿写:
在仿写的时候我们要考虑遍历整个字符串来查找,若第一个元素相同则要继续往下对比,如果全部找到后则可以一直走到要查找字符串的'\0'位置,这时就算查找到了要查找的字符串,便返回查找字符串的首元素对应被查找字符串的地址。但如果没有被找到,有不同,则直接跳过本次循环,从被查找字符串的下一个位置开始查找。若一直遍历完到'\0'后还是找不到,则返回NULL,表示找不到该字符串。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* arr1, const char* arr2)
{
assert(arr1 && arr2);
const char* str1 = arr1;
const char* str2 = arr2;
if (*arr2 == ' ')//当输入查找的为空字符串时,直接输出arr1
{
return (char*)arr1;
}
while (*arr1)
{
str1 = arr1;
str2 = arr2;
if (*str1 == *str2)
{
while (*str2)
{
str1++;
str2++;
if (*str1 != *str2)
{
if (*str2 == '\0')
{
return (char*)arr1;
}
arr1++;
break;
}
else
{
continue;
}
}
}
else
{
arr1++;
}
}
return NULL;
}
int main()
{
char arr1[] = "abbcdebcdfg";
char arr2[] = "bcd";
char* ret = my_strstr(arr1, arr2);
if (NULL == ret)
{
printf("没有找到\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
这样子的效率仍然不够高,在以后可以研究一下KMP算法。
编译器的参考写法:
char * __cdecl strstr (
const char * str1,
const char * str2
)
{
char *cp = (char *) str1;
char *s1, *s2;
if ( !*str2 )
return((char *)str1);
while (*cp)
{
s1 = cp;
s2 = (char *) str2;
while ( *s2 && !(*s1-*s2) )//这里是判断相同,如果相同,则继续往下判断相同,当不相同或为s2为'\0'时,退出循环
s1++, s2++;
if (!*s2)//这里是判断此次从要对比的字符串的位置开始比到s2是否比完,如果比完了(也就是s2为'\0'时),就直接返回找到的那个位置
return(cp);
cp++;//这里是已经判断了比到s2并没有比完,说明要从cp位置的下一个字符开始比较,直至比到了或者比到字符串都遍历了一遍还没有找到
}
return(NULL);//当遍历完要对比字符串的所有元素都没有办法找到的时候,我们就会返回空指针,代表该字符串中没有我们要查找的字符串
}
strtok
用来分隔字符串的函数
char* strtok(char* str,const char* sep)
strtok的使用解释:
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回NULL指针。
strtok函数的使用
#include <stdio.h>
#include <string.h>
int main()
{
const char* p = "@.";//遇到要分割的标志
char arr[] = "zhengxiaolv@qq.com";
char buf[50] = { 0 };//一般为了不改变原来的字符串,会选择copy到另一数组上来
strcpy(buf, arr);
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}
(为防止保存的位置在使用完函数后被销毁,strtok在保存的时候会有一个静态的变量来储存上一个保存的位置,用来下一次查找下个标记)
错误信息报告
strerror
c语言中规定了一些错误信息
错误码——错误信息
0-“No Error”
1-
2-
....
在这里,strerror将错误码翻译出对应的错误信息。
c语言可以操作文件
当库函数使用的时候,发生错误会把error这个全局的错误变量设置为本次执行库函数产生的错误码
errno是c语言提供的一个全局变量,可以直接使用,放在errno.h的头文件中。
字符分类函数
在对字符分类时,以前的我们是使用例如(ch>='A'&&ch<='Z')的方式来判定ch字符是否为大写字符,在判断是否为其他类型字符是也是用的这种形式,代码量大且有些冗长,而现在我们可以通过字符分类函数来简化我们的代码:
字符分类函数的使用举例:
#include <stdio.h>
int main()
{
printf("%d\n",isspace('!'));
return 0;
}
如果返回非0的数,则为空白字符,反之,返回0的话则为空白字符。
字符操作
字符转换
小写→大写:toupper() 大写→小写:tolower()
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <ctype.h>
int main()
{
char ch = 0;
ch = getchar();
if (islower(ch))
{
printf("%c\n", toupper(ch));
}
else
{
printf("%c\n", tolower(ch));
}
return 0;
}
内存操作函数
memcpy
strcpy是拷贝字符串的,而拷贝int等类型时不行,这时候就需要用到memcpy,任何大小都可以拷贝。
void* memcpy(void* destination,const void* source,size_t num);
///size_t num——拷贝字节的个数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int p[10] = { 0 };
memcpy(p, &arr[5], 5 * sizeof(int));
for (i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
return 0;
}
memcpy的仿写:
//my_memcpy的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dst,const void* ori, int sz)
{
assert(dst && ori);
void* str = dst;
int i = 0;
for (i = 0; i < sz; i++)
{
*((char*)dst + i) = *((char*)ori + i);
}
return str;
}
int main()
{
int i = 0;
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[5] = { 0 };
my_memcpy(arr2, arr1, sizeof(arr1[0]) * 5);
for (i = 0; i < 5; i++)
{
//printf("%d\n", *((int*)my_memcpy(arr2, arr1, sizeof(arr1[0]) * 5)+i));
printf("%d\n", arr2[i]);
}
return 0;
}
还有一个天才仿写法:
void* my_memcpy(void* dst, const void* ori, size_t sz)
{
assert(dst&&ori);
void* str = dst;
while (sz--)
{
*(char*)dst = *(char*)ori;
((char*)dst)++;
((char*)ori)++;
}
return str;
}
过渡:
创建一个int型数组,里面装有{1,2,3,4,5,6,7,8,9,10},此时把1,2,3,4,5的位置移动到3,4,5,6,7位置,此时用我们自己写的my_memcpy会发现打印出的结果和我们想象中的不一样。不是我们想要的,因为在原来代码中,拷贝会覆盖掉。
而在我们使用编译器给出的memcpy函数来尝试时,发现并不会出现这样的错误,那么我们的仿写是不是出现了错误呢?
其实,c语言只要求:memcpy能拷贝不重叠的内存空间就可用来,memmove去出来纳西重叠拷贝,都是发现vs的memcpy也能实现重叠拷贝。所以我们在仿写memcpy时只需要做到copy不重叠的就好。
memmove
memmove处理重叠的内存拷贝。
与memcpy差不多用法,但是可以直接覆盖重复的地方。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr + 2, arr, 5 * sizeof(int));
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
memmove的仿写:
在外面实现仿写前,先思考这样几种情况:
- 覆盖内容包含(或不包含)部分被覆盖内容,并且在被覆盖内容前方
- 覆盖内容包含(或不包含)部分被覆盖内容,并且在被覆盖内容后方
上面的情况可以分为以下图解,我们通过图解来一一解决:
当出现情况一时:
经过研究后可以发现,当在这种情况下将要移动的元素从后面开始移动,就能解决被覆盖掉的情况出现。
当出现情况二时:
和情况一相反,当在这种情况下将要移动的元素从前面开始移动,就能解决被覆盖掉的情况出现。这里就不用图解来解释了。
在经过对两种情况的分析后,终于可以写出如下代码:
//memmove的仿写
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dst, void* ori, int sz)
{
void* str = dst;
int i = 0;
assert(dst && ori);
if (dst > ori)
{
for (i = sz-1; i >= 0; i--)
{
*((char*)dst + i) = *((char*)ori + i);
}
}
else
{
for (i = 0; i <sz; i++)
{
*((char*)dst + i) = *((char*)ori + i);
}
}
//另一种写法
/*int i = 0;
for (i = sz-1; i >= 0; i--)
{
*((char*)dst + i) = *((char*)ori + i);
}*/
/*if (dst > ori)
{
while (sz--)
{
*((char*)dst + sz) = *((char*)ori + sz);
}
}
else
{
while (sz--)
{
*(char*)dst = *(char*)ori;
((char*)dst)++;
((char*)ori)++;
}
}*/
return str;
}
int main()
{
int i = 0;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);
for (i = 0; i < 10; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
memset
设置内存为特定的字符,但是都是放置的以字节为单位的
因为是以字节为单位来改变的,所以其实使用上不太方便
void* memset(void* dest,int c,size_t count);//这里的c可以为字符或者整型
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "wojiushishizhegezemexie";
memset(arr, 'x', 10);
printf("%s\n", arr);
return 0;
}
memcmp
int memcmp(const void* ptr1,const void* ptr2,size_t num)
比较从ptr1和ptr2指针开始的num个字节
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,7,5 };
int arr2[] = { 1,2,3,7,5 };
if (memcmp(arr1, arr2, 16) > 0)
{
printf("前者大\n");
}
else if (memcmp(arr1, arr2, 16) < 0)
{
printf("后者大\n");
}
else
{
printf("一样大\n");
}
return 0;
}
在学习了这么多字符函数与内存函数后要多多使用和仿写,来加深一下记忆。
收获满满的一天~