字符函数和字符串函数
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串中或者字符数组中。字符串常量适用于那些对它不做修改的字符串函数。
strlen
strlen函数的介绍
size_t strlen ( const char * str );
strlen函数用于求字符串的长度
注:
- 字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
- 参数指向的字符串必须要以 ‘\0’ 结束。否则strlen函数计算出字符串的长度可能是随机值
- 函数的返回值为size_t,是无符号的。
strlen函数的使用
int main()
{
char arr[] = "abc";
int len = strlen(arr);
printf("%d\n", len); //3
return 0;
}
strlen函数的模拟实现
size_t my_strlen(const char* str)
{
size_t count = 0;//计数器
assert(str != NULL);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
经典面试题
注:
- strlen函数的返回类型是无符号整型,上面的代码的if语句中的表达式会进行算术转换。
- 库函数中strlen的模拟实现返回类型是无符号整形这是因为字符串的长度不可能是负数(一定是大于等于0的);而在模拟实现strlen函数时返回类型可以是整形这样可以避免上述代码中所出现的情况
strcpy
strcpy函数的介绍
char* strcpy(char * destination, const char * source );
strcpy用于字符串的拷贝
注:
- 源字符串必须以 ‘\0’ 结束。
- strcpy函数会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。(目标空间可以是栈区、堆区,不可以是字符常量区)
strcpy函数的使用
int main()
{
char arr1[20] = "###########";
//arr = "hello";//err arr数组名是首元素的地址,地址是编号,hello不能放在编号上应该放在编号所指向空间里
char* p1 = "hello";
strcpy(arr1, p1); //hello'\0'#####
char arr2[] = { 'a', 'b', 'c' };
//strcpy(arr1, arr2); //err 会发生指针越界访问,程序崩溃
char* str = "xxxxxxxxxxxxxxxxxxx";
char* p2 = "hello world";
//strcpy(str, p2);//err 程序崩溃,目标空间必须是可修改的
return 0;
}
strcpy函数的模拟实现
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
strcat
strcat函数的介绍
char * strcat ( char * destination, const char * source );
strcat用于字符串追加(字符串连接)
注:
- 源字符串必须以 ‘\0’ 结束。目标字符串必须以’\0’ 结束
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改
- 使用strcat函数时字符串不能自己给自己追加('\0’被覆盖从而找不到结束标志,导致死循环)
strcat函数的使用
int main()
{
char arr1[20] = "hello \0#########";
char arr2[] = "world";
strcat(arr1, arr2);//字符串追加(连接)
printf("%s\n", arr1); //hello world'\0'####
return 0;
}
strcat函数的模拟实现
char* my_strcat(char* dest, const char*src)
{
char* ret = dest;
assert(dest && src);
//1. 找目标字符串中的\0
while (*dest)
{
dest++;
}
//2. 追加源字符串,包含\0
while(*dest++ = *src++)
{
;
}
return ret;//返回的目标空间的起始地址
}
strcmp
strcmp函数的介绍
int strcmp ( const char * str1, const char * str2 );
strcmp用于比较两个字符串的大小的
注:
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
strcmp函数的使用
int main()
{
//strcmp - 字符串比较大小的
int ret1 = strcmp("aaa", "aaa");
printf("%d\n", ret1);
int ret2 = strcmp("abbb", "abq");//-1
printf("%d\n", ret2);
int ret3 = strcmp("abbb", "ab");//1
printf("%d\n", ret3);
return 0;
}
strcmp函数的模拟实现
//版本一
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
if (*s1 > *s2)
{
return 1;
}
else
{
return -1;
}
}
//版本二
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
注:C语言标准规定strcmp库函数实现时满足如果第一个字符串大于第二个字符串返回大于零的数,如果第一个字符串小于第二个字符串返回小于零的数,如果第一个字符串等于第二个字符串返回等于零的数即可。而VS编译器中在实现strcmp时第一个字符串大于第二个字符串返回1,第一个字符串小于第二个字符串返回-1,第一个字符串等于第二个字符串返回0。这只是在VS编译器中strcmp返回值是这样的而在其他编译器下strcmp的返回值就不一定是1,-1,0,但是在所有编译器下strcmp函数的返回值一定是大于零、小于零、等于零(因为这是C语言标准规定的)
strncpy
strncpy函数的介绍
char * strncpy ( char * destination, const char * source, size_t num );
strncpy用于拷贝num个字符
注:
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
- VS编译器中的strcpy_s函数相当于是strncpy函数的一种版本,但是相较于strncpy增添了一些机制
- strncpy中的num如果大于源字符串中的字符个数,拷贝完源字符串中的字符之后会自动在目的字符串之后将剩余的num个字符补’\0’
strncpy函数的使用
int main()
{
char arr1[20] = "abcdefghi";
char arr2[] = "qwer";
strncpy(arr1, arr2, 6); //qwer\0\0ghi
char arr3[20] = "abcdef";
strncpy(arr3, arr2,2); //qwcdef
return 0;
}
strncpy函数的实现
strncat
strncat函数的介绍
char * strncat ( char * destination, const char * source, size_t num );
strncat用于追加num个字符
注:
- strncat函数中的num如果大于源字符串中的字符个数,那么在追加到源字符串中的‘\0’时会自动结束(无论num还有多少没追加)。如果小于源字符串中的字符个数,那么追加num个源字符串中的字符会在目的字符串之后自动添加’\0’
strncat函数的使用
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strncat(arr1, arr2, 10); //hello world\0
char arr3[20] = "hello ";
char arr4[] = "world";
strncat(arr3, arr4, 3); //hello wor\0
return 0;
}
strncat函数的实现
strncmp
strncmp函数的介绍
int strncmp ( const char * str1, const char * str2, size_t num );
strncmp用于比较num个字符
注:
- 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
strncmp函数的使用
int main()
{
char* p = "abcdef";
char* q = "abcqwert";
int ret1 = strcmp(p, q); //-1
int ret2 = strncmp(p, q, 3); //0
int ret3 = strncmp(p, q, 4); //-1
return 0;
}
strncmp的实现
注:
- strcmp、strcpy、strcat都是长度不受限制的字符串函数
- strncmp、strncpy、strncat都是长度受限制的字符串函数
strstr
strstr函数的介绍
char * strstr ( const char * str1, const char * str2 );
strstr函数用于在字符串中找另一个字符串
注:
- 如果strstr函数找到了str2所指向的字符串在str1所指向字符串中的位置,那么返回一个在str1所指向的字符串中第一次出现str2所指向的字符串的指针。如果strstr函数没有找到str2所指向的字符串在str1所指向字符串中的位置,那么返回一个空指针
strstr函数的使用
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
//在arr1中查找是否包含arr2数组
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了:%s\n", ret);
}
return 0;
}
strstr函数的模拟实现
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
if (*str2 == '\0')
{
return (char*)str1;
}
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cp;
}
cp++;
}
return NULL;
}
注:
- strstr库函数在VS编译器中也是用上面的方法实现的
- KMP算法(KMP算法是字符串查找算法的一种)比暴力查找算法效率要高
strtok
strtok函数的介绍
char * strtok ( char * str, const char * sep );
strtok函数用于切割字符串的
注:
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok函数的使用
int main()
{
char arr[] = "lc17612269709@163.com hello";
char* p = "@. ";
char tmp[30] = { 0 };
strcpy(tmp, arr);
char* ret = NULL;
for (ret = strtok(tmp, p); ret != NULL; ret=strtok(NULL, p))
{
printf("%s\n", ret);
}
//lc17612269709\0163\0com\0hello
return 0;
}
注:
- strtok函数实现记忆功能需要通过静态变量(猜测),它会每次记录上一次查找分隔符标记的位置。
- 如果一个函数是依赖于全局变量,这个函数就不够通用不够灵活。
- strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。
- 分割字符串时不能用’\0’作为分割符去分割字符串(字符串的结束标志是’\0’)
strerror
-
使用库函数的时候,调用库函数失败时都会设置错误码。
-
C语言中存在全局的错误码(int errno;)(全局变量),只要调用库函数发生了错误就把错误码放到这个errno变量里
-
使用全局错误码需要引用头文件errno.h
char * strerror ( int errnum );
strerror函数用于打印错误码对应的错误信息
注:
- strerror函数返回错误码,所对应的错误信息
strerror函数的使用
实例一:
实例二:
perror
void perror ( const char * str );
perror函数用于打印错误信息
注:
- strerror函数是把错误码转换成错误信息,是否需要打印看程序员需求
- perror函数直接打印错误信息
- perror函数的功能是把错误码转化为错误信息),再打印错误信息(包含了自定义的信息)。
- perror函数用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数str所指的字符串会先打印出,加上:后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
perror函数的使用
实例一:
字符分类函数
函数 | 如果它的参数符合下列条件就返回真 |
---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
字符分类函数的使用:
实例一:isdigit
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = '#';
int ret = isdigit(ch);
printf("%d\n", ret);
return 0;
}
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = '0';
int ret = isdigit(ch);
printf("%d\n", ret);
return 0;
}
注;isdigit 如果是数字字符返回非0的值,如果不是数字字符返回0
实例二:islower
注:用法与上方isdigit类似
字符转换函数
int tolower ( int c ); // 大写转小写
int toupper ( int c ); // 小写转大写
字符转换函数的使用:
实例一:
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[20] = { 0 };
scanf("%s", arr);
int i = 0;
while (arr[i] != '\0')
{
if (isupper(arr[i]))
{
arr[i] = tolower(arr[i]);
}
printf("%c", arr[i]);
i++;
}
return 0;
}
注:toupper用法与tolower用法类似
memcpy
本函数需要调用string.h的头文件
void * memcpy ( void * destination, const void * source, size_t num );
memcpy函数用于内存拷贝(不包括内存重叠)
注:
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。(即destination是复制的,source是被复制的,num是字节数)
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
memcpy函数的使用:
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
memcpy(arr2, arr1, 20);
return 0;
}
memcpy函数的模拟实现:
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dst, const void* src, size_t num)
{
void* ret = dst;
assert(dst && src);
while (num--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;
}
注意:建议使用上面实现字节拷贝的方法(这种是没问题的),不建议使用*(char *)dst++ = *(char *)src++;这样的字节拷贝方法(在.c源文件下可以编过,但在.cpp源文件下不可以编过(这种方法是有问题的))。
补充:
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dst, const void* src, size_t num)
{
void* ret = dst;
assert(dst && src);
while (num--)//4 3 2 1
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
//*(char*)dst++ = *(char*)src++;
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
my_memcpy(arr1+2, arr1, 20);
return 0;
}
解析
注:memcpy函数应该拷贝不重叠的内存
注:C语言标准规定memcpy函数只要实现不重叠拷贝即可,而VS中的实现既可以拷贝不重叠也可以拷贝重叠内存
memmove
void * memmove ( void * destination, const void * source, size_t num );
memmove函数用于内存拷贝(包括内存重叠)
注:
- 与memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
memmove函数的使用:
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1+2, arr1, 20);
return 0;
}
注:memcpy函数只要实现不重叠拷贝就可以了,而VS中实现即可以拷贝不重叠也可以拷贝重叠内存
memmove函数的模拟实现
分析
#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, const void* source, size_t num)
{
assert(dest && source);
void* ret = dest;
void* r = dest;
void* s = source;
if (dest < source)
{
//从前向后
while (num--)
{
*(char*)dest = *(char*)source;
dest = (char*)dest + 1;
source = (char*)source + 1;
}
return ret;
}
else
{
//从后向前
while (num--)
{
r=(char*)dest + num;
s=(char*)source + num;
*(char*)r = *(char*)s;
//*((char*)dest + num) = *((char*)source + num);
}
return ret;
}
}
memmcmp
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
memcmp函数用于内存比较
注:
- 比较从ptr1和ptr2指针开始的num个字节
- 返回值与strcmp类似(返回值设计思路一样)
memcmp函数的使用:
int main()
{
float arr1[] = { 1.0,2.0,3.0,4.0 };
float arr2[] = { 1.0,3.0 };
int ret=memcmp(arr1, arr2, 8);
printf("%d\n", ret);
return 0;
}
memset
void * memset ( void * ptr, int value, size_t num );
memset函数用于内存设置
注:
- 指的是前ptr所指向的num个字节的内容设置成指定的value的值
- memset函数功能是以字节为单位设置内存的
memset函数的使用:
int main()
{
int arr[10] = { 0 };
memset (arr,1,20 );
return 0;
}