C语言 一些字符串库函数和其模拟实现-爱代码爱编程
字符串函数(下列头文件均为“string.h”)
1.strlen (求字符串长度)
原型
- 字符串计数不需修改字符串,因此用conut修饰;
模拟实现
#include<stdio.h>
#include<Windows.h>
#include<assert.h>
#pragma warning(disable:4996)
//方法1:计数器法--需要单独设置一个变量
size_t myStrlen1(const char *arr) //字符串不需修改字符串,因此用conut修饰
//size_t在vs2013指无符号整型unsigned int
{
assert(arr); //assert函数用来判断字符串是否不为空
int count = 0;
while (*arr)
{
count++;
arr++;
}
return count;
}
//方法2:递归法--不创建临时变量
size_t myStrlen2(const char *arr)
{
assert(arr);
if (*arr == '\0')
{
return 0;
}
else
{
return 1 + myStrlen2(arr + 1);
}
}
//方法3:指针法--利用尾指针-头指针获得长度
size_t myStrlen3(const char *arr)
{
assert(arr);
char *p = arr;
while (*p != '\0')
{
p++;
}
return p - arr;
}
int main()
{
const char *arr = "abcd1234";
int len1 = myStrlen1(arr);
printf("方法1字符串arr长度为:%d\n", len1);
int len2 = myStrlen2(arr);
printf("方法2字符串arr长度为:%d\n", len1);
int len3 = myStrlen3(arr);
printf("方法3字符串arr长度为:%d\n", len1);
system("pause");
return 0;
}
2.strcpy (字符串拷贝)
原型
- 将后面的参数拷贝到前面的参数,无需修改后者,因此后面参数用const修饰;
- 注意后面的字符串参数大小不能大于前者,否则会报错;
- 该函数返回值为char *类型,这是为了支持该类库函数的链式调用;
模拟实现
#include<stdio.h>
#include<Windows.h>
#include<assert.h>
#pragma warning(disable:4996)
//编程习惯之-变量名:ret->result(结果);src->source(源数据);dest->destination(目标)
char *myStrcpy(char *dest, const char *src)
{
assert(src); assert函数用来判断字符串是否不为空
assert(dest);
char * ret = dest;
while ((*dest++ = *src++));
return ret;
}
int main()
{
const char *src = "abcd";
char dest[30] = { "12345" };
printf("before:%s\n",dest);
printf("after:%s\n", myStrcpy(dest, src));
system("pause");
return 0;
}
3.strcat(字符串拼接)
原型
- 将后面的参数拼接到前面的参数,无需修改后者,因此后面参数用const修饰;
- 前者字符串空间须足够大以容纳拼接后字符串;
- 注意不能进行自我拼接(这样拼接时会出现原始串不断增加的情况);
- 该函数返回值为char *类型,这是为了支持该类库函数的链式调用;
模拟实现
#include<stdio.h>
#include<Windows.h>
#include<assert.h>
#pragma warning(disable:4996)
char *myStrcpy(char *dest, const char *src)
{
assert(src);
assert(dest);
char * ret = dest;
while (*dest) //将指针指向dest字符串尾部
{
dest++;
}
while ((*dest++ = *src++)); //从尾部开始将后者字符串拼接
return ret;
}
int main()
{
const char *src = "abcd";
char dest[30] = { "12345" };
printf("before:%s\n", dest);
printf("after:%s\n", myStrcpy(dest, src));
system("pause");
return 0;
}
4.strcmp(字符串比较)
原型
- 两字符串进行大小比较,无需修改字符串,因此两参数用const修饰;
- 字符串比较是通过两字符串第一个不同的字符的ASSIC码值来判断;
- 若str1>str2,返回1;若str1=str2,返回0;若str1<str2,返回-1;
模拟实现
#include<stdio.h>
#include<Windows.h>
#include<assert.h>
#pragma warning(disable:4996)
int myStrcmp(const char *str1, const char *str2)
{
int ret = 0;
assert(str1);
assert(str2);
//两字符串指针相减,若相同则看下一个字符,直到出现大小不同的字符或'\0'。
while (!(ret = *(unsigned char *)str1 - *(unsigned char *)str2) && *str2)
{
++str1, ++str2;
}
if (ret < 0)
{
ret = -1;
}
else if (ret>0)
{
ret = 1;
}
return (ret);
}
int main()
{
const char *str1 = "abcd4";
const char *str2 = "abcd5";
int comp = myStrcmp(str1, str2);
if (comp > 0)
{
printf("str1>str2\n");
}
else if (comp < 0)
{
printf("str1<str2\n");
}
else
{
printf("str1=str2\n");
}
system("pause");
return 0;
}
5.strncpy(长度受限字符串拷贝)
原型
- “安全拷贝”函数,即相比strcpy函数来说,参数增加了需要拷贝后者字符串的长度参数;
- 拷贝后不会自带’\0’,需要自己加;
6.strncat(长度受限字符串拼接)
原型
- “安全拼接”函数,相比strcat函数来说,参数增加了需要拼接后者字符串的长度参数;
- 默认会在拼接后的字符串加’\0’;
7.strncmp(长度受限字符串比较)
原型
- “安全比较”函数,比较两字符串的前num个字符;
- 运算规则同strcmp;
8.strstr(字符串子串查找)
原型
- 该函数能返回str1里第一次出现str2函数的第一个字符地址;
应用
- 替换一段字符串的某一个字串
- 通过while循环实现查找一段字符串中某字符串出现的位置即次数;
(以下为两个strtr的应用举例)
//strstr example_1 替换
//将一个长字符串的一个小字符串替换
int main()
{
char str[] = "This is a simple string.";
char *pch;
pch = strstr(str, "simple");
strncpy(pch, "sample", 6);
puts(str);
system("pause");
return 0;
}
//strstr example_2 查找
//从一个长字符串找到小字符串的具体位置
int main()
{
const char *s1 = "abcd1234worldABCD-1world,nkworld.";
const char *s2 = "world";
const char *s = s1;
int count = 0;
char *sub_str = NULL;
while (1)
{
sub_str = strstr(s, s2);
if (sub_str == NULL)
{
break;
}
count++;
//sub_str = strcpy(sub_str, s2);
printf("第%d个%s的位置:%d\n", count, s2, sub_str - s1);
s = sub_str + 1;
}
system("pause");
return 0;
}
模拟实现
char *myStrstr(const char*str1, const char *str2)
{
assert(str1);
assert(str2);
char* str_p = (char*)str1;
char* str_q = (char*)str2;
char* judge_p = str_p; //外部循环瞄点
if (*str2 == '\0')
{
return NULL;
}
while (*judge_p!='\0')
{
const char* move_p = judge_p ; //内部循环str1比较指针
char* judge_q = str_q; //内部循环str2比较指针
while (*move_p && *judge_q && *move_p == *judge_q)
{
move_p++;
judge_q++;
}
if (*judge_q == '\0')
{
return judge_p;
}
judge_p++;
}
return NULL;
}
int main()
{
const char *s1 = "abcd1234world5678";
const char *s2 = "world";
char *s = myStrstr(s1, s2);
printf("before:%s\n", s1);
printf("after:%s\n", s);
system("pause");
return 0;
}
9.strtok(字符串分割)
原型
- 分割字符串函数:遍历参数一所指向的字符串,遇到参数二所包含的分割字符后即返回对应字符串首指针;
- 第二个参数可存放多个分割字符,比如:",._ "(双引号内的字符都是,包括空格);
- 一次使用只能返回一个分割字串,因为只有一个返回值;
- 即如果要多次对一个字符串使用该函数,第一次使用后下一次传参只能将参数一设为NULL,否则将清空记录,从新对该字符串起始位置遍历;
- strtok库函数内部有static变量保存原字符串地址作为静态全局变量,当分割一个后,自动移向第一个分隔符后一个地址;
- 分割字符串本质是将原字符串的对应分隔字符转为’\0’;
- 【注意】由上个条例可知该库函数会修改参数一,故参数一所指向的字符串不能定义在字符常量区;
应用
- 通过多次引用该函数将一个长字符串分割为多个小字符串;
- 注意:由上面的特性4,5可知,重复调用函数以分割多个子字符串时,第一个参数不用变;
//strtok example_1
//将一个长字符串分割为多个小字符串
int main()
{
char s1[] = "You are so beautiful,but not belong to me.";
char *s = strtok(s1, ", .");
while (s != NULL)
{
printf("%s\n", s);
s = strtok(NULL, ", .");
}
system("pause");
return 0;
}
10.strerror(错误报告
原型
- 通过错误码返回错误信息;
- 错误码的理解:Eg:
return 0;
中的0就是main函数返回的一个“错误码”,一般0代表success; - 错误码的存在方便计算机,错误信息方便程序员;计算机擅长处理数字但不擅长处理字符串,与人相反;
- C语言系统全局变量 errno ,在使用C库函数出错时,会自动设置相应错误信息的错误码;
应用
#include<stdio.h>
#include<Windows.h>
#include<string.h>
#include<errno.h>
#pragma warning(disable:4996)
//strerror example
//通过打开错误文件了解错误信息与错误码
//使用系统全局变量errno必须加头文件<errno.h>
int main()
{
printf("before errno:%d\n", errno); //输出errno原始内容
FILE * pf = NULL;
pf = fopen("test.text", "r");
if (pf == NULL)
{
printf("after errno:%d\n", errno); //输出errno遇到错误后自动修改的内容
printf("ERROR Opening file test.text:%s\n", strerror(errno));
}
system("pause");
return 0;
}
字符分类函数
字符分类函数函数(若其参数符合下列条件就返回真) 头文件:<ctype.h>
iscntrl :任何控制字符
isspace :空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit :十进制数字 0~9
isxdigit :十六进制数字,包括所有十进制数字,小写字母af,大写字母AF
islower :小写字母a~z
isupper :大写字母A~Z
isalpha :字母az或AZ
isalnum :字母或者数字,az,AZ,0~9
ispunct :标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph :任何图形字符
isprint :任何可打印字符,包括图形字符和空白字符
内存操作函数(下列头文件均为“string.h”)
- mem系列库函数的操作基本单位是字节
- 参数类型为void *表明可以对任何类型进行该操作
1.memcpy(按字节内存拷贝)
原型
- 不能进行自我拷贝,这也是与memmove的区别所在;(但现在的编辑器已支持,意味着两函数没有区别)
模拟实现+特殊应用
#include<stdio.h>
#include<Windows.h>
#include<string.h>
#include<assert.h>
#pragma warning(disable:4996)
//模拟实现memcpy
//test2与test3为特殊应用
void* myMemcpy(void* dest, const void* src, size_t len)
{
void* ret = dest;
assert(dest);
assert(src);
while (len--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
const char* src = "abcd";
char dest[30] = { "12345" };
//test1.普通测试
printf("before:%s\n", dest);
printf("after:%s\n", (char*)myMemcpy(dest, src, strlen(src)));
//test2.正向自我拷贝:
myMemcpy(dest, dest + 1, (strlen(dest) - 1));
printf("正向自我拷贝:%s\n", dest);
//test3.逆向自我拷贝
myMemcpy(dest + 1, dest, (strlen(dest) - 1));
printf("逆向自我拷贝:%s\n", dest);
system("pause");
return 0;
}
- 逆向拷贝的错误体现了其不能自我拷贝
- 具体原因:出现内存重叠,该函数一边拷贝一边将拷贝后的数据当作新数据拷贝给下一个;
2.memmove( 可按字节拷贝两个重叠内存)
原型
模拟实现+特殊应用
#include<stdio.h>
#include<Windows.h>
#include<string.h>
#include<assert.h>
#pragma warning(disable:4996)
//模拟实现memmove
//test2与test3为特殊应用
void* myMemmove(void* dest, const void* src, size_t len)
{
void* ret = dest;
assert(dest);
assert(src);
char* p = (char*)dest;
char* q = (char*)src;
if (p > q && p < q + len) //逆向自我赋值情况,特殊处理
{
//从尾部赋值即可 从右向左
p += (len-1);
q += (len-1);
while (len--)
{
*p = *q;
p--;
q--;
}
}
else
{
//从左向右
while (len--)
{
*p = *q;
p++;
q++;
}
}
return ret;
}
int main()
{
const char* src = "abcd";
char dest[30] = { "12345" };
//test1.普通测试
printf("before:%s\n", dest);
printf("after:%s\n", (char*)myMemmove(dest, src, strlen(src)));
//test2.正向自我拷贝:
myMemmove(dest, dest + 1, (strlen(dest) - 1));
printf("正向自我拷贝:%s\n", dest);
//test3.逆向自我拷贝
myMemmove(dest + 1, dest, (strlen(dest) - 1));
printf("逆向自我拷贝:%s\n", dest);
system("pause");
return 0;
}
- 逆向拷贝未出错,与memcpy函数差异既体现于此;
- 对于库函数来说,方法更复杂也更完善,现如今两函数无差别;
3.memset(按字节内存赋值)
原型
- 功能:将ptr所指向的地址逐字节将num个字节初始化为value;
- size_t为无符号整数类型简写;
应用
#include<stdio.h>
#include<Windows.h>
#include<string.h>
#pragma warning(disable:4996)
//memset可以使数组的初始化不用循环
int main()
{
int a[5];
memset(a, 0, sizeof(a)); //注意不是sizeof(a) / sizeof(a[0])
int b[5];
memset(b, 1, sizeof(b)); //按字节初始化 故赋值1不会达到预期
for (int i = 0; i < 5; i++)
{
printf("%d ", a[i]);
}
printf("\n");
for (int j = 0; j < 5; j++)
{
printf("%d ", b[j]);
}
printf("\n");
system("pause");
return 0;
}
4.memcmp(比较内存内容大小)
原型
- 比较时严格按照ASCII码处理,包括数字类型;
- 可以比较ASCII任何字符,而strcmp只能比较字符;
- 若ptr1>ptr2,返回1;若ptr1=ptr2,返回0;若ptr1<ptr2,返回-1;