函数介绍:
1. 求字符串长度
1.1 strlen
2. 长度不受限制的字符串函数
2.1 strcpy
2.2 strcat
2.3 strcmp
3. 长度受限制的字符串函数
3.1 strncpy
3.2 strncat
3.3 strncmp
4. 字符串查找
4.1 strstr
4.2 strtok
5. 错误信息报告
5.1 streror
6. 字符操作
6.1 字符分类函数
6.2 字符转换函数——tolower、toupper
7. 内存操作函数
7.1 memcpy
7.2 memmove
7.3 memset
7.4 memcmp
C语言中对字符和字符串有很多处理,但是C语言本身没有字符串类型,字符串通常放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数。
——求字符串长度
size_t strlen ( const char * str );
#include
int main()
{
int len = strlen("abcdef");
printf("%d\n", len);
return 0;
}//6
之前写strlen()函数的模拟实现中有3种方法:
1、计数器的方法;
2、递归的方法;
3、指针-指针的方法
#include
#include
//因为求的是字符串长度,不会改变str所指向字符串的内容,所以加const修饰,
//即使str所指向的内容想要被修改也没有机会了。
int my_strlen(const char* str)
{
//断言:保证指针的有效性:
assert(str);
//用计数器的方法实现:
int count = 0;
while (*str != '\0')//或while(*str)都可以
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
//表面传的是字符串,实际传的是字符串首字符地址。
printf("%d\n", len);
return 0;
}//6
sizeof——是操作符,计算大小。sizeof计算的结果返回的类型是size_t;size_t 是专门为sizeof的返回值设计的。
size_t——其实是unsigned int类型,只表示正数。
#include
#include
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
{
printf(">");
}
else
{
printf("<=");
}
return 0;
}//>
#include
#include
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1\n");
}
else
{
printf("str1str1
strlen()函数返回的是无符号数,两个无符号的数相减得到的也是无符号的数,上述代码中会把-3当做无符号的数处理,是一个非常大的正数而会引入bug。若strlen()返回值设计成int,这类问题就不会存在。
strlen()的返回值可以写成int类型,size_t类型或unsigned_int类型都可以。
上述代码强制类型转换为int可以得出“<=”:
#include
#include
int main()
{
if ((int)strlen("abc") - (int)strlen("abcdef") > 0)
{
printf(">");
}
else
{
printf("<=");
}
return 0;
}//<=
或:
#include
#include
int main()
{
if (strlen("abc") > strlen("abcdef"))
{
printf(">");
}
else
{
printf("<=");
}
return 0;
}//<=
注意:
使用库函数时需要包含头文件,不包含会有bug。
使用strlen()函数时:
字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包
含 '\0' )。
参数指向的字符串必须要以 '\0' 结束。
函数的返回值为size_t,是无符号的。
模拟实现strlen:三种方式:
方式1:计数器方式:
#include
int my_strlen(const char* str)
{
int count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdefg";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}//7
方式2:不创建临时变量计数器,递归实现
#include
int my_strlen(const char* str)
{
if (*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
int main()
{
char arr[] = "abcdefg";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}//7
方式3:指针-指针
#include
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
{
p++;
}
return p - s;
}
int main()
{
char arr[] = "abcdefg";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}//7
——strcpy()函数用来拷贝字符串,会把源字符串内容拷贝到目标空间中,\0也会被拷贝过去。
char* strcpy(char * destination, const char * source );
/把arr1的内容拷贝放到arr2中
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}//abcdef
注意:在使用strcpy()函数时:
源字符串必须以 '\0' 结束。
会将源字符串中的 '\0' 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串,防止造成越界访问而使程序崩溃。
目标空间必须可变。
#include
#include
int main()
{
char arr1[] = { 'a','b','c','d','e','f' };
char arr2[20] = "XXXXXXXX";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}//程序会崩溃,拷贝内容混乱
#include
#include
int main()
{
char arr1[] = { 'a','b','c','d','e','f','\0' };
char arr2[20] = "XXXXXXXX";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}//abcdef
#include
#include
int main()
{
char arr1[] = { 'a','b','c','d','e','f','\0' };
const char* p = "xxxxxxxxxx";
strcpy(p, arr1);
printf("%s\n", p);
return 0;
}//程序崩溃,位置发生访问冲突
strcpy()函数可以将源字符串内容拷贝到已有数据的数组中。
注意:遇到\0的时候,打印也结束了。所以即使目标空间在\0(这里的\0是从源数据中拷贝的)后面还有数据也不能打印了。
strcpy()函数的模拟实现:
#include
#include
//把源头数据拷贝到目标空间中,整个过程中,源数据是不需要发生变化的,目标空间需要变化
//所以在源数据前面可以用const修饰限定,保护源头数据。
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*src)
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//拷贝\0
return ret;
}
int main()
{
char arr1[] = { 'a','b','c','d','e','f','\0' };
char arr2[] = "xxxxxxxx";
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}//abcdef
优化代码:
#include
#include
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = { 'a','b','c','d','e','f','\0' };
char arr2[20] = "xxxxxxxx";
printf("%s\n", my_strcpy(arr2, arr1));
return 0;
}//abcdef
使用指针的时候,注意:
1、指针不知道赋什么值,就赋给NULL;
2、指针使用完之后,赋值NULL。
3、NULL不能使用。
所以使用指针时,不是空指针就是有效的指针,只有这两种状态才能避免野指针等问题的出现。
assert在断言时只能判断是不是空指针,并不能判断是不是野指针。
链式访问:把一个函数的返回值作为另一个函数的参数。
函数返回值写void,就不能链式访问了,不能把该函数的返回值作为其他函数的参数。
尽量不要返回局部变量的地址,可以返回局部变量。
#include
int* test()
{
int a = 10;//0x0012ff40
return &a;
}
int main()
{
//p里面放的是:0x0012ff40
int* p = test();
//此时p指向的空间已经还给操作系统,而这个指针p还记住这个空间,
//等去再访问时就会非法访问内存
//是空间销毁不是地址销毁,指针所指向的空间不属于这个程序了就不能使用了
return 0;
}
——把源字符串内容追加到目标字符串的后面。
char * strcat ( char * destination, const char * source );
strcpy()函数是拷贝,覆盖原来的数据。
strcat()函数是把源字符串内容追加到目标字符串的后面。
#include
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
//把arr2中的数据追加到arr1中
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//helloworld
原理:arr2找到arr1的‘\0’,把‘\0’覆盖掉然后拷贝arr2的内容。这里arr2是源字符串,‘\0’不会拷贝。
#include
#include
int main()
{
char arr1[30] = "hello";
char arr2[] = { 'w','o','r','l','d' };
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//源字符串并没有以'\0'结束,程序崩溃
#include
#include
int main()
{
char arr1[30] = {'h','e','l','l','o'};
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//helloworld
#include
#include
int main()
{
char arr1[] = "hello";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//缓冲区溢出
#include
#include
int main()
{
const char arr1[30] = "hello";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//会报警告
注意:
源字符串必须以 '\0' 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
模拟实现strcpy()函数:
#include
#include
char* my_strcat(char* dest, const char* src)
{
//把源头数据追加到目标空间中:
//因为这两个指针指向的内容都会进行访问,
//所以需要保证指针都为非空指针
assert(dest && src);
//1、找到目标空间中的\0——遍历字符串
char* ret = dest;
while (*dest)
{
dest++;
}
//2、追加内容到目标空间
//拷贝
while (*dest++ = *src++)
{
;
}
return ret;//返回的是目标空间的起始地址
}
int main()
{
char arr1[30] = "hello";//目标
char arr2[] = "world";//源
printf("%s\n", my_strcat(arr1, arr2));//arr2是源字符串
return 0;
}//helloworld
注意:
#include
#include
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest++)
{
/*dest++*/;
}
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}//错误,hello
此时word在'\0'的后面追加过去的,追加进去了但是因为放在了\0的后面,所以在打印的时候看到'\0'就不再打印了。
#include
#include
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*++dest)
{
/*dest++*/;
}
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}//helloworld
虽然这里此时打印没问题,但是当arr1是空字符串时就会出现Bug,*++dest是直接就把空字符串里面的‘\0’跳过了,又追加到'\0'的后面了。
代码不能只在一个例子中试,需要在各个场景中实现。
库函数中提供的参考代码:
char * __cdecl strcat (
char * dst,
const char * src
)
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while((*cp++ = *src++) != '\0') ; /* Copy src to end of dst */
return( dst ); /* return dst */
}
char * __cdecl strcpy(char * dst, const char * src)
{
char * cp = dst;
while((*cp++ = *src++) != '\0')
; /* Copy src over dst */
return( dst );
}
即由此写代码:
#include
#include
char* my_strcat(char* dst, char* src)
{
assert(dst && src);
char* ret = dst;
while (*ret)
{
ret++;
}
while ((*ret ++ = *src++)!='\0')
{
;
}
return dst;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}//helloworld
#include
#include
char* my_strcat(char* dst, char* src)
{
assert(dst && src);
char* ret = dst;
while (*dst)
{
dst++;
}
while (*src)
{
*dst = *src;
dst++;
src++;
}
*dst = *src;
return ret;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
printf("%s\n", my_strcat(arr1, arr2));
return 0;
}//helloworld
——用来比较两个字符串。比较的是对应位置上的字符的大小——比较的是字符的ASCII码值。a、b、c、d……按照顺序ASCII码值依次增大。
int strcmp ( const char * str1, const char * str2 );
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字;
第一个字符串等于第二个字符串,则返回0;
第一个字符串小于第二个字符串,则返回小于0的数字。
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[] = "bbq";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}//-1
比的不是长度。
"abcdef";
"bbq";
这两个字符串对应的第一个字符比,a
如果是:
"abcdef";
"abq";
这两个字符串对应的第一个字符相等,就比较第二个对应的字符,第二个字符相等就比较第三对……直到比较双方的‘\0’。
#include
#include
int main()
{
char arr1[] = "bbq";
char arr2[] = "bbq";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}//0
#include
#include
int main()
{
char arr1[] = "qbq";
char arr2[] = "bbq";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}//1
模拟实现strcmp()函数:
#include
#include
int my_strcmp(const char* str1, const char* str2)//因为只访问内容不会修改这两个字符串内容所以可以加const保护
{
//因为后面有对这两个指针解引用操作,所以需要断言是否是空指针
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abcd";
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}//-1
‘\0’的ASCII码值是0。
优化代码:
#include
#include
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abcd";
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}//-100
注意:在VS编译器下返回的是1、-1、0这三个值;在不同编译器下实现返回的值不同。
#include
#include
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abcd";
int ret = my_strcmp(arr1, arr2);
if (ret == 1)
{
printf("arr1>arr2");
}
else if (ret == -1)
{
printf("arr1
所以这个代码是错误的。不总是返回的是1、-1和0,更改:
#include
#include
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abcd";
int ret = my_strcmp(arr1, arr2);
if (ret > 0)
{
printf("arr1>arr2");
}
else if (ret < 0)
{
printf("arr1
注意:两个字符串是不能相减的,两个字符串不能用等号来判断是否相等,更不能相减。
if ("abq" == "abc")
错误代码,"abq"表达式产生的是它的a的地址(首字符的地址);
"abc"表达式产生的是它的a的地址;
这两个地址一定不相等,所以不能用等号比。
if("abc" - "abq")
意思是这两个地址相减比较的是地址,就不是比较的两个字符串的内容是否相等了;
所以两个字符串不能相加,不能相减。
如果两个字符串是常量字符串,那么二者的地址是相同的;
但是如果是两个字符串数组,即使字符串内容相同地址也不会相同,因为是两个完全不同的空间。
总结:
strcpy()函数在拷贝的时候,直到把源字符串中的\0拷贝完之后才停止拷贝,即拷贝到\0。
strcat()函数在追加的时候,也只在乎\0的问题,追加到\0。
strcmp()函数比较字符串的时候不相等就结束,相等就比较到\0。
这三个函数在拷贝的时候并没有考虑拷贝的长度,一直到\0,所以这三个函数均是长度不受限制的字符串函数,与字符串的长度没有关系。这三者头文件均是
——拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
char * strncpy ( char * destination, const char * source, size_t num );
#include
#include
int main()
{
char arr1[] = "xxxxxxxxxxxxx";
char arr2[] = "hello world";
//若把arr2中的hello这5个字符拷贝到arr1中:
strncpy(arr1, arr2, 5);//只是把前5个hello拷贝过去
printf("%s\n", arr1);
return 0;
}//helloxxxxxxxx
这里就与\0没有什么关系了。
#include
#include
int main()
{
char arr1[] = "xxxxxxxxxxxxx";
char arr2[] = "he";
strncpy(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}//he
如果长度不够,会主动放上'\0'
strncpy()函数相对较灵活,更可控、相对安全。
——把源字符串的num个字符追加到目标空间中。
char * strncat ( char * destination, const char * source, size_t num );
#include
#include
int main()
{
char arr1[20] = "hello";
char arr2[] = "world";
//把arr2中的world这5个字符追加到arr1中
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}//helloworld
#include
#include
int main()
{
char arr1[20] = "hello";
char arr2[] = "world";
//把arr2中的world中的3个字符追加到arr1中
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}//hellowor
#include
#include
int main()
{
char arr1[20] = "hello\0";
char arr2[] = "world";
//把arr2中的world中的3个字符追加到arr1中
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}//hellowor
#include
#include
int main()
{
char arr1[20] = "hello\0xxxxxxx";
char arr2[] = "world";
//把arr2中的world中的3个字符追加到arr1中
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}//hellowor
strncat追加后还是字符串,所以会在追加指定的几个字符后主动自己加上\0,所以追加完就停止打印。
#include
#include
int main()
{
char arr1[20] = "hello\0xxxxxxx";
char arr2[] = "world";
//把arr2中的world这5个字符追加到arr1中
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}//helloworld
#include
#include
int main()
{
char arr1[20] = "helloxxxxxxx";
char arr2[] = "world";
strncat(arr1, arr2, 7);
printf("%s\n", arr1);
return 0;
}//helloxxxxxxxwor
#include
#include
int main()
{
char arr1[20] = "helloxxxxxxx";
char arr2[] = "world";
//把arr2中的world中3个字符追加到arr1中
strncat(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}//helloxxxxxxxwor
所以停止追加的条件是:
1、遇到\0;
2、追加的数字小于源字符串长度时,追加完要追加的字符后就不追加了。
——比较num个字符串大小。
int strncmp ( const char * str1, const char * str2, size_t num );
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqqqqqq";
int ret = strncmp(arr1, arr2, 3);
printf("%d\n", ret);
return 0;
}//0
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqqqqqq";
int ret = strncmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}//-1
#include
#include
int main()
{
char arr1[] = "abcwef";
char arr2[] = "abcqqqqqq";
int ret = strncmp(arr1, arr2, 4);
printf("%d\n", ret);
return 0;
}//1
strncpy()函数 —— 拷贝
strncat ()函数 —— 追加
strncmp()函数 —— 比较
这三者是长度受限制的字符串函数,头文件均是
——字符串查找函数,作用是:两个字符串str1、str2;在str1中找str2这个子串,如果在str1中找到str2这个子串了,就返回子串的起始地址;如果在str1中多次找到str2,这个子串,则返回的是第一次找到子串的起始位置;如果找不到str2这个字符串则函数就返回空指针。
char * strstr ( const char *str1, const char * str2);
#include
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcd";
//在arr1中找arr2,返回值类型是char*
char* ret= strstr(arr1, arr2);
if (NULL == ret)
{
printf("没找到\n");
}
else
{
//如果能找到就返回b的地址,则打印字符串就会打印bcdefabcdef这一串
printf("%s\n", ret);//从ret地址向后打印这个内容
}
return 0;
}//bcdefabcdef
#include
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcdq";
char* ret= strstr(arr1, arr2);
if (NULL == ret)
{
printf("没找到\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}//没找到
注意:子串一定是连续的,strstr()函数是找连续完整的子串。
模拟实现strstr()函数:
①简单的情况:(只用匹配一次)
思路:
看第一个能否匹配:str和substr指向的第一个字符a和b并不相等,则让str向后走一步(走一个字符)此时str指向的b和substr指向的b相等,让str再向后走一步,此时就让substr向后走一步,str和substr指向的内容又一致,再让二者都向后走一步,发现又一致都是d,然后又让str和substr二个指针都向后走一步,此时substr遇到了\0,说明已经把子串匹配完了。
——找到的情况
简而言之:
str和substr都是指向各自的起始地址,让这两个指针比,如果不相等就让str向后走,如果相等两个指针同时向后走,直到substr字符串里遇到\0。
②复杂的情况:(一次匹配不成功还会匹配第二次、第三次……)
思路:
让s1和s2向后走,str记住它整个字符串的起始位置;substr记住它整个子串字符串的起始位置。cur指针最开始指向str的起始位置。
首先认为str从起始位置有可能找到子串(bbc)所以把cur赋给s1,即s1指向a,s就赋给子串的起始位置。
s1和s2各自指向的是a和b并不相等,意味着这个位置开始是不肯能匹配成功,所以cur不用记住这个位置,则让cur指针向后走指向b,此时就假设从这个位置有可能匹配成功,就把cur赋给s1,这里s2回到起始位置指向b,s1指向的b和s2指向的相等,让s1和s2均各自向后走,二者均指向的是b,又相等,则二者均各自向后走一步,发现s1此时指向的是b,而s2指向的是c,二者不相等,说明从这一次cur的位置开始匹配是不可能成功的,所以让cur向后走一步指向b,此时让s1回到cur指向的位置处,s2还回到整个子串的起始位置处指向b,s1和s2指向的相等,二者均各走向后走一步,都是b相等,二者均各自又向后走,都是c相等,二者均各自向后走一步,发现s2遇到\0,说明已经找到子串。
在cur不断向后走的过程中,cur指向的字符串还没空(还没指向\0,或者说cur还不是空指针)说明还有字符可以被查找,还有可能匹配成功。
while循环停止的条件:s1和s2不相等;s2中遇到\0(找到子串,应该停下);s1遇到\0(被查找的字符串已经查找完,不可能找到子串了)。
s1和s2比较的前提是二者均不是(二者均还没遇到)\0。
所以实现代码:
#include
#include
char* my_strstr(const char* str, const char* substr)
//这里定义的两个指针类型是const修饰的,但要注意返回类型是char*
{
//在str字符串中找substr字符串,只是查找可以加const修饰
//对两指针断言:保证这两指针不为空指针,
//若arr1为空字符串,即str是空指针则不能对其查找,也肯定找不到。
//若arr2是空字符串,(arr1正常)则子串是空字符串(第一个字符就是\0)是没法查找的。
//创建两个指针记住str和substr的起始位置
const char* s1 = str;
const char* s2 = substr;
//s1和s2代替str和substr向后走
const char* cur = str;//cur这个指针记住可能会匹配成功的地址
//可能会匹配多次情况:记住可能会匹配成功的地址,万一匹配成功这个地址就是子串所在位置的起始地址
assert(str && substr);
if (*substr == '\0')//特殊情况
{
//此情况没法查找
return (char*)str;
//return str;需要强制类型转换为不被const修饰的指针类型(防止编译器报警告)
}
while (*cur)//cur不是空指针,即*cur指向的不是\0
{
s1 = cur;
s2 = substr;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)//可能有很多个字符要比较
//这里也可以写成:while(*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
//不相等就让cur向后走一步,重新下一次匹配
/*cur++;
s1 = cur;
s2 = substr;*/
//找到的子串的起始位置是cur
if (*s2 == '\0')
{
return (char*)cur;
/*return cur;*///需要强制类型转换为不被const修饰的指针类型(防止编译器报警告)
}
cur++;
}
//cur为\0时跳出循环:就不能再找到了
return NULL;
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (NULL == ret)
{
printf("没找到\0");
}
else
{
printf("%s\n", ret);
}
return 0;
}//bbcdef
//注意这里*s1为\0时不作任何处理也没有问题,或者返回NULL
//这种情况:
//arr1:abb
//arr2:bbc
//*s1提前遇到\0,可以提前结束
去掉注释:
#include
#include
char* my_strstr(const char* str, const char* substr)
{
const char* s1 = str;
const char* s2 = substr;
const char* cur = str;
assert(str && substr);
if (*substr == '\0')
{
return (char*)str;
}
while (*cur)
{
s1 = cur;
s2 = substr;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
//这里也可以写成:while(*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cur;
}
cur++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (NULL == ret)
{
printf("没找到\0");
}
else
{
printf("%s\n", ret);
}
return 0;
}//bbcdef
上述是模拟实现了strstr()函数,但是这里写的算法效率不够高,可以用KMP算法查找子字符串。
KMP算法学习——B站大博哥(B站号)
注意:
一个安全的指针(被const修饰的)返回去变成一个不安全的指针(没有被const修饰)就存在一个类型上的差异,编译器会报警告:不同的“const”限定符。
空字符串和空指针不是一个概念。
空字符串:
char arr[] = "";
char* p = "";
p指向的是空字符串。但这里指针p有值,p是一个有效的地址,只不过这个有效的地址指向的字符串第一个字符就是\0,是个空字符串。
空指针:
char* p = NULL;
指针变量p的内容是空,就是0,p里面放的就是空指针。当p是空指针时,它什么都没有指向。
看两种指针的写法:
char* p1;
char *p2;
p1和p2是一模一样的指针变量。
连续定义多个指针变量时建议写成:
char *p1, *p2, *p3……
若是:
char *p1, p2;
这里p1是指针,p2是char类型的。
当定义一个指针变量时建议写成:
char* p1;
p1是指针变量,它的类型是char*。
——可以把一个字符串切割成几个字符串。切割是按照分割符切的。
举例:
邮箱名:"[email protected]"
这一串字符串是由@和.这两个符号分割开的;
若想现在从这一串字符串中提取非分割字符:
提取keli;
提取yeah;
提取net。
此时就用strtok()这个函数。
char * strtok ( char * str, const char * sep );
1. sep参数是个字符串,是一个char*的指针,指向了一个字符串,定义了用作分隔符的字符集合,如例子中的@和.把这两者放在一起,即分割符的集合就是“@.”(无所谓顺序,包含起来就可以)。
#include
int main()
{
const char* p = "@.";
//调用strtok()函数时:
strtok(, p);//第二个参数就可以传p,p指向的就是分隔符的集合
return 0;
}
2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
#include
int main()
{
const char* p = "@.";
//调用strtok()函数时:
char arr[] = "[email protected]";//arr数组中的字符串是由@和.这两个分隔符分开的各个字符串
strtok(arr, p);//第二个参数就可以传p,p指向的就是分隔符的集合
return 0;
}
"[email protected]"中“keli”是一个标记,这个字符串里可以有一个标记也可以有多个标记也可以没有标记,如空字符串也行,标记是由一个或多个分隔符分割开的。这里arr就是strtok()函数的第一个参数。
3. strtok()函数的功能:
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。
如例子中的keli是一个标记,yeah是一个标记,net是一个标记(也可以理解为子段),当strtok()函数第一次作用于[email protected]这个字符串的时候,它会找到其中一个标记,如第一个标记“keli”,strtok()函数会把后面的@符号改成\0,并且返回一个指向这个标记的指针,即返回的是k的地址。
所以第一次调用strtok()函数返回k的地址并且把@置换成\0,即从k的地址向后keli就可以打印出来——拿到了一个字符串。
4. strtok函数会改变被操作的字符串,(如例子中把@改成\0)所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。意思是如本身想处理arr数组“[email protected]”,从里面提取各个字段(标记),一般不会直接处理arr本身,一般会创建一个临时数组如buf,把arr中的内容拷贝放到临时数组buf中,接下来的操作对象就是buf,buf数组里放的就是“[email protected]”这个内容,可以修改buf里面的内容,并且修改buf里面的内容不会影响到arr数组。
#include
#include
int main()
{
const char* p = "@.";
//调用strtok()函数时:
char arr[] = "[email protected]";
char buf[50] = { 0 };
strcpy(buf, arr);
strtok(arr, p);
return 0;
}
5. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
如例子中strtok()函数传参传的是arr,是"[email protected],则此时arr是一个有效的地址,不是空指针,strtok()函数就会找到arr数组中的第一个标记:keli,strtok()函数就会记住keli这个标记的位置。
6. strtok()函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
意思是:有可能给arr数组传的是一个NULL空指针,strtok()函数记住keli这个标记的位置后就会从keli后面找下一个标记。而当strtok()函数的第一个参数是空指针时还是会从拷贝的buf同一个字符串里面找被保存位置开始查找下一个标记,遇到.就找到了标记,找到之后也会把.置换成\0,然后返回y的地址,则就可以打印出yeah这个子段。
这是第一个参数是空指针时会往后走。
strtok()函数第一个参数不是空指针时,strtok找到的是第一个标记,从第二个标记往后的标记要查找的话,第一个参数就要为空指针,因为当第一个参数为空指针时函数将在同一个字符串被保存位置开始找下一个标记,当把下一个标记找到时,就会把这个标记位置保存好然后下一次再调用strtok()函数时传第一个参数时,第一个参数为空时就会从被保存位置再找下一个标记,这样才能把后面所有的标记找出来。
如果字符串中不存在更多的标记,则返回 NULL 指针。
综上:
strtok()函数的使用习惯:
strtok()函数找第一个标记的时候,函数的第一个参数不是NULL;
strtok()函数找非第一个标记的时候,函数的第一个参数是NULL。
——这可真实一个古怪的函数!
注意:使用NULL空指针时需要引头文件
#include
#include
int main()
{
const char* p = "@.";
//调用strtok()函数时:
char arr[] = "[email protected]";
char buf[50] = { 0 };//"[email protected]"
strcpy(buf, arr);
//若找到keli:
//strtok(buf, p);//返回一个k的地址,char*类型指针接收
char* str = strtok(buf, p);
printf("%s\n", str);//keli
//当第二次要拿yeah这个子段时:则再次调用strtok()函数,返回的是y的地址
str = strtok(NULL, p);//这次传的是空指针
printf("%s\n", str);//yeah
//拿到net,从第二次调用开始接下来的每次调用就传的是空指针了
str = strtok(NULL, p);
printf("%s\n", str);//net
//若这里再去调用strtok()函数,就会返回空指针:NULL,就不能再找了
str = strtok(NULL, p);
printf("%s\n", str);//(null)
str = strtok(NULL, p);
printf("%s\n", str);//(null)
return 0;
}
有没有修改buf里面的内容呢?
会,strtok()函数会改变被处理的字符串。
但其实,上述的访问方法很low!
因为在看到字符串"[email protected]"有3个子段的情况下才进行第一次调用,第二次调用、第三次调用。如果字符串里面有50个子段或不知道字符串内容,不知道有多少段则不适合这种方法。
优化代码:
#include
#include
int main()
{
const char* p = "@.";
char arr[] = "[email protected]";
char buf[50] = { 0 };
strcpy(buf, arr);
char* str = NULL;//创建一个char*的空指针,初始化为NULL
for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n", str);//打印str地址所指向的内容
}
return 0;
}//输出:
//keli
//yeah
//net
解读代码:
因为strtok()函数在调用时只有第一次不一样,找第一个标记时传的是非空指针,找第二个标记传的都是空指针。所以由此写for循环:
for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n", str);
}
for循环中,初始化部分写的是:调用strtok()函数,传buf、传p,调用后返回值放到str中,如果返回值str不等于空指针(说明返回地址是有意义的,它确实指向的是字符串),则进入循环打印str所指向的内容,这次打印完成之后,若再访问字符串后面的子段,则在for语句中再次调用strtok()函数,因为不是第一次调用,所以传空指针,传p,然后把函数的返回值再放入str中,放进去之后又判断返回值str是否为空指针,不是则进入循环打印访问的内容……若有一次strtok()函数返回的是空指针,则空指针不等于空指针为假就跳出循环。
由此:
#include
#include
int main()
{
const char* p = "@.#*";
char arr[] = "[email protected]#come*on";
char buf[50] = { 0 };
strcpy(buf, arr);
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}//输出:
//keli
//yeah
//net
//hehe
//success
//come
//on
几个分隔符连续放在一起,打印时就没有子段,以空字符串处理忽略掉:
#include
#include
int main()
{
const char* p = "@.#*";
char arr[] = "[email protected].#*on";
char buf[50] = { 0 };
strcpy(buf, arr);
char* str = NULL;
for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n", str);
}
return 0;
}//输出:
//keli
//yeah
//net
//on
对strtok()函数调用后为什么还能够保存上次被调用留下的地址的分析:
如果让下一次strtok()函数调用时函数返回值(保存的位置)不销毁还存在,则可以使用静态的变量。静态变量在第一次被调用完之后留下的值下一次函数调用时还在。所以strtok()函数内部可能就有静态变量来保存位置便于下次它调用时使用。
——返回错误码所对应的错误信息。
char * strerror ( int errnum );
参数是整型错误码,返回值是char*类型指针。
在C语言中规定了一些错误信息,错误信息由两部分组成。一个是错误码,一个错误码对应一个错误信息,如0在C语言中代表No Error,1代表一个意思,2代表一个意思,3代表一个意思……C语言中规定了很多错误信息,0是错误码,"No Error"是0所对应的错误信息。把错误码翻译成它所对应的错误信息就会便于理解。
strerror()函数就可以把错误码翻译成错误信息。
strerror()函数的使用方法:
给一个错误码就会返回错误码所对应错误信息的起始地址。如给0返回的就是No Error这个字符串的起始地址。
用strerror()函数测试0-9的错误信息:
错误码——错误信息
0 —— No error
1 —— Operation not permitted
2 —— No such file or directory
3 —— No such process
4 —— Interrupted function call
5 —— Input/output error
6 —— No such device or address
7 —— Arg list too long
8 —— Exec format error
9 —— Bad file descriptor
错误信息本来就是常量字符串。
使用场景:
C语言可以操作文件:打开文件有fopen()函数:
fopen()函数:
参数分别是:文件名、打开文件的方式;
返回方式是FILE*类型的指针。
如果使用这个函数,则需要确定打开的文件和打开文件的方式和返回值。
#include
#include
int main()
{
FILE* pf = fopen("test.txt", "r");
//检测:
//打开失败:
if (NULL == pf)
{
//pf指向空指针则说明出错,但若是不知道什么原因(文件不存在或权限不够……)
//打印出错的原因:
//strerror();//把错误码翻译成错误信息,错误码是什么?
//当库函数使用的时候,发生错误会把errno这个全局变量的错误信息设置为本次执行库函数产生的错误码
//这里调用fopen()函数失败就会把errno这个全局变量的错误信息设置为本次执行函数产生的错误码
//errno是C语言提供的全局变量,可以直接使用,放在errno.h文件中,所以使用errno需要包含头文件
printf("%s\n",strerror(errno));//返回的是char*
return 0;
}
//打开成功:
//则读文件
//……
// ……
//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}//No such file or directory(这里打印的是发生错误的原因)
//因为没有创建这个文件夹
//因为在工程路径下并没有text.txt这个文件,则以“r”的方式打印一定会
//打印失败,因为fopen()函数调用失败时会返回空指针
//若此时去创建这个文件则读取成功运行成功(不打印什么)
当一个函数调用完之后,分析错误原因时就可以在函数调用完之后,在后面用strerror()函数,传错误码,就会返回这个错误码所对应的错误信息,然后就会打印这个错误信息。
注意errno是全局变量,是全局共用的,当检测一个函数调用完之后的错误之后,同一个项目中检测下一个函数若发生错误就会更新errno,就是其他函数(程序)的错误提示了。
函数 | 如果它的参数符合下列条件就返回真 |
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 | 任何可打印字符,包括图形字符和空白字符 |
这些函数可以判断一个字符的种类,头文件均是
举例:isspace()函数
——判断一个字符是否是空白字符,即在屏幕上看不见打印了什么,比如空格就是空白字符。
int isspace( int c );
参数就是所判断的字符,得到的是该字符的ASCII码值,返回类型是int。
isspace returns a non-zero value if c is a white-space character (0x09 – 0x0D or 0x20). iswspace returns a non-zero value if c is a wide character——参考MSDN部分
如果参数c是一个空白字符时,isspace()函数就返回一个非0值;
如果参数c不是一个空白字符时,isspace()函数就会返回一个0。
#include
#include
int main()
{
printf("%d\n", isspace(' '));//空白字符
return 0;
}//8——非0数字
#include
#include
int main()
{
printf("%d\n", isspace('!'));
return 0;
}//0
用处——判断获得的字符是不是空白字符:
#include
#include
int main()
{
char ch = 'w';
//空白字符就进入:
if (isspace(ch))
{
printf("hehe\n");
}
//不是空白字符:
else
{
printf("haha\n");
}
return 0;
}//haha
其他函数的使用和isspace()函数类似。
再举例:
判断一个数字是否是数字字符:
#include
int main()
{
char ch = '0';
if (ch >= '0' && ch <= '9');//如果ch在这个范围内则说明ch是数字字符
{
}
return 0;
}
这种方法不够标准化。
则现在用函数——统一标准:
#include
#include
int main()
{
char ch = '0';
if (isdigit(ch))
{
}
return 0;
}
建议多使用库函数。
tolower()函数
——把大写字母转换为小写
int tolower ( int c );
toupper()函数
——把小写字母转换为大写
int toupper ( int c );
这两个函数的头文件均是
#include
#include
int main()
{
char ch = 0;
ch = getchar();//获取一个字符放到ch中
//ch如果是小写就转大写,如果是大写就转小写:
if (islower(ch))//是小写
{
//转大写:
ch = toupper(ch);
}
else
{
//转小写:
ch = tolower(ch);
}
printf("%c\n", ch);
return 0;
}
//q
//Q
——从源数据拷贝num个数据到目标空间中。从source的位置开始向后复制num个字节的数据到destination的内存位置。
void * memcpy ( void * destination, const void * source, size_t num );
num是指拷贝字节的个数,单位是字节,类型是无符号整型。
拷贝的对象可能是整型、字符串、结构体等数据,void*的指针可以接收任意数据的地址。
#include
#include
int main()
{
/*char arr1[] = "abcdef";
char arr2[] = { 0 };*/
//把arr1中的内容拷贝放到arr2中——用字符串拷贝函数
//strcpy()函数是用来拷贝字符串的,拷贝字符串时找\0
/*strcpy(arr2, arr1);*/
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
//把arr3中的前5个元素拷贝放到arr4中
//因为arr3是整型数据,不是字符串,不能用拷贝字符串的strcpy函数
//——memcpy()函数不关心拷贝的数据类型
//拷贝5个整型,则是20个字节
//memcpy(arr4, arr3, 20);//可以计算20的:
memcpy(arr4, arr3, 5 * sizeof(arr3[0]));
//看arr4中的内容:
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr4[i]);
}
return 0;
}//1 2 3 4 5
//若拷贝arr3中的6,7,8,9,10则需要把arr3中6的地址作为源数据,则是:
//memcpy(arr4, arr3+5, 5 * sizeof(arr3[0]));
//arr3+0指向的是1,传的参数是数组名,是首元素地址
这个函数在遇到 '\0' 的时候并不会停下来。
如果source和destination有任何的重叠,复制的结果都是未定义的。
注意memcpy()函数不能倒着拷贝,只要是内存块中的数据都可以用此函数拷贝。
若把char类型的数组放到整型数组中,存放数据的都是内存块可以拷贝,一个int4个字节,1个char1个字节,则拷贝过去就相当于一个整型空间里放了四个字符,这4个字符的ASCII码值合起来组合成一个整型,按照整型的方式解读出来就是一个整型的值。
模拟实现memcpy()函数:
#include
#include
//把源拷贝到目标空间之后返回的是目标空间的起始地址
void* my_memcpy(void* dest, const void* src, size_t num)//int num也可以
{
//需要解引用操作则断言指针是否有效,保证指针为非空指针。
assert(dest && src);
//void*的指针不能解引用,也不能++、--
//所以对dest和src指针进行强制类型转换:
//需要强制类型转换为char*类型,如果强制类型转换为int*会很容易出错,
//如拷贝num=7个字节,则+1拷贝一个字节+1拷贝一个字节就拷贝了8个字节,所以拷贝7个字节很难实现。
//所以转换为char*拷贝7次7个字节即可,所以转换为char*更合适。
void* ret = dest;
while (num--)//后置--,先使用后--
{
*(char*)dest = *(char*)src;
/*dest++;
src++;*///错误,这里相当于对void*类型指针++
//强制类型转换都是临时的
dest = (char*)dest + 1;
src = (char*)src + 1;
//强制类型转换为char*,跳过一个字符,因为拷贝一个字节需要向后移动一个,
//一个字节一个字节地拷贝,有20个字节就移动20次
}
return ret;
}
int main()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
my_memcpy(arr4, arr3 + 5, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr4[i]);
}
return 0;
}//6 7 8 9 10
若把1,2,3,4,5拷贝放到3,4,5,6,7的空间:
源头从1地址开始。则arr3就变成了={1 2 1 2 3 4 5 8 9 10}:
#include
#include
void* my_memcpy(void* dest, const void* src, size_t num)//int num也可以
{
assert(dest && src);
void* ret = dest;
while (num--)//后置--,先使用后--
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
/*int arr4[5] = { 0 };*/
//想要的结果:1 2 1 2 3 4 5 8 9 10
my_memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}//1 2 1 2 1 2 1 8 9 10
打印的结果是:1 2 1 2 1 2 1 8 9 10而不是:1 2 1 2 3 4 5 8 9 10:
分析:
因为是从源头数据一个字节一个字节的拷贝到目标空间中:1所占的四个字节一个一个地向3中拷贝,2的四个字节一个字节一个字节地向4中放,此时1已经把原来的3覆盖,2已经把原来的4覆盖,所以准备把3的四个字节拷贝到5的位置上时,其实是把1的四个字节拷贝到了5的位置上;当把4的四个字节拷贝放到6中时其实是把2的四个字节拷贝放到6的位置……同理,1和2又分别覆盖掉原来的5和6,拷贝了20个字节即是1 2 1 2 1。
这发生了内存重叠——源和目标重叠,在源和目标重叠的情况下不应该用自己写的my_memcpy()函数,用的是memmove()函数,memmove()函数在拷贝内存数据的时候可以重叠。
memmove()函数的参数和返回类型与memcpy一模一样,用法也一样。
——和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。
void * memmove ( void * destination, const void * source, size_t num );
#include
#include
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
//my_memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
memmove(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}//1 2 1 2 3 4 5 8 9 10
但是,也发现:
#include
#include
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
//my_memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
/*memmove(arr3 + 2, arr3, 5 * sizeof(arr3[0]));*/
memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}//1 2 1 2 3 4 5 8 9 10
库函数中的memcpy()函数也支持重叠拷贝;
但其实,C语言只要求:
memcpy()函数能拷贝不重叠的内存空间就可以了;
memmove()函数去处理那些重叠内存的拷贝。
但是发现VS环境中memcpy()函数也能实现重叠拷贝,所以它超预期完成了C语言所规定的要求。
所以模拟实现的strcpy()函数没有错误,只是没有处理重叠内存的拷贝,也不能保证库函数的memcpy()函数永远都支持内存重叠拷贝,因为在不同编译器下实现会不同。
上述代码直接用memmove()函数也可以,memmove()函数包含了memcpy()的功能。如memcpy()函数的功能是A,则memmove()函数的功能就是A+B。(即能拷贝重叠的又能拷贝不重叠的)
模拟实现memmove()函数:
举例:
①若把1, 2, 3, 4, 5拷贝放到3, 4, 5, 6, 7中:
思路:
不用额外创建空间,正不行则倒着放:
把5放到7位置上,把4放到6位置上,把3放到5位置上,把2放到4位置上,把1放到3置上,即完成了1,2,3,4,5放到3,4,5,6,7上。
拷贝顺序:
拷贝5,拷贝4,拷贝3,拷贝2,拷贝1(倒顺序的源数据)
②若把3,4,5,6,7拷贝放到1,2,3, 4,5中:
此时倒着拷贝就会内存重叠,需要正着从前向后拷:
拷贝3,拷贝4,拷贝5,拷贝6,拷贝7(正顺序的源数据)
则不同情况不同的分析:
注意:是源数据拷贝到目标空间中,内存重叠的这块空间既可能是源空间又可能是目标空间,但是const修饰的是指针,限制的是不能通过指针改变所指向的空间内容(内存)而不是限制内存。
dest没有被const修饰限制,即dest没有被保护,所以可以通过dest指针去改变它指向空间的内容。
const修饰src指针,限制的是src,所以是不能通过src指针去改变所指向空间内容的任何数据,但是那块空间内容没有被限制死。
若思路是临时创建一块空间,则需要先拷贝,后拷贝回来,操作麻烦,时间和空间都浪费,所以没必要单独增加一块空间。
现在分析:什么情况下从前向后拷贝,什么情况下从后向前拷贝?
写方法1的代码:
分析其中由前向后拷贝放入的情况:
while (num--)
{
*(char*)dest = *(char*)src;//拷贝一个字节
dest = (char*)dest + 1;
src = (char*)src + 1;
}
分析其中由后向前拷贝放入的情况:
若把4,5,6,7拷贝放到6,7,8,9中:
一个字节一个字节的拷贝;
由后向前拷贝,7拷贝到9位置上,则需要找7在存储内存中的最后一个字节。
在内存中若按小端存储:
同理:找到9在存储内存中的最后一个字节就是*((char*)dest+num)。
while (num--)
{
*((char*)dest + num) = *((char*)src + num);//拷贝一个字节
//+num是跳过num个字节
}
整体实现:
#include
#include
#include
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(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);//拷贝一个字节
//+num是跳过num个字节
}
}
return ret;
}
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
//1 2 1 2 3 4 5 8 9 10
my_memmove(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}//1 2 1 2 3 4 5 8 9 10
#include
#include
#include
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(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 ret;
}
void test1()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
//把3,4,5,6,7拷贝放入1,2,3,4,5位置上:
//3 4 5 6 7 6 7 8 9 10
my_memmove(arr3, arr3 + 2, 5 * sizeof(arr3[0]));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);
}
}
int main()
{
test1();
return 0;
}//3 4 5 6 7 6 7 8 9 10
所以此算法彻底实现了无论是从前向后拷贝还是从后向前拷贝。
注意:
*(char*)dest++ = *(char*)src++;
这种代码是错误的,编译器不允许通过。强制类型转换是临时的,++时强制类型转换还没有产生。这样简化代码会使优先级产生问题。
一般情况下不会有:dest = src,没有自己拷贝自己的。所以不会写在代码中。(写上也只是徒增代码的判断)
——设置内存。把一块内存以字节为单位设置成自己想要的值。
void *memset( void *dest, int c, size_t count );
dest——指针指向的目的地;
c——要被设置的字符,以字节为单位;
count——字符的个数。
#include
#include
int main()
{
//memset()函数针对字符数组:
char arr[15] = { 0 };
//把从arr开始,把arr的前10个字节改成字符x:
memset(arr, 'x', 10);
//memset()函数针对整型数组:
int brr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//把从arr开始,把arr的前10个字节全部改成0:
//memset(brr, '0', 10);//错误,是数字0
memset(brr, 0, 10);//改了3的前两个字节
int crr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memset(crr, 'x', 10);
return 0;
}//78是十六进制,写成十进制是120,是小写的x的ASCII码值
memset()可以改整型为字符,根据需求都可以设置。
——内存比较,从ptr1和ptr2指针开始向后比较num个字节,一对字节一对字节地比较,最多比较num对。
int memcmp ( const void * ptr1,const void * ptr2,size_t num );
与strcmp()相似,但是strcmp()函数比较到\0就停止了,memcmp并不在乎有没有\0,执行的就是比较num个字符,比较num个字节。
memcmp()函数的返回值:<0、>0、=0的数字,和strcmp()函数一样。
#include
#include
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcmp(arr1, arr2,6);//比较前6个字节,2里面比较一半
int cmp = memcmp(arr1, arr2, 8);//比较的是前8个字节,即比较的是1和2
printf("%d %d\n", ret, cmp);
return 0;
}//0 0
#include
#include
int main()
{
int arr1[] = { 1,2,7,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcmp(arr1, arr2, 8);//比较前6个字节,2里面比较一半
int cmp = memcmp(arr1, arr2, 9);//比较的是前8个字节,即比较的是1和2
printf("%d %d\n", ret, cmp);
return 0;
}//0 1
比较的是:在内存中一对字节中放的数据谁大谁小。
总结:
memcpy()函数——拷贝num个字节
memmove()函数——拷贝num个字节
memset()函数——设置num个字节
memcmp()函数——比较num个字节
这四个函数都是以字节为单位的,头文件均是
寄个知识小卡片:
everything(库函数)、VS编译器中看到的是参考代码,VS真正调用的不是这部分参考代码,我们使用的库函数如printf()函数、memmove()函数、syrcpy()函数等都是静态库里的函数,VS还有其他编译器都已经把这些函数编成静态库了比如LIBC.LIB、LIBCMT.LIB、MSVCRT.LIB,而这些静态库由编译器主动引入,所以只要包含这些函数的头文件,使用库函数时库函数所对应的静态库就会被自动导入进去,所以用的是静态库而不是这些.c的源文件。