文章目录
🥇字符串函数
C语言中本身并没有字符串这一类型,字符串通常存放在常量字符串或者字符数组之中。而在我们平时编程的时候,却需要频繁地对字符串进行操作、处理,这时候又该怎么办呢?C语言为我们提供了一系列处理字符串的库函数,接下来我将详细地介绍这些意义非凡的函数。
1.strlen
1️⃣是什么?
strlen函数是string头文件中最常见的一个函数,用于求字符串的长度。 strlen英文全称string length,既字符串长度,可以顾名思义地来记忆。
2️⃣具体用法:
✅这是从c++ reference上截取的介绍,strlen函数的用法就是向其传入一个字符串数组的首元素地址,其返回值是字符的个数。
⭕演示代码如下:
#include<string.h>
int main()
{
char str[] = "abc";
int len1 = strlen(str);
int len2 = strlen("abc");
printf("%d\n%d", len1,len2);
return 0;
}
!!这里应该注意的是,"abc"表示的也是一个字符串数组,其传给strlen函数的是首元素地址。
运行结果:
💭下面我们试着运行一下这个代码:
#include<string.h>
int main()
{
char str1[] = "abc";
int len1 = strlen(str1);
char str2[] = { 'a','b','c'};
int len2 = strlen(str2);
printf("%d\n%d", len1,len2);
return 0;
}
运行结果:
❓为什么这里的str2的长度会是13呢?乍一看不是abc三个元素,长度为3吗?
这里需要补充一个知识点。在C语言中,系统会在字符串数组最后加上一个’\0’作为结束标志。而strlen函数的工作原理就是计算传入数组 (首元素地址指针指向的数组)在’\0’之前的元素个数,既该字符串长度。
而该代码块中所定义的str数组并不是一个字符串数组,因此’\0’的位置并不在最后一个元素后面,而是一个随机的位置,strlen函数会一直计算长度直到遇见’\0’才停止工作并返回结果。
因此,strlen(str2)会返回一个随机值,该值不代表该数组的长度。
通过调试验证了str1字符串数组末端有一个’\0’而str2没有。
2.strcpy
1️⃣是什么?
strcpy,英文全称copies strcpy,也是一个string头文件中较为常见的函数,用于将一个字符串的内容复制到另一个数组中。
2️⃣具体用法
(再次引用c++ reference中的描述,发现这种英文的工具网站的描述比中文的清晰地多)
✅strcpy的使用方法是向其传入两个指针,前者是目标数组首元素地址指针,后者是原字符串数组地址指针,将原字符串数组(包括末尾结束标志’\0’)拷贝到目标数组中。若目标数组中本身具有元素,则根据原字符串数组的长度一一覆盖。(❗注意:为了防止数组溢出,目标数组长度应大于原字符串数组。)
⭕演示代码如下:
#include<string.h>
int main()
{
char str1[] = "abcdefg";
char str2[] = "123";
strcpy(str1, str2);//str2==>str1
printf("%s", str1);//打印拷贝处理后的str1
return 0;
}
运行结果:
可见,str2的内容完美地拷贝到str1中了。
通过调试可以发现,str2的内容覆盖了str1前四个元素,而后面的元素依然存在。但是因为str2的’\0’也跟着拷贝过去了,所以在打印的时候,由于printf格式控制符是%s(既字符串类型),在遇到第一个’\0’时则认为它是字符串结束标志,结束打印。因此,打印出来的结果是字符串"123"。
💭综上所述,在使用strcpy函数时,我们应该注意以下几点:
- 源字符串必须以’\0’结束
- 会将源字符串的’\0’拷贝到目标空间中
- 目标空间必须足够大以存放源字符串
- 目标空间必须可变
3.strcmp
1️⃣是什么?
strcmp函数,英文全称compares string(字符串比较), 既用于比较两个字符串,那么这里是比较字符串的什么呢?
比较规则:
先比较两个字符串的第一个字符的ASCII码值(这里视为第一对字符),(下面的比较均是ASCII码值的比较)如果第一个字符串的第一个字符大于第二个字符串的第一个字符,则返回大于0的数;反之,则返回小于0的数;若两个字符相等,则进行下一对字符的比较,直到出现有一对字符不同则返回相应的值。若两个字符串所有的字符都相同,则返回数字0。
2️⃣具体用法:
传入两个指针,代表两个字符串数组的首元素地址。
⭕演示代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20] = { 0 };
char str2[20] = { 0 };
scanf("%s%s", str1, str2);
int ret = strcmp(str1, str2);
if (ret > 0)
{
printf(">\n");
}
else if (ret < 0)
{
printf("<\n");
}
else
{
printf("=\n");
}
return 0;
}
运行结果:
可见,这里的比较和我们所描述的相一致。
⭕这里应该注意,'\0'的ASCII码是0,它与其他字符都小。
4.strcat
1️⃣是什么?
strcat,英文全称concatenates string(连接字符串),这里很容易理解,strcat函数就是用于连接两个字符串的,那么它是如何连接的呢?接下来我将介绍一下它的具体用法。
2️⃣具体用法
✅如图所示,使用strcat函数需向其传入两个指针,前者是目标数组首元素地址指针,后者是源字符串数组地址指针(与strcpy类似,只不过这里是将原字符串接到目标字符串后面)。传参成功后,strcat会将原字符串的第一个字符覆盖到目标字符串的’\0’位置,后面再接着原字符串剩下的全部字符,直到遇见’\0’后结束,保证了拼接后的字符串只有一个结束标志’\0’。
⭕演示代码如下:
#include<string.h>
int main()
{
char str1[5]="ab";
strcat(str1, "cd");
printf("%s\n", str1);
char str2[5] = "ab";
char str3[3] = "cd";
strcat(str2, str3);
printf("%s\n", str2);
return 0;
}//原字符串可以用字符串数组也可以直接用常量字符串,结果相同
运行结果:
如果我们想让拼接后的字符串更长,能不能修改一下原字符串实现呢?让我们来试试看💭
int main()
{
char str1[5]="ab";
strcat(str1, "cde");
printf("%s\n", str1);
}
运行结果:
运行出错,引发了异常,这是为什么呢?
💭这里是因为发生了数组溢出,由于"cde"字符串加上’\0’有四个字符,而str1我们规定了长度为5,当"cde"拼接在str1后覆盖了它的’\0’之后,拼接字符串一共有六个字符,超过了目标字符串str1的长度,无法容纳拼接后的字符串,所以发生了错误。
⭕由此我们可得,在使用strcat函数时,要规定给目标字符串一个合适的长度,防止数组溢出。
💡那么,既然我们可以实现字符串的追加,那么一个字符串是否可以追加它自己呢?
我们来看看下面的代码
int main()
{
char str[20] = "abcd";
strcat(str, str);
printf("%s\n", str);
}
运行结果发生了异常
这是为什么呢?strcat在追加时,会把目标空间的’\0’覆盖掉,然后源字符串再依次追加直到遇到源字符串的’\0’为止。而这里的目标字符串和源字符串相同,一旦‘\0’被覆盖了,就无法停止追加,最后引发异常,陷入死循环
如图:
💭综上所述,在使用strcat函数时应该注意以下几点:
- 源字符串必须以’\0’结束
- 目标空间应该足够大以容纳拼接后的字符串
- 目标空间必须可变
- 字符串不能自己追加自己
🔎下面介绍几个和上述几个函数长得很像的“兄弟”函数,他们在上述函数的基础上多了一个参数n,限制了预处理字符串的字符个数,让处理字符串更加灵活起来。
5.strncpy
1️⃣是什么?
功能与strcpy相同,都是拷贝字符串,不过strncpy可以规定拷贝的字符个数
2️⃣具体用法
这里的参数num的意思是从源字符串复制到目标空间中的字符个数,可以由程序员自行规定。
⭕演示代码如下:
int main()
{
char str[10] = "abc";
strncpy(str, "defg", 3);
printf("%s\n", str);
return 0;
}
运行结果:
⭕如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个
int main()
{
char str[10] = "abcxxxxxx";
strncpy(str, "defg", 7);
printf("%s\n", str);
return 0;
}
⭕如果源字符串的长度大于num,则拷贝完num个字符之后,不会再末尾再追加\0
💭使用strncpy函数依然需要注意以下几点:
- 目标空间应该足够大以容纳拼接后的字符串
- 目标空间必须可变
6.strncat
1️⃣是什么?
功能与strcat一样都是追加字符串,而strncat可以限制所要追加的字符个数
2️⃣具体用法
参数num为要从源字符串追加到目标字符串中的字符个数
⭕代码演示
int main()
{
char str[20] = "hello ";
strncat(str, "world", 3);
printf("%s\n", str);
return 0;
}
运行结果:
🔎值得注意的是
strncat函数会在追加后的字符串末尾加上一个'\0'
当num大于源字符串长度时,strncat不同于strncpy,而是只把整个源字符串追加到目标空间
证明1:
⭕运行以下代码
int main()
{
char str[20] = "hello ";
*(str + 9) = 'x';
strncat(str, "world", 3);
printf("%s\n", str);
return 0;
}
证明2:
⭕运行以下代码
int main()
{
char str[20] = "hello\0xxxxxxxxxxxxx";
strncat(str, "world", 8);
printf("%s\n", str);
return 0;
}
7.strncmp
1️⃣是什么?
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
2️⃣具体用法
num=3时
int main()
{
char str1[] = "abcde";
char str2[] = "abcdxy";
int ret = strncmp(str1, str2, 3);
printf("%d", ret);
return 0;
}
num=5时
int main()
{
char str1[] = "abcde";
char str2[] = "abcdxy";
int ret = strncmp(str1, str2, 5);
printf("%d", ret);
return 0;
}
8.strstr
1️⃣是什么
strstr,英文全称Locate substring,既定位子字符串。strstr的返回值比较特殊,它是通过传入一个母字符串和一个子字符串,然后在母字符串中定位子字符串的位置,返回值为指向母字符串中第一次出现的子字符串的指针。若子字符串不属于母字符串的一部分,则返回空指针NULL。
2️⃣具体用法
⭕演示代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char str[20] = "I love you";
char* p1 = strstr(str, "love");
char* p2 = strstr(str, "me");
printf("%p\n%p\n%p\n",str, p1, p2);
return 0;
}
运行结果:
9.strtok
1️⃣是什么
这是一个比较“奇怪”的函数,用于切割字符串,可以把一个字符串按照特定的标记分割开来,之所以说他奇怪,是因为使用方法比较奇怪。
2️⃣具体用法
🎈这里的参数部分比较特殊。delimiters是一个字符串,定义了用作分隔符的字符集合('\0’也算一个)。str是指向想要分割的字符串的指针,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。
🔎工作原理:
- strtok函数找到str中的下一个标记,并将其用 ‘\0’ 替换,返回一个指向这个‘\0’之前字符串的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
⭕演示代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdef@xxx.com";
char str2[20] = { 0 };
const char deli[] = "@.";//'\0'也是一个分割标记
strcpy(str2, str1);//拷贝到str2中进行操作
char* ret = str2;
for (ret = strtok(str2, deli);ret != NULL;ret = strtok(NULL, deli))
{
printf("%s\n", ret);
}
return 0;
}
⭕运行结果
值得注意的是,当两个分隔符挨在一起时,后一个分隔符会被strtok跳过
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdef@@xxx.com";//两个分隔符挨在一起
char str2[20] = { 0 };
const char deli[] = "@.";
strcpy(str2, str1);
char* ret = str2;
for (ret = strtok(str2, deli);ret != NULL;ret = strtok(NULL, deli))
{
printf("%s\n", ret);
}
return 0;
}
结果相同
10.strerror
1️⃣是什么
返回错误码所对应的错误信息
2️⃣具体用法
#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件
int main()
{
FILE* pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
printf("Error opening file unexist.ent: %s\n", strerror(errno));
//errno: Last error number
//errno是一个全局变量,它的不同值对应不同的错误信息,具体取决于编译器如何实现
return 0;
}
11.字符转换函数
#include <ctype.h>//包含头文件
int toupper(int c)//小写转大写
int tolower(int c)//大写转小写
注意:如果c不是字母,或者使用toupper时c本来就是大写字母、使用tolower时c本来就是小写字母,则此时c不发生变化。
🎈示例
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "ABCdef";
char* p = str;
while (*p)
{
if (*p >= 65 && *p <= 90)
{
*p = tolower(*p);
}
else if(*p >=97 && *p <= 132)
{
*p = toupper(*p);
}
p++;
}
printf("%s\n", str);
return 0;
}
运行结果:
12.字符分类函数
⭕利用字符分类函数我们可以改进一下11中的代码:
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "ABCdef";
char* p = str;
while (*p)
{
if (isupper(*p))//利用函数进行判断
{
*p = tolower(*p);
}
else if(islower(*p))//利用函数进行判断
{
*p = toupper(*p);
}
p++;
}
printf("%s\n", str);
return 0;
}
🥈 内存函数
🎈内存函数,顾名思义就是处理内存的函数。从前面的字符串函数的学习我们知道,字符串可以进行拷贝、追加、比较等操作,对字符串的操作即是字符数组的操作。那么,如果我们要处理整型数组,或是其他各种类型的数组、结构体,又该怎么做呢?显然,我们可以通过内存函数来实现。下面我将介绍几个内存函数,他们也都包含着string.h头文件中。
1.memcpy
1️⃣是什么
内存拷贝,函数memcpy从source的位置开始向后拷贝num个字节的数据到destination的内存位置。
💡 需要注意的是 :
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的
2️⃣具体用法
⭕演示代码如下:
#include <stdio.h>
int main()
{
int arr1[20] = { 0 };
int arr2[] = { 1,2,3,4,5 };
memcpy(arr1, arr2, 20);
int i = 0;
for (i = 0;i < 5;i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
运行结果:
2.memmove
1️⃣是什么
memmove和memcpy的功能大同小异,唯一的区别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
2️⃣具体用法
⭕演示代码如下:
int main()
{
char str[] = "abcdef";
memmove(str, str + 2, 3);
printf("%s\n", str);
return 0;
}
⭕运行结果
3.memcmp
1️⃣是什么
比较从ptr1和ptr2指针开始的num个字节,比较规则与strcmp相同
2️⃣具体用法
⭕演示代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int 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;
}
⭕运行结果
4.memset
1️⃣是什么
将 ptr 所指向的内存空间的前num个字节数设置为指定值value
2️⃣具体用法
⭕演示代码如下:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memset(arr, 0, 40);
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
⭕运行结果
🥉库函数的模拟实现
1.strlen模拟实现
🌈三种方法
方法1:
//计数器法
#include <stdio.h>
#include <assert.h>
int my_strlen(char* ps)
{
assert(ps);
int count = 0;
while(*ps)
{
count++;
ps++;
}
return count;
}
int main()
{
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
方法2:
//指针-指针法
#include <stdio.h>
#include <assert.h>
int my_strlen(char* ps)
{
assert(ps);
char* tmp = ps;
while (*ps)
{
ps++;
}
return ps - tmp;
}
int main()
{
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
方法3:
//递归法
#include <stdio.h>
#include <assert.h>
int my_strlen(char* ps)
{
assert(ps);
if (*(ps + 1) == '\0')
{
return 1;
}
else
{
return 1 + my_strlen(ps + 1);
}
}
int main()
{
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
2.strcpy模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++)
;
return dest;
}
int main()
{
char str1[20] = { 0 };
char str2[] = "abcdef";
my_strcpy(str1, str2);
printf("%s\n", str1);
return 0;
}
3.strcmp模拟实现
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 && *str2 && (*str1 == *str2))
{
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else if (*str1 < *str2)
{
return -1;
}
else
{
return 0;
}
}
int main()
{
char str1[] = "abcd";
char str2[] = "abef";
int ret = my_strcmp(str1, str2);
if (ret > 0)
{
printf("str1>str2\n");
}
else if (ret < 0)
{
printf("str1<str2\n");
}
else
{
printf("str1=str2\n");
}
return 0;
}
//改进版
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 && *str2 && (*str1 == *str2))
{
str1++;
str2++;
}
return *str1 - *str2;
//因为返回的值只是大于0、小于0或等于0的数字,所以可以直接用两个字符做差作为返回值
}
int main()
{
char str1[] = "abcd";
char str2[] = "abef";
int ret = my_strcmp(str1, str2);
if (ret > 0)
{
printf("str1>str2\n");
}
else if (ret < 0)
{
printf("str1<str2\n");
}
else
{
printf("str1=str2\n");
}
return 0;
}
4.strcat模拟实现
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
//找到目标空间中\0的位置
char* tmp = dest;
while (*tmp)
{
tmp++;
}
//从\0位置开始拷贝src
while (*tmp++ = *src++)
;
return dest;
}
int main()
{
char str1[20]="I love ";
my_strcat(str1, "you");
printf("%s\n", str1);
return 0;
}
5.strstr模拟实现
#include <stdio.h>
#include <assert.h>
const char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* p1 = str1;
const char* p2 = str2;
const char* p = str1;
while (*p)
{
while (*p1 == *p2 && *p2)//比较停止的可能情况:1.两个字符不同 2.有一个字符串遇到了\0
{
p1++;
p2++;
}
if (*p2 == '\0')
{
return p;
}
p++;
p1 = p;
p2 = str2;
}
return NULL;
}
int main()
{
char str[] = "abbbcde";
char* ptr = my_strstr(str, "bbc");
printf("%d\n", ptr - str);
return 0;
}
⭕原理图
6.memcpy模拟实现
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
//我们无法确定内存中的数据的类型,因此需要用到泛型指针,
//再通过类型强转成char*的指针,使得每次可以访问一个字节(单位化)
{
assert(dest && src);
char* pd = (char*)dest;
char* ps = (char*)src;
while (num--)
{
*pd++ = *ps++;
}
return dest;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr_copy[10] = { 0 };
my_memcpy(arr_copy, arr, 40);
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", arr_copy[i]);
}
return 0;
}
7.memmove模拟实现
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
char* pd = (char*)dest;
char* ps = (char*)src;
if (pd < ps)//从前往后
{
while (num--)
{
*pd++ = *ps++;
}
}
else//从后往前
{
while (num--)
{
*(pd + num) = *(ps + num);
}
}
return dest;
}
int main()
{
char str[] = "abcdef";
my_memmove(str, str + 2, 3);
printf("%s\n", str);
return 0;
}
⭕原理图
📌总结
🎈🎈库函数的灵活应用是程序员的一大重要技能!在总结这篇文章的时候我发现自己的string库函数以及其他库函数仍有不了解的地方,需要不断地查资料去了解。总结下来,巩固了自己对这部分知识的掌握,也希望能为您带来帮助,感谢支持!欢迎大佬雅正。