- 文章主题:字符、字符串与内存函数🌏
- 所属专栏:深入理解C语言📔
- 作者简介:更新有关深入理解C语言知识的博主一枚,记录分享自己对C语言的深入解读。😆
- 个人主页:[₽]的个人主页🏄🌊
前言
字符函数属于库文件ctype.h
,而字符串与内存函数属于库文件string.h
,那么它们分别具有什么样的功能呢?我们又能否在C语言中对其进行模拟呢?下面是我有关这些函数的介绍以及对其中的一些函数进行的模拟。
函数介绍
一. 字符函数
1. 字符分类函数
函数声明 | 返回真的参数条件 |
---|---|
int iscntrl ( int c ); | 任何控制字符 |
int isspace ( int c ); | ’ ‘(空白字符/空格), ‘\f’(换页), ‘\n’(换行), ‘\t’(制表符), ‘\v’(垂直制表符) , ‘\r’(回车) |
int isdigit ( int c ); | 十进制数字 ‘0’-‘9’ |
int isxdigit ( int c ); | 十六进制数字,包括所有十进制数字,小写字母 ‘a’-‘f’,大写字母 ‘A’-‘F’ |
int islower ( int c ); | 小写字母 ‘a’-‘z’ |
int isupper ( int c ); | 大写字母 ‘A’-‘Z’ |
int isalpha ( int c ); | 字母 ‘A’-‘Z’ ‘a’-‘z’ |
int isalnum ( int c ); | 字母 ‘A’-‘Z’ ‘a’-‘z’ ‘0’-‘9’ |
int ispunct ( int c ); | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
int isgraph ( int c ); | 任何图形字符 |
int isprint ( int c ); | 任何可打印字符,包括图形字符和空白字符 |
2. 字符转换函数
函数 | 效果 |
---|---|
int tolower ( int c ); | 大写字母转成小写字母返回,小写字母仍以小写字母返回 |
int toupper ( int c ); | 小写字母转成大写字母返回,大写字母仍以大写字母返回 |
二. 字符串函数
1. 求字符串长度
strlen
size_t strlen ( const void* str );
- 字符串以
'\0'
作为结束标志,strlen
函数返回的是在字符串中'\0'
前面出现的字符个数(不包含'\0'
)。 - 参数指向的字符串必须要以
'\0'
结束。 - 注意函数的返回值为size_t,是无符号的。(
易错
) - 学会strlen函数的模拟实现。
例:
/* 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.
2. 长度不受限制的字符串函数
strcpy
char* strlen ( char* destination, const char* source );
-
将
source
指向的 C 字符串复制到destination
指向的数组中,包括'\0'
(并在该点处停止)。 -
为避免溢出,
destination
指向的数组的大小应足够长,以包含与source
相同的 C 字符串(包括'\0'
),并且不应在内存中与source
重叠。
例:
/* 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
strcat
char* strcat ( char* destination, const char* source );
- 将
source
字符串的内容衔接到destination
字符串之后。destination 中的'\0'
被 source 的第一个字符覆盖,并且'\0'
会包含在由destination
中两者的串联形成的新字符串的末尾。
例:
/* 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;
}
输出:
these strings are concatenated.
strcmp
int strcmp ( const char* str1, const char* str2 );
-
将 C 字符串 str1 与 C 字符串 str2 进行比较。
-
此函数开始相同位置对应比较每个字符串的字符。如果它们彼此相等,则继续往下比较,直到字符不同或达到
'\0'
为止。
例:
/* strcmp example */
#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!
3. 长度受限制的字符串函数
strncpy
char* strncpy ( char* destination, const char* source, size_t num );
-
将
source
的前 num 个字符复制到destination
。如果在复制 num 个字符之前找到source
C 字符串的末尾(由'\0'
表示),则destination
将用0(即0所代表的'\0'
)填充,直到总共写入 num 个字符。 -
如果
source
的长度大于 num,则不会在目标末尾隐式追加'\0'
。因此,在这种情况下,destination
不一定能被视为以'\0'
结尾的 C 字符串(这样读取它会溢出)。 -
destination
和source
不得重叠(重叠时更安全的替代方案为Memmove)。
例:
/* 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
strncat
char* strncat ( char* destination, const char* source, size_t num );
-
将
source
的前 num 个字符衔接到destination
之后,外加一个'\0'
。 -
如果
source
中 C 字符串的长度小于 num,则仅复制'\0'
之前的内容。
例:
/* strncat example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[20];
char str2[20];
strcpy (str1,"To be ");
strcpy (str2,"or not to be");
strncat (str1, str2, 6);
puts (str1);
return 0;
}
输出:
To be or not
strncmp
int strncmp ( const char* str1, const char* str2, size_t num );
- 将 C 字符串 str1 的字符数与 C 字符串 str2 的字符数进行比较。
- 此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续往下比较,直到字符不同或达到
'\0'
,或直到两个字符串中的num个字符相同。
例:
/* strncmp example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[][5] = { "R2D2" , "C3PO" , "R2A6" };
int n;
puts ("Looking for R2 astromech droids...");
for (n=0 ; n<3 ; n++)
if (strncmp (str[n],"R2xx",2) == 0)
{
printf ("found %s\n",str[n]);
}
return 0;
}
输出:
Looking for R2 astromech droids...
found R2D2
found R2A6
4. 其他
strstr
const char* strstr ( const char* str1, const char* str2 );
-
返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回一个
NULL
指针。 -
匹配过程不包括
'\0'
,但匹配会停止到此为止。
例:
/* 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
strtok
char* strtok ( char* str, const char* delimiters );
-
对此函数的一系列调用将 str 拆分为标记,这些标记是由分隔符中的任何字符分隔的连续字符序列。
-
在第一次调用时,该函数需要一个 C 字符串作为 str 的参数,str 的第一个字符用作扫描
delimiters
的起始位置。在后续调用中,该函数需要一个NULL
指针,并使用最后一个标记末尾之后的位置作为扫描的新起始位置。 -
为了确定标记的开头和结尾,该函数首先从起始位置扫描
delimiters
中未包含的第一个字符(该字符成为标记的开头)。然后从标记的开头开始扫描分隔符中包含的第一个字符,该字符成为标记的末尾。如果找到'\0'
,扫描也会停止。 -
delimiters
的这一末尾会自动替换为'\0'
,并且delimiters
的开头由函数返回。 -
在对 strtok 的调用中找到 str 的
'\0'
后,对此函数的所有后续调用(以NULL
指针作为第一个参数)将返回 null 指针。 -
找到最后一个
delimiters
的点由要在下一次调用中使用的函数在内部保存(不需要特定的库实现来避免数据争用)。
例:
/* strtok example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="- This, a sample string.";
char * pch;
printf ("Splitting string \"%s\" into tokens:\n",str);
pch = strtok (str," ,.-");
while (pch != NULL)
{
printf ("%s\n",pch);
pch = strtok (NULL, " ,.-");
}
return 0;
}
输出:
Splitting string "- This, a sample string." into tokens:
This
a
sample
string
strerror
char* strerror ( int errnum );
-
解释 errnum 的值,生成一个字符串,其中包含一条描述错误条件的消息,就像由库的函数设置为 errno 一样。
-
返回的指针指向静态分配的字符串,程序不得修改该字符串。对此函数的进一步调用可能会覆盖其内容(不需要特定的库实现来避免数据争用)。
-
strerror 生成的错误字符串可能特定于每个系统和库实现。
例:
/* strerror example : error list */
#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));
return 0;
}
输出:
Error opening file unexist.ent: No such file or directory
perror(与字符串函数strerror效果相似的打印函数,属于<stdio.h>(标准输入输出库)
)
void perror ( const char* str );
-
将 errno 的值解释为错误消息,并将其打印到 stderr(标准错误输出流,通常是控制台),可以选择在其前面加上 str 中指定的自定义消息。
-
errno 是一个整数变量,其值描述调用库函数生成的错误条件或诊断信息(C 标准库的任何函数都可以为 errno 设置值,即使此引用中未明确指定,即使没有发生错误),有关详细信息,请参阅 errno。
-
perror 生成的错误消息与平台相关。
-
如果参数 str 不是 null 指针,则打印 str,后跟冒号 : 和空格。然后,无论 str 是否为 null 指针,都会打印生成的错误说明,后跟换行符
'\n'
。 -
perror 应该在产生错误后立即调用,否则可能会被调用其他函数覆盖。
例:
/* perror example */
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile=fopen ("unexist.ent","rb");
if (pFile==NULL)
perror ("The following error occurred");
else
fclose (pFile);
return 0;
}
输出:
The following error occurred: No such file or directory
三. 内存函数
memcpy
void* memcpy ( void* destination, const void* source, size_t num );
-
将 num 字节的值从
source
指向的位置直接复制到destination
指向的内存块。 -
source
指针和destination
指针指向的对象的基础类型与此函数无关;结果是数据的二进制副本。 -
该函数不检查
source
中的任何'\0'
——它始终准确复制 num 个字节。 -
为避免溢出,
destination
参数和source
参数指向的数组的大小应至少为 num 个字节,并且不应重叠(对于重叠的内存块,memmove 是一种更安全的方法)
例:
/* 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
memmove
void* memmove ( void* destination, const void* source, size_t num );
-
将 num 字节的值从
source
指向的位置复制到destination
指向的内存块。复制就像使用中间缓冲区一样进行,允许destination
和source
重叠。 -
source
指针和destination
指针所指向的对象的基础类型与此函数无关;结果是数据的二进制副本。 -
该函数不检查
source
中的任何'\0'
——它始终准确复制 num 个字节。 -
为避免溢出,
destination
参数和source
参数所指向的数组的大小应至少为 num 个字节。
例:
/* 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.
memcmp
int memcmp ( const void* ptr1, const void* ptr2, size_t num );
- 将 ptr1 指向的内存块的第一个字节与 ptr2 指向的第一个 num 字节进行比较,如果它们都匹配,则返回零,或者返回一个不同于零的值,如果它们不匹配,则表示哪个值更大。 请注意,与 strcmp 不同,该函数在找到 null 字符后不会停止比较。
例:
/* memcmp example */
#include <stdio.h>
#include <string.h>
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;
}
输出:
'DWgaOtP12df0' is greater than 'DWGAOTP12DF0'.
函数模拟实现
字符串函数
my_strlen
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str);
int i = 0;
while (str[i])//计数器,到'\0'时退出循环,停止计数
i++;
return i;
}
int main()
{
char str[100] = { 0 };
scanf("%s", str);
printf("lens: %zd", my_strlen(str));
return 0;
}
my_strcpy
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
int i = 0;
while (dest[i] = src[i++])//复制到'\0'时,条件判断为0,跳出while循环,复制动作停止
;
return dest;
}
int main()
{
char str1[100] = { 0 }, str2[100] = { 0 };
scanf("%s%s", str1, str2);
printf("cpyed: %s", my_strcpy(str1, str2));
return 0;
}
my_strcmp
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
int i = 0;
while (str1[i] || str2[i])//c1=c2且不是均为'\0'时进入循环
{
if (str1[i] - str2[i])//如果对应字符不相同的话,则返回它们的差值
//(可保证c1>c2时返回>0;c1<c2时返回<0)
return str1[i] - str2[i];
i++;
}
return 0;//c1=c2且均为'\0'则终止循环,两字符串相同返回0
}
int main()
{
char str1[100] = { 0 }, str2[100] = { 0 };
scanf("%s%s", str1, str2);
printf("%d", my_strcmp(str1, str2));
return 0;
}
my_strcat
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* cat = dest + strlen(dest), i = 0;//定义及赋值衔接处的地址变量
while (cat[i] = src[i++])//如果没到'\0'就会继续赋值
;
return dest;//返回赋值后的字符串地址
}
int main()
{
char str[20] = { 0 };
strcpy(str, "Hello ");
my_strcat(str, "bit!");
puts(str);
return 0;
}
my_strstr
#include <stdio.h>
#include <assert.h>
const char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (str2[0] == '\0')
return str1;
const char* cp = str1;//记录原字符串中的子字符串比较位置
const char* s1;//str1单位字符比较指针
const char* s2;//str2单位字符比较指针
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1&&*s2&&*s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return cp;
cp++;
}
return NULL;
}
int main()
{
char str1[] = "abbbcdef", str2[] = "bcd";
if (my_strstr(str1, str2) == NULL)
printf("NULL");
else
printf("%s", my_strstr(str1, str2));
return 0;
}
my_strncpy
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
int lens = strlen(src);
for (int i = 0; i < num; i++)
{
if (i < lens)
dest[i] = src[i];
else//num大于lens时,在dest中填充0
dest[i] = 0;
}
return dest;
}
int main()
{
char str1[] = "abcdefghijk", str2[] = "Wang";
printf("%s", strncpy(str1, str2, 7));
return 0;
}
my_strncat
#include <stdio.h>
#include <assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest && src);
int lens = strlen(dest);
char* pc = dest + lens;
int i = 0;
for (i = 0; i < num; i++)
{
if (i == lens)//检测到赋值到'\0'时, 立马停止赋值
break;
pc[i] = src[i];
}
pc[i] = '\0';//衔接完之后的字符串追加一个'\0'作为字符串的结尾
return dest;
}
int main()
{
char str1[10] = "abc", str2[] = "zzzzz";
printf("%s", my_strncat(str1, str2, 4));
return 0;
}
内存函数
my_memmove
#include <stdio.h>
#include <assert.h>
void* memmove(void* dest, const void* src, size_t num)
{
if (dest < src)//前->后赋值
{
for (int i = 0; i < num; i++)
((char*)dest)[i] = ((const char*)src)[i];
}
else//后->前赋值
{
for (int i = num - 1; i >= 0; i--)
((char*)dest)[i] = ((const char*)src)[i];
}
return dest;
}
int main()
{
char str[] = "abcdef";
printf("%s", memmove(str, str + 3, 3));
return 0;
}
my_memcpy
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
char* cp = dest;
for (int i = 0; i < num; i++)
*(cp + i) = *(((const char*)src)+i);//通过强转为char类型来进行以字节为单位的循环复制内存结构
return dest;
}
struct
{
char name[15];
int age;
}person, person_copy;
int main()
{
char myname[] = "Petter";
person.age = 19;
my_memcpy(person.name, myname, sizeof(myname));
my_memcpy(&person_copy, &person, sizeof(person));
printf("person_copy: %s, %d\n", person_copy.name, person_copy.age);
return 0;
}
结语
以上就是我对字符、字符串与内存函数的介绍以及对其中一些的函数模拟,希望你能够对这些函数有更加深刻的理解。设计逻辑上有所不足的欢迎评论区积极进行指正,让我们共同成长,一起进步!