目录
前言
字符串函数与内存函数总汇(讲解的)
字符串函数介绍
strlen(字符串长度计算函数)
strcpy/strncpy(字符串拷贝函数)
strcat/strncat(字符串拼接函数)
strcmp/strncmp(字符串比较函数)
strstr(字符串查找函数)
strtok(字符串切分函数)
strerror(返回错误原因的描述字符串函数)
内存函数介绍
memcpy(内存拷贝函数)
memmove(内存移动函数)
memcmp(内存比较函数)
前言
本章主要讲解:
- 字符和字符串的库函数的使用和注意事项
字符串函数与内存函数总汇(讲解的)
- 求字符串长度:strlen
- 长度不受限制的字符串函数:strcpy strcat strcmp
- 长度受限制的字符串函数:strncpy strncat strncmp
- 字符串查找:strstr strtok
- 错误信息报告:strerror
- 内存操作函数:memcpy memmove memset memcmp
写在前面的话:
C语言本身是没有字符串类型的
字符串通常放在常量字符串(不做修改)中或者字符数组(可以修改)中来处理
字符串函数介绍
strlen(字符串长度计算函数)
- 定义:
size_t strlen ( const char * str );
- 注意:
- 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包 含 '\0' )
- 参数为字符指针,且是指向的字符串必须要以 '\0' 结束的指针(否则会出错)
- size_t 的定义为 typedef unsigned int size_t;即 strlen 函数返回类型为无符号整型
- 易错题:
#include <stdio.h>
#include <string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "abc";
if (strlen(str2) - strlen(str1) > 0)
{
printf("呵呵\n");
}
else
{
printf("哈哈\n");
}
return 0;
}
//输出结果:呵呵
//解释:对于strlen函数返回的是无符号整形,无符号数的加减还是无符号数(永远大于0)
- 函数演示:
#include<stdio.h>
#include<string.h>
int main()
{
char ch[] = "abcdef";
printf("%d\n", strlen(ch));
return 0;
}
//输出结果:6
- 模拟实现:
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
//对于传入的指针我们并希望对它指向的对象的内容进行修改,我们可以用const进行修饰
{
assert(str);//传入指针不能为NULL,记得包含头文件<assert.h>
size_t len = 0;
while (*str++)//*的优先级高于后置++,如果为'\0'则不进入循环('\0'的ASCII码值为0)
{//进入后指针str++
len++;
}
return len;
}
strcpy/strncpy(字符串拷贝函数)
- 定义:
char *strcpy( char *strDestination//目标字符串//,
const char *strSource//来源字符串);
char *strncpy( char *strDest, const char *strSource, size_t count//拷取长度(字节为单位));
- 区别:
strcpy:一直拷贝到'\0'(包括'\0')(并不安全)
strncpy:参数count用来控制拷贝字符串的长度(如果超过了源字符串的长度则会在拷完后再补0)(相对安全)
- 注意:
- 源字符串必须以 '\0' 结束
- 目标空间必须足够大(以确保能存放源字符串),且必须可修改
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "Hello world!";
char str2[20] = { 0 };
char str3[20] = { 0 };
strcpy(str2, str1);
strncpy(str3, str1, 2);
printf("%s\n", str2);
printf("%s\n", str3);
return 0;
}
//输出结果:
//Hello world!
//He
- 模拟实现:
char* my_strcpy(char* des, const char* src)
{
assert(des&&);//指针不为NULL
char* ret = des;//记录字符串首元素地址
while (*des++ = *src++)//先*指针进行赋值,赋值后对des指向的内容进行判断,再进行指针++
{
;
}
return ret;//返回字符串首元素地址
}
#include<assert.h>
char* my_strncpy(char* des, const char* src, int n)
{
assert(des && src);
char* ret = des, *cp = (char*)src;
int count = 0;
while (*cp++)//计算源字符串长度
{
count++;
}
for (int i = 0; i < n; i++)
{
if (i < count)
{
*des++ = *src++;
}
else//拷贝个数大于长度补0
{
*des++ = '0';
}
}*des = '\0';//末尾加上结束符
return ret;
}
strcat/strncat(字符串拼接函数)
- 定义:
char *strcat( char *strDestination, const char *strSource );
char *strncat( char *strDest, const char *strSource, size_t count );
- 区别:
strcat:将源字符串拼接到目标字符串后面(从目标字符串的'\0'位置开始拼接,一直拼接到源字符串'\0'处,包括'\0')
strncat:参数
count
用来控制拼接字符个数(大于源字符串个数也只拼接到源字符串的'\0')
- 注意:
- 源字符串必须以 '\0' 结束
- 目标空间必须足够大(以确保能存放拼接的字符) ,且必须可变
函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[100] = "hello ";
char str2[100] = "hello ";
char str3[] = "world!";
strcat(str1, str3);
strncat(str2, str3, 4);
printf("%s\n", str1);
printf("%s\n", str2);
return 0;
}
//输出结果:
//hello world!
//hello worl
- 模拟实现:
char* my_strcat(char* des, const char* str)
{
assert(des && str);
char* ret = des;
//找到des最后一个字符
while (*des)
{
des++;
}
//将str中字符串拷贝至des末尾
while (*des++ = *str++)
{
;
}
return 0;
}
char* my_strncat(char* des, const char* str, size_t n)
{
assert(des && str);
char* ret = des;
while (*des)
{
des++;
}
for (size_t i = 0; i < n && *str; i++)
{
*des++ = *str++;
}
*des = '\0';//末尾追加结束符
return ret;
}
strcmp/strncmp(字符串比较函数)
- 定义:
int strcmp( const char *string1, const char *string2 );
int strncmp( const char *string1, const char *string2, size_t count );
- 区别:
strcmp:比较每个字符对应的ASCII码值大小
strncmp:参数count控制了比较字符数量(比较字符个数不超过两字符串个数(含'\0')较少者)
- 标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
- 注意:
比较到出现另个字符不一样或者一个字符串结束或者count个字符全部比较完
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcd";
char str2[] = "abcd";
char str3[] = "accd";
printf("%d\t", strcmp(str1, str2));
printf("%d\t", strcmp(str1, str3));
printf("%d\t", strncmp(str1, str2,2));
printf("%d\t", strncmp(str1, str3,2));
return 0;
}
//输出结果:0 -1 0 -1
- 模拟实现:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
//字符相等就比较下一个,中间如果存在两字符不相等则结束遍历
while (*str1 && *str2 && *str1 == *str2)
{
str1++;
str2++;
}
//结果返回两者ASCII码差值
return (*str1 - *str2);
}
int my_strncmp(const char* str1, const char* str2, size_t n)
{
assert(*str1 && *str2);
for (size_t i = 0; i < n - 1 && *str1 && *str2; i++)
{
//不同时直接结束,再返回此刻两者的差值
if (*str1 != *str2)
break;
str1++;
str2++;
}
//或结束遍历后(n-1次)直接比较最后一位字符
return (*str1 - *str2);
}
strstr(字符串查找函数)
- 定义:
char *strstr( const char *string, const char *strCharSet );
注:如果字符串
strCharSet
在string
出现,则返回string
中第一次出现该字符串的首地址,否则返回NULL
- 模拟实现:
- 首先在str1中找到与str2首字符相同的字符,后对str2后面的字符进行逐个比较
- 如果在后续逐个比较过程中出现了不同的字符,这时候就需要str1返回到之前刚开始对字符比较的地方的后一个位置再进行比较,且str2需要返回到首字符
- 如果在后续逐个比较过程中,str2指向的字符为\0这就代表在str1中找到了str2这个字符串,则返回str2首字符对应于str1所在的地址
- 还有则是遍历后str1指向的字符为\0(在没满足str2指向的字符为\0时),这就表示在str1中找不到str2这个字符串,则返回NULL
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
if (*str2 == '\0')
return (char*)str1;//如果str2指向的对象如:char ch[]="";(即内容为'\0')直接返回str1
const char* s1;
const char* s2;
const char* p = str1;
//str1指向的对象内容不为'\0'则进入循环
while (*p)
{
//进入循环调整s2,s1位置
s1 = p;
s2 = str2;
//相同则进行遍历
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
//s2遍历到结束符即找到了,返回此次开始的地址
if (*s2 == '\0')
{
return (char*)p;
}
//(上面条件不满足)s1到了'\0'则说明开始位置p都到了结束符,那么已经找不到了
if (*s1 == '\0')
{
return NULL;
}
//此次查找没有找到,则使开始位置p指向下一个位置
p++;
}
return NULL;
}
strtok(字符串切分函数)
- 定义:
char *strtok( char *str//被切分对象,
const char *sep//分隔符号集合);
- 注意:
- 第一个参数指定一个字符串,它包含了0个或者多个由
sep
字符串中一个或者多个分隔符分割的标记strtok
函数找到str
中的首个分隔符,并将其用'\0
'替代,且返回分隔符前一个字符串首元素地址- strtok函数的第一个参数不为 NULL ,函数将找到strToken中首个分隔符,strtok函数会记忆该分隔符后一个字符的位置
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被记忆的位置开始,查找下一个分隔符
- 如果字符串中不存在更多的标记,则返回 NULL 指针
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "- This, a sample string.";
char* pch;
pch = strtok(str, " ,.-");
while (pch != NULL)
{
printf("%s\t", pch);
pch = strtok(NULL, " ,.-");
}
return 0;
}
//输出结果:This a sample string
#include <stdio.h>
#include <string.h>
int main()
{
char* p = "abcde@163.com";
const char* sep = ".@";
char arr[30];
char* str = NULL;
strcpy(arr, p);//将数据拷贝一份,处理arr数组的内容
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\t", str);
}
}
//输出结果:abcde 163 com
- 模拟实现:
char* strtok (char* str, const char* delim)
{
// 生成替换字符表
char table[256] = {0};
while (*delim != '\0')
{
table[*delim] = 1;
delim++;
}
// 使用 static 类型指针保存上一次函数调用时的字符串地址
static char* pstr = NULL;
if (str != NULL)
{
pstr = str;
}
// 保证 pstr 指向以非替换字符为首的子字符串
while (*pstr != '\0' && table[*pstr] == 1)
{
pstr++;
}
// ret 保存返回子字符串的首地址
char* rst = (*pstr != '\0') ? pstr : NULL;
while (*pstr != '\0')
{
if (table[*pstr] == 1)
{
// 切割得到子字符串,且 pstr 最后指向子字符串的下一字符
*pstr++ = '\0';
break;
}
else
{
pstr++;
}
}
return rst;
}
strerror(返回错误原因的描述字符串函数)
- 定义:
char *strerror( int errnum );
- 区别:
strerror:从内部数组中搜索错误号
errnum
,并返回一个指向错误消息字符串的指针perror:打印+strerror(直接打印出来)
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *stream;
if ((stream=fopen("crt_fopen.c", "r")) == NULL) //以读取的方式打开文件
{
perror("perror says open failed");
printf("strerror says open failed: %s\n", strerror(errno));
}
else
{
printf("open succeeded on input file\n");
fclose(stream);
}
return 0;
}
注:通过fopen()函数打开指定的文件,如果打开该文件失败,则fopen()函数的返回值是NULL,此时可以通过perror()函数或者strerror()函数显示错误信息
内存函数介绍
- 内存函数与字符串函数的区别:
字符串函数:只能对字符串进行操作
内存函数:能够操作任何数据类型(对对象储存的内容进行操作)
memcpy(内存拷贝函数)
- 定义:
void *memcpy( void *dest, const void *src, size_t count //以字节为单位);
- 注意:
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
- 这个函数在遇到 '\0' 的时候并不会停下来
- 如果source和destination有任何的重叠,复制的结果都是未定义的
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6 };
int arr2[5] = { 0 };
memcpy(arr2, arr1, 16);
int i = 0;
int size = sizeof(arr2) / sizeof(arr2[0]);
for (i = 0; i < size; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
//输出结果:1 2 3 4 0
- 模拟实现:
void* my_memcpy(void* des, const void* src, size_t n)
{
assert(des && src);
void* ret = des;//记录初始位置
while (n--)//循环拷贝
{
*(char*)des = *(char*)src;
(char*)des = (char*)des + 1;
(char*)src = (char*)src + 1;
}
return ret;
}
memmove(内存移动函数)
- 定义:
void *memmove( void *dest, const void *src, size_t count );
注:
memmove
函数相比于memcpy
函数能够很好的实现重复地址拷贝(如果source和destination有任何的重叠也能达到想要的结果)
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6 };
int arr2[3] = { 0 };
memmove(arr1 + 2, arr1, 16);
int i = 0;
int size = sizeof(arr1) / sizeof(arr1[0]);
for (i = 0; i < size; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
//输出结果:1 2 1 2 3 4
- 模拟实现:
void* my_memmove(void* des, const void* src, size_t n)
{
assert(des && src);
void* ret = des;
if (des < src)
{
//如果目标在源地前面则从前到后拷贝
while (n--)
{
*(char*)des = *(char*)src;
(char*)des = (char*)des + 1;
(char*)src = (char*)src + 1;
}
}
else
{
//目标在源地后面则从后到前拷贝
while (n--)
{
*((char*)des + n) = *((char*)src + n);
}
}
return des;
}
memcmp(内存比较函数)
- 定义:
int memcmp( const void *buf1, const void *buf2, size_t count );
注:比较从ptr1和ptr2指针开始的num个字节
- 函数演示:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,3,4,6 };
printf("%d ", memcmp(arr1, arr2, 16));
return 0;
}
//输出结果:0
- 模拟实现:
int my_memcmp(const void* src1, const void* src2, size_t n)
{
assert(src1 && src2);
while (--n)
{
//遇到不同直接退出
if (*(char*)src1 != *(char*)src2)
{
break;
}
(char*)src1 = (char*)src1 + 1;
(char*)src2 = (char*)src2 + 1;
}
return *(char*)src1 - *(char*)src2;
}