“普吉岛有扶桑花的晚霞和汹涌澎湃藏着爱的海底 以及浓郁的风吹开少年椰子味的心事”
大家好,这里是新一,请多关照。在本篇博客中,新一将会为大家介绍C语言字符串及内存函数,干货满满哟。(以下结果均在VS2022 - X64中编译)希望在方便自己复习的同时也能帮助到大家。
废话不多说,直接进入我们的文章。
一. 函数介绍
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中,字符串常量适用于那些对它不做修改的字符串函数,今天新一就来为大家介绍字符串及其内存函数。
这里新一只介绍最常用的strlen函数。
size_t strlen ( const char * str );
#include
int main()
{
//strlen返回值类型是无符号数所以结果出来虽然是-3
//但按照无符号数计算将是一个很大的正数
if (strlen("abc") - strlen("qwerty") > 0)
{
printf(">\n");//>
}
else
{
printf("<=\n");
}
//改进
if ((int)strlen("abc") - (int)strlen("qwerty") > 0)
{
printf(">\n");
}
else
{
printf("<=\n");//<=
}
return 0;
}
注意
1. 字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
2. 参数指向的字符串必须要以 ‘\0’ 结束。
3. 注意函数的返回值为size_t,是无符号的( 易错 )
字符串拷贝函数 - strcpy
char * strcpy(char * destination, const char * source );
int main()
{
//1.正常拷贝
char arr1[20] = { 0 };
char* arr2 = "abcdef";
//2.常量字符串拷贝失败
char* arr3 = "duihsaiud";//arr3指向的是常量字符串,空间不可修改
char* arr4 = "abcdef";
//3.程序奔溃
char arr5[20] = { 0 };
char arr6[] = {'a','b','c'};
//char arr6[] = {'a','b','c','\0'};//正确写法
//注意拷贝的字符串必须含有\0_否则报错
// \0也会被拷贝进去 - 调试即可验证
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
注意
1. 源字符串必须以 ‘\0’ 结束。
2. 会将源字符串中的 ‘\0’ 拷贝到目标空间。
3. 目标空间必须足够大,以确保能存放源字符串。
4. 目标空间必须可变。
字符串追加函数 - strcat
char * strcat ( char * destination, const char * source );
int main()
{
char arr1[20] = "hello\0XXXX";
char arr2[] = "bit";
//源字符串必须含有\0 - 调试可证明
//目标字符串是根据\0的位置来进行追加的
//报错 - 没有足够大的空间
char arr3[5] = { 'a','b','c','e','f'};
char arr4[] = "bit";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
注意
1. 源字符串必须以 ‘\0’ 结束。
2. 目标空间必须有足够的大,能容纳下源字符串的内容。
3. 目标空间必须可修改
字符串比较函数 - strcmp
int strcmp ( const char * str1, const char * str2 );
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
int ret = strcmp(arr1, arr2);
//strcmp函数比较的不是字符串的长度
//而是比较字符串中对应的位置的字符的ascii码值的大小,相同则比较下一对,直到不同或者都遇到\0
printf("%d\n", ret);
//返回值为整数 <0 =0 >0 代表小于 等于 大于
return 0;
}
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
上述长度不受限制的字符串函数使用是有风险的,由于没有限制,相当于操作不会考虑空间不足等问题,导致程序奔溃
受限制的字符串拷贝函数 - strncpy
char * strncpy ( char * destination, const char * source, size_t num );
int main()
{
char arr1[] = "abcdef";
char arr2[] = "adsuidasczx";
strncpy(arr1, arr2, 3);//相比strcpy函数相对安全
printf("%s\n", arr1);//只拷贝了前三个字符 adsdef
return 0;
}
注意
1. 拷贝num个字符从源字符串到目标空间。
2. 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
受限制的字符串追加函数 - strncat
char * strncat ( char * destination, const char * source, size_t num );
int main()
{
char arr1[20] = "abcdef\0XXXXXXX";
char arr2[] = "qwuqwqywiyt";
strncat(arr1, arr2, 3);//追加指定位数后会追加一个\0-可自加
//如果追加位数过多,该函数也不会多追加
printf("%s\n", arr1);
return 0;
}
注意
1. 追加num个字符到上述字符串第一个\0之后的部分
2. 如果追加太多,超过预留空间,该函数也不会强行追加而导致报错
受限制的字符串比较函数 - strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdq";
int ret = strncmp(arr1, arr2, 3);
printf("%d\n", ret);//0
return 0;
}
比较结束
1. 字符不一样
2. 其中一个字符串结束
3. num个字符全部比较完
小科普
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
if (arr1 < arr2)
{
}
char* p = "abc";//当字符串作为表达式出现时,实际上返回值就是其地址
if ("abc" < "abcdef")
{
//实际上比的就是地址
}
}
上述受限制的字符串操作函数使用起来会更加安全
查找子串函数 - strstr
char * strstr ( const char * str1, const char * str2);
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "cdef";
char* ret = strstr(arr1, arr2);
if (NULL == ret)
{
printf("找不到子串\n");
}
else
{
printf("%s\n", ret);//返回地址,向后打印直到\0
//cdefabcdef
}
return 0;
}
注意: 匹配成功后返回第一个字符的地址
字符串切割函数 - strtok
char * strtok ( char * str, const char * sep );
tips:
1. sep参数是个字符串,定义了用作分隔符的字符集合
2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
3. strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
4. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。(说明函数内有静态变量)
5. strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
6. 如果字符串中不存在更多的标记,则返回 NULL 指针。
实例
int main()
{
char arr[] = "[email protected] nihao";
char buf[100] = { 0 };//准备好复制字符串
strcpy(buf, arr);//拷贝字符串
const char* sep = " @.";//确定sep分割数组的元素
//strtok
printf("%s\n", strtok(buf, sep));//只找第一个标记
printf("%s\n", strtok(NULL, sep));//从保存好的位置开始继续向后查找
printf("%s\n", strtok(NULL, sep));//从保存好的位置开始向后找
printf("%s\n", strtok(NULL, sep));//从保存好的位置开始向后找
//优化算法 - 循环打印
//char* str = NULL;
/*for (str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}*/
return 0;
}
错误码报告函数 - strerror
我们运行代码时,肯定看到过运行框最后一行“进程已停止,代码为0”,肯定还有过其他情况,其实这可以用函数strerror来看看数字代表什么意思
#include
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
return 0;
}
同时该函数还能找到错误的原因,我们写完代码即可用该函数判断判断,程序有没有错误
#include
#include
#include
int main()
{
int* p = (int*)malloc(INT_MAX);//向堆区开辟内存 - 开辟过大导致错误
if (p == NULL)
{
//printf("%s\n", strerror(errno));
//perror更方便
perror("malloc");//需要传一个字符串,随便写就行
return 1;
}
return 0;
}
#include
int main()
{
int ret = isdigit('a');//判断数字
printf("%d\n", ret);
/*char ch = '1';
int ret = islower(ch);
printf("%d\n", ret);*///只要打印出来为非0数字,那么就为真
return 0;
}
int main()
{
char ch = 'A';
putchar(toupper(ch));//转大写
putchar(tolower(ch));//转小写
return 0;
}
内存复制函数 - memcpy
void * memcpy ( void * destination, const void * source, size_t num );
tips:
1. 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
2. 这个函数在遇到 ‘\0’ 的时候并不会停下来。
3. 如果source和destination有任何的重叠,复制的结果都是未定义的。
//作者实现memcpy函数的时候要符合所有类型的情况,故返回值为void *
/* memcpy example */
#include
#include
struct {
char name[40];
int age;
} person, person_copy;
int main()
{
char myname[] = "Pierre de Fermat";
//using memcpy to copy string :
memcpy(person.name, myname, strlen(myname) + 1);//复制名字
person.age = 46;
//using memcpy to copy structure:
memcpy(&person_copy, &person, sizeof(person));//复制整个person结构体
printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
return 0;
}
重叠内存拷贝函数 - memmove
void * memmove ( void * destination, const void * source, size_t num );
tips:
1. 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2. 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
/* memmove example */
#include
#include
int main ()
{
char str[] = "memmove can be very useful......";
memmove (str+20,str+15,11);
puts (str);
return 0;
}
内存比较函数 - memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
tips:
比较从ptr1和ptr2指针开始的num个字节
#include
#include
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n = memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0) printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);
else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
return 0;
}
内存设置函数 - memset
void * memset ( void * ptr, int value, size_t num );
tips:
1. 以字节为单位来初始化内存单元为value
2. 表示给指针ptr初始化num个内存空间
int main()
{
int arr[] = { 0x11223344,0x22222222,3,4,5 };
memset(arr, 0xf, 20);
return 0;
}
二. 模拟实现
计数器方式
//计数器方式
int my_strlen(const char * str) {
int count = 0;
while(*str)
{
count++;
str++;
}
return count; }
递归
//不能创建临时变量计数器
int my_strlen(const char * str) {
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
指针 - 指针
//指针-指针的方式
int my_strlen(char *s) {
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
tips:
1.参数顺序
2.函数的功能,停止条件
3.assert
4.const修饰指针
5.函数返回值
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
char *my_strcat(char *dest, const char*src) {
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* cur = str1;
while (*cur)
{
s1 = cur;
s2 = str2;
while ((*s1 == *s2) && *s1 && *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cur;
}
cur++;
}
return NULL;
}
其实还有更高效的算法,那就是我们的KMP算法了,感兴趣的友友可以移步这篇博客KMP算法保姆级教程
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
int ret = 0;
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
//不相等
return *s1 - *s2;
}
void* my_memcpy( void* dest, const void* src, size_t count )
{
assert(dest && src);
void* ret = dest;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
//从前向后拷贝
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//从后向前拷贝
while (count--)
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
家人们,学到这里我们已经理解了字符串函数的相关内容,相信以后也知道如何做利用有关的函数了,后续新一会持续更新C语言的有关内容,学习永无止境,技术宅,拯救世界!