目录
函数介绍:
C语言中对字符和字符串有很多处理,但是C语言本身没有字符串类型,字符串通常放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数。
1. 求字符串长度
1.1 strlen
——求字符串长度
size_t strlen ( const char * str );
#include <stdio.h>
int main()
{
int len = strlen("abcdef");
printf("%d\n", len);
return 0;
}//6
之前写strlen()函数的模拟实现中有3种方法:
1、计数器的方法;
2、递归的方法;
3、指针-指针的方法
#include <assert.h>
#include <stdio.h>
//因为求的是字符串长度,不会改变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 <stdio.h>
#include <string.h>
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
{
printf(">");
}
else
{
printf("<=");
}
return 0;
}//>
#include <stdio.h>
#include <string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1\n");
}
else
{
printf("str1<str2");
}
return 0;
}//str2>str1
strlen()函数返回的是无符号数,两个无符号的数相减得到的也是无符号的数,上述代码中会把-3当做无符号的数处理,是一个非常大的正数而会引入bug。若strlen()返回值设计成int,这类问题就不会存在。
strlen()的返回值可以写成int类型,size_t类型或unsigned_int类型都可以。
上述代码强制类型转换为int可以得出“<=”:
#include <stdio.h>
#include <string.h>
int main()
{
if ((int)strlen("abc") - (int)strlen("abcdef") > 0)
{
printf(">");
}
else
{
printf("<=");
}
return 0;
}//<=
或:
#include <stdio.h>
#include <string.h>
int main()
{
if (strlen("abc") > strlen("abcdef"))
{
printf(">");
}
else
{
printf("<=");
}
return 0;
}//<=
注意:
使用库函数时需要包含头文件,不包含会有bug。
使用strlen()函数时:
字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包
含 '\0' )。
参数指向的字符串必须要以 '\0' 结束。
函数的返回值为size_t,是无符号的。
模拟实现strlen:三种方式:
方式1:计数器方式:
#include <stdio.h>
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 <stdio.h>
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 <stdio.h>
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
2. 长度不受限制的字符串函数
2.1 strcpy
——strcpy()函数用来拷贝字符串,会把源字符串内容拷贝到目标空间中,\0也会被拷贝过去。
char* strcpy(char * destination, const char * source );
/把arr1的内容拷贝放到arr2中
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[20] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}//abcdef
注意:在使用strcpy()函数时:
源字符串必须以 '\0' 结束。
会将源字符串中的 '\0' 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串,防止造成越界访问而使程序崩溃。
目标空间必须可变。
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = { 'a','b','c','d','e','f' };
char arr2[20] = "XXXXXXXX";
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}//程序会崩溃,拷贝内容混乱
#include <stdio.h>
#include <string.h>
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 <stdio.h>
#include <string.h>
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 <stdio.h>
#include <assert.h>
//把源头数据拷贝到目标空间中,整个过程中,源数据是不需要发生变化的,目标空间需要变化
//所以在源数据前面可以用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 <stdio.h>
#include <assert.h>
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 <stdio.h>
int* test()
{
int a = 10;//0x0012ff40
return &a;
}
int main()
{
//p里面放的是:0x0012ff40
int* p = test();
//此时p指向的空间已经还给操作系统,而这个指针p还记住这个空间,
//等去再访问时就会非法访问内存
//是空间销毁不是地址销毁,指针所指向的空间不属于这个程序了就不能使用了
return 0;
}
2.2 strcat
——把源字符串内容追加到目标字符串的后面。
char * strcat ( char * destination, const char * source );
strcpy()函数是拷贝,覆盖原来的数据。
strcat()函数是把源字符串内容追加到目标字符串的后面。
#include <stdio.h>
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 <stdio.h>
#include <string.h>
int main()
{
char arr1[30] = "hello";
char arr2[] = { 'w','o','r','l','d' };
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//源字符串并没有以'\0'结束,程序崩溃
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[30] = {'h','e','l','l','o'};
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//helloworld
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "hello";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//缓冲区溢出
#include <stdio.h>
#include <string.h>
int main()
{
const char arr1[30] = "hello";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}//会报警告
注意:
源字符串必须以 '\0' 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
模拟实现strcpy()函数:
#include <stdio.h>
#include <assert.h>
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 <stdio.h>
#include <assert.h>
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 <stdio.h>
#include <assert.h>
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 );
}