【C/C++】基础:常见字符串与内存库函数
摘要:在本篇博客中将介绍各种常见的字符串与内存操作库函数,希望通过查询资料,理解掌握库函数并且实现各种库函数,达成可以自主学习使用查询陌生库函数的目的。同时还需要注意各种每个库函数之间的细节。所有的资料查询来自:cplusplus.com - The C++ Resources Network
文章目录
一. 字符串函数
字符串函数针对于字符串而言,在C语言中存在一个名为 string.h 的库,其中包含了对字符串的各种操作。而在C语言中,不存在字符串类型,因此常会把字符串存储于字符数组与常量字符串中
1.1 strlen
//函数定义与实现
size_t strlen ( const char * str );
size_t __cdecl strlen (
const char * str
)
{
const char *eos = str;
while( *eos++ ) ;
return( eos - str - 1 );
}
作用:获取字符串的长度
返回值:字符串的大小
解释:传入字符串首地址,遍历字符串直到访问到 ‘\0’ 停止访问并返回字符串长度(不包含 ‘\0’)
注意事项:返回类型为 size_t ,为无符号整数,没有负数
使用样例:
/* strlen example */
#include <stdio.h>
#include <string.h>
int main ()
{
char szInput[256];
printf ("Enter a sentence: ");
gets (szInput);
printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput));
return 0;
}
//Enter sentence: just testing
//The sentence entered is 12 characters long.
模拟实现:在此通过简单的循环遍历完成对数组的访问,根据来自资料显示的内容,访问到 ’\0’ 时返回字符串长度,注意不包含’\0’。最后的返回值,通过指针的运算来实现,注意指针进行计算时,其值为指向自身类型的数量。
size_t _strlen(const char *str) {
assert(str);
const char* ptr = str;
while(*ptr!= '\0'){
ptr++;
}
return ptr - str;
}
1.2 strcpy
//函数的定义
char * strcpy ( char * destination, const char * source );
作用:将源字符串拷贝到目的字符串
返回值:目的字符串的首地址
解释:将源字符串’\0’ 以前的内容拷贝到目的字符串中,包括 ‘\0’
注意事项:①源字符串必须以 ‘\0’ 结束;②会将源字符串中的 ‘\0’ 拷贝到目的字符串各种;③目标空间必须足够大,以确保能存放源字符串;④目标空间必须可修改;
使用样例:
/* strcpy example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[]="Sample string";
char str2[40];
char str3[40];
strcpy (str2,str1);
strcpy (str3,"copy successful");
printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);
return 0;
}
//输出:
//str1: Sample string
//str2: Sample string
//str3: copy successful
模拟实现:根据解释内容,该函数是通过 ‘/0’ 作为终止条件的,因此同样只需要通过循环遍历,不断复制直到 ‘/0’ 为止。还需要注意的是要将 ‘/0’ 也复制过去。当然还需要注意返回类型,是目标字符串的指针,因为需要不断遍历,因此我们要将其提前保存,以下是代码实现:
char* _strcpy(char* destination, const char* source) {
char* return_ptr = destination;
while ((*destination = *source) != '\0') {
destination++;
source++;
}
return return_ptr;
}
1.3 strcat
//函数定义
char * strcat ( char * destination, const char * source );
作用:将源字符串最佳到目的字符串中
返回值:目的字符串的首地址
解释:将源字符串追加到目标字符串。目标中的终止空字符被源字符串的第一个字符覆盖,并在目标中两者的连接形成的新字符串的末尾添加一个空字符。
注意事项:① 无法追加自身,因为当追加第一个字符后,终止空字符会被覆盖,这样便无法结束追加。如果追加自身可以使用后续将会提到的 strncat 函数;② 源字符串必须以 ‘\0’ 结束;③ 目标空间必须有足够的大,能容纳下源字符串的内容; ④目标空间必须可修改;
使用案例:
#include <stdio.h>
#include <string.h>
int main (){
char str[80];
strcpy (str,"these ");
strcat (str,"strings ");
strcat (str,"are ");
strcat (str,"concatenated.");
puts (str);
return 0;
}
//输出
//these strings are concatenated.
模拟实现:同样通过解释内容开始分析,首先寻找到终结空字符,然后再通过循环逐步追加,直到达到源字符串的终止符为止。还需要注意返回类型,需要提前保存目的字符串的首地址,代码如下:
char* _strcat(char* destination, const char* source) {
assert(destination);
assert(source);
char* ret = destination;
while (*destination) {
destination++;
}
while (*destination++ = *source++) {
}
return ret;
}
1.4 strcmp
//函数定义
int strcmp ( const char * str1, const char * str2 );
作用:比较两个字符串
返回值:返回值大于0,则第一个不匹配的字符在str1中的值大于str2中的值;返回小于0,则第一个不匹配的字符在str1中的值小于str2中的值;返回值为0,两个字符串相等。
解释:函数从第一个字符串开始比较。如果它们相等,则继续往后比较,直到字符不同或到达一个终止的空字符为止,具体结束条件可以查看返回值的要求。
注意事项:函数执行字符的二进制比较,需要考虑特定于语言环境规则的函数。
使用案例:
#include <stdio.h>
#include <string.h>
int main (){
char key[] = "apple";
char buffer[80];
do {
printf ("Guess my favorite fruit? ");
fflush (stdout);
scanf ("%79s",buffer);
} while (strcmp (key,buffer) != 0);
puts ("Correct answer!");
return 0;
}
//输出
//Guess my favourite fruit? orange
//Guess my favourite fruit? apple
//Correct answer!
模拟实现:按照解释的内容,通过循环逐个比较内容,如果相等则继续往后比较,直到达到终止条件。
int _strcmp(const char* str1, const char* str2) {
assert(str1);
assert(str2);
while (*str1 == *str2) {
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
1.5 strncpy
//函数定义
char * strncpy ( char * destination, const char * source, size_t num );
作用:拷贝num个字符从源字符串到目标空间。
返回值:目的字符串的首地址
解释:与strcpy相似,但是可以通过参数 n,来控制复制的数量
注意事项:①如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到达到num个;②目标地址和源地址不能重叠;
使用案例:
/* strncpy example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[]= "To be or not to be";
char str2[40];
char str3[40];
/* copy to sized buffer (overflow safe): */
strncpy ( str2, str1, sizeof(str2) );
/* partial copy (only 5 chars): */
strncpy ( str3, str2, 5 );
str3[5] = '\0'; /* null character manually added */
puts (str1);
puts (str2);
puts (str3);
return 0;
}
//输出
//To be or not to be
//To be or not to be
//To be
模拟实现:由于与strcpy类似,只需要更正循环次数和补充终止结束符即可。
1.6 strncat、strncmp
解释:strncat、strncmp 与strcat、strcmp的主要区别是增加了个参数num,可以通过num来完成对特定数量字符串 的操作。
注意事项:strncat可以完成对自身的追加
补充:strcpy、strcmp、strcat 与 strcnpy、strncmp、strncat 是两种不同类型的函数。前者归类为长度不受限制的字符串函数,后者归类为长度受限制的字符串函数。
1.7 strstr
//函数定义
const char * strstr ( const char * str1, const char * str2 );
char * strstr ( char * str1, const char * str2 );
作用:查看 str1 中是否存在 str2 ,并返回寻找到的首元素的地址。
返回值:第一次找到相应字符串的首地址
解释:在一个字符串中另一个字符串是否存在,如果存在返回str2在str1中第一次出现的位置,不存在则返回空指针。
使用案例:
/* strstr example */
#include <stdio.h>
#include <string.h>
int main (){
char str[] ="This is a simple string";
char * pch;
pch = strstr (str,"simple");
if (pch != NULL)
strncpy (pch,"sample",6);
puts (str);
return 0;
}
//输出
//This is a sample string
模拟实现:其实这个函数的实现方法有很多,这里为了解释方便,便使用最暴力的方法。首先要配对字符,通过两个遍历指针来指向两个字符串。在str1中用指针遍历,当找到后通过一个临时指针匹配,如果不匹配则返回到遍历指针,继续向后寻找,直到找到为止。如果找到,直接返回str1的遍历指针,如果未找到也可以返回str1的遍历指针,因为最后也是字终止结束符。以下是代码实现:
const char* _strstr(const char* str1, const char* str2) {
//指针初始化
const char* ptr_str1 = str1;
const char* ptr_str2 = str2;
const char* ptr_temp = ptr_str1;
//循环:
//条件:str1未遍历完
//过度:ptr_str1往后移动
for (;*ptr_str1 != '\0'; ptr_str1++) {
if (*ptr_str1 == *ptr_str2) {
//将临时指针相等处
ptr_temp = ptr_str1;
//不断的比较
while(*ptr_str2 == *ptr_temp) {
//相等就继续往后比较
ptr_str2++;
ptr_temp++;
//如果str2遍历完,则返回str1的第一次相等的地址
if (*ptr_str2 == '\0')
return ptr_str1;
}
//如果匹配失败了,将ptr_str2初始化到初始位置
ptr_str2 = str2;
}
}
//匹配失败返回空指针
return NULL;
}
1.8 stroke
//函数定义
char * strtok ( char * str, const char * delimiters );
作用:根据标记拆分字符串
参数:①delimiters参数是个字符串,定义了用分隔符的字符集合; ②第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记;
返回值:根据标记拆分字符串,找到元素的首地址,如果找不到返回空指针
解释:①strtok函数找到str中的下一个标记,并将其用 ‘\0’ 结尾,返回一个指向这个标记的指针;②strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;③strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记;④如果字符串中不存在更多的标记,则返回 NULL 指针;
注意事项:①字符串必须可修改,但一般会增加一个临时拷贝来完成操作;②留意第一个参数NULL的使用,在使用案例会通过注释完成说明。
使用案例:
void test_strtok() {
//定义字符串
const char* str = "fatone@csdn.com";
//定义分隔符集合
const char* sep = ".@";
char arr[30];
char* str_temp = NULL;
//将数据拷贝一份,处理arr数组的内容
strcpy(arr, str);
//一般是通过循环完成分割
//循环初始化内容为第一次分割,第一个参数为要分隔的字符串,返回首地址
//如果返回值不为空,说明未找完,故继续分割,但第一个参数之后都要成为NULL
//找到就返回首地址,继续循环,否则退出循环
//注:每次分割完后,都会将分隔符置为'\0'
for (str_temp = strtok(arr, sep); str_temp != NULL; str_temp = strtok(NULL, sep)){
printf("%s\n", str_temp);
}
}
1.9 strerror
//函数定义
char * strerror ( int errnum );
作用:返回错误信息
参数:错误编码
返回值:错误信息字符串的首地址
解释:通过错误编码返回错误信息
注意事项:①需要包含头文件 #include <errno.h>;②返回的指针指向静态分配的字符串,该字符串不能被程序修改;③strerror产生的错误字符串可能特定于每个系统和库实现;
使用案例:
/* strerror example : error list */
#include <stdio.h>
#include <string.h>
#include <errno.h>
void test_strerror() {
//各种错误码类型
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
//示例
FILE* pFile;
pFile = fopen("unexist.ent", "r");
if (pFile == NULL)
printf("Error opening file unexist.ent: %s\n", strerror(errno));
}
二. 字符函数
2.1 字符分类函数
作用:根据字符判断类型
解释:传入字符参数后,判断是否符合条件,如果参数符合返回为真,不符合返回为假
注意事项:包含头文件 #include <ctype.h>
函数 | 作用(如果参数符合条件返回为真) |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母az或AZ |
isalnum | 字母或者数字,az,AZ,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
使用案例:
void test_ischar() {
char ch = 'A';//'0' '1' '2' '3' .... '9'
int ret = isxdigit(ch);
printf("A isxdigit? %d\n", ret);
ret = islower(ch);
printf("A islower? %d\n", ret);
}
2.2 字符转换函数
作用:根据要求,对字符进行转换
解释:传入参数后,根据需求转换字符,转换后返回
注意事项:包含头文件 #include <ctype.h>
函数 | 作用(返回转换后的结果) |
---|---|
tolower | 转换为小写 |
toupper | 转换为大写 |
使用案例:
void test_tochar() {
char ch1 = 'W';
char ch2 = 'w';
printf("%c\n", tolower(ch1));//ch+32
printf("%c\n", toupper(ch2));//ch-32
}
//输出:
//w
//W
三. 内存操作函数
3.1 memcpy
//函数定义
void * memcpy ( void * destination, const void * source, size_t num );
作用:内存块复制
返回类型:拷贝后的目的内存块的首地址
解释:函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
注意事项:①不同于strcpy,这个函数在遇到 ‘\0’ 的时候并不会停下来;②如果source和destination有任何的重叠,复制的结果都是未定义的,如果出现会有重叠的问题,最好使用将会提到的memmove函数;③要防止溢出的问题;④当num的字节数大于source时,就会复制终止结束符。
使用案例:
/* memcpy example */
#include <stdio.h>
#include <string.h>
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) );
printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
return 0;
}
//输出:person_copy: Pierre de Fermat, 46
模拟实现:在此模拟实现,在参数中发现有空指针,大家都知道空指针可以防止任何指针内容。在获得需要拷贝的地址后,我们可以对最小字节单位进行复制,只需要将空指针转换为char类型指针,再不断拷贝即可。同样需要注意返回类型,由于是返回目的地址,在我们实现过程中会发生改变,因此提前用一个临时变量将其记载,最后返回。代码实现如下:
void* _memcpy(void* destination, const void* source, size_t num) {
assert(destination);
assert(source);
//返回指针定义
void* return_ptr = destination;
//复制num个字节,循环进行num次
for (size_t i = 0; i < num; i++) {
//复制一个字节
*(char*)destination = *(char*)source;
//复制完后向后推一个字节
destination = (char*)destination + 1;
source = (char*)source + 1;
}
return return_ptr;
}
3.2 memcpy
//函数定义
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
作用:比较从ptr1和ptr2指针开始的num个字节
返回值:返回值大于0,则第一个不匹配的内存块在ptr1中的值大于ptr2中的值;返回小于0,则第一个不匹配的内存块在ptr1中的值小于ptr2中的值;返回值为0,两个内存块相等。
解释:函数从第一个字符串开始比较。如果它们相等,则继续往后比较,直到字符不同或到达一个终止的空字符为止,具体结束条件可以查看返回值的要求。
注意事项:与strcmp不同的是,该函数在找到空字符后不会停止比较。
使用案例:
/* memcpy example */
#include <stdio.h>
#include <string.h>
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) );
printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
return 0;
}
//输出:'DWgaOtP12df0' is greater than 'DWGAOTP12DF0'.
模拟实现:与strcmp相似,按照解释的内容,通过循环逐个比较内存块,如果相等则继续往后比较,直到达到num次。在此我们只需要主要对内存快点访问,通过char*指针来完成每个内存块的遍历。按以下代码逻辑,需要注意的的是循环次数num-1次即可,否则会指向后面的内存块。实现代码如下:
int _memcmp(const void* ptr1, const void* ptr2, size_t num) {
assert(ptr1);
assert(ptr2);
for (size_t i = 0; i < num -1 ; i++) {
if (*(char*)ptr1 == *(char*)ptr2) {
ptr1 = (char*)ptr1 + 1;
ptr2 = (char*)ptr2 + 1;
}
if (*(char*)ptr1 > *(char*)ptr2)
return 1;
if(*(char*)ptr1 < *(char*)ptr2)
return -1;
}
return 0;
}
3.3 memmove
//函数定义
void * memmove ( void * destination, const void * source, size_t num );
作用:移动内存块的数据,将num个字节的数据从源内存块移动到目的内存块。
返回值:移动后的目的内存块地址
解释:与memcpy相似,不过和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。memmove仿佛在内存中有一个缓冲区一样,允许重叠。
注意事项:如果源空间和目标空间出现重叠,就得使用memmove函数处理;同时也需要防止溢出;
使用案例:
/* memmove example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "memmove can be very useful......";
memmove (str+20,str+15,11);
puts (str);
return 0;
}
//输出:memmove can be very very useful.
模拟实现:memcpy之所以不能完成重叠访问,是因为重叠访问会使得原来存在的内存块被覆盖。在此为了简单实现memmove,我们使用一个较为巧妙的方法,使得在复制过程中,在未被复制之前,内存块内容不会被替换。
首先我们分析为什么会被覆盖,以将int str[30]为例,内容为字符串"abcdefghijklmno\0"。希望用memcpy函数拷贝自身将source = str,拷贝到destination = str + 3,拷贝7个字节,调用函数为:memcpy(str+3,str,7); 当拷贝第一个内存块中的内容时,将会改变后面需要改变的内容,这样就发生错误。但是如果我们从后面开始移动就不会出现相应问题。可是从后面移动也不能解决所有问题,因为,如果source = = str + 3,而destination = str 时也会出现覆盖问题,这时就需要从前开始移动,其示意图如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2LX90Yw-1663514865385)(D:/Blogs/C_C++/c–c–review/library%20_unction/memcpy_question.png)]
为此,我们只需要分情况讨论什么时候从前移动,何时从后移动即可。当发生重叠时,如果源内存块在目的内存块之后,就从前开始复制,如果源内存块在目的内存块之前,就从后开始复制。代码实现如下:
void* _memmove(void* destination, const void* source, size_t num) {
void* ret = destination;
//当目标内存块在源内存块之前
if (destination <= source) {
for (int i = 0; i < num; i++) {
*(char *)destination = *(char *)source;
destination = (char*)destination + 1;
source = (char*)source + 1;
}
}
//当目标内存块在源内存块之后
else {
//提前偏移到末尾
destination = (char*)destination + num - 1;
source = (char*)source + num -1;
for (int i = 0; i < num; i++) {
*(char*)destination = *(char*)source;
destination = (char*)destination - 1;
source = (char*)source - 1;
}
}
return ret;
}
3.4 memset
//函数定义
void * memset ( void * ptr, int value, size_t num );
作用:填充内存块
参数:ptr:指向要填充的内存块的指针;value:要设置的值,该值作为int传递,但函数使用该值的无符号字符转换填充内存块;num:填充的字节数;
返回值:返回填充后的内存块的地址
解释:将ptr指向的内存块的第一个num字节设置为指定值(解释为无符号字符)。
使用案例:
/* memset example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "almost every programmer should know memset!";
memset (str,'-',6);
puts (str);
return 0;
}
//输出:------ every programmer should know memset!
四. 方法分享
以上的函数都是笔者在cplusplus.com - The C++ Resources Network查找的,在此想为大家分享一下自己查资料的方式。在打开连接后,我们会发现其中有一个搜索引擎,我们可以通过此寻找到我们所需要的的库函数。在搜索完毕后,我会先从加粗的字体入手,这是函数的功能概述。之后才会从他的定义出发,分别查看参数与返回值,最后才会去看函数的解释。完成学习后,还需要手敲一遍示例,加深自我学习的印象。
补充:
- 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
传递,但函数使用该值的无符号字符转换填充内存块;num:填充的字节数;