前言:
本篇重点介绍处理字符和字符串的库函数的使用和注意事项,让我们更好的使用字符串和字符串库函数,去理解运用一些库函数或自定义函数。字符串和内存函数共两篇。
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组 中。 字符串常量适用于那些对它不做修改的字符串函数。
在学习库函数的过程中,我们最好可以去理解和掌握它的原理,那么库函数的定义我们可以在c++的官网查看,也就是下方链接,进去之后搜索自己需要的库函数就可以了。
就是这里 → cplusplus.com
字符串和内存函数:
- 一.求字符串长度
- 1.strlen
- 二.长度不受限制的字符串函数
- 1.strcpy
- 2.strcat
- 3.strcmp
- 三.长度受限制的字符串函数介绍
- 1.strncpy
- 2.strncat
- 3.strncmp
一.求字符串长度
1.strlen
首先我们大概了解一下这个库函数的基本信息:
strlen
函数的功能可以理解为获取字符串中首字母元素的地址,然后往下去找'\0'
,一边寻找一边数数,每跳过一个字符数1,直到找到'\0'
,,然后回看一共走过了多少字节。
比如:
#include <string.h>
int main()
{
char arr[] = { "abcde" };
char arr2[] = { 'a','b','c','d','e'};
int sz = strlen(arr);
int sz2 = strlen(arr2);
printf("%d\n", sz);//5
printf("%d", sz2);//17(不唯一)
return 0;
}
在这里我们需要记得,strlen
函数计算的结束符号是'\0'
,所以在上面代码中,字符串是先天就在末尾上带上'\0'
的,而定义的arr2是多个字符组合起来的字符串,是没有带上'\0'
的。当strlen计算的时候,到了字符’e’后还不会停,而是直到继续计算下去遇到一个随机的'\0'
才结束,所以sz2计算得到的是一个随机值。
然后我们要注意到的是:strlen
的返回值是size_t
,也就是unsigned int
(无符号整形),所以我们要注意一些返回时的参数变化。
比如:
#include <string.h>
int main()
{
if (strlen("abc") - strlen("abcdef") > 0)
{
printf("大于0\n");
}
else
{
printf("小于0\n");
}
return 0;
}
分析这段代码的时候,是不是前面的应该是3,后面的是6,所以3-6是负数,打印的是小于0。在这里打印的实际上是大于0,原因就是类型的问题。
(strlen("abc")
得到的是size_t类型的值,而strlen("abcdef")
也是,所以相减得到的也是size_t,无符号整形是没有负数的,所以最终打印的是大于0。如果真的想要这样写,而结果也不出错,可以在(strlen("abc")
前面强制类型转换得到int。
所以我们在使用strlen需要注意的有:
1.字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
2.参数指向的字符串必须要以 ‘\0’ 结束。
3.注意函数的返回值为size_t,是无符号的。
然后我们既然理解了strlen函数的定义和使用功能,我们也可以自己尝试自定义一个函数去实现strlen的功能:
第一种(计数器):
#include <stdio.h>
int my_strlen(char* str) {
int count = 0;
while (*str++ != '\0') {
count++;
}
return count;
}
int main() {
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
第二种(指针-指针):
#include <stdio.h>
int my_strlen(char* str) {
char* start = str;
char* end = str;
while (*end != '\0') {
end++;
}
return end - start;
}
int main() {
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
第三种(递归):
#include <stdio.h>
int my_strlen(char* str) {
if (*str != '\0') {
return 1 + my_strlen(str+1);
} else {
return 0;
}
}
int main() {
char str[] = "abcdef";
int len = my_strlen(str);
printf("%d\n", len);
return 0;
}
二.长度不受限制的字符串函数
我们下面学的strcpy
,strcat
还有strcmy
,都是长度不受限制的字符串函数,接下来我们还会学习长度受限制的字符串函数。为什么叫不受限制呢,因为他们在处理字符串的时候,都不会去在意他的长度,所以就是不受限制的。
1.strcpy
先看定义:
strcpy
从定义上看就是把一个字符串的内容拷贝到另一个字符串里,char * destination
就是目的地址, const char * source
就是源,就是把源拷贝到目的地址去。
① 那么拷贝的时候对拷贝的内容有什么要求呢,我们看一下这一段代码:
int main()
{
char arr[] = { "xxxxxxxxxxxxxx" };
char arr2[] = { "abcde" };
char cc[] = { "xxxxxxxxxxxxxxxx" };
char cc2[] = { "abcde\0fgh" };
strcpy(arr, arr2);
strcpy(cc, cc2);
//执行结果是什么?
printf("%s\n", arr);//abcde
printf("%s", cc);//abcde
}
为什么cc2被拷贝过去的时候没有把全部都拷贝过去,这是因为strcpy
是字符串拷贝,而字符串的意思就是遇到'\0'
就结束这段字符,所以在拷贝过去的时候,当strcpy
遇到'\0'
就停下来了。而且打印的时候没有打印出cc后面的xxx
,意味着'\0'
也是被拷贝了的。
② 那么下面这个代码呢:
int main()
{
char arr[] = { "xxxxxxxxxxxxxx" };
char arr2[] = { 'a','b','c','d' };
strcpy(arr, arr2);//EOF!
printf("%s", arr);
return 0;
}
运行起来会是这样的,引发写入冲突异常:
实际上,当这段由字符组成的字符串传过去的时候,由于没有'\0'
结束标志,strcpy
会一直读下去,直到直到'\0'
,当得到的字符串比arr字符串长的时候,拷贝过去的就要放到非arr区域的内存,这就导致写入错误了。这样子同理得出,我们拷贝过去的目的地址要足够大,能容纳拷贝内存,才可以正常运行。
③ 接下来一段代码:
int main()
{
const char *p = { "xxxxxxxxxxxxxx" };
char arr2[] = {"abcd"};
strcpy(p, arr2);//EOF!
printf("%s", p);
return 0;
}
这个代码运行的时候也会出现同样的错误,在这里我们定义的是一个常量字符串,是不能修改的,而且加了const修饰,也就是拷贝过去也不能修改其内容,这个代码也是错误的。
接下来又到了我们实现自定义函数的时候了:
my_strcpy(自定义拷贝函数)
#include <assert.h>
char* my_strcpy(char* arr,const char* arr2)
{
assert(arr && arr2);//断言
int* p = arr;
while ((*arr++) = *(arr2++))
{
;
}
return p;
}
int main()
{
char arr[20] = "xxxxxxxxxxxxxxx";
char arr2[] = "bcdefg";
my_strcpy(arr, arr2);
printf("%s", arr);
return 0;
}
总结:
使用strcpy函数的时候需要注意:
1.源字符串必须以 ‘\0’ 结束。
2.会将源字符串中的 ‘\0’ 拷贝到目标空间。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变。
2.strcat
老规矩,先来看一下定义:
这里我们是不是看见跟strcpy有些相似的参数char * destination
, const char * source
,同样是目的地和源,但这里的意思不是拷贝,而是衔接上,也就是将一段字符串接上另一端字符串。
int main()
{
char arr1[20] = "abcde";
strcat(arr1, "fgh");
printf("%s", arr1);//abcdefgh
return 0;
}
但是注意,使用strcat
要注意以下几点:
① 目的字符串要有足够空间:
至少能容纳下源字符串的内容。
int main()
{
char arr1[] = "abcde";
strcat(arr1, "fgh");
printf("%s", arr1);
return 0;
}
假设是这样子的代码,虽然可以运行出结果,但是会报告错误,因为我们在创建arr的时候只定义了6个字节,那么接上去访问的就是非法地址了。同样的没有定义足够的空间,代码就会报错。
② 源字符串必须以'\0'
结束:
int main()
{
char arr[20] = "abcd";
char arr2[] = {'e','f','g'};
strcat(arr, arr2);
return 0;
}
当你链接起来起源字符串后,肯定需要有结束标准’\0'
,如果没有,那么同样会导致和strcpy
函数一样的结果,继续访问下去未知的字符,知道随机遇到一个'\0'
。
③ 目标空间必须可修改:
这里的道理和上面的strcpy
中一样,大家可以尝试和上面的strcpy
函数一样,在目的地的字符数组上加上const
,就不能修改了。
最后
自定义函数实现strcat
功能:
#include <stdio.h>
#include <string.h>
#include <assert.h>
char * my_strcat(char* desc, char* src)
{
assert(desc && src);
char* p = desc;
while (*desc != '\0')
{
desc++;
}
while (*desc++ = *src++)
{
;
}
return p;
}
int main()
{
char arr[20] = "abcd";
char arr2[] = "efg";
printf("%s", my_strcat(arr, arr2));
return 0;
}
3.strcmp
这是一个什么样的库函数呢?我们来看一下:
strcmp
是比较字符串的,比较的是字符串的内容,不是长度。
这里的参数是const char * str1
, const char * str2
两个字符串的首元素地址,我们再来看一下定义:将C字符串str1
与C字符串str2
进行比较。所以它是一个比较字符串的库函数,这里需要注意的是,当strcmp比较两个字符串的时候,是一个一个的进行比较,而且比较的是这两个字符的ascii码值。
再来看一下,当比较完之后,显示的结果是什么样子的
这里说明了,当字符串str1
和字符串str2
比较的时候,如果字符串str1
和字符串str2
一样,那么返回值就是0。如果不相等,返回值就不是0。当str1
和str2
中第一个不匹配的字符的ascii
码值比较,如果str1
的大,则返回值是正数,反之就是负数。
附ASCII码表:
① 我们来用代码说明一下:
int main()
{
char arr1[] = "abc";
char arr2[] = "abc";
char arr3[] = "bc";
char arr4[] = "c";
int ret1 = strcmp(arr1, arr2);
int ret2 = strcmp(arr1, arr3);
int ret3 = strcmp(arr2, arr4);
int ret4 = strcmp(arr4, arr3);
printf("%d\n", ret1);//0
printf("%d\n", ret2);//-1
printf("%d\n", ret3);//-1
printf("%d\n", ret4);//1
return 0;
}
第一个是
"abc"
和"abc"
比较,所以结果为0
第二个是
"abc"
和"bc"
比较,显然不一样,第一个不一样的就是第一个字符,也就是’a’和’b’不一样,而后者的ASCII码较大,所以是后者大于前者,结果打印-1
第三个是
"abc"
和"c"
比较,也是不一样,然后不一样在第一个字符,a和c比,c大,所以后者比前者大,打印了-1
第四个是
"c"
和"bc"
比较,还是不一样,不一样也在第一个字符,c比b大,所以前者比后者大,打印的是1
② 然而,当然如果两个字符串是下面这样子的情况,比较的时候就是比较字符串结束标准的'\0'
了,打印也是看哪一个大:
int main()
{
char arr1[] = "abc";
char arr2[] = "ab";
//实际上就是"abc\0"和"ab\0"比较
//第一个不同的字符是c和\0,明显c的ASCII值大
int ret1 = strcmp(arr1, arr2);
printf("%d\n", ret1);//1
return 0;
}
又到了一年一度的自定义实现函数环节:
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;
}
int main()
{
char arr1[] = "abc";
char arr2[] = "a";
int ret1 =my_strcmp(arr1, arr2);
if (ret1 == 0)
{
printf("=");
}
else if (ret1 < 0)
{
printf("<");
}
else if (ret1 > 0)
{
printf(">");
}
return 0;
}
三.长度受限制的字符串函数介绍
上面我们学习了几个函数,都是不受长度限制的,但是不受限制有时候就会出现一些问题,比如非法访问啊,数组越界之类的,所以为了安全,我们在这几个字符串里面加上了一个n,也就是strncpy
,strncat
,strncmp
,多了一个参数限制,也就安全起来了。
1.strncpy
我们先来看一下strncpy
的定义,还有比较一下strcpy
和它的区别:
这里最明显就是多了一个参数,这个就是最明显的区别啦,当我们使用strncpy
的时候,我们最后一个参数决定着我们拷贝字符串的数量,也就是可以决定我们的拷贝范围了。
① 我们来尝试用代码去弄出来:
int main()
{
char arr[] = "xxxxxxxxxx";
char arr2[] = "abcd";
strncpy(arr, arr2, 2);
printf("%s", arr);
return 0;
}
当我们将代码运行的时候,我们选择的2就显现出作用了,字符串arr2其实有4个字符(不带’\0’的话),然而我们拷贝过去的只是两个字符,这就是这个参数的作用。
② 那如果拷贝的内容比拷贝的源字符串内容多呢:
我们来试一下:
int main()
{
char arr[] = "xxxxxxxxxx";
char arr2[] = "abcd";
strncpy(arr, arr2, 7);
printf("%s", arr);
return 0;
}
我们可以调试起来,观察字符串的变化情况
strncpy
函数调用前:
strncpy
函数调用后:
这样我们可以明显的观察到,我们的拷贝参数是7,而arr2
带上'\0'
也就5个字符,那拷贝过去的是什么呢,我们通过观察,看见在超出拷贝内容后,拷贝过去的是'\0'
。也就是说,拷贝超出的每一位,拷贝过去的都会补一个'\0'
。
自定义函数实现:
#include <assert.h>
char* my_strncpy(char* arr, const char* arr2, int n)
{
assert(arr && arr2);//断言
char* p = arr2;
char* q = arr;
while (n) {
if ((*arr = *arr2) != 0)
{
arr++;
arr2++;
n--;
}
else
{
*arr++ = '\0';
n--;
}
}
return q;
}
int main()
{
char arr[20] = "xxxxxxxxxxxxxxx";
char arr2[] = "bcdefg";
int n = 0;
printf("请输入需要复制的字符位输:>");
scanf("%d", &n);
my_strncpy(arr, arr2, n);
printf("%s", arr);
return 0;
}
2.strncat
接下来是strncat
:
同样的也是多了一个参数限制,让他在追加字符串的时候可以规定追加的数量。
① 我们来用一下试试:
int main()
{
char arr[20] = "abc";
char arr2[] = "defg";
strncat(arr, arr2,2);
printf("%s", arr);//abcde
return 0;
}
得到的结果就是:abcde
所以说参数的功能就是控制追加字符串的长度,那追加字符串只追加两个,会被覆盖掉字符串的’\0’然后再追加吗?我们可以试一下,观察变化:
int main()
{
char arr[20] = "abc\0xxxxxx";
char arr2[] = "defg";
strncat(arr, arr2,2);
printf("%s", arr);
return 0;
}
通过对比我们发现,strncat
在追加的时候,如果有'\0'
,确实是会将'\0'
覆盖,然后再在追加字符后再加上一个'\0'
。
② 当输入参数大于源字符串长度时:
int main()
{
char arr[20] = "abc";
char arr2[] = "defg";
strncat(arr, arr2,7);
printf("%s", arr);
return 0;
}
当输入参数大于源字符串长度时,会向strncpy
一样在后面追加’\0’吗,我们调试起来观察后得知,是不会的,当strncat
的追加参数大于源字符串长度的时候,也仅仅会追加字符串的内容,并不会再补上'\0'
。
自定义函数环节:
#include <stdio.h>
#include <string.h>
#include <assert.h>
char* my_strncat(char* desc, char* src, int n)
{
assert(desc && src);
char* p = desc;
while ( *desc != '\0')
{
desc++;
}
while ((n>0) && (*desc++ = *src++))
{
n--;
}
return p;
}
int main()
{
char arr[20] = "abcd";
char arr2[] = "efg";
int n = 0;
scanf("%d", &n);
printf("%s", my_strncat(arr, arr2,n));
return 0;
}
3.strncmp
同样的strncmp
也是增加了一个参数,可以让我们选择对比前多少个字符,这里就不再重新阐述了。直接上代码:
int main()
{
char arr[] = "abcd";
char arr2[] = "abd";
int ret1 = strncmp(arr, arr2, 2);
int ret2 = strncmp(arr, arr2, 3);
int ret3 = strncmp(arr, arr2, 4);
printf("%d\n", ret1);//0
printf("%d\n", ret2);//-1
printf("%d\n", ret3);//-1
return 0;
}
结果展示:
在这里,显然看出是选择位数比较得出不同的结果,而ret2
和ret3
的结果是一样的,显然在起码比较不一样之后,后面实际上就不需要比较下去了。
自定义函数实现:
int my_strncmp(const char* s1, const char* s2, int n)
{
assert(s1 && s2);
while ((n>0) && (*s1 == *s2))
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
n--;
}
if (n == 0)
{
return 0;
}
else
{
return *s1 - *s2;
}
}
int main()
{
char arr1[] = "abcde";
char arr2[] = "abcef";
int n = 0;
scanf("%d", &n);
int ret1 = my_strncmp(arr1, arr2,n);
if (ret1 == 0)
{
printf("=");
}
else if (ret1 < 0)
{
printf("<");
}
else if (ret1 > 0)
{
printf(">");
}
return 0;
好啦,本篇的内容就先到这里,关于字符串和内存函数还有第二篇噢,敬请期待。关注一波,互相学习,共同进步。
还有一件事: