字符串函数(使用这些库函数都应该包含头文件string.h)
在编程的过程中,我们经常要处理一些字符串,为了方便操作,C语言标准库中提供了⼀系列库函数,接下来我们就学习⼀下这些函数。
strlen(求字符串长度)
size_t strlen ( const char * str );
注意:1. 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含'\0' );
2. 参数指向的字符串必须要以 '\0' 结束,否则会返回一个随机值,随机值的出现是因为无法找到'\0'会往后继续寻找,直至找到'\0'为止;
3. 注意函数的返回值为size_t,是无符号的( 易错 )
模拟实现strlen函数:
int my_strlen(const char * str)
{
int count = 0;
assert(str); //断言防止str为空指针(NULL),使用时应该包含头文件assert.h
while(*str) //当*str为'\0'时停止
{
count++;
str++;
}
return count;
}
strcpy(将源字符串拷贝到目标空间)
char* strcpy(char * destination, const char * source );
注意:1.源字符串必须以 '\0' 结束。
2.会将源字符串中的 '\0' 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可修改。
模拟实现strcpy函数:
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest; //后面会改变目标空间的首地址,所以应使用指针记住首地址最后返回这个首地址
assert(dest&&src);//断言两者不为空指针(NULL)
while((*dest++ = *src++))
{
;//空语句
}
return ret;
}
strcat(字符串追加)
注意:1.源字符串必须以 '\0' 结束;
2.目标字符串中也得有\0 ,否则没办法知道追加从哪里开始;
3.目标空间必须有足够的大,能容纳下源字符串的内容;
4.目标空间必须可修改。
模拟实现strcat函数:
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;//记住首地址,同时作为返回值
assert(dest&&src);
while(*dest)
{
dest++;//找到追加位置
}
while((*dest++ = *src++))
{
;//空语句
}
return ret;
}
strcmp(字符串比较)
标准规定:第⼀个字符串大于第⼆个字符串,则返回大于0的数字 ;
第⼀个字符串等于第⼆个字符串,则返回0 ;
第⼀个字符串小于第⼆个字符串,则返回小于0的数字 ;
那么如何判断两个字符串? 其实就是比较两个字符串中对应位置上字符ASCII码值的大小。
模拟实现strcmp函数:
int my_strcmp (const char * str1, const char * str2)//返回值为int类型
{
int ret = 0 ;//记住首地址,同时作为返回值
assert(str1 && str2);//判断不为空指针(NULL)
while(*str1 == *str2)
{
if(*str1 == '\0')//如果相等就返回0
return 0;
str1++;
str2++;
}
return *str1-*str2;//如果不等就返回大于0或小于0的值
}
但是这样拷贝,追加,比较有可能不安全,比如:可能拷贝太多了超出目标空间大小又或者不知道
要追加多少个字符,可能追加超出目标空间……
那么这时c语言就推出了它们的变种:strncpy,strncat,strncmp……,在原来的基础上多了一个参数num,那么这样我们就知道了到底要拷贝多少个字符,追加多少个,比较前多少个,这样来说就相对安全一点。
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
拷贝num个字符从源字符串到目标空间,如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
模拟实现strncpy函数:
char* my_strncpy(char* str1, const char* str2, size_t num)
{
char* ret = str1;//记住首地址,最后作为返回值
assert(str1 && str2);//判断不为空
while (num--)//拷贝num个字符
{
*str1++ = *str2++;
}
return ret;
}
strncat
char * strncat ( char * destination, const char * source, size_t num );
将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加⼀个 \0 字符,如果source 指向的字符串的长度小于num的时候,只会将字符串中到 \0 的内容追加到destination指向的字符串末尾。
模拟实现strncat函数:
char* my_strncat(char* str1, const char* str2, size_t num)
{
char* ret = str1;//记住首地址,最后作为返回值
assert(str1 && str2);//判断不为空
while (*str1)
{
str1++;//找到追加位置
}
while (num--)
{
*str1++ = *str2++;
}
*str1 = 0;//再追加⼀个\0字符,因为前面循环跳出时就已经str1++了,所以这里直接追加\0就行
return ret;
}
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提前发现不⼀ 样,就提前结束,⼤的字符所在的字符串⼤于另外⼀个。如果num个字符都相等,就是相等返回0,这里不提供模拟实现由读者自行完成。
strstr(寻找子串)
char * strstr ( const char * str1, const char * str2);
函数返回字符串str2在字符串str1中第⼀次出现的位置,字符的比较匹配不包含 \0 字符,以 \0 作为结束标志。
模拟实现strstr函数:
const char* my_strstr(const char* str1, const char* str2)
{
const char* str = str1;//记住首地址,在每次比较后如果不匹配就++
const char* s1 = NULL; //开始时防止野指针
const char* s2 = NULL;
assert(str1 && str2); //判断不为空
if (*str2 == '\0') //str2为\0那么就是所以字符串的子串,直接返回str1
{
return str;
}
while (*str) //当不匹配就++,从下一次字符继续比较
{
s1 = str; //每次不匹配时重置
s2 = str2;
while (*s1 == *s2)
{
s1++;
s2++;
if (*s2 == '\0') //如果s2为\0就代表找到了
{
return str;
}
}
str++;
}
return NULL; //如果没有找到就返回空指针
}
strtok(分割字符串)
char * strtok ( char * str, const char * sep);
其中sep参数指向⼀个字符串,定义了用作分隔符的字符集合;而str参数指定⼀个字符串,它包含了0个或者多个由sep字符串中⼀个或者多个分隔符分割的标记;
注意:strtok函数找到str中的下⼀个标记,并将其用\0 结尾,返回⼀个指向这个标记的指针。另外strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串⼀般都是临时拷贝的内容并且可修改。
strtok函数的第⼀个参数不为 NULL ,函数将找到str中第⼀个标记,strtok函数将保存它在字符串中的位置。strtok函数的第⼀个参数为 NULL ,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标记。如果字符串中不存在更多的标记,则返回 NULL 指针。例如:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "192.168.6.111";
char* sep = ".";//从'.'处断开
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
//不为空(NULL)时跳出,每次打印到\0为止,打印一次就将\0前面的字符置为NULL然后寻找下一个断点
{
printf("%s\n", str);
}
return 0;
}
strerror(寻找错误)
char * strerror ( int errnum );
strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。
在不同的系统和C语言标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头文件中说明的,C语言程序启动的时候就会使用⼀个全局变量errno来记录程序的当前错误码,不过程序启动的时候errno是0,表示没有错误,但是当我们在使用标准库中的函数的时候发生了某种错误,就会报出对应的错误码,存放在errno中,而⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。例如:
#include <errno.h>
#include <string.h>
#include <stdio.h>
//我们打印⼀下0~10这些错误码对应的信息
int main()
{
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
内存函数
因为是内存函数,而不同类型的变量在内存中的大小不一样,所以使用类型中的void类型作为参数,最后在运算或修改时强制类型转换为char*类型(以字节为单位),这样就可以不管是什么类型,就都可以运算或修改了
memcpy(拷贝没有重叠的内存)
void * memcpy ( void * destination, const void * source, size_t num );
函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到 '\0' 的时候并不会停下来。如果source和destination有任何的重叠,复制的结果都是未定义的。
模拟实现memcpy函数:
void* my_memcpy(void* str1, const void* str2, size_t num)
{
void* ret = str1; //记住首地址,作为返回值
assert(str1&& str2);//判断不为空(NULL)
while (num--)
{
*(char*)str1 = *(char*)str2;//void型的指针不能直接运算需要先强制类型转换
str1 = (char*)str1 + 1; //不能直接强制类型转换然后++
str2 = (char*)str2 + 1;
}
return ret;
}
memmove(拷贝有重叠的内存)
void * memmove ( void * destination, const void * source, size_t num );
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和⽬标空间出现重叠,就得使用memmove函数处理。另外,在追加字符串我们一般都是从前往后追加,那么当我们要追加的字符串是字符串本身时,从前往后追加就有可能改变字符串所在空间内容,同时导致追加错误,比如说:一个字符串是123456789,从3的位置追加从1位置的五个数12345,那么最后的结果就是121212189了这时就追加错误了。那么这时我们该怎么办呢?其实我们也可以从后往前追加,那么我们就不会导致追加错误了,但是怎样才能满足不管怎样都能正常追加呢,从上面的例子我们可以看出,当要追加的字符串(12345)在被追加的字符串地址的前面时就是从后往前追加,同理不难得出:当要追加的字符串地址在被追加的字符串地址的后面时就是从前往后追加的结论,那么我们模拟实现拷贝有重叠内存就可以这样:
void* my_memmove(void* str1, const void* str2, size_t num)
{
void* ret = str1;//记住首地址,作为最后的返回值
assert(str1 && str2);//判断不为空指针NULL
if (str1 < str2)//判断内存大小
{
while (num--)
{
*(char*)str1 = *(char*)str2;
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
}
else
{
while (num--)
{
*((char*)str1 + num) = *((char*)str2 + num);//直接对内存处理所以不需要额外++了
}
}
return ret;
}
memset(设置内存)
void * memset ( void * ptr, int value, size_t num );
memset是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。比如:
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "hello world";
memset (str,'x',6);
printf(str);
return 0;
}
//结果:xxxxxxworld
memcmp(比较大小)
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
比较从ptr1和ptr2指针指向的位置开始,向后的num个字节,返回值和strcmp一样。