文章目录
0.前言
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
字符串常量 适用于那些对它不做修改的字符串函数.
1.函数介绍
strlen
#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)//不希望arr内容被修改
{
assert(str != NULL);//断言
//char* end = str;//把安全的str交给了安全的end,会报警告,end也要加const
const char* end = str;
while (*end != '\0')
{
end++;
}
return end - str;//指针-指针得到的是元素个数
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
库函数里的strlen返回值是size_t,也就是unsigned int
size_t也有其缺点
#include<stdio.h>
#include<string.h>
int main()
{
if (strlen("abc") - strlen("abcdef") < 0)
{
printf("1\n");
}
else
{
printf("2\n");//会输出2,size_t返回的始终是正数
}
return 0;
}
//解决方案:
1.把strlen返回值强制类型转化为int
2.或者先用strlen计算出结果,再拿结果做运算
注意:参数指向的字符串必须要以 ‘\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).
Return Value
Each of these functions returns the destination string. No return value is reserved to indicate an error.
-
拷贝字符串
-
源字符串必须以 ‘\0’ 结束。
-
会将源字符串中的 ‘\0’ 拷贝到目标空间。
-
目标空间必须足够大,以确保能存放源字符串。
-
目标空间必须可修改。不能是常量字符串
printf遇到\0也会停止,也不打印\0
模拟实现
#include <stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
//*src不能被修改
{
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++)
{
;//先拷贝后++
//既拷贝了,又让斜杠0停止了
}
return ret;
}
int main()
{
char arr1[20] = { "xxxxxxxxxxxxx" };
char arr2[] = { "hello" };
printf("%s\n", my_strcpy(arr1, arr2));//链式访问
return 0;
}
关于指针:
- 指针不知道赋值为什么,就赋值为NULL
- 指针使用完后,要赋值为NULL
strcat
char*strcat ( char* destination, const char*source );
Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null character is included at the end of the new string formed by the concatenation of both in destination.
Return Value
Each of these functions returns the destination string (strDestination). No return value is reserved to indicate an error.
- 源字符串必须以 ‘\0’ 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
模拟实现strcat
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);//判断是否为空指针
//1.先找到目标空间的\0
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));
return 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 */
}
//__cdecl是一种调用约定,不用管它
strcmp
int strcmp( const char *string1, const char *string2 );
Return Value
< 0 string1 less than string2
0 string1 identical to string2
> 0 string1 greater than string2
如果直接用==比较字符串
if("abc" == "abq");
这里比较的a的地址和另一个a的地址,肯定不相等
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc";
char arr2[] = "abd";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);//-1
return 0;
}
模拟实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
//实际上比较的是ASCII值大小
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abd";
int ret1 = strcmp(arr1, arr2);
int ret2 = my_strcmp(arr1, arr2);
printf("%d\n", ret1);//-1
printf("%d\n", ret2);//-1
return 0;
}
改进一下:
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
改成
return *str1 - *str2;
库函数源代码:
int __cdecl strcmp(
const char* src,
const char* dst
)
{
int ret = 0;
while ((ret = *(unsigned char*)src - *(unsigned char*)dst) == 0 && *dst)
{
++src, ++dst;
}
return ((-ret) < 0) - (ret < 0); // (if positive) - (if negative) generates branchless code
}
长度受限制的字符串函数
strncpy
char *strncpy( char *strDest, const char *strSource, size_t count );
count
Number of characters to be copied
Return Value
Each of these functions returns strDest. No return value is reserved to indicate an error.
如果源数据不够count的个数,多余的会用\0替代
int main()
{
char arr1[] = "xxxxxxxxxx";
char arr2[] = "hello world";
strncpy(arr1, arr2, 5);
printf("%s\n", arr1);//helloxxxxx
return 0;
}
int main()
{
char arr1[] = "xxxxxxxxxx";
char arr2[] = "he";
strncpy(arr1, arr2, 5);
printf("%s\n", arr1);//he
return 0;
}
strncat
char *strncat( char *strDest, const char *strSource, size_t count );
追加完之后会主动放一个\0在后面的
int main()
{
char arr1[20] = "helloxxxxxxx";
char arr2[] = "hello";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);//helloxxxxxxxhello
return 0;
}
从\0开始追加,并把开始的那个\0覆盖掉
int main()
{
char arr1[20] = "hello\0xxxxxx";
char arr2[] = "hello";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);//hellohello
return 0;
}
如果追加的个数大于元数据元素个数
int main()
{
char arr1[20] = "hello\0xxxxxx";
char arr2[] = "hello";
strncat(arr1, arr2, 7);
printf("%s\n", arr1);//hellohello
return 0;
}
//也是只追加到\0为止,把源数据追加完了就不再继续追加了
strncmp
int strncmp( const char *string1, const char *string2, size_t count );
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqqqq";
int ret = strncmp(arr1, arr2, 4);//d小于q,应该返回的是-1
printf("%d\n", ret);//-1
return 0;
}
strstr
字符串查找函数
char *strstr( const char *string, const char *strCharSet );
Find a substring.
Return Value
Each of these functions returns a pointer to the first occurrence of strCharSet in string, or NULL if strCharSet does not appear in string. If strCharSet points to a string of zero length, the function returns string.//返回第一次找到的地址
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
if (NULL == ret)
{
printf("找不到\n");
}
else
{
printf("%s\n", ret);//bcdefabcdef
}
return 0;
}
模拟实现strstr
abbbcdefbbcdef
bbc
第一次匹配失败时,得回到str的第二个b上重新开始,但是由于如果直接让str和substr移动的话,就找不到一开始的起始位置了,因此用s1,s2来替代
每一次查找失败,需要下次开始时多移动一步,就需要有个cur来++
动手画一下图就好理解了
char* my_strstr(const char* str, const char* substr)
{
const char* s1 = str;
const char* s2 = substr;
const char* cur = str;
assert(str && substr);
if (*substr == '\0')
{
return (char*)str;
//str本身是安全的指针,有const修饰,如果不强制类型转换,返回成char*这个不安全指针会报警告
}
while (*cur != '\0')
{
s1 = cur;
s2 = substr;
while (*s1 != '\0' && *s2 != '\0' && *s1==*s2)//优先级 == > != > &&
//可以优化为 while (*s1 && *s2 && *s1==*s2 )
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cur;
}
cur++;
}
return NULL;//cur从头走到尾了都找不到
}
int main()
{
char arr1[] = "abbbcdefbbcdef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (NULL == ret)
{
printf("找不到\n");
}
else
{
printf("%s\n", ret);//bbcdefbbcdef
}
return 0;
}
缺点:算法效率较低,可以用KMP算法实现
库函数源代码
#include<string.h>
char*
strstr(register const char* s1, register const char* s2)
{
while(s1 && *s1) {
if(strncmp(s1, s2, strlen(s2)) == 0)
return ( char*)s1;
s1 = strchr(s1+1, *s2);
}
return NULL;
}
KMP算法
一种改进的字符串匹配算法,核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的
1.为什么主串i不回退
2.j的回退位置
目的:i不回退,j回退到一个特定的位置
问题:假设有这么一个串,怎么能确定这个j回退的位置呢
因为i不回退,所以要尽量在主串中找到和子串匹配的一部分串
所以第一次j回退到2的位置
next数组:保存子串某个位置匹配失败后,回退的位置
求next数组:
如果找不到2个相等的真子串,那么此时next里面放的就是0
找到的话,next数组里放的就是那两个串单独的长度
a b a b c a b c d a b c d e
-1 0 0 1 2 0 1 2 0 0 1 2 0 0
一次最多增加1
假设next[i] = k 推出公式:
又长度相等,即k-1-0 = i-1-x
则x = i-k
如果p[i] == p[k],中间的是p数组,8下标是a,3下标也是a
给左边加上p[k],给右边加上p[i]
那么在next[i] = k的基础上且p[i] == p[k],推出了,next[i+1] = k+1
如果p[i] != p[k]呢?
那就一直回退,直到找到p[i] == p[k],就能利用公式next[i+1] = k+1
此时next[6] = 1
KMP实现
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
void GetNext(const char* sub, int* next, int lenSub)
{
next[0] = -1;
if (lenSub == 1)//只有1个元素,next数组只能赋值1个
{
return;
}
next[1] = 0;
int i = 2;
int k = 0;//i前一项的k
//注意手算next数组与用代码算next数组区别,i还没求,得先求i-1
//手算:p[i] == p[k] --》next[i+1] = k+1
//代码算:p[i-1] == p[k] --》next[i] = k+1
while (i<lenSub)
{
if (k == -1 || sub[i-1] == sub[k])
{
next[i] = k + 1;
i++;
k++;
}
else//不相等,k需要往回退到next对应的下标处,再看p[i-1] == p[k]是否成立
//如果k一直回退,退到-1,此时k越界了,且说明中间是找不到2段相等的子串,即next[i]=0
{
k = next[k];
}
}
}
int KMP(const char* str, const char* substr, int pos)
{
assert(str && substr);
int lenStr = strlen(str);
int lenSub = strlen(substr);
if (lenStr == 0 || lenSub == 0)
{
return -1;
}
if (pos < 0 || pos >= lenStr)
{
return -1;
}
int* next = (int*)malloc(sizeof(int)*lenSub);//开辟相应大小的next数组
assert(next != NULL);
GetNext(substr, next, lenSub);
int i = pos;//遍历主串
int j = 0;//遍历子串
while (i < lenStr && j < lenSub)
{
if (j == -1 || str[i] == substr[j])
{
i++;
j++;
}
//注意,如果第一个字符就匹配失败,j会回到-1,会产生数组越界
//此时j恰好需要++回到0,i也应该指向下一个
else
{
j = next[j];//不相等回退到next数组中相应的j下标
}
}
free(next);//next置空
if (j >= lenSub)
{
return i - j;//找到了
}
return -1;//遍历完之后都找不到
}
int main()
{
printf("%d\n", KMP("ababcabcdabcde", "abcd", 0));//5
printf("%d\n", KMP("ababcabcdabcde", "abcdf", 0));//-1 找不到
printf("%d\n", KMP("ababcabcdabcde", "ab", 0));//0 一开始就有
return 0;
}
代码算next数组
k回退到-1
next数组优化
0 1 2 3 4 5 6 7 8
a a a a a a a a b
-1 0 1 2 3 4 5 6 7
假设在5下标匹配失败,就要回到4位置,再回到3位置,一直回到0位置
为何不一步回到0位置呢?前面的字符都一样,第5个不匹配,前面肯定也都不匹配
nextval数组:
0 1 2 3 4 5 6 7 8
a a a a a a a a b
-1 0 1 2 3 4 5 6 7 --next值
-1 -1 -1 -1 -1 -1 -1 -1 7 --nextval值
1.回退到的位置和当前字符一样,就写回退到那个位置的next值
2.如果回退到的位置和当前字符不一样,就写当前字符原来的next值
选项答案第一个next值是从0开始的,所以需要+1
strtok
char *strtok( char *strToken, const char *strDelimit );
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
- strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。//由此猜测应该是用了静态变量
- strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
- strtok函数找第一个标记的时候,函数的第一个参数不是NULL
- strtok函数找非第一个标记的时候,函数的第一个参数是NULL
非常烂的使用方法
#include<stdio.h>
#include<string.h>
int main()
{
const char* p = "@.";//这个无需顺序区分
char arr[] = "yzq2076188013@qq.com";
char buff[50] = { 0 };
strcpy(buff, arr);
char* str = strtok(buff, p);//yzq2076188013
printf("%s\n", str);
str = strtok(NULL, p);//qq
printf("%s\n", str);
str = strtok(NULL, p);// com
printf("%s\n", str);
return 0;
}
优雅使用
//巧妙使用for循环
int main()
{
const char* p = "@.";//这个无需顺序区分
char arr[] = "yzq2076188013@qq.com";
char buff[50] = { 0 };
strcpy(buff, arr);
char* str = NULL;
for (str = strtok(buff, p); str != NULL; str = strtok(NULL, p))
{
printf("%s\n",str);
}
return 0;
}
strerror
错误码
c语言中规定了一些信息
错误码 - 错误信息
0 - "No Error"
1 -
2 -
3 -
strerror 可以把错误码翻译成错误信息
int main()
{
for (size_t i = 0; i < 10; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor
fopen
FILE *fopen( const char *filename, const char *mode );
Each of these functions returns a pointer to the open file. A null pointer value indicates an error.
c语言可以操作文件
打开文件 – fopen
当库函数使用的时候,发生错误会把errno这个全局的错误变量设置为本次执行库函数产生的错误码
errno是c语言提供的一个全局变量,可以直接使用,放在errno.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 | 任何可打印字符,包括图形字符和空白字符 |
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = 'w';
if (isspace(ch))
{
//空白字符返回非0
printf("%d\n", isspace(ch));
}
else
{
printf("%d\n", isspace(ch));//0
//非空白字符返回0
}
return 0;
}
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = '0';
if(ch >= '0' && ch <= '9')
{
//...
}
if (isdigit(ch))
{
//...这样代码才更加统一
}
return 0;
}
字符转换
<stdlib.h> and <ctype.h>
int tolower ( int c );
int toupper ( int c );
#include<stdio.h>
#include<ctype.h>
int main()
{
char ch = 0;
ch = getchar();
if (islower(ch))
{
ch = toupper(ch);
}
else
{
ch = tolower(ch);
}
printf("%c\n", ch);
return 0;
}
2.内存函数
memcpy
内存拷贝函数
void *memcpy( void *dest, const void *src, size_t count );
void*可以接收任意类型的数据
memcpy returns the value of dest
count
Number of bytes to copy
#include<stdio.h>
#include<memory.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = { 0 };
strcpy(arr2,arr1);//拷贝字符串的
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
memcpy(arr4, arr3, 20);
for (size_t i = 0; i < 5; i++)
{
printf("%d ", arr4[i]);//1 2 3 4 5
}
return 0;
}
模拟实现
#include<stdio.h>
#include<memory.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;//强制类型转换只是临时的,并不改变dest的类型
dest = (char*)dest + 1;;//void*不能直接++
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
my_memcpy(arr4, arr3+5, 5*sizeof(arr3[0]));
for (size_t i = 0; i < 5; i++)
{
printf("%d ", arr4[i]);//
}
return 0;
}
缺陷:
#include<stdio.h>
#include<memory.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;//强制类型转换只是临时的,并不改变dest的类型
dest = (char*)dest + 1;;//void*不能直接++
src = (char*)src + 1;
}
return ret;
}
void test1()
{
//将arr3中的12345拷贝放到34567中
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
my_memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
for (size_t i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);//结果竟然是1 2 1 2 1 2 1 8 9 10?
}
}
int main()
{
test1();
return 0;
}
//因为是一个字节一个字节拷贝,先把1 2拷到3 4里面了 继续从3里面拷贝时,取出来的就还是1了
优化:使用memmove函数,拷贝内存函数时,可以重叠
memmove
void *memmove( void *dest, const void *src, size_t count );
void test1()
{
//将arr3中的12345拷贝放到34567中
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
// my_memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
memmove(arr3+2, arr3, 5 * sizeof(arr3[0]));
for (size_t i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);//1 2 1 2 3 4 5 8 9 10
}
}
int main()
{
test1();
return 0;
}
其实,C语言只要求
memcpy能拷贝不重叠的内存空间就可以了
memmove去处理那些重叠内存拷贝
memmove包含了memcpy的功能
但VS的memcpy也能处理重叠内存拷贝
void test1()
{
//将arr3中的12345拷贝放到34567中
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
// my_memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
// memmove(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
for (size_t i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);//1 2 1 2 3 4 5 8 9 10
}
}
int main()
{
test1();
return 0;
}
模拟实现
#include<stdio.h>
#include<memory.h>
#include<string.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
//从前向后拷贝
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
//从后向前拷贝
else
{
while (num--)//20就变成19
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
void test1()
{
//将arr3中的12345拷贝放到34567中
int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr4[5] = { 0 };
my_memmove(arr3+2, arr3, 5 * sizeof(arr3[0]));
for (size_t i = 0; i < 10; i++)
{
printf("%d ", arr3[i]);//1 2 1 2 3 4 5 8 9 10
}
}
int main()
{
test1();
return 0;
}
memset
void *memset( void *dest, int c, size_t count );
设置内存函数,以字节为单位
#include<stdio.h>
#include<memory.h>
int main()
{
char arr[20] = { 0 };
memset(arr, 'x', 10);//前10个字符被改成x
for (size_t i = 0; i < 20; i++)
{
printf("%c ", arr[i]);//x x x x x x x x x x
}
return 0;
}
//针对整型数组也行,整型4个字节,memset是一个字节一个字节修改的,需要注意大小端
#include<stdio.h>
#include<memory.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memset(arr, 0, 10);
for (size_t i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//0 0 0 4 5 6 7 8 9 10
}
//01 00 00 00 02 00 00 00 03 00 00 00 ...小端存储
//00 00 00 00 00 00 00 00 00 00 00 00 ...
return 0;
}
memcmp
int memcmp( const void *buf1, const void *buf2, size_t count );
Return Value : Relationship of First count Bytes of buf1 and buf2
< 0 buf1 less than buf2
0 buf1 identical to buf2
> 0 buf1 greater than buf2
#include<stdio.h>
#include<memory.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,3,4,5 };
int ret = memcmp(arr1, arr2, 8);
printf("%d\n", ret);//0 前8个字节都相等返回0
return 0;
}