目录
字符分类函数
函数 | 如果它的参数符合下列条件就返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车'\r',制表符'\t',或垂直制表符'\v' |
isdigit | 十进制数字0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母a~z或A~Z |
isalnum | 字母或数字a~z,A~Z或0~9 |
ispunct | 标点符号,任何不属于数字或字母的图像字符(可打印符号) |
isgraph | 任何图像字符 |
isprint | 任何可打印字符,包括图像字符和空白字符 |
函数讲解
这些函数的使用方法都十分类似
int islower ( int c );
如果为假,函数会返回0,如果为真,函数会返回非零的值
iscntrl用来判断一个字符是否为控制字符
isspace用来判断一个字符是否为空白字符
isdigit用来判断字符或者字符串是否为数字
islower用来判断一个字符是否为小写字母
isupper用来判断一个字符是否为大写字母
isalpha用来判断一个字符是否为字母
isalnum用来判断一个字符是否为字母或者数字
ispunct用来判断一个字符是否为标点符号
isgraph用来判断一个字符是否为图形字符
isprint用来判断一个字符是否是可打印的
图形字符如下:
使用样例
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "Hello World\n";
char c;
while (str[i])
{
c = str[i];
if (islower(c))
c -= 32;
putchar(c);
i++;
}
return 0;
}
这里首先要包含头文件 ctype.h
然后使用了库函数 islower用来判断字符是否为小写字母
如果字符为小写字母则将字符减去32改成大写字母
所以这个程序的作用是将字符串中所有的小写字母改成大写字母
字符转换函数
C语言提供了2个字符转换函数
int tolower ( int c );
int toupper ( int c );
函数讲解
tolower可以将参数传进去的大写字母转小写
toupper可以将参数传进去的小写字母转大写
使用样例
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "Hello World\n";
char c;
while (str[i])
{
c = str[i];
if (islower(c))
c = toupper(c);
putchar(c);
i++;
}
return 0;
}
与上面近乎一致,只是将c -= 32变成了c = toupper(c)
两者的作用都相同,所以最终的结果也是一致的
strlen的使用和模拟实现
strlen的使用
该函数的作用是可以计算一个字符串的长度
也可以理解为计算'\0'之前出现的字符个数(不包含'\0')
这个长度最终以size_t函数的返回值形式出现(size_t为无符号整型)
我们只需要传一个字符串的起始地址给它即可(不一定是起始地址,从哪里传就从哪里开始计算)
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Hello World";
int ret = strlen(str);
printf("%d\n", ret);
return 0;
}
答案是由10个大小写字母和1个空白字符组成,所以答案是11
strlen的模拟实现
strlen的模拟实现有三种方法
计数器
#include <stdio.h>
size_t my_strlen(const char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdefg";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
注:如果有str为什么可以str++的问题可以看我前面的博客中的const修饰变量
这里使用了1个变量count计数,随着str的增加count一同增加,直到碰到'\0'就停止即可
递归(不创建临时变量)
#include <stdio.h>
size_t my_strlen(const char* str)
{
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char arr[] = "abcdefg";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
这里使用递归逐渐增加str的位置,并且每递归一次就增加1,最终返回这个值即可
这个方法可以不需要创建临时变量
指针-指针
这里需要有一个知识点
两个同样类型的指针相减会得出它们之间元素的个数,以此得出第三个方法
#include <stdio.h>
size_t my_strlen(const char* s)
{
char* p = s;
while (*p != '\0')
p++;
return p - s;
}
int main()
{
char arr[] = "abcdefg";
int ret = my_strlen(arr);
printf("%d\n", ret);
return 0;
}
多创建一个指针变量p让它走到'\0'之前,最后指针-指针即可
strcpy的使用和模拟实现
strcpy的使用
该函数的作用是可以将一个字符串的内容拷贝到另一个字符串中,以需要拷贝的字符串中的'\0'作为标志结束
注意:目标空间必需要足够大且可修改,不然会越界
strcpy的模拟实现
#include <stdio.h>
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
while (*dest++ = *src++);
return ret;
}
int main()
{
char arr1[] = "abcdefg";
char arr2[20];
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
这里的模拟实现先是创建了一个 char* ret用来保存原来目标字符串的起始地址(因为后面dest会改变),这样为后来的返回值做准备
这里直接简写成了用一个循环判断即可,当src为'\0'的时候赋值给dest自然就会停下循环
strcat的使用和模拟实现
strcat的使用
该函数的作用是可以把一个字符串连接到另一个字符串上面,也是以'\0'为标志结束
注意:目标空间必需要足够大且可修改,不然会越界
strcat的模拟实现
#include <stdio.h>
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
while (*dest)
dest++;
while (*dest++ = *src++);
return ret;
}
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "World";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
这里的模拟实现先是创建了一个 char* ret用来保存原来目标字符串的起始地址(因为后面dest会改变),这样为后来的返回值做准备
第一个循环的作用是找到dest的第一个'\0'
第二个循环即类似strcpy拷贝到末尾即可
最后返回起始dest的地址
strcmp的使用和模拟实现
strcmp的使用
该函数的作用是对两个字符串比较大小,从起始地址开始直到碰到'\0'结束一 一比较
如果直到结束两个字符都是相同的那么就说明字符串相等,则返回0
如果str1大于str2,返回大于0的数字,str1小于str2,返回小于0的数字(具体什么数字不做要求)
strcmp的模拟实现
#include <stdio.h>
int my_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
int main()
{
char arr1[10] = "abcde";
char arr2[10] = "abq";
int ret = my_strcmp(arr1, arr2);
printf("%d", ret);
return 0;
}
一个循环遍历两个字符串
如果走到'\0'则结束返回0
如果循环条件结束,则判断谁的ASCLL码值大,返回1或-1
strstr的使用和模拟实现
strstr的使用
该函数会在str1中寻找字符串str2
如果找不到则返回null
如果找到了会返回找到位置的起始地址
strstr的模拟实现
#include <stdio.h>
char* my_strstr(const char* str1, const char* str2)
{
char* ptr1 = str1;
char* ptr2 = str2;
char* cur = str1;
while (*cur != '\0')
{
ptr1 = cur;
ptr2 = str2;
while (*ptr1 == *ptr2 && cur != '\0' && ptr2 != '\0')
{
ptr1++;
ptr2++;
}
if (*ptr2 == '\0')
return cur;
cur++;
}
return NULL;
}
int main()
{
char arr1[20] = "hello@world.c";
char arr2[20] = "llo";
char* ret = my_strstr(arr1, arr2);
printf("%s", ret);
return 0;
}
这里需要定义几个char* 类型的变量来代替str1和str2移动
因为我们如果只有str1和str2就算能够找到str1中的str2也无法后退返回原来的地址了
例如:
如果我们从str1中找str2的时候,发现了第一个l,那么往后走发现第二个不是o的时候,我们如何返回呢?
str2判断完str1的ll不是lo的时候str2又怎么返回到str2的起始地址l中呢?
所以这就是需要定义多个char* 类型的变量来走的原因
第一个循环的cur是用来遍历的
第二个循环是用来判断字符串是否相同的
最后都返回找到的起始位置或者NULL即可
strncpy的使用和模拟实现
strncpy和strcpy的区别就差了一个n
这类型的函数有很多,都是加了一个n
这类函数会更加安全,因为strcpy拷贝的时候如果没有控制好数组的大小就会有越界的风险
所以strncpy的安全性会更高
strncpy的使用
strncpy可以自己选择需要拷贝多少个数据,我们只需要传参的时候多传一个num即可
这个num的单位是字节
strncpy的模拟实现
#include <stdio.h>
char* my_strncpy(char* dest, const char* str, size_t num)
{
char* ret = dest;
int i = 0;
for (int i = 0; i < num; i++)
{
*(dest + i) = *(str + i);
}
return ret;
}
int main()
{
char arr1[] = "abcdefg";
char arr2[20] = { 0 };
my_strncpy(arr2, arr1 + 1, 5);
printf("%s", arr2);
return 0;
}
这里的实现和strcpy差不多
只不过用了个for循环来控制需要拷贝几个字符串即可
strncat的使用和模拟实现
strncat的使用
strcat是连接字符串
那么strncat就是可以决定需要连接几个字符串
strncat的模拟实现
#include <stdio.h>
char* my_strncat(char* dest, char* str, size_t num)
{
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
for (int i = 0; i < num; i++)
{
*(dest++) = *(str++);
}
return ret;
}
int main()
{
char arr1[20] = "hello";
char arr2[20] = " world";
my_strncat(arr1, arr2 + 1, 3);
printf("%s", arr1);
return 0;
}
具体实现也和strcat差不多
for循环控制连接字符串个数
strncmp的使用和模拟实现
strncmp的使用
strcmp是比较两个字符串
strncmp是比较指定个数的两个字符串
其他的基本一致
strncmp的模拟实现
#include <stdio.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
for (int i = 0; i < num; i++)
{
if (*str1 > *str2)
return 1;
if (*str1 < *str2)
return -1;
str1++;
str2++;
}
return 0;
}
int main()
{
char arr1[10] = "abcde";
char arr2[10] = "abq";
int ret = my_strncmp(arr1, arr2, 3);
printf("%d", ret);
return 0;
}
用for循环控制比较的字符个数
其他的与strcmp差不多一致
memcpy的使用和模拟实现
该函数需要头文件和前面的一样 <string.h>
memcpy的使用
我们学会了字符串的拷贝方法,但是如果我们要拷贝的是一个整形数组,或者浮点型数组,怎么办呢?
这时候就需要我们的memcpy上场了
它是可以将内存的内容拷贝走,并不是像strcpy一样只针对字符串,它针对所有人
该函数的返回值为一个void*的指针
第一个参数是要拷贝的目的地
第二个参数是要拷贝内容的地方
第三个参数是一个数字,表示拷贝内容的大小,单位是字节
那么来看看下面这串代码:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, sizeof(int) * 10);
for (int i = 0; i < 10; i++)
printf("%d ", arr2[i]);
return 0;
}
这串代码是把整形数组arr1的内容全部拷贝到arr2中
最终打印结果如下:
memcpy的模拟实现
void* my_memcpy(void* dest, const void* source, size_t num)
{
void* ret = dest;
for (int i = 0; i < num; i++)
{
*(char*)dest = *(char*)source;
(char*)dest += 1;
(char*)source += 1;
}
return ret;
}
这里定义了一个void* ret的指针变量用来保存目标的数组的第一个下标的地址,防止后续使用dest++后找不到该位置
然后用了一个for循环将source里的一个个字节的内容拷贝到dest中即可
char*就是一个字节,*(char*)就是该字节的内容
再让dest和source往后走一个字节即可
走一个字节也是强制类型转换为char*再+1即可走一个字节
memmove的使用和模拟实现
memmove的使用
memmove的使用和momcpy的使用基本上是一模一样的
刚刚那串代码改一下函数一样能使用
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memmove(arr2, arr1, sizeof(int) * 10);
for (int i = 0; i < 10; i++)
printf("%d ", arr2[i]);
return 0;
}
但是他们还是有区别的,具体区别请看下文
memmove的模拟实现
void* my_memmove(void* dest, const void* source, size_t num)
{
void* ret = dest;
if (dest < source)
{
while (num--)
{
*(char*)dest = *(char*)source;
(char*)dest += 1;
(char*)source += 1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)source + num);
}
}
return ret;
}
该函数的实现和momcpy有些地方也很像
但是多了一个if语句的判断和一个while循环
if语句是判断两者谁的地址大
然后第一个while循环是从前往后拷贝内存,第二个while循环是从后往前拷贝内存
所以if语句来决定while是该从前往后还是从后往前拷贝
为什么要这么做呢?
memcpy和memmove的区别
memcpy和memmove的本质区别其实就在于当dest和source重叠的时候
当我们的函数的参数是这样的时候
memcpy(arr1 + 2, arr1, sizeof(int) * 5);
在一些编译器下 ,memcpy的拷贝是会出问题的,如果string.h代码里的memcpy函数是类似上面的my_memcpy函数那样实现的话
如果要拷贝的dest与source重叠时,只是从前往后拷贝或者从后往前拷贝就会出现问题
arr1 = 1,2,3,4,5,6,7,8,9,10
若执行上述代码后
arr1 = 1,2,1,2,1,2,1,8,9,10
就是因为在从前往后拷贝的时候把后面即将要开始拷贝的内容给覆盖了,就出现重复的1,2
这时候就需要memmove出场的时候了
在上面的my_memmove就很好的解决了内存重叠的问题,可以分情况选择从前往后拷贝还是从后往前拷贝
总结:memcpy和memmove的区别在当有dest和source有内存重叠的时候,memcpy的拷贝可能会出问题,但memmove不会
完