本篇文章及后面的几篇文章将会详细介绍和学习字符串操作函数及内存操作函数。
介绍:C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。(字符串常量适用于那些对它不做修改的字符串函数.
在之前的学习中,我们会经常使用一个字符串函数求字符串的长度-- strlen。
strlen求字符串长度的算法分析:strlen接收到字符串起始位置的地址时,比较该地址处的内容是否为’\0’,若不为’\0’, 字符串的长度 + 1。
函数介绍:strlen
size_t strlen(const char* str);
头文件:string.h
函数名:strlen
函数参数:str,参数类型是const char* ,即需要进行求字符串长度的起始地址
函数返回类型: size_t,size_t是unsigned int的类型重定义,是无符号整型。库函数使用size_t类型可能考虑的是字符串的长度不可能是负数,所以用了无符号类型size_t。
函数功能:计算字符串的长度
重点内容:
(1)字符串以’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)。
(2)参数指向的字符串必须要以’\0’结束。(如果没有’\0’,可能会出现随机值)
(3)注意函数的返回值为size_t,是无符号的(易错)(strlen(“abc”) - strlen("abcdef) < 0为假,注意strlen返回类型)
(4)学会strlen函数的模拟实现
strlen函数的模拟实现三种方法:
①计数器方法
②递归方法(不创建临时变量求字符串长度)
③指针 - 指针方法
#include
#include
//1.计数器实现求字符串长度函数
int my_strlen1(const char* str)//整个过程不改变指针指向内容,加上const
{
assert(str != NULL);//加上断言,防止接收空指针
int count = 0;
while (*str != '\0')//也可以直接用while(*str)
{
count++;
str++;
}
return count;
}
//2.递归实现求字符串长度,不用创建临时变量
int my_strlen2(const char* str)
{
assert(str != NULL);
if (*str != '\0')//也可以直接用if(*str)
{
return 1 + strlen(str + 1);//不能直接使用str++,可以使用++str,建议直接用str+1
}
else
return 0;
}
//3.指针-指针得到中间元素的个数,实现求字符串长度
int my_strlen3(const char* str)
{
assert(str != NULL);
const char* tmp = str;//创建临时指针变量保存str起始值
while (*str != '\0')
{
str++;
}
return str - tmp;
}
int main()
{
char arr[] = "abcdefgh";
int ret1 = my_strlen1(arr);//1.计数器方法
int ret2 = my_strlen2(arr);//2.递归方法
int ret3 = my_strlen3(arr);//3.指针 - 指针方法
printf("%d\n", ret1);
printf("%d\n", ret2);
printf("%d\n", ret3);
return 0;
}
如果我们要将一个字符串的内容拷贝到另外一个字符串空间中时,需要使用字符串拷贝- -strcpy函数,在正式介绍strcpy函数之前,我们先看一段代码:
#include
#include
int main()
{
char arr1[] = "abcdefghik";
char arr2[] = "hello";
//将字符串arr2拷贝给arr1
strcpy(arr1, arr2);
//用调试窗口观察arr1变化
return 0;
}
函数介绍:strcpy
char* strcpy(char* destination,const char* source);
头文件:string.h
函数名:strcpy
函数参数:
参数1:destination, 类型:char* ,表示将字符串拷贝的目的地位置
参数2:source,类型:char* ,表示被拷贝字符串拷贝的源地址起始位置。
函数返回类型: char*, 实际上就是返回destination(目的地)的起始位置
函数功能:字符串拷贝
重点内容:
(1) Copies the C string pointed by source into the array pointed by
destination, including the terminating nulcharacter(and stopping at that point).
(2)源字符串必须以‘\0’结束。 (源字符串如果没有’\0’, 那么strcpy在拷贝的时候,不知道什么时候停止,可能会造成越界访问)
(3)会将源字符串中的’\0’拷贝到目标空间。
(4)目标空间必须足够大,以确保能存放源字符串。 (目标空间不够大,也会导致越界访问)
(5)目标空间必须可变。
(要将源字符串的内容拷贝到目标空间,目标空间当然要可以变化,才能接收拷贝过来的字符)
(6)学会模拟实现。
模拟实现strcpy
#include
#include
char* my_strcpy(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* dest_start = dest;
//拷贝src指向字符串的内容到dest指向的空间,包括'\0'
//方法一:常规思路版
//while (*src != '\0')
//{
// *dest = *src;
// dest++;
// src++;
//}
//*dest = *src;
//方法二:代码精简版
while (*dest++ = *src++)
{
;
}
return dest_start;
}
int main()
{
char arr1[] = "abcdefghik";
char arr2[] = "hello";
my_strcpy(arr1, arr2);//模拟实现strcpy函数
printf("%s", arr1);
return 0;
}
如果我们要将一个字符串的内容追加到另外一个字符串的末尾空间中时,需要使用字符串拷贝-- - strcat函数,在正式介绍strcat函数之前,我们先看一段代码:
#include
#include
int main()
{
char arr1[30] = "hello";
//arr1空间需要足够大来接收追加过来的内容
//否则会造成越界访问
char arr2[] = "world";
//将字符串arr2内容追加给arr1
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
strcat在进行追加的时候是否会将’\0’追加过去?
strcat函数进行字符串追加的算法分析:
函数介绍:strcat
char* strcat(char* destination,const char* source);
头文件:string.h
函数名:strcat
函数参数:
参数1:destination, 类型:char* ,表示将字符串追加的目的地位置
参数2:source,类型:char* ,表示被追加字符串的源地址起始位置。
函数返回类型: char*,实际上就是返回destination(目的地)的起始位置
函数功能:字符串追加
重点内容:
(1)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 ofthe new string formed by the concatenation of both in destination.
(2)源字符串必须以‘\0’结束。目标空间也必须包含’\0’,以确定追加的起始位置。
(3)目标空间必须有足够的大,能容纳下源字符串的内容。
(4)目标空间必须可修改。
(5)字符串自己给自己追加,如何 ?
用strcat自己给自己追加会导致程序崩溃,无法实现自己追加自己!(从’\0’开始追加,自己追加自己的时候,’\0‘改成了被追加的首字符,再寻找追加字符串结束的’\0’时,找不到’\0’,会导致死循环)
模拟实现strcat
#include
#include
char* my_strcat(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* dest_start = dest;
//1.找到目的空间中的'\0'
while (*dest != '\0')//跳过不是'\0'的字符
{
dest++;
}
//2.追加
while (*dest++ = *src++)
{
;
}
return dest_start;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "world";
my_strcat(arr1, arr2);//模拟实现strcat
printf("%s", arr1);
return 0;
}
strcat从’\0’开始追加,自己追加自己的时候,’\0‘改成了被追加的首字符,再寻找追加字符串结束的’\0’时,找不到’\0’,会导致死循环
怎么样才能自己追加自己呢?
使用strncat函数,后面会进行详细讲解
如果我们要比较两个字符串的是否相等,或者比较字符串大小,不能用操作符 == 来直接进行判断,而是需要用到字符串比较函数strcmp
函数介绍:strcmp int strcmp(const char* str1,const char* str2);
头文件:string.h
函数名:strcmp
函数参数:
参数1:str1, 类型:char* ,表示将进行比较的第一个字符串
参数2:参数2:str2, 类型:char* ,表示将进行比较的第二个字符串
函数返回类型: int, 返回两个字符串比较的结果
函数功能:字符串比较
重点内容:
(1)This function starts comparing the first character of each string.lf they are equal to each other, itcontinues with the following pairs until the characters differ or until a terminating null - character isreached.
标准规定︰
(2)第一个字符串大于第二个字符串,则返回大于O的数字。
(3)第一个字符串等于第二个字符串,则返回0
(4)第一个字符串小于第二个字符串,则返回小于0的数字
那么如何判断两个字符串?
逐个字符进行比较
模拟实现strcmp
#include
#include
int my_strcmp(const char* p1, const char* p2)
{
assert(p1 && p2);
while (*p1 == *p2)
{
if (*p1 == '\0')
{
return 0;
}
p1++;
p2++;
}
//方法一:vs实现的方式
//if (*p1 > *p2)
//{
// return 1;
//}
//else
//{
// return -1;
//}
//方法二:linux下gcc实现方式
return *p1 - *p2;
}
int main()
{
char* p1 = "abcdef";
char* p2 = "abqwt";
if (my_strcmp(p1, p2) > 0)
{
printf("%s > %s\n", p1, p2);
}
else if (my_strcmp(p1, p2) == 0)
{
printf("%s = %s\n", p1, p2);
}
else
{
printf("%s < %s\n", p1, p2);
}
return 0;
}
strncpy与strcpy相比较多了一个字母n,这个n代表的是需要拷贝字符的个数,也就是说strncpy需要关注拷贝字符的个数,而不是像strcpy那样关注’\0’。
举例:
#include
#include
int main()
{
char arr1[10] = "abcdefg";
char arr2[] = "1234";
int len = 0;//len是拷贝字节的个数
scanf("%d", &len);
strncpy(arr1, arr2, len);
printf("%s", arr1);
return 0;
}
char* strncpy(char* destination,const char* source,size_t num);
头文件:string.h
函数名:strncpy
函数参数:
【参数1】destination,类型:char*,拷贝字符的目的地位置,即接收字符的起始位置
【参数2】source,类型:char* ,拷贝字符的源地址,即拷贝字符串的开始位置。
【参数3】num,类型size_t,拷贝字符的个数,用来控制拷贝字符的长度。
函数返回类型:char* ,返回接收字符的起始位置。
函数功能:指定个数的字符串拷贝
重点内容
(1)Copies the first num characters of source to destination.lf the end of the source C string(which issignaled by a null - character) is found before num characters have been copied, destination is paddedwith zeros until a total of num characters have been written to it.
(2)拷贝num个字符从源字符串到目标空间。
(3)如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
(4)如果拷贝的字符长度大于目的地空间的容量,则会破坏dest字符串后面的\0,要避免这种情况发生
模拟实现strncpy
#include
#include
//模拟实现strncpy
//方法一
//char* my_strncpy(char* dest, const char* src, size_t n)
//{
// char* dest_start = dest;
// while ((n > 0) && (*src != '\0'))
// {
// *dest = *src;
// dest++;
// src++;
// n--;
// }
// while (n > 0)
// {
// *dest = '\0';
// dest++;
// n--;
// }
// return dest_start;
//}
//方法二
char* my_strncpy(char* dest, const char* src, size_t count)
//count比n更有实际意义
{
assert(dest != NULL);//引用断言
assert(src != NULL);
char* start = dest;
while (count && (*dest++ = *src++) != '\0')
{
count--;
}
if (count)
{
while (count--)
{
*dest++ = '\0';
}
}
return start;
}
int main()
{
char arr1[10] = "abcdefg";
char arr2[] = "1234";
size_t len = 0;
scanf("%d", &len);
my_strncpy(arr1, arr2, len);
printf("%s", arr1);
return 0;
}
strcat函数是字符串追加,在使用的时候以src的’\0’作为追加结束标志,因此在使用strcat来追加一个字符串数组本身的时候,会因\0被提前覆盖而无法追加成功。
strncat与strcat相比多了一个字母n,这个n代表的是需要追加字符的个数,也就是说strncat需要关注追加字符的个数,而不是像strcat那样关注’\0’。
strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\0’。(这也是我们在追加的时候,不用关注原dest, src中’\0’,仅需关注追加字符的个数的原因)
#include
#include
int main()
{
char arr1[15] = "12345\0xxxxxxx";
char arr2[] = "abcd";
size_t count = 0;
scanf("%d", &count);
strncat(arr1, arr2, count);
printf("%s", arr1);
return 0;
}
char* strncat(char* destination,const char* source,size_t num);
头文件:string.h
函数名:strncat
函数参数:
【参数1】destination,类型:char*,被追加字符的目的地位置,即接收追加字符的起始位置
【参数2】source,类型:char* ,追加字符的源地址,即追加字符串的开始位置。
【参数3】num,类型size_t,追加字符的个数,用来控制追加字符的长度。
函数返回类型:char* ,返回接收字符的起始位置。
函数功能:指定个数的字符串追加
重点内容:
(1)Appends the first num characters of source to destination, plus a terminating null - character.
(2)lf the length of the C string in source is less than num, only the content up to the terminating null - character is copied.
(3)如果追加的字符长度大于目的地空间的剩余容量,则出现越界访问,要避免这种情况发生。
模拟实现strncpy
#include
#include
//方法一
//char* my_strncat(char* dest, const char* src, size_t count)
//{
// char* start = dest;
// //dest找到\0的位置
// while (*dest!='\0')
// {
// dest++;
// }
// while (count)
// {
// //将src中的字符追加给dest
// *dest = *src;
// dest++;
// src++;
// //如果src以及指向\0的位置,提前结束循环
// if (*src == '\0')
// {
// break;
// }
// count--;
// }
// *dest = '\0';//字符个数追加完毕后,再单独追加'\0'
// return start;
//}
//方法二
char* my_strncat(char* dest, const char* src, size_t count)
{
assert(dest != NULL && src != NULL);
char* start = dest;
while (*dest++)
;
dest--;
while (count--)
if ((*dest++ = *src++) == '\0')
return start;
*dest = '\0';
return start;
}
int main()
{
char arr1[15] = "12345\0xxxxxxx";
char arr2[] = "abcd";
size_t count = 0;
scanf("%d", &count);
my_strncat(arr1, arr2, count);
printf("%s", arr1);
return 0;
}
而strncmp与strcmp相比多了一个字母n,这个n代表的是需要比较字符的个数,也就是说strncmp需要关注比较字符的个数,而不是像strcmy那样仅关注’\0’。strncmp的返回结果与strcmp一样,返回 > 0, == 0, < 0 的整数。
以下面代码为例:
#include
#include
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcd12";
int ret = 0;
size_t count = 0;
scanf("%d", &count);
ret = strncmp(arr1, arr2, count);
if (ret > 0)
{
printf("arr1 > arr2\n");
}
else if (ret == 0)
{
printf("arr1 = arr2\n");
}
else
{
printf("arr1 < arr2\n");
}
return 0;
}
int strncmp(const char* p1,const char* p2,size_t count); 头文件:string.h
函数名:strcmp
函数参数:
【参数1】p1,char* 类型,表示用来比较的其中一个字符串。
【参数2】p2,char* 类型,表示用来比较的另一个字符串。
【参数3】count,size_t类型,表示参与比较的字符个数。
函数返回类型:int类型,根据比较的具体结果返回相应的数值
Exit :
returns <0 if str1 < str2
returns 0 if str1 == str2
returns >0 if str1 > str2
函数功能:
Compares two strings for ordinal order.The comparison stops after :
(1) a difference between the strings is found,
(2) the end of the strings is reached, or
(3) count characters have been compared.
重点内容:
(1)比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
库函数源代码参考:
int __cdecl strncmp
(
const char* first,
const char* last,
size_t count
)
{
size_t x = 0;
if (!count)
{
return 0;
}
/*
* This explicit guard needed to deal correctly with boundary
* cases: strings shorter than 4 bytes and strings longer than
* UINT_MAX-4 bytes .
*/
if (count >= 4)
{
/* unroll by four */
for (; x < count - 4; x += 4)
{
first += 4;
last += 4;
if (*(first - 4) == 0 || *(first - 4) != *(last - 4))
{
return(*(unsigned char*)(first - 4) - *(unsigned char*)(last - 4));
}
if (*(first - 3) == 0 || *(first - 3) != *(last - 3))
{
return(*(unsigned char*)(first - 3) - *(unsigned char*)(last - 3));
}
if (*(first - 2) == 0 || *(first - 2) != *(last - 2))
{
return(*(unsigned char*)(first - 2) - *(unsigned char*)(last - 2));
}
if (*(first - 1) == 0 || *(first - 1) != *(last - 1))
{
return(*(unsigned char*)(first - 1) - *(unsigned char*)(last - 1));
}
}
}
/* residual loop */
for (; x < count; x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char*)first - *(unsigned char*)last);
}
first += 1;
last += 1;
}
return 0;
}
查找字符串,找子字符串。
#include
#include
int main()
{
char arr1[] = "abbbcdefbcd";
char arr2[] = "bcd";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("Can not find!\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
char* strstr(const char* str1, const char* str2);
头文件:string.h
函数名:strstr
函数参数:
【参数1】str1,char* ,用于查找字符串的母串。
【参数2】str2,char*,待查找字符串的子串。
函数返回类型:char* ,返回查找到的地址
函数功能:查找字符串 / 找子字符串
重点内容:
(1)如果在str1中查找了字串str2,则返回str1中字串str2的起始地址
(2)如果查找不到,返回空指针NULL
(3)如果str1中含有多个str2字串内容,返回的是第一个被查找到的str2起始地址
模拟实现strstr:
#include
#include
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
char* cur = (char*)str1;
char* p1 = NULL;
char* p2 = NULL;
if (*str2 == '\0')
return (char*)str1;
while (*cur != '\0')
{
p1 = cur;
p2 = str2;
while ((*p1 == *p2) && (*p1 != '\0') && (*p2 != '\0'))
{
p1++;
p2++;
}
if (*p2 == '\0')
{
return cur;//找到字串的情况
}
cur++;
}
return NULL;//找不到字符的情况
}
其实库函数的实现逻辑跟我们实现的方式是一样的,两者有所区别的是代码风格,库函数的代码风格更加简练,需要有一定的编程功底!
库函数源代码参考:
char* __cdecl strstr(
const char* str1,
const char* str2
)
{
char* cp = (char*)str1;
char* s1, * s2;
if (!*str2)
return((char*)str1);
while (*cp)
{
s1 = cp;
s2 = (char*)str2;
while (*s1 && *s2 && !(*s1 - *s2))
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
}