在前面的学习过程中,我们见到过strlen函数:求字符串的长度、strcpy函数:拷贝字符串、strcmp函数:比较字符串是否相等。其实还有许多关于字符串的函数,那么本篇就来一起学习一下这些字符串函数的用法和细节!
目录
1.strlen函数
关于strlen函数我们并不陌生,因为在之前的文章中经常使用它,strlen函数的作用就是求一个字符串函数的长度,不包括\0,可以通过C语言函数工具来了解一下它:
注意:
1.字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
2.size_t strlen ( const char * str );
3.参数指向的字符串必须要以 '\0' 结束。
4.注意函数的返回值为size_t,是无符号的( 易错 )
1.1 实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = { "abcdef" };
char str2[] = { "abc\0def" };
char str3[] = { 'a','b','c','d','e','f'};
printf("strlen(str1) = %d\n", strlen(str1));
printf("strlen(str2) = %d\n", strlen(str2));
printf("strlen(str3) = %d\n", strlen(str3));
return 0;
}
1.2模拟实现
学会了怎么使用strlen函数,那么可以模拟实现一下计算字符串长度函数--strlen
首先strlen是统计\0之前的字符的长度、strlen函数返回值是size_t类型的,函数参数是指针类型,所以可以使用指针的运算来编写strlen函数的模拟实现
方法一:计数器
#include <stdio.h>
#include <assert.h>
//只是统计个数但是不用改变字符串,所以使用const来修饰会更安全
int my_strlen(const char* str)
{
assert(str); //断言,判断str是否为空指针
//计数器
int count = 0;
while (*str++) //循环的判断条件为str不为\0
{
count++;
}
return count;
}
int main()
{
char str[] = { "abcdef" };
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
方法二:指针-指针
#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
{
assert(str);
char* p = (char*)str; //记录初始位置
while (*++str) //找到\0
{
;
}
return str - p; //用\0处的地址 - 起始位置得到中间的元素个数
}
int main()
{
char str[] = { "abcdef" };
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
方法三:递归(不创建临时变量)
#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
{
assert(str);
if (*str) //不包括\0
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char str[] = { "abcdef" };
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
2.strcpy函数
strcpy函数用来拷贝字符串,使用工具来查看一下:
注意:
1.源字符串必须以 '\0' 结束。
2.会将源字符串中的 '\0' 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变。
2.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[80] = { "************" };
char str2[] = { "Hello CSDN!" };
printf("拷贝前:> %s\n", str1);
strcpy(str1, str2);
printf("拷贝后:> %s\n", str1);
return 0;
}
2.2模拟实现
首先strcpy函数的返回类型为指针,参数类型也为指针,其次strcpy在拷贝的时候会将源字符串的\0拷贝到目标字符串中,也就是目标字符串不能为常量字符串
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* str = dest; //记录初始位置
while (*dest++ = *src++) //拷贝的时候包括字符串
{
;
}
return str;
}
int main()
{
char str1[80] = { "************" };
char str2[] = { "Hello CSDN!" };
printf("拷贝前:> %s\n", str1);
my_strcpy(str1, str2);
printf("拷贝后:> %s\n", str1);
return 0;
}
3.strcat函数
strcat是一个追加字符串的函数,将源字符串追加到目标字符串的后面:
注意:
1.源字符串必须以 '\0' 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。4.在追加的时候会将之前的\0覆盖掉并在追加完之后加上\0
5.尽量不要使用strcat函数自己给自己追加字符串
3.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[80] = { "Hello " };
char str2[] = { "CSDN!" };
printf("追加前:> %s\n", str1);
strcat(str1, str2);
printf("追加后:> %s\n", str1);
return 0;
}
如果自己给自己追加字符串就会出现问题,我们可以来分析一下:
char str[10] = {"abc"};
strcat(str,str);
如果要将str追加到str中,先将a放到后面的\0的位置,然后将b放到a的后面,再将c放到b的后面,这时就会发现str中的\0已经被重新放进来的a覆盖掉了,就没有\0了,所以追加就会一直追加下去,再也找不到\0了,所以进入了死循环,因此使用strcat自己给自己追加会将本身的内容改变,导致无法找到\0,会进入死循环。
3.2模拟实现
要实现strcat函数,要先找到目标字符串的\0的位置,然后将其覆盖掉,并在最后再加上\0
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* str = dest; //记录开始的位置
while (*++dest) //找到目标函数\0的位置
{
;
}
while (*dest++ = *src++)
{
;
}
return str;
}
int main()
{
char str1[80] = { "Hello " };
char str2[] = { "CSDN!" };
printf("追加前:> %s\n", str1);
my_strcat(str1, str2);
printf("追加后:> %s\n", str1);
return 0;
}
4.strcmp函数
strcmp函数是用来比较两个字符串是否相等,如果相等则返回1:
4.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[10] = { "abcdef" };
char str2[10] = { "abcdef" };
if (0 == strcmp(str1, str2))
{
printf("Yes\n");
}
else
printf("No\n");
return 0;
}
4.2模拟实现
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2); //断言
while (*str1++ == *str2++) //判断是否相等
{
if (*str1 == '\0') //如果相等判断是否为\0,如果为\0则表示已经比较完
return 0;
}
//如果比较不相等则返回差值
return *str1 - *str2;
}
int main()
{
char str1[10] = { "abcdf"};
char str2[10] = { "abcdef"};
if (0 == my_strcmp(str1, str2))
printf("Yes\n");
else
printf("No\n");
return 0;
}
前面提到的
strcpy、strcmp、strcat都是长度不受限制的函数,举一个简单的例子:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = { "abcdef" };
char str2[5] = { 0 };
//将str1的字符串拷贝到str2中,但是str2只有5个元素的大小,那拷贝不下呀
//但是,strcpy会强制性的拷贝进去,虽然有的编译器在编译的时候没有错误
//但是当程序运行起来之后会崩溃,但是依然将str1的元素拷贝进了str2
strcpy(str2, str1);
printf("%s\n", str2);
return 0;
}
所以,类似于这种长度不受限制的函数相对于长度受限制的函数是不安全的,因此还有长度受限制的函数,那么接下来就看一看:
5.strncpy函数
strncpy还是拷贝字符串的函数,但是它可以控制拷贝多少个字符:
strncpy的函数参数多了一个size_t的count,表示将源字符串的count个字符拷贝到目标字符串中,如果count小于源字符串的长度,则不会自动添加\0,如果count大于目标字符串,剩下的会自动用\0补充
5.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = { "abcdef" };
char str2[5] = { 0 };
strncpy(str2, str1, 4);//拷贝4个字符abcd
printf("%s\n", str2);
return 0;
}
6.strncat函数
strncat函数是追加字符串的函数,可以控制追加多少个字符串:
6.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[80] = { "we are the " };
char str2[] = { "best writers" };
strncat(str1, str2, 12); //空格也算是一个字符
printf("%s\n", str1);
return 0;
}
注:strncat函数是可以自己给自己追加的
7.strncmp函数
strncmp函数用来比较字符串,可以控制比较多少个字符:
7.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20] = { "abcedf" };
char str2[20] = { "abcdef" };
if (0 == strncmp(str1, str2, 4))
printf("Yes\n");
else
printf("No\n");
return 0;
}
8.strstr函数
strstr函数是在一个字符串里面找另外一个字符串,如果找到了则返回第一次出现的地址,如果找不到,则返回指针(NULL):
8.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdef";
char str2[] = "bcd";
char* p = strstr(str1, str2);
if (p == NULL)
printf("找不到\n");
else
printf("%s\n", p);
return 0;
}
8.2模拟实现
strstr函数是在str1中找str2第一次出现的位置,所以最简单的方法就是记录str1的起始位置,记录str2的起始位置,然后每次加1,往后面找,如果相等,则加一,如果不相等直接返回 NULL就可以了,如果前面的字符相等,而str2也找到了\0,那就证明找到了,返回第一次出现的地址就行
但是呢,还有一种复杂的情况,匹配的次数可能不止一次,还有可能是多次:
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* s1 = NULL; //用来维护str1
char* s2 = NULL; //用来维护str2
char* cp = NULL; //记录匹配的位置
//cp来记录str2在str1中第一次出现的位置,所以cp刚开始是指向str1的
cp = (char*)str1;
//如果cp都找到str1的\0了还没有匹配成功,则返回NULL
while (*cp)
{
s2 = (char*)str2;
s1 = cp;
//只要s1和s2不为\0,如果字符相等,就继续往后面找
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
//如果*s2为\0则表示匹配成功
if (*s2 == '\0')
{
return cp;
}
//如果不相等,就从下一个字符往后找
cp++;
}
return NULL;
}
int main()
{
char str1[] = "abbbcde";
char str2[] = "bbc";
char* p = my_strstr(str1, str2);
if (p == NULL)
printf("找不到\n");
else
printf("%s\n", p);
return 0;
}
9.strtok函数
strtok函数是一个可以分隔字符串的函数,通过给定特定的字符将字符串分隔:
注意:
1.strDelimit参数是个字符串,定义了用作分隔符的字符集合第一个参数指定一个字符串,它包含了0个或者多个由strDelimit字符串中一个或者多个分隔符分割的标记。
2.strtok函数找到strToken中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
3.strtok函数的第一个参数不为 NULL ,函数将找到strToken中第一个标记,strtok函数将保存它在字符串中的位置。
4.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
5.如果字符串中不存在更多的标记,则返回 NULL 指针
9.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = { "yctr@nrt.cn" };
char buff[30] = { 0 };
strcpy(buff, arr);
const char* p = "@."; //分割字符串
char* str = NULL;
for (str = strtok(buff, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}
10.strerror函数
strerror函数是一个返回错误码和错误信息的函数:
10.1实例演示
关于strerror的使用要结合动态开辟内存函数以及文件操作相关的知识进行使用,关于这些知识在后面我也会给大家分享出来。
文件操作:
1.打开文件
2.打开文件的时候,如果文件的打开方式是"r"(r表示的意思是读文件)
3.文件存在则打开成功,文件不存在打开失败
4.打开文件失败的话,会返回NULL
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pc = fopen("test.txt", "r");
if (pc == NULL)
{
printf("打开文件失败,错误原因是:> %s\n", strerror(errno));
return 1;
}
//操作文件
//......
//关闭文件
fclose(pc);
pc = NULL;
return 0;
}
如果看不懂这些代码表示的意思也没有关系,后面也会有关于内存的动态开辟和文件操作相关的知识,现在只是直到这个函数该怎么用
与strerror相对应的一个函数perror也可以显示错误信息 :
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
//打开文件
FILE* pc = fopen("test.txt", "r");
if (pc == NULL)
{
perror("打开文件失败,原因:>");
return 1;
}
//操作文件
//......
//关闭文件
fclose(pc);
pc = NULL;
return 0;
}
这样子写也是可以达到效果的!
前面提到的函数都是关于字符串操作的函数,也就是说只能用来操作字符串,但是在真的使用情景中我们不仅仅要操作字符串类型的数据,还需要操作别的数据,因此,就需要使用内存函数,比如:
memcpy:内存拷贝
memmove:内存移动
memcmp:内存比较
memset:内存修改(之前提到过)
......
11.memcpy函数
memcpy函数可以拷贝内存,不管是什么类型,什么数据,都可以拷贝:
注意:
1.这个函数在遇到 '\0' 的时候并不会停下来。
2.如果source和destination有任何的重叠,复制的结果都是未定义的。3.memcpy函数返回的是目标空间的起始地址。
11.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[5] = { 0 };
//拷贝整形数组
memcpy(arr2, arr1, 20);//要注意这里的20单位是字节,一个整形是4个字节,所以就是5个整形
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr2[i]);
}
putchar('\n');
return 0;
}
11.2模拟实现
要实现memcpy函数来拷贝任何类型的数据,我们可以采用一个字节一个字节的方式进行拷贝
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
//记录初始位置
void* str = dest;
//一次拷贝一个字节,所以应该循环num次
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return str;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[5] = { 0 };
my_memcpy(arr2, arr1, 20);//要注意这里的20单位是字节,一个整形是4个字节,所以就是5个整形
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr2[i]);
}
putchar('\n');
return 0;
}
12.memmove函数
前面提到过memcpy是不能自己操作自己的,但是memmove可以操作自己,memmove函数的作用是进行内存移动:
12.1实例演示
#include <stdio.h>
#include <string.h>
void test1()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr + 4, arr + 2, 16);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test2()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr + 2, arr + 4, 16);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test1();
test2();
return 0;
}
12.2模拟实现
这种情况我们就可以先将5移动到3的位置,然后将6移动到4的位置,再将7移动到5的位置,最后将8移动到6的位置,所以结果就是1 2 5 6 7 8 7 8 9 10,这样就实现了内存移动。
这种情况我们可以将3移动到5的位置,然后将4移动到6的位置,移动到这里就会发现5这里的5都已经被3替代了,如果还要移动的话就会将3移动到7的位置,同样6也被4覆盖掉了,这时再移动的话就将4移动到8的位置,所以结果就变成了1 2 3 4 3 4 3 4 9 10,但是我们预期的的结果是1 2 3 4 3 4 5 6 9 10,所以,就不能使用从前向后移动的方法,但是如果使用从后向前的移动方法不是就可以达到效果了嘛:先将6移动到8,5移动到7,4移动到6,3移动到5,这样就达到了预期的目的。
所以在模拟实现memmove函数时,不能只有简单的从前向后移动,也不能只有从后向前移动,要将两种方法结合起来,在合适的时机使用合适的方法,那如果选择呢?当目标空间的地址小于源空间地址(dest<src)就可以使用从前向后的移动方式,如果目标空间的地址大于源空间的地址(dest>src)就可以使用从后向前的移动方式。
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* str = dest;
//判断使用哪种方法
//dest<src :从前向后移动
//dest>src :从后向前移动
if (dest < src)
{
//从前向后移动
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//从后向前移动
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return str;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove( arr + 4,arr + 2, 16);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
C语言规定:
memcpy拷贝不重叠的内存
重叠部分就交给memmove来移动
13.memcmp函数
根据strcmp就可以直到memcmp是什么作用,memcmp是可以比较内存的的大小:
13.1实例演示
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,8 };//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 07 00 00 00
int arr2[] = { 1,2,3,4,6 };//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 06 00 00 00
int ret = memcmp(arr1, arr2, 17);
printf("%d\n", ret);
return 0;
}
14.memset函数
memset函数可以改变内存里面的内容:
14.1实例演示
#include <string.h>
#include <stdio.h>
int main()
{
char arr[] = "hello world";
printf("修改前:> %s\n", arr);
memset(arr, 'x', 5);
printf("修改后:> %s\n", arr);
return 0;
}
15.字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
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 | 任何可打印字符,包括图形字符和空白字符 |
这些函数的用法都是一模一样,我们可以举例子来演示几个:
isdigit函数:是否为十进制数组
如果不是十进制数字则返回0,如果是十进制数组则返回的是非0
15.1实例演示
#include <stdio.h>
#include <ctype.h>
int main()
{
//判断是否为10进制数字
printf("%d\n", isdigit('0')); //是返回非0
printf("%d\n", isdigit('w')); //不是返回0
//判断是否为空白字符
printf("%d\n", isspace(' ')); //是返回非0
printf("%d\n", isspace('w')); //不是返回0
//判断是否为字母
printf("%d\n", isalpha('a')); //是返回非0
printf("%d\n", isalpha('8')); //不是返回0
return 0;
}
其他的函数就不一一列举了,这些函数的使用方法都是一样的,感兴趣的老铁可以自己敲一敲,用一用。
16.字符转换函数
大写字母A的ASCII值是65,小写字母a的ASCII值是97,两者相差32,所以给大写字母A加上32就转化为小写字母a,或者给小写字母a减去32就得到了大写字母A,还有一种方法就是字符转化函数:可以将大写字母转化为小写字母,将小写字母转化为大写字母。
int tolower ( int c ); //将大写字母转化为小写字母
int toupper ( int c ); //将小写字母转化为大写字母注意:如果不能转换,将返回初始值。
16.1实例演示
#include <ctype.h>
#include <stdio.h>
int main()
{
printf("%c\n", tolower('A'));
printf("%c\n", tolower('B'));
printf("%c\n", toupper('a'));
printf("%c\n", toupper('b'));
return 0;
}
16.2练习
输入一串字符串,将里面的大写字母转化为小写字母
#include <ctype.h>
#include <stdio.h>
int main()
{
char str[128] = { 0 };
//输入
gets(str);
int i = 0;
while (str[i])
{
if (isupper(str[i])) //判断是否为大写字母
{
str[i] = tolower(str[i]); //如果是大写字母则转换为小写字母
}
//输出
printf("%c", str[i]);
i++;
}
return 0;
}
本期关于字符串函数和内存函数的分享就到此为止,如果觉得有用就留下你的赞吧!感谢各位老铁!