目录
字符串函数
求字符串长度
strlen
用于求字符串长度
其声明为:
求的是’\0’之前的字符串长度,不计算’\0’ ,但字符串结尾必须包含\0
注意返回类型是size_t
无符号类型
ps:在字符串中\0(ASCII是0)不是0(ASCII是48),如果字符串中想用字符 0 ,就 ‘0’ 这样表示
//常用正确的写法
char arr[] = "abc";
int len = strlen(arr);
printf("%d\n", len);//3
//容易出错的写法
char arr[] = { 'a','b','c' };//这种写法,数组arr只有3个元素,后面紧接着没有'\0'
int len = strlen(arr);
printf("%d\n", len);//15,是随机数,有误,此时strlen从'a'向后找15个字节才找到了'\0'
strlen的模拟实现
#include<assert.h>
int my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str++)
{
count++;
}
return count;
}
int main()
{
char arr[] = "ab";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
关于size_t在题目中的影响
if(strlen("abc") - strlen("abcdef") > 0)
{
printf(">\n");
}
else
{
printf("<=\n");
}
//结果为 >
//strlen("abc") - strlen("abcdef") 这个表达式计算会先发生整型提升, 3 - 6 = -3,-3以无符号整型的方式进行解读,这会是一个非常大的数10000000000000000000000000000011
//简单来说就是无符号整型 - 无符号整型 == 无符号整型
//库函数写成size_t是为了表现,计算出来的字符串长度一定为正数,所以用size_t,但是就会遇到这种 两个strlen相减的问题
//而我们写的模拟strlen,用int,就不会发生这种问题,但对于函数本身表达上,就不如库函数,size_t和int各有各的优点
长度不受限制的字符串函数
strcpy
字符串拷贝函数
其声明为:
将source拷贝到destination中,destination是数组,source可以是数组也可以直接是字符串(“ABCD”)(常量字符串结尾自动会有’\0’)
拷贝过去的字符串必须以’\0’结尾,并且会带着’\0’一起拷贝
注意destination的空间大小一定要大于source的空间大小
destination必须是可以修改的空间,也就是说不能传递常量字符串(char* = “abc”;)
strcpy在VS中需要这么一行代码#define _CRT_SECURE_NO_WARNINGS 1
,才能使用(与scanf相同),VS中提供了strcpy_s
,其实和后面要学到的strncpy是相同的
char arr[20] = { 0 };
//现在想把hello拷贝到arr中
arr = "hello";//err
strcpy(arr, "hello");//注意arr和"hello"都是拿到的首地址,"hello"的首地址是'h'的地址,和char* p = "hello"中p拿到的是'h'的地址同理
strcpy的模拟实现
#include<assert.h>
char* my_strcpy(char* str, const char* str_cpy)
{
assert(str && str_cpy);
char* p = str;
while (*str++ = *str_cpy++)//这行代码有三个作用1、将str_cpy赋值给str 2、都同时后置++让包括'\0'在内的每一个字符都能赋值 3、while停止是由'\0'赋值给str后发生的
{
;
}
return p;
}
int main()
{
char arr[20] = { 0 };
char arr2[] = "ade";
printf("%s\n", my_strcpy(arr, arr2));
return 0;
}
strcat
字符串追加函数
其声明为:
destination是数组,source可以是数组可以是字符串(同strcpy)
destination的空间需要足够大,不让source追加字符串时发生越界访问
destination中的字符串必须以\0结尾
从destination第一次出现’\0’开始追加,注意追加时source会覆盖destination结尾的’\0’
source要以’\0’结尾,同时会把结尾的’\0’传给destination
int main()
{
char arr1[10] = "abc";
char arr2[] = "def";
printf("%s", strcat(arr1, arr2));//abcdef
return 0;
}
strcat的模拟实现
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);//当dest和src都不为NULL时才不会报错
char* ret = dest;
while (*dest)//让dest指向\0的位置
{
dest++;
}
while (*dest++ = *src++)//追加
{
;
}
return ret;
}
int main()
{
char arr[20] = "hello ";
printf("%s\n", my_strcat(arr, "world"));
return;
}
ps:strcat不能自己追加自己strcat(arr, arr)
此时arr中的\0被覆盖,追加时找不到\0也就停不下来了,死循环
(strncat可以追加自己)
strcmp
字符串比较函数
其声明为:
错误认识案例:
char* pa = "obc";
char* pb = "abcdef";
if(pa > pb)//err 这样实际比的是pa和pb的首地址,地址的大小和字符串的大小是无关的
......
if("obc" > "abcdef")//err 这样实际比的是'o'的地址和'a'的地址的大小(这些字符串都能放在指针变量pa、pb中了,那肯定表示的是地址)
正确的比较过程
例:(“abz”, “abeq”)
具体比较过程:'a’和’a’比,ASCII码值相等,跳到下一个字符,'b’和’b’比,ASCII码值相等,跳到下一个字符,'z’和’e’比,'z’的ASCII码大于’e’的ASCII码,所以strcmp认为字符串"abz"大于字符串"abeq"
strcmp(“abz”,“abz”),'a’和’a’比,‘b’和’b’比,‘z’和’z’比,’\0’和’\0’比,全部都相等,此时strcmp才会判断这两个字符串相等
'\0’比任何字符都要小 其ASCII码为0
(C语言标准是<0 =0 >0,但在VS中对应的就是 -1 0 1,这只是一种实现的方式,还可以是ASCII码之差而来的一些正负值)
strcmp的模拟实现
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)*
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')//*str1和*str2已经相等了,只需要其中之一等于'\0'
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char* q = "abbss";
char* p = "abbss";
int ret = my_strcmp(q, p);
if (ret > 0)
{
printf("q > p");
}
else if(ret < 0)
{
printf("q < p");
}
else
{
printf("q == p");
}
return 0;
}
长度受限制的字符串函数
strncpy
长度受限制的字符串拷贝函数
其声明为:
从source中拷贝num个字符到destination中
相较于strcpy函数来说更安全,让程序员会考虑source的字符串长度和destination的字符串长度
char arr[] = "ABCDEFG";
strncpy(arr, "CGGOOD", 2);
printf("%s\n", arr);//CGCDEFG
//如果num的长度大于source字符串长度,依然能够拷贝过去,多余的补\0
char arr[] = "ABCDEFGHIJ";
strncpy(arr, "XPQ", 6);
//arr - X P Q \0 \0 \0 G H I J
//如果num的长度小于source字符串长度,**不会在结尾补\0**
char arr[] = "**********";
strncpy(arr, "ABCD", 2);
printf("%s\n", arr);//AB********,后面的 * 都能打印出来,说明没有补\0
strncat
其声明为:
相较于strcat函数来说更安全,让程序员会考虑source的字符串长度和destination的字符串长度
//如果num的长度大于source字符串长度,补了一个\0就直接结束了
char arr[20] = "***\0*********";
strncat(arr, "OPQ", 10);
//***OPQ\0******
//如果num的长度小于source字符串长度,会在结尾补一个\0
char arr[20] = "****\0*******";
strncat(arr, "ABCD", 3);
//****ABC\0****
//strncat相较于strcat而言,能做到字符串自身追加
char arr[20] = "ABCDE";
strncat(arr, arr, 5);
printf("%s\n", arr);//ABCDEABCDE
char arr[20] = "ABCDE";
strncat(arr, arr, 7);
printf("%s\n", arr);//ABCDEABCDEAB,这里限定了追加字符串的长度,所以就不会因为\0死循环报错
strncmp
其声明为:
只比较str1和str2前num个字符
char arr[] = "ABCDD";
char* p = "ABCE";
int ret = strncmp(arr, p, 3);
printf("%d\n", ret);//0
ret = strncmp(arr, p, 4);
printf("%d\n", ret);//-1
char arr1[] = "ACEDD";
char arr2[] = "AGQL";
int ret = strncmp(arr1, arr2, 3);
printf("%d\n", -1);//比到'C'和'G'就会停止
字符串查找
strstr
在一个字符串中查找,是否含有另一个字符串
其声明为:
在str1中查找,是否含有字符串str2,如果含有,返回第一次出现时字符串开头的地址,如果不含有,返回空指针NULL
char str1[] = "ABCDSDJBCDCHG";
char str2[] = "BCD";
strstr(str1, str2);//返回的是第一个字符串"BCD"的首地址
模拟实现strstr
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if(*str2 == '\0')//当str2为空时
{
return (char*)str1;
}
while (*str1)//这里和后面的str1++;对应起来,目的在于让str1中每一个字符都有被查找的机会,都会进if比较一次
{
const char* p1 = str1;
const char* p2 = str2;//进入if寻找子串的过程中防止str1和str2的位置变化,用p1和p2去具体寻找
char* start_str = (char*)p1;//这里p1的类型含有const,如果赋给ret,要么也加上const,要么强制类型转换成(char*)
while (*p1 && *p2 && (*p1 == *p2))//具体寻找子串,这里的*p1和*p2是为了1、让p1和p2遇到\0就会跳出循环,在逻辑上避免了越界访问 2、防止出现*p1和*p2都等于\0,进入循环的情况
{
p1++;
p2++;
}
if (*p2 == '\0')//此时的p2如果指向\0也就是说在str1中找到了str2字符串
{
return start_str;
}
str1++;
}
return NULL;
}
int main()
{
char arr1[] = "ABCDEJICBCDa";
char arr2[] = "BCDa";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)//
{
printf("没找到\n");
}
else
{
printf("找到了:%s\n", ret);
}
return 0;
}
strtok
切割字符串的函数
其声明为:
str是被切割的字符串
sep是定义的用作分隔符的字符集合
例如字符串"www.baidu/19230012"中的 ‘.’ '/'就是分隔符,"./"就是用作分隔符的字符集合
这个函数会改变str字符串,所以一般使用时,用备份的字符串进行操作
char arr[] = "wqz.baidu/19230032";
char* p = "./";
char tmp[] = { 0 };
strtok(tmp, p);
strtok第一个参数不为NULL,函数将找到str中第一个标记(分隔符),并且函数将保存该分隔符在字符串中的地址
strtok第一个参数为NULL,函数将在同一个字符串str中被保存的地址开始,查找下一个标记(分隔符)
分隔符不能是’\0’,因为strtok遇到’\0’时会认为字符串已经结束
保存的方式可以用static,让某个变量在出strtok函数时,不会被销毁
如果字符串中不存在更多的标记(分隔符)则返回NULL
举例:
char arr[] = "wqz.baidu/19230032";
char* p = "./";
char tmp[] = { 0 };
char* ret = NULL;
ret = strtok(tmp, p);//第一次调用strtok,将第一个标记(分隔符)置为'\0',并保存标记的地址,返回'w'的地址
printf("%s\n", ret);//wqz
ret = strtok(NULL, p);//第二次调用,从标记地址开始向后寻找下一个标记,又将其置为'\0'并保存地址,返回'b'的地址
printf("%s\n", ret);//baidu
ret = strtok(NULL, p);本次调用向后找,发现'\0',表面该字符串分割结束,返回'1'的地址
printf("%s\n", ret);//19230032
//如果此时再调用strtok,会返回NULL
改进版本
char arr[] = "wqz.baidu/19230032";
char* p = "./";
char tmp[] = { 0 };
char* ret = NULL;
//将第一次调用strtok用的tmp放在for的初始化部分,strtok的跳出,用返回值是否为NULL来控制,调整部分,也就是后面的每次调用,都用strtok(NULL, p)
//初始化 - 判断 - printf - 调整 - 判断 - printf - 调整 ......
for(ret = strtok(tmp, p); ret != NULL; ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
错误信息报告
strerror
将错误码转换成错误信息(打印的问题交给printf)
其声明为:
int errno
- 这是C语言的全局变量,当某次库函数执行失败、调用错误时,会将错误码(一些数字:1、2、3…)放在errno变量中,使用errno变量前,需要引头文件#include<errno.h>
每一个错误码都对应了一条错误信息,这些错误码和其对应的信息是由C语言本身完成的,我们可以通过调用strerror这个函数,来查找错误码所对应的信息,是一种检验程序错误的方式
每个公司在开发软件时,都会有自己一套的错误信息和其对应的错误码
int main()
{
FILE* pf = fopen("test.txt", "r");//当前程序下没有这个文件时,fopen函数就执行失败了,此时fopen返回NULL,并给errno变量该函数错误时对应的错误码
if(pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;//这个return 1,也就让main函数到这里就停止了
}
fclose(pf);
pf = NULL;
return 0;
}
补充:
perror函数:#include<stdio.h>
打印错误信息
其声明为:
当某库函数使用错误或者是调用错误时,用perror函数,可直接打印错误信息
此时我们就不需要自己去使用errno变量,也就不用引用errno的头文件
字符操作函数
这些函数都是判断一个字符是否是iscntrl(任意)字符、是否是isdigit(数字)字符、是不是isxdigit(十六进制数字)字符…
这些函数用法基本相同,学习一两个即可都明白
例如 - isdigit函数:
int isdigit(int c)
c是每一个字符对应的ASCII码
如果c不是数字字符,返回 0
如果c是数字字符,返回非0的值(真)
#include<ctype.h>
int main()
{
char ch = '#';
int ret1 = isdigit(ch);
printf("%d\n", ret1);//0 - 为假,ch不是数字字符
char sh = '2';
int ret2 = isdigit(ch);
printf("%d\n", ret2);//4 - 为真,sh是数字字符
}
当然这些函数的实现非常容易,只是用一组库函数来让代码统一,更简便
if(ch >= 'A' && ch <= 'Z')//isdigit函数
printf("1");
else
printf("0");
字符转换函数(大小写转换)
tolower - 大写转小写
toupper - 小写转大写
需要头文件#include<ctpye.h>
如果字符串中有数字字符或者tolower处理的字符串中有小写字符等,这些字符都不处理
int tolower(int c)
比如实现一个功能:让字符串中所有大写转换成小写,小写不用管,然后打印字符串
#include<ctpye>
int main()
{
char arr[] = { 0 };
scanf("%s", arr);
int i = 0;
while(arr[i] != '\0')
{
if(isupper(arr[i]))
{
arr[i] = tolower(arr[i]);
}
printf("%d\n", arr[i]);
i++;
}
}
内存函数
memcpy
内存拷贝函数
其声明为:
将source中的内容拷贝到destination,拷贝的数据宽度(单位为字节)用num来决定
任何类型都能拷贝!
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 5*sizeof(int));//从arr1中拷贝20个字节的内容到arr2中
ps:C语言标准中memcpy只需要实现不重叠拷贝即可,但在VS中,memcpy也实现了重叠拷贝(memmove的标准),我们在模拟时按照C语言标准实现的
模拟实现:
#include<assert.h>
void* my_memcpy(void* str2, const void* str1, size_t num)
{
assert(str1 && str2);
void* p = str2;
while (num--)
//后置减减,让num在20时进一次循环......在1时进一次循环,该次进入循环后num变成0,下一次就进不了循环了,也就是让循环执行了num次
{
*((char*)str2)++ = *((char*)str1)++;
//我们要拷贝这块内存块中每个字节中的内容,所以就用字符指针类型来进行处理
//对str1和str2强制类型转换成char*类型
//这行代码的理解可以回忆模拟strcpy函数中的代码 - *str++ = *str_cpy++
//主要在考虑优先级问题 - 强制类型转换 () 、自加自减 ++-- 它们优先级相同,结合方向是自右向左
//*((char*)str1)++ 的理解:对括号内(char*)str1进行++,然后对(char*)str1解引用
//比较稳妥的就这么写
}
return p;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
int* p= (int*)my_memcpy(arr2, arr1, 5*sizeof(int));
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
该函数的拷贝方式是从前向后拷贝
缺陷:在str1和str2内存出现重叠时,有可能会出现拷贝错误的情况
memcpy(arr1 + 3, arr1, 5*sizeof(int))
- ×
拷贝方式在拷贝出现内存重叠现象时会有影响,错误的使用会出现拷贝过程中覆盖的问题,进而导致拷贝出错
ps:src - 拷贝来源 dest - 拷贝目的地 红色为src的拷贝内容 绿色是dest的拷贝内容
记忆:在dest < src 时,从前向后拷贝
在dest > src 时,从后向前拷贝
memmove
也是内存(内容)拷贝函数
其声明为:
内存重叠时,也可以正常拷贝(按照上述的记忆方式进行if-else分支处理)
模拟实现:
#include<assert.h>
void* my_memmove(void* str2, const void* str1, int num)*
{
assert(str1 && str2);
void* start_str2 = str2;
if (str1 < str2)//if-else是为了解决str1和str2出现内存重叠的情况
{
//从后向前拷贝
while (num--)//num第一次进去为19
{
*((char*)str2 + num) = *((char*)str1 + num);
//(char*)str2 + num指向了最后一个元素的最后一个字节的地址
//随着num--,就能改变str2的空间中每一个字节
}
}
else
{
//从前向后拷贝
while (num--)
{
*((char*)str2)++ = *((char*)str1)++;
}
}
return start_str2;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = (int*)my_memmove(arr1 + 2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
memset
内存设置函数
其声明为:
将ptr所指向的空间的前num个字节的内容设置成value值
一般用于字符数组
int arr[10] = {0};
memset(arr, 1, 20);//将arr前20个字节的内容全部设置成1
//将前五个元素中每个字节都设置成了01
//arr[0] == ... == arr[4] == 0x01010101 == 16,843,009
memcmp:
内存比较函数
int memcmp (const void* ptr1, const void* ptr2, size_t num)
memcmp的返回值与strcmp完全相同
ptr1 == ptr2 返回零
ptr1 > ptr2 返回大于零的数
ptr1 < ptr2 返回小于零的数
仿照memset对每个地址进行比较即可