引言
C语言中对字符和字符串的处理是很频繁的,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中,或者字符数组中。字符串常量适用于对其不做修改的字符串函数。
计算字符串长度
strlen
size_t strlen ( const char * str );
求出字符串str的长度
以空字符‘\0’为结束标志(不包括空字符)从起始位置开始计算直到遇到‘\0’就结束。
简单的用法:
int main(){
int len = strlen("abcdef");
printf("%d\n",len);//输出结果为:6
}
abcdef 正好六个字符,结果就是6。
假设如果是下面这种情况结果是什么呢?
int main(){
char arr[]={'a','b','c','d','e','f'};
int len2= strlen(arr);
printf("%d\n",len2);
}
这个输出结果可能是6可能是大于6的数,这个是不确定的,不同的编译器不同的操作系统可能都有随机的值。因为arr[]并没有限定它有多大,所以我当他在这个字符数组中寻找‘\0’的时候找到f后面它还是没找到会一直向后找,我们也不能确定这个内存空间后面是什么东西,所以也就不确定他会有多少的长度。所以如果想要用strlen函数 计算这样一个字符数组中字符的数量,需要我们手动加上一个‘\0’。
我们可以自己手动实现一个这样的函数,方法有很多计数器,递归,或者用指针-指针都可以,这里我们用计数器简单实现一下。
int my_Strlen(char * str){
int count=0;
assert(str!=NULL);
while (*str){
count++;
str++;
}
return count;
}
最终的效果和strlen是一样的。核心逻辑就是找到一个不为‘\0’的计数器就自增1。
还有一个地方值得注意
int main(){
if(strlen("abc")- strlen("abcdef")>0){
printf("hello");
} else{
printf("world");
}
}
这是个很简单的逻辑判断,大家想一下这里会输出hello呢还是输出world?
这里答案其实输出的是hello,为什么不是输出world呢?
注意 ,strlen的返回值是个size_t 是个自定义类型,其实我们可以将其理解为unsigned int,即
size_t ==unsigned int
所以 strlen 返回值是个无符号数,那么两个无符号数相减无论怎么减结果都是无符号数,所以这个条件判断语句永远为真。
长度不受限制的字符串函数
strcpy
字符串拷贝,将一个字符串拷贝到另一个字符串地址。
char * strcpy ( char * destination, const char * source );
将源地址的字符串拷贝到目标地址,包括终止空字符‘\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
那接下来,如果我们自己想实现一个这样的函数,应该怎么写函数体呢?
/*
* 手写strcpy
*/
char * my_strcpy(char * dest,const char *src){
assert(dest !=NULL);
assert(src !=NULL);
char *ret =dest;
while (*dest++ = *src++){
}
return ret;
}
int main(){
char arr1[]="abcdefg";
char arr2[]="hello";
my_strcpy(arr1,arr2);
printf("%s\n",arr1);
printf("%s\n",arr1);
}
这里可以看到我在手写这个拷贝字符串函数的时候,有一个点写的很精简。
while (*dest++ = *src++){
}
就是这里,但是在说这里之前我先把整体思路给大家,拷贝字符串,本质上其实是拷贝指针指向的一个一个的字符。拷贝完一个字符目的和源指针后移一位。那这其实是一个循环,循环结束的条件是当拷贝完‘\0’的时候循环结束。同时我们不应该修改源地址的字符串。
所以在看这个while循环,写的不这么简洁的话可以这样写。
while (*src !='\0'){
*dest = *src;
*src++;
*dest++;
}
*dest = *src;
这是比较容易的写法,while循环判空条件是*src不等于空字符。循环体就是对应字符赋值,结束之后我们仅仅是把空字符前面的所有字符赋值过去了,空字符还没赋值,还需要将空字符赋值过去。
但是其实这个代码就可以直接转换成我上面写的。首先先赋值在自增,自增完判断是否为空不为空继续循环。只不过是个空的循环体。这样写更加简洁点。大家可以自行试试。
输出结果
strcat
串联字符串,将源字符串追加到目的字符串后面。
char * strcat ( char * destination, const char * source );
目标中的终止空字符被源字符串第一个字符覆盖,并且最后加上一个终止空空字符‘\0’。
源和目的地址不能相同。也就是说不能给自己后面追加自己。
目标字符串的所占空间应该保证足够大能够放得下源字符串追加后的整个字符串。(不能越界)
使用如下:
/* strcat example */
#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;
}
输出结果:
那接下来我们自己实现。
char* my_strcat(char *dest ,const char *src){
assert(dest !=NULL);
assert(src !=NULL);
char * ret = dest;
while(*dest!='\0'){
dest++;
}
while (*dest++ = *src++){
;
}
return ret;
}
int main(){
char str[50]="aabb";
char str1[50]="ccdd";
char * strr= my_strcat(str,str1);
printf("%s",strr);
}
实现思路是,我们先找到目标地址的空字符'\0',然后开始拷贝字符串的操作跟strcpy是一样的,一个一个字符拷贝到目标地址,最后将空字符拷贝进去即可,这个也不难。
strcmp
比较两个字符串,函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续以下对,直到字符不同或达到终止空字符。主要包括三种情况 大于、小于、等于。
int strcmp ( const char * str1, const char * str2 );
返回值:
其实就是第一个不相同的字符,如果ptr1小于ptr2那就是<,类似可以判断出=和 >。
简单的使用一下。
int main(){
char *p1="abcde";
char *p2="abdef";
int ret = strcmp(p1,p2);
printf("%d\n",ret);
return 0;
}
输出结果
也就是说p1<p2。因为c的ASCII是小于d的。所以这个字符串p1小于字符串p2
接下来自己实现。
int my_strcmp(const char *str1, const char * str2){
assert(str1 !=NULL);
assert(str2 !=NULL);
while(*str1 !=NULL && *str2 !=NULL){
if (*str1>*str2){
return 1;
} else if (*str1<*str2){
return -1;
}else{
str1++;
str2++;
}
}
return 0;
}
int main(){
char *p1="abcde";
char *p2="abcde";
int ret = my_strcmp(p1,p2);
printf("%d\n",ret);
return 0;
}
输出结果为:
除此之外我们也可以这样写。
int my_strcmp(const char *str1, const char *str2) {
assert(str1 != NULL);
assert(str2 != NULL);
// while(*str1 !=NULL && *str2 !=NULL){
// if (*str1>*str2){
// return 1;
// } else if (*str1<*str2){
// return -1;
// }else{
// str1++;
// str2++;
// }
// }
// return 0;
//这样的话可以少判断一些。
while (*str1 == *str2) {
if (*str1=='\0')
return 0;
str1++;
str2++;
}
return (*str1 - *str2);
}
长度受限制的字符串函数
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
对比strcpy我们发现在参数部分多了一个参数,num。这个num单位是字节。
实例:
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;
}
输出结果是:
也就是说用户可以自己指定你要拷贝几个字节的内容。限制了拷贝的长度。
strncat
char * strncat ( char * destination, const char * source, size_t num );
同样也是后面多了一个参数 num,单位为字节。也就是说num限制了你要追加的字节数。如果这个num大于源字符串长度,那么拷贝到空字符位置,否则只拷贝num个字符就停止。后续附加‘\0’。
实例:
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;
}
输出结果:
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
同样也是多了一个限制字节数的参数num。
如果num小于字符串的字节数。那就只比较num个字节的字符。否则就比较全部字符串。
举个例子:
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;
}
输出结果为:
这个实例是利用num进行模糊查询。在一个字符串数组中,查询出来有关R2的字符串。
只比较前两个字节。
匹配子字符串
strstr
const char * strstr ( const char * str1, const char * str2 );
char * strstr ( char * str1, const char * str2 );
查找一个字符串中是否含有这个子字符串。如果有返回找到该子串第一个匹配的地址。
也很简单理解。给个例子:
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;
}
输出结果:
找到str中“simple”将其替换为 “sample”
这个simple就是str的子字符串。找到之后,拷贝前六位因为这个子字符串是大于6的所以后面的原样放上即可。
最后结果就是只修改了simple。
那我们自己实现:
char * my_strstr(const char * str1 ,const char * str2){
assert(str1 != NULL && str2 !=NULL);
char *s1=str1;
char *s2=str2;
char * flag=str1;
if(*str2=='\0'){
return str1;
}
while(*flag !='\0'){
s1=flag;
s2=str2;
while ((*s1 !='\0') && (*s2 != '\0') && (*s1==*s2)){
s1++;
s2++;
}
if (*s2 =='\0'){
return flag;
}
flag++;
}
return NULL;
}
int main(){
char *p1="abcdef";
char *p2="cde";
char * ret=my_strstr(p1,p2);
puts( ret);
}
输出结果:
这个实现起来相对复杂。
1.如果传入的str2是空字符串,那么直接返回str1即可
2.因为是要在str1中找,所以用while循环直到str1指向空结束循环。但是我们不需要每次都从第一个字母开始匹配,因为匹配失败的话也只需要回溯到本轮匹配的第一个字符,所以用flag值代替字符串str1。
3.接着我们需要循环判断字符串str1和str2的各自第一个字符开始比较,如果相同则各自继续指向自己的下一个字符。
(但是我们最好不要直接那传过来的str1和str2直接比较,因为我们需要返回匹配的子字符串,如果直接使用str1和str2直接向后移,我们就没办法返回子字符串了。所以需要一个标记 flag。标记我们某一轮匹配的起始位置,万一匹配成功 可以回溯。)
4.如果找到str2是‘\0’那么说明我们在str1中找到了str2这个字符串。那接下来就只需要把flag指向的字符串给返回即可。因为我们本轮匹配没有失败,所以flag并没有向后指,他还是指向本轮匹配的第一个字符。
5.如果本轮匹配失败则对flag进行指向下一个操作。
6.如果循环str1指向‘\0’,那也就意味着整个算法结束了并没有找到符合要求的子串。那就返回NULL,表示未找到。
分割字符串
strtok
char * strtok ( char * str, const char * delimiters );
目标字符串str (要被分割的字符串)
delimiters 指分隔符
函数特点:
对该函数的调用将str分解为token,这些token是由分隔符或其他任何字符分隔的连续字符序列。
在第一次调用时,函数期望一个字符串作为str的参数,其第一个字符被用作扫描token的起始位置。在后续调用中,该函数期望一个空指针,并使用最后一个token结束后的位置作为扫描的新起始位置。
为了确定token的开始和结束,函数首先从起始位置扫描分隔符中不包含的第一个字符(这成为token的开始)。然后从token的开头开始扫描分隔符中包含的第一个字符,这成为token的结束。如果找到终止空字符,扫描也会停止。
token的这一端会自动替换为空字符,token的开头由函数返回。
一旦在对strtok的调用中找到str的终止空字符,对该函数的所有后续调用(以空指针作为第一个参数)都会返回空指针。
找到最后一个token的点由下次调用时使用的函数在内部保存(不需要特定的库实现来避免数据竞争)。
简单说用法:
假设 有个1个ipv4的地址 192.168.3.6
那么我们就可以用strtok这个函数 用.作为分隔符。将这个ip地址分割为
192 168 3 6 这几个子串。
当然分隔符也可以为多个
给一个实例:(这是其中一种用法,比较复杂的用法。)
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;
}
输出结果:
将这个句子根据空格分割出来了这几部分
所以我们可以看出来这个函数似乎具有“记忆功能”。
因为我们调用一次后它自动帮我们记住了切割后的指针。所以这个函数是有个静态变量的。
也就是说运行第一次切出来-之后整个字符串是以NULL起始 用strtok(NULL,“”)这里的NULL其实是每次切割后的字符串首地址。
错误报告
strerror
char * strerror ( int errnum );
返回错误码所对应的信息。
值得注意的是:Strerror产生的错误字符串可能特定于每个系统和库实现。
接下来我们先看看这个strerror函数是什么意思。
/*
* strerror
*/
int main(){
char *str = strerror(0);
char *str1 = strerror(1);
char *str2 = strerror(2);
printf("%s\n%s\n%s\n",str,str1,str2);
return 0;
}
输出结果
也就是说用一个char类型指针接受一个strerror函数的返回值。然后再参数部分给定不同的num就会输出不同的错误类型。
也就是把错误吗翻译成错误的信息。
但是一般怎么用呢?我们会有一个变量errno放在strerror()的参数中
即
#include <errno.h>
strerror(errno)
但是需要引入头文件errno.h
errno是一个全局错误码变量 ,当C语言的库函数在执行过程中,发生了错误,就会把对应的错误码,赋值到errno中。
写一个简单的小实例:
int main(){
FILE * pf= fopen("test.txt","r");
if (pf==NULL){
printf("%s\n", strerror(errno));
} else{
printf("open file successfully");
}
return 0;
}
输出结果为
使用fopen函数打开一个test.txt的文件但是这个文件是没有的。就会报错没有文件或目录。
错误码有很多
例如:
/*
* Error codes
*/
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* Input/output error */
#define ENXIO 6 /* Device not configured */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file descriptor */
#define ECHILD 10 /* No child processes */
#define EDEADLK 11 /* Resource deadlock avoided */
/* 11 was EAGAIN */
#define ENOMEM 12 /* Cannot allocate memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#if __DARWIN_C_LEVEL >= __DARWIN_C_FULL
#define ENOTBLK 15 /* Block device required */
#endif
#define EBUSY 16 /* Device / Resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* Operation not supported by device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* Too many open files in system */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Inappropriate ioctl for device */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
这只是一部分。我们可以在errno.h中看到。
字符分类函数
属于ctype.h
分类函数
函数 | 如果其参数符合条件就反回真 | |
iscntrl | 任何控制字符 | |
isspace | 空白字符:空格‘ ’换页‘\f’回车‘\r’等 | |
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 | 任何可打印字符,包括图形字符和空白字符 |
这里好理解,简单给个例子:
/**
* 字符分类函数
*/
int main(){
char ch = 'w';
printf("%d",islower(ch));
return 0;
}
输出:
也就是说如果这个字符是小写字符返回非零值 不是小写字母则返回0,其他也是相同的用法,读者可以自行尝试。
转换函数
tolower | 转小写 | |
toupper | 转大写 |
/**
*大小写转换函数
* @return
*/
int main(){
char ch= tolower('q');
putchar(ch);
char ch1= tolower('Q');
putchar(ch1);
char ch2= toupper('Q');
putchar(ch2);
char ch3= toupper('q');
putchar(ch3);
return 0;
}
输出结果
如果本身就是小写那么 tolower函数保持字符不变
如果本身是大写tolower函数将字符转换为小写。toupper函数同理。
内存函数
memcpy
在讨论memcpy之前我们回顾上面也有一种拷贝函数就是strcpy或者strncpy。那为什么已经有了这种拷贝函数,还有再出一个内存拷贝呢。这里我们需要明确的是:
字符串拷贝函数strcpy或strncpy其实是从起始地址的第一个字节开始拷贝,遇到空字符‘\0’就停止了,而内存中存储‘\0’是通过00来存储的一旦整个内存中有一个字节是00就会拷贝失败。就会拷贝停止。所以 字符串拷贝在这里是有局限性的。所以就有了memcpy这个函数。
举个例子
int main(){
int arr1[]={1,2,3,4,5};
int arr2[5]={0};
strcpy(arr2,arr1);
for (int i = 0; i < 5; ++i) {
printf("%d",arr2[i]);
}
return 0;
}
我们期望它能够将arr1中的内容拷贝到arr2中去,但是输出结果为:
之所以会出现这种原因是因为:
我们将arr1在内存中的存储画出来
然后观察到拷贝到红色箭头这里的时候出现了00,也就是‘\0’
那么也就触发了字符串拷贝结束的标志所以其实拷贝到这里就停止了。所以并不能实现我们所想的拷贝整个数组。
那memcpy其实就能解决这个问题。
void * memcpy ( void * destination, const void * source, size_t num );
memcpy这个拷贝函数实际上是从source的位置开始向后拷贝num个字节到destination的位置
即使遇到‘\0’也不会停下来。如果源和目的又任何的重叠,复制的结果都是为定义的。
那用这个函数对上面那个数组进行拷贝如下
int main(){
int arr1[]={1,2,3,4,5};
int arr2[5]={0};
// strcpy(arr2,arr1);
memcpy(arr2,arr1,sizeof(arr1));
for (int i = 0; i < 5; ++i) {
printf("%d",arr2[i]);
}
return 0;
}
输出结果:
这样就实现了对arr1这个数组的拷贝
当然这个是int类型数组的拷贝,memcpy他的参数定义是void类型也就意味着什么样类型的数据都可以被拷贝。我们可以自行尝试一下拷贝其他类型。
接下来我们自己模拟实现一个内存拷贝函数。
void* my_memcpy(void* dest,const void* src,size_t num){
assert(dest!=NULL);
assert(src!=NULL);
//在开始之前我们需要将地址记录一份以便后续return返回
void* ret=dest;
//执行循环拷贝num次
char* p1=dest;
char* p2=src;
while (num--){
//第一步void类型是无法解引用,要先解决这个问题
//我们把dest强转为 char*类型
*p1=*p2;
//每次 源和目的都向后移动
++p1;
++p2;
}
return ret;
}
int main(){
int arr1[]={1,2,3,4,5};
int arr2[5]={0};
// strcpy(arr2,arr1);
my_memcpy(arr2,arr1,sizeof(arr1));
for (int i = 0; i < sizeof (arr1)/4; ++i) {
printf("%d",arr2[i]);
}
}
就能够实现memcpy同样的效果。
但是注意这个函数不能够将内存中的一部分拷贝到同一个内存中的另一个地址
比如1 2 3 4 5 6 7 想把4 5 6 拷贝到1 2 3 的位置上是会出错的,不能这样用。
那如果想要实现这种重叠拷贝怎么做呢,那就涉及到下一个要讨论的内存函数 memmove了。
memmove
void * memmove ( void * destination, const void * source, size_t num );
在C语言标准中:
memcpy只要处理不重叠的内存拷贝即可
memmove处理重叠内存的拷贝
但是实际上,memcpy也能够处理重叠的内存拷贝。(属于超额完成任务了)
所以如果说在C语言中memcpy和memmove实现的效果可以说是一样的。
但如果说我们想要自己实现这样一个可重叠的内存拷贝应该怎么做?
思路是这样:首先要分情况,
1.如果要拷贝的src地址是位于dest的地址之后,那么就从前往后开始拷贝
其中橙色的三个数字 3 4 5是要拷贝的。然后紫色方框的1 2 3 是我们要拷贝到的位置,这种情况我们要从前开始拷贝,也就是将3拷贝到dest的第一个位置,将4拷贝到第二个位置,将5拷贝到第三个位置。这样不会出现异常情况。
2.如果要拷贝的src地址是位于dest的地址之前,那么就从后往前开始拷贝。
这次是要把橙色src三个数组拷贝到dest紫色方框中去,如果这个时候还从前往后拷贝那么很容易发现src中数字4 和5都会被覆盖掉。如果不想要发生这种情况,就要从后往前拷贝。就把5放在dest的最后一个位置上,把4放在倒数第二个位置上,把3放在dest第一个位置上。这样就不会出现原数据被覆盖了。
所以这里要先比较src和dest的位置。如果dest<src 那就从前往后拷贝,如果dest>src那就从后往前拷贝。
按照如上思路我们自己写出代码
void* my_memmove(void* dest,const void* src,size_t num){
assert(dest!=NULL);
assert(src!=NULL);
//在开始之前我们需要将地址记录一份以便后续return返回
void* ret=dest;
char *p1=dest;
char *p2=src;
//分情况 分为两种
// 1.从前往后拷贝
if(src>dest){
while (num--){
//第一步void类型是无法解引用,要先解决这个问题
//我们把dest强转为 char*类型
*p1=*p2;
//每次 源和目的都向后移动
++p1;
++p2;
}
}else{
// 2.从后往前拷贝
while (num--){
*(p1+num)=*(p2+num);
}
}
return ret;
}
从前向后拷贝跟之前memcpy的核心逻辑是一样的也就是13-19行。主要是从后往前拷贝,这里我们以
int main(){
int arr3[]={1,2,3,4,5,6,7,8,9,10};
my_memmove(arr3+2,arr3,20);
};
来分析 将arr3中的从3开始的五个数字 int类型一个数字占据4个字节 20/4就是5个数移动到起始位置,也就是覆盖掉1和2。
这是arr3
以及arr3的内存视图(绿色部分)
从内存中我们可以看出来实际上每个数组只占用了其四个字节的第一个字节。
然后试运行完毕的arr3
以及对应的内存视图(绿色部分)
其实执行的过程就是如下所示
把1,2,3,4,5 的位置整体往后移动两格覆盖掉6 和7,这里分析清楚之后我们从后往前一个一个拷贝其实难度就是在于怎么通过dest 和src分别拿到他们个字最后一个数字的起始位置,我们这里给了一个num也就是20个字节
从1到5是20个字节。但是如果直接将地址中加20 那么会跳过数字5,会拿到内存中05后面的00的地址
所以这里我们要先让num自减,也就是让dest 和src分别+19即可 然后将src的值肤质给dest
也就是
*(p1+num)=*(p2+num);
然后while循环的值会依次减少,那么每次都是从后往前拷贝1个字节,这样就实现了可重叠的内存拷贝。
memcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
英文详解如下
但其实这个函数本质上是比较内存中存的东西的大小。举个例子
int main(){
int arr1[]={1,2,3,4,5};
int arr2[]={1,2,5,4,3};
int ret=memcmp(arr1,arr2,8);
printf("%d",ret);//0
}
比较arr1和arr2的前八个字节我们知道二者是相同的,其输出结果为0
int main(){
int arr1[]={1,2,3,4,5};
int arr2[]={1,2,5,4,3};
int ret=memcmp(arr1,arr2,9);
printf("%d",ret);//-2
}
这里比较前9个字节,明显我们可以知道arr2是大于arr1的,所以比较这里arr1<arr2输出结果为-2
这个很简单会用即可。
memset
void * memset ( void * ptr, int value, size_t num );
英文解释
内存设置
就是给内存中填充数据,举个例子如下。
int main(){
char arr[10]="";
memset(arr,'&',10);
return 0;
}
同时看内存视图中
全为0接下来,执行memset函数
可以看到这块内存中被我们填充了符号‘&’,其ascii值为26。
这个函数第一个参数为内存地址,第二个为要填充的内容,第三个为要填充的字节数量。
那假设我们在执行一次这个函数将前五个字节改为符号‘#’,内存中会是如何呢?
int main(){
char arr[10]="";
memset(arr,'&',10);
memset(arr,'#',5);
return 0;
}
可以看到前五个字节已经被改为‘#’ ascii值为23。