今天我们来介绍处理字符和字符串的库函数的使用以及一些函数的模拟。会使用这些库函数,再写程序的过程中也会更灵活、更方便。下面我们一起学习。
一.字符串函数
strlen(计算字符串长度)
size_t strlen ( const char * str );
- 字符串已经’\0’作为结束标志,strlen函数返回的是在字符串中’\0’ 前面出现的字符个数(不包含’\0’ )。
- 参数指向的字符串必须要以’\0’ 结束。
- 注意函数的返回值为size_t,是无符号的(小心使用strlen之间的减法,容易出错,下面会具体举例)
strlen的模拟实现(计数器count)
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char *p)
{
int count = 0;
assert(p != NULL);
while (*p != '\0')
{
count++;
p++;
}
return count;
}
int main()
{
char arr[20] = "welcome";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
strlen的模拟实现(递归)
#include<stdio.h>
#include<assert.h>
//abcdef
//1+bcdef
//1+1+cdef
//...
size_t my_strlen2(const char *str)
{
assert(str != NULL);
if (*str != '\0')
{
return 1 + my_strlen2(str + 1);
}
else
{
return 0;
}
}
int main()
{
char arr[20] = "welcome";
int len = my_strlen2(arr);
printf("%d\n", len);
return 0;
}
strlen的模拟实现(指针-指针为中间所隔的元素个数)
#include<stdio.h>
#include<assert.h>
size_t my_strlen3(const char *str)
{
assert(str != NULL);
const char *start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[20] = "welcome";
int len = my_strlen3(arr);
printf("%d\n", len);
return 0;
}
下面我们来说说用size_t做返回类型的一个弊端,就是上面所说strlen的减法问题
#include<stdio.h>
#include<string.h>
int main()
{
char *p1 = "abc";
char *p2 = "abcd";
//if (strlen(p1) > strlen(p2)) //正确写法
if (strlen(p1) - strlen(p2)>0) //3-4
{
//strlen()函数返回size_t,相当于unsigned int
//unsigned int-unsigned int = unsigned int
//strlen(p1) - strlen(p2)=3-4=-1
//内存中的是补码:11111111 11111111 11111111 11111111
//此时被当作unsigned int处理 -> 结果大于0 ->输出hehe
printf("hehe\n");
}
else
{
printf("haha\n");
//代码输出结果hehe,但是按逻辑分析结果应该是haha,代码中出现bug
}
return 0;
}
我们在使用strlen函数做减法的时候一定要注意,一不小心就会写错,我们可以避免使用减法,可以用strlen做比较,一般也能达到效果。
strcpy(字符串拷贝)
char* strcpy(char * destination, const char * source );
- 源字符串必须以’\0’ 结束。
- 会将源字符串中的’\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
- 返回值为char*是为了实现链式访问
strcpy的模拟实现
char* my_strcpy(char *dest, const char *src)
{
char *start = dest;
assert(dest != NULL);
assert(src != NULL);
while (*dest++ = *src++)
{
;
}
return start;
}
strncpy(将指定长度的字符串进行拷贝,长度单位为字节)
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
char * strncpy ( char * destination, const char * source, size_t num );
strncpy的模拟实现
char * my_strncpy(char *dest, const char *src,size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char *start = dest;
while (num&&(*dest++ = *src++))
{
num--;
}//跳出循环,num个字符已经复制完或者num>strlen(src),将src字符已经全复制完
if (num)//num大于字符串的个数
{
while (--num)
{
*dest++ = '\0';
}
}
else
{
*dest = '\0';
}
return start;
}
strcat(字符串连接)
char * strcat ( char * destination, const char * source );
- 源字符串必须以’\0’ 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
- 字符串不能自己给自己追加,在追加的过程中会把’\0’覆盖,就会一直往后追加,程序崩溃。
strcat的模拟实现
char *my_strcat(char *dest, const char *src)
{
assert(dest != NULL);
assert(src != NULL);
char *start = dest;
//找'\0'
while (*dest != '\0')
{
dest++;
}
//拷贝数据
while (*dest++ = *src++)
{
;
}
return start;
}
strncat(将指定长度的字符串进行连接,单位为字节)
char * strncat ( char * destination, const char * source, size_t num );
- 将src所指的字符串的前num个字符添加到dest所指字符串的结尾处,并覆盖’\0’,实现字符串的连接。
- num大于源字符串的长度,连接源字符串完成就结束;num小于源字符串长度,连接完指定的字符后,直接在后面加’\0’。
strncat的模拟实现
char *my_strncat(char *dest, const char *src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char *start = dest;
while (*dest != '\0')
{
dest++;
}
while (num && (*dest++ = *src++))
{
num--;
}
if (num)
{
return start;
}
else
{
*dest='\0';
}
return start;
}
strcmp(字符串比较)
int strcmp ( const char * str1, const char * str2 );
- 标准规定:
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
- 比较两个字符串:两个字符串中的字符一对一进行比较,若哪个字符串的字符比较大,则这个字符串大,比较结束,返回相应的值;若两个字符串中的字符从开始到结束,全都相等,则这两个字符串相等。
strcmp的模拟实现
int my_strcmp(const char *s1, const char *s2)
{
assert(s1 != NULL);
assert(s2 != NULL);
while (*s1 == *s2)
{
if ('\0' == *s1)
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
strncmp(比较两个字符串的前num个字符)
int strncmp ( const char * str1, const char * str2, size_t num );
- 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
strncmp的模拟
int my_strncmp(const char *s1, const char *s2, size_t num)
{
assert(s1 != NULL);
assert(s2 != NULL);
while (num)
{
if ((*s1 == '\0') && (*s2 == '\0'))//若num大于或等于字符串长度,判断两个字符串是否完全相等
{
return 0;
}
if (*s1 == *s2)
{
s1++;
s2++;
}
else
{
break;
}
num--;
}
if (num == 0)//判断前num个字符是否相等
{
return 0;
}
return *s1 - *s2;
}
strstr(判断字符串str1是否是str2的子串)
char *strstr(const char *str, const char *strCharSet);
- 若str1是str2的子串,则strstr返回str1在str2中首次出现的位置;若str1不是str2的子串,则返回NULL
strstr的模拟实现
char *my_strstr(const char *str, const char *strCharSet)
{
assert(str != NULL);
assert(strCharSet != NULL);
const char *s1= str;
const char *s2 = strCharSet;
const char *tmp = str;
if (*strCharSet == '\0')
{
return (char*)str;
}
while (*tmp)
{
s1 = tmp;//tmp指针暂存从哪个位置开始查找子字符串,s1是一个一个字符往后查找的指针
s2 = strCharSet;//每次重新查找子字符串都从头开始
while (*s2 && *s1 && *s1 == *s2)//*s2所指的位置和*s1所指的位置都不为空
{
s1++;
s2++;
}
if (*s2 == '\0')//找到子字符串
{
return (char*)tmp;
}
tmp++;//如果没有找到子字符串,tmp就指向往后一个位置
}
return NULL;
}
strstr的应用实例
判断一个字符是不是另一个字符旋转而来的
例如:bcdea 是不是abcde旋转而来的
思路:abcde后面连接abcde,abcdeabcde,这个字符串中包括所有旋转的可能性,只需要判断是不是这个字符串的子串,就会知道是不是旋转而来的
int main()
{
char arr[20] = "abcde";
char arr1[] = "bcdea";
//字符串长度不相等,一定不是旋转得来的
if (strlen(arr) != strlen(arr1))
{
printf("不是旋转得来的\n");
return 0;
}
strncat(arr, arr, strlen(arr));//连接两个相同的字符串,构造旋转的全部可能
char *ret = strstr(arr, arr1);
if (NULL == ret)
{
printf("不是旋转得来的\n");
}
else
{
printf("是旋转得来的\n");
}
return 0;
}
strtok(根据分隔符的字符集合切割字符串,将字符串切位一段一段的字符串)
char * strtok ( char * str, const char * sep );
- sep参数是个字符串,定义了用作分隔符的字符集合
- strtok函数找到str中的一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一次调用str不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数后面的调用str参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针
strtok的使用举例
int main()
{
char arr[] = "3479676393@qq.com";
char arr1[20] = { 0 };
char arr2[] = "@.";
strcpy(arr1, arr);
char *p = NULL;
for (p = strtok(arr1, arr2); p != NULL; p = strtok(NULL, arr2))
{
printf("%s\n", p);
}
return 0;
}
strerror(获取系统错误信息或打印用户程序错误信息)
char * strerror ( int errnum );
- 返回错误码所对应的错误信息。
- 当发生错误时,所产生错误码会放在errno这个全局变量中
- errno要包含头文件#include<errno.h>
- 没有错误时,errno默认值为0
strerror使用举例
FILE *p = fopen("text.txt", "r");
if (p == NULL)
{
printf("打印错误信息:%s\n", strerror(errno));//p为NULL,没有打开文件,就会报没有打开的原因
}
else
{
printf("打开成功\n");
fclose(p);
p = NULL;
}
根据所打印的信息,就会知道打开文件失败的原因,很容易找到错误。
perror(打印错误信息)
void perror( const char *string );
- perror与strerror的功能相同,perror可以直接打印错误
- 写一些提示语句,这样可以很快定位错误位置
perror使用举例
FILE *p = fopen("text.txt", "r");
if (p == NULL)
{
perror("打开文件,打印错误信息");
}
二、字符函数
字符函数需要头文件#include<ctype.h>
字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
字符转换
int tolower(int c); //将字符转为小写
int toupper(int c); //将字符转为大写
字符转换实例
字符串中的字符都转为大写
#include<stdio.h>
#include<ctype.h>
int main()
{
int i = 0;
char str[] = "Test String.\n";
while (str[i])
{
if (islower(str[i]))
{
str[i] = toupper(str[i]);
}
printf("%c", str[i]);
i++;
}
return 0;
}
三、内存操作函数
memcpy(内存拷贝函数)
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
- memcpy可以复制任意数据类型。
memcpy的模拟实现
void *my_memcpy(void *dest, const void *src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
void *start = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1 ;
src = (char*)src + 1;
}
return start;
}
memmove(内存拷贝函数)
void * memmove ( void * destination, const void * source, size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
memmove的模拟实现
void *my_memmove(void *dest,const void *src, size_t num)
{
void *ret = dest;
assert(dest != NULL);
assert(src != NULL);
//src<dest 从后向前
//src>dest 从前向后
//src>dest+num 从前向后,从后向前都可
//后面两种情况可以归为一种 从前向后
if (src > dest)
{
//前-后
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;
}
memcmp(内存比较)
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
- 比较从ptr1和ptr2指针开始的num个字节
- 若返回值大于0,*ptr1>*ptr2
- 若则返回值小于0,*ptr1<*ptr2
- 若则返回值等于0,*ptr1=*ptr2
memset(内存设置)
void *memset( void *dest, int c, size_t count );
- 以dest位置起后面count个字节用c代替,并返回dest
- 将数全部重新设置为0比较常见
- 一个字节一个字节设置内存
总结
在用函数之前,要把函数的返回值,参数,以及相关的条件了解清楚,这样可以避免一些弯路。要注意strlen函数相关的减法问题,容易出错。