目录
字符函数和字符串函数
函数介绍
strlen
strcpy
strcat
strcmp
strncpy
strncat
strncmp
strstr
strtok
strerror
memcpy
memmove
memcmp
字符函数和字符串函数
本章重点
- strlen
- strcpy
- strcat
- strcmp
- strncpy
- strncat
- strncmp
- strstr
- strtok
- strerror
- memcpy
- memmove
- memset
- memcmp
前言
C 语言中对字符和字符串的处理很是频繁,但是 C 语言本身是没有字符串类型的,字符串通常放在 常量字符串 中 或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串数。
函数介绍
strlen
size_t strlen ( const char * str );
- 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
- 参数指向的字符串必须要以 '\0' 结束。
- 注意函数的返回值为size_t,是无符号的( 易错 )
- 学会strlen函数的模拟实现
注:size_t 即无符号整型(unsigned int)
strlen使用:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = { 'a','b','c','d','e','f','\0'};//计算字符数组长度时末尾必须加'\0',
//否则计算出的长度为随机值.
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
return 0;
}
strlen模拟实现:
//方法一:使用计数器
size_t my_strlen(char* str)
{
assert(str);//检查指针有效性
int count = 0;//计数器
//while (*str != '\0')
//{
//count++;
//str++;
//}
//简化
while(*str++)
{
count++;
}
return count;
}
//方法二:递归
size_t my_strlen2(char* str)
{
assert(str);
if (!*str)
{
return 0;
}
else
{
return 1 + my_strlen(str + 1);
}
}
//方法三:指针减指针
size_t my_strlen3(char* str)
{
assert(str);
char* cur = str;
while (*cur)
{
cur++;
}
return cur - str;//两指针相减,结果为他们之间的元素个数
}
注:下面的代码结果如何?
#include <stdio.h>
#include <string.h>
int main()
{
const char*str1 = "abcdef";
const char*str2 = "bbb";
if(strlen(str2)-strlen(str1)>0)//由于strlen函数的返回值为无符号整型,
//所以在计算时,会恒为正数。自己实现时返回值可根据实际情况而定
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}
strcpy
char* strcpy(char * destination, const char * source );
- Copies the C string pointed by source into the array pointed by destination, including the terminating null
- character (and stopping at that point).
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
- 学会模拟实现
strcpy使用:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[10] = "xxxxxxxxx";
char arr2[] = "abcdef";
printf("%s\n", strcpy(arr1, arr2));//将arr2字符串中的内容拷贝到arr1中(包括'\0)
return 0; //需保证arr1的空间大于等于arr2的空间
}
strcpy模拟实现:
#include<assert.h>//用assert函数需包含此头文件
char* my_strcpy(char* dest, const char* src)//返回值为目标空间的起始地址,src中的字符串
//不需要改变,为避免被修改所以再它前面加上const
{
assert(dest && src);//检查指针的有效性
char* ret = dest;//保存目标空间的起始地址,dest后面会移动
//while (*src!='\0')
//{
// *dest = *src;
// dest++;
// src++;
//}
//简化
while (*dest++ = *src++)//src先将值赋给dest,然后dest和src才++
{
;
}
return ret;
}
strcat
char * strcat ( char * destination, const char * source );
- Appends a copy of the source string to the destination string. The terminating null character indestination is overwritten by the first character of source, and a null-character is included at the end ofthe new string formed by the concatenation of both in destination.
- 源字符串必须以 '\0' 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 字符串自己给自己追加,如何?
strcat使用:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[10] = "abcd";
char arr2[] = "efgh";
printf("%s", strcat(arr1, arr2));//将arr2中的字符串追加到arr1中的字符串后面
return 0; //需保证arr1的空间容纳连接后的字符串
}
strcat模拟实现:
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest)//因为是将src中的字符串追加到dest的后面所以需先找到dest中'\0'的位置
{
dest++;
}
while (*dest++ = *src++)//同strcpy
{
;
}
return ret;
}
其中大概步骤如图:
strcmp
int strcmp ( const char * str1, const char * str2 );
- This function starts comparing the first character of each string. If they are equal to each other, itcontinues with the following pairs until the characters differ or until a terminating null-character isreached.
- 标准规定:
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
- 那么如何判断两个字符串?
strcmp使用 :
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdxx";
int ret = strcmp(arr1, arr2);
if (ret > 0)
{
printf("arr1 > arr2\n");
}
else if (ret < 0)
{
printf("arr1 < arr2\n");
}
else
{
printf("arr1 = arr2\n");
}
return 0;
}
strcmp模拟实现
#include<assert>
int my_strcmp(const char* str1, const char* str2)//str1和str2都不需要被改变
{
assert(str1 && str2);
while (*str1 == *str2)//如果找到不相等的字符直接返回他们的ascll码值之差,
//而不是将所有的字符全部比较完之后再返回.
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
前面学习了这些字符串函数之后,大家是否有些许收获呢?
其实这些函数中有些是不安全的,比如:strcpy中如果目标空间的大小不能够容纳原空间的字符串,就会造成数组越界访问,strcat中也是同样的道理。还有如果字符串自己给自己追加,是不能使用strcat函数的;如果我们只想比较字符串中的部分字符串的大小,而不是全部,也不能使用strcmp。C语言中给了一些长度受限的字符串函数,而前面的函数是长度不受限的字符串函数。
strncpy
char * strncpy ( char * destination, const char * source, size_t num );
- Copies the first num characters of source to destination. If the end of the source C string (which issignaled by a null-character) is found before num characters have been copied, destination is paddedwith zeros until a total of num characters have been written to it.
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
strncpy使用:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "xxxxxxxxxx";
char arr2[] = "xx";
char arr3[] = "abcdef";
printf("%s\n", strncpy(arr1, arr3, 8));//拷贝了8个字符,所以字符串中的'\0'也会被拷贝
printf("%s\n", strncpy(arr2, arr3, 1));//拷贝了1个字符,'\0'不会被拷贝
return 0;
}
strncpy模拟实现:
#include<assert.h>
char* my_strncpy(char* dest, const char* src, int count)//参数count为需要拷贝的字节数
{
assert(dest && src);
char* ret = dest;
while(count--)
{
*dest++ = *src++;
}
return ret;
}
strncat
char * strncat ( char * destination, const char * source, size_t num );
- Appends the first num characters of source to destination, plus a terminating null-character.
- If the length of the C string in source is less than num, only the content up to the terminating nullcharacter is copied.
strncat使用:
/* 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;
}
strncat模拟实现:
char* my_strncat(char* dest, const char* src, int count)
{
assert(dest && src);
char* ret = dest;
while (*dest)//找到目标字符串中'\0'的位置
{
dest++;
}
while (count--)
{
*dest++ = *src++;//从'\0'处开始追加
}
*dest = '\0';//末尾需'\0'
return ret;
}
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
- 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
strncmp使用:
/* 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;
}
strncmp模拟实现:
int my_strncmp(char* str1, char* str2, int count)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (count <= 0)
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
strstr
char * strstr ( const char *str1, const char *str2 );
- 在str1中查找 是否存在str2字符串,如果有则返回str2第一次出现的位置的地址,否则返回NULL;
strstr使用:
/* strstr example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="This is a simple string";
char * pch;
pch = strstr (str,"simple");
strncpy (pch,"sample",6);
puts (str);
return 0;
}
strstr模拟实现:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1)
{
char* cur1 = str1;
char* cur2 = str2;
while (*cur1 == *cur2)
{
cur1++;
cur2++;
if (*cur2 == '\0')
{
return str1;
}
}
str1++;
}
return NULL;
}
此情况下可直接判断str2是否是str1的子串,并且返回相应的值。
当比较到第三个字符时,两字符不相等,而str1并未结束,后续还需比较,所以cur2需回到起始地址,而cur1会回到第一个相等字符的下一个字符的地址处(即第二个b的地址) 。
strtok
char * strtok ( char * str, const char * sep );
- sep参数是个字符串,定义了用作分隔符的字符集合。
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改
- 变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok使用:
/* 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;
}
strerror
char * strerror ( int errnum );
返回错误码,所对应的错误信息。
/* 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));
//出现错误时,会将错误码(一种错误对应一个错误码)放进errno中,strerror会将这个错误码对应的内容打印出来
//errno: Last error number
return 0;
}
例如:
#include<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
return 0;
}
对应错误码结果为:
memcpy
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
- 返回值为目标空间首地址。
memcpy使用:
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9 };
int arr2[10] = { 0 };
int* ret = memcpy(arr2, arr1, 16);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ret[i]);
}
return 0;
}
memcpy模拟实现:
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t count)//以字节为单位拷贝
{
assert(dest && src);
void* ret = dest;
while (count--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
memmove
void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
memmove使用:
/* 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模拟实现:
情况一:不存在内存重叠,可直接将内容移动。
情况二:存在内存重叠,且目标空间地址大于源空间地址, 如图将1移动到3的位置之后,3就会被覆盖,在移动3时实际移动的是1.
解决方法:从后往前移动,先移动4,再移动3直到所有字节移动完。
情况三:存在内存重叠,且目标空间地址小于源空间地址,如果再从后向前移动也会导致前面的内容被覆盖。
解决方法:从前往后移动,先移动3,再移动4直到所有字节移动完。
实现代码:
void* my_memmove(void* dest, const void* src, size_t count)
{
assert(dest && src);
void* ret = dest;
if (dest > src)//情况二
{
void* cur1 = (char*)src + count - 1;//从后往前移动,需先找到两个空间的末尾地址
void* cur2 = (char*)dest + count - 1;//减1是因为加count之后会直接跳到目标空间和源空间的尾地址的下一个字节的地址
while (count--)
{
*(char*)cur2 = *(char*)cur1;//void*内型指针不能直接加减或解引用操作,需强制类型转换
cur2 = (char*)cur2 - 1;//指针移动
cur1 = (char*)cur1 - 1;
}
}
else//情况1一,三
{
void* cur1 = (char*)src;
void* cur2 = (char*)dest;
while (count--)
{
*(char*)cur2 = *(char*)cur1;
cur2 = (char*)cur2 + 1;
cur1 = (char*)cur1 + 1;
}
}
return ret;
}
memcmp
int memcmp ( const void * ptr1,
const void * ptr2,
size_t num );
- 比较从ptr1和ptr2指针开始的num个字节
- 返回值如下:
memcmp使用:
/* 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;
}
memcmp模拟实现:
int my_memcmp(const void* str1, const void* str2, int count)
{
assert(str1 && str2);
while (*(char*)str1 == *(char*)str2)
{
count--;
if (count <= 0)
{
return 0;
}
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
return (char*)str1 - (char*)str2;
}