字符数组是一个很细节的语法,涉及很多知识点,这篇文章我主要分享一下如何理解字符数组,以及对应的sscanf、sprintf有什么用
1.字符数组的初始化以及内容修改易错点
字符数组的初始化方式有两种,一种是直接用字符串进行初始化,另一种是大括号加上字符或字符串进行初始化
这两种初始化方式均可,但是如果我现在想要更改数组的内容呢,那应该怎样操作?
很多人会第一时间想到直接给arr赋上一个新的字符串,但这显然是不行的。
接下来分析原因:
a.数组名的理解:我们要清楚字符数组的数组名是首元素地址。更详细地说,数组名arr是一个指针类型,其存储的值是首元素地址。对于这个变量有一个特殊操作就是&arr,这个操作是取出整个数组的地址,整个数组的地址是首元素的地址,所以&arr和arr得到的值是相同的。但由于这个特殊操作,我们无法得到存储arr这个指针的地址,不过这确实没有任何意义,很多人甚至都意识不到这个问题。
arr这个指针还有个常属性,即arr存储的值不能被修改,类似于const int* p,这就导致arr的指向的空间区域在一开始就确定了,不能被修改了。
b.表达式的返回值:我们还要清楚表达式的返回值是存储在一个临时变量里的,这个临时变量同样具有常属性,这个临时变量我们是看不到的,如果有变量接收就把这个临时变量的值赋给这个变量。如果学过C++的引用,那么我们对这个临时变量的印象就会很深刻。
在字符数组的两种初始化表达式语句中,"Hello"返回的值是字符'H'的首元素地址,{'Hell", 'o'}返回的也是括号内第一个字符'H'的地址。更详细一点,就是这些初始化表达式的值(地址)存到一个临时变量里,再把临时变量里的这个地址赋给arr这个指针,完成初始化。
后续对字符串的访问其实就是靠的这个返回的地址的。
下面看一种情况:
这种情况也同理,只有在定义的时候,在arr这个指针还没有明确指向哪一块空间的时候,我们可以对它初始化,但如果不初始化,那么编译器会自动分配一块空间并把这块空间的最低地址赋给指针arr,这个时候arr就再也无法进行修改了。在上面这张图中,我们可以看到如果我们想要将"Hello"存到数组,直接赋值操作是不行的,因为就像上面所说的,arr = "Hello";本质是修改arr这个指针存储的值,这是绝对不允许的。我们明显需要其它方法来实现这种操作,达到在这块指定的空间里修改值的操作。
2.为什么字符数组会遇到这种情况
其实arr具有的常属性适用于所有的数组,一旦数组被定义了,首元素的地址是一定不能变的。
至于在int、float、double等类型的数组我们很少遇到这个情况,是因为几乎没有人会写出下面的代码:
一般而言,我们都会针对arr[0]这种写法来修改数组里面的值,这其实就相当于在指定空间里面修改值的操作,而不是直接去修改arr指向的空间。
但是字符数组里面存储的信息大多是连续的,如一个单词,一个句子,我们需要批量修改字符数组里面的值,单个字符修改太慢,且又不能使用循环来解决,因此我们才会容易犯上面的错误。而这个需求在其它类型的数组里面遇到的情况相对较少,通常都是针对某个数据进行修改,如果是批量修改,写很容易通过循环来解决。
3.字符串或内存函数解决方案
有以下几种函数可以帮助我们批量修改字符串中的值,它们都是直接到对应内存区域里修改值实现的。
(1)strcpy
标准格式:char * strcpy ( char * destination, const char * source )
(2)strcat
标准格式:char * strcat ( char * destination, const char * source );
(3)memcpy
标准格式:void * memcpy
( void * destination, const void * source, size_t num );
(4)memmove
标准格式:void * memmove ( void * destination, const void * source, size_t num );
其中内存函数的第三个参数是拷贝的字节,一般情况推荐用memmove
4.sscanf和sprintf
上面的字符串函数和内存函数已经可以实现我们全部的需求,但是仍然不方便,这个时候就可以考虑sprintf。
sprintf就是将我们想要输入的任何数据直接存入字符数组中。
我们把数组的地址传过去,函数就能自动找到对应的区域并将这块区域的数据进行修改。
这非常方便,同时这个函数也可以使用占位符,和printf一致。
sscanf也是对字符数组内的数据进行操作,和scanf的功能类似。但是scanf是需要我们从屏幕中输入数据,再进行读取。而sscanf是直接在字符串中对数据进行读取。读取的规则和scanf一致。