Part 1 前言
各位村友大家好,欢迎来到自学编程村~
本节内容,我们将会来介绍C语言的一个非常重要的概念——数组。
有第一节的铺垫,实际上我们对数组是有一个初步的、浅显的印象的。它会有助于我们本节的理解。本节内容实际上还是比较轻松的,我们也会将数组对应到内存存储空间中来去帮助大家理解。
哈哈还是要说一句老生常谈的话题:没有关注的朋友们,为了防止找不到,可以关注了再看呦~~~
以下,是本节的思维导图:
Part 2 上节回顾
我们在此,一起回顾一下上节和上上节的内容。
上节和上上节的内容,我们主要讲了:
1、表达式;
2、语句;语句的含义,重点讲了分支语句和循环语句。从含义、分类、应用等方面来进行描述。同时,也补充了一些其他的语句。
3、本节重点讲了函数,从函数的基本概念、分类(库函数、自定义函数),然后讲到了函数的调用,传值和传址调用;
4、讲到了函数的栈帧。还讲到从反汇编的角度来理解函数初始化、调用及销毁过程。
好了,闲话少说,我们抓紧进入正题吧。
Part 3 数组的概念
我们老规矩,在介绍一个知识点之前介绍它到底是什么。
数组,说白了就是(1)一群相同元素的结合;(2)并且它们在内存当中是连续排列的;(3)我们可以通过下标的方式去随机访问它们。
如下,摘自百度百科:
我们在之后的学习过程当中将会通过自己运用,来去深刻体会其中的含义(来日方长哈哈哈哈)。
Part 4 一维数组
数组创建的语法形式是怎么样的呢?
对一维数组来说,是这个样子的:
type array_name[const n];
它是什么意思?
type表示一个类型。比如int,char等;
array_name表示你的数组名,就好像变量名一样;
n是一个常数(const即代表常量的意思,注意,这是C90及以前的标准)。注意,这里不可以是负数。
我们来看:假设这样一个代码:
char arr2[5] ;
如上所示,这里的arr2为数组名,数组里有5个char类型元素。
int n = 3;
int a[n];
由上图可知,在vs2019中,会报错,而且报错的理由就是表达式必须要含有常量值。哪怕你是const修饰的变量。
在C99之前,这种写法是不允许的,而在C99之后,这种写法是允许的。而我们vs编译器的编译环境并不能很好的支持C99的语法,如果我们在gcc编译器或者其他对新语法更加支持的编译器下,这种写法是可以的。
我们在这里可以给大家演示一下:(不过我们会用到一系列还没有介绍过的东西,所以我们只要看结果就行,不用关注过程)(我们是在Linux系统、最新版本的gcc环境下来为大家进行演示)
(如上图为源代码)
这是我们写的代码:
这两个图片说明我们编译和运行成功。没有报错,那么就说明我们写的代码是正确的。
也就是说,这种写法是可以的。但必须说的是,在C99(即C语言99年的版本)标准之后才支持这种写法。
需要注意一下的是,变长数组是不支持初始化的。并且变长数组也是不能用static、extern等关键字修饰的。
况且我们以后如果用变长数组一般都用动态开辟,直接用这样的变长数组还是很少的。
char arr2[5] = {'0','1','2','3','4'};
int a[] = { 1, 2, 3};
char arr1[] = { 'a','b','c'};//方式1
char arr2[] = "abc"; //方式2
我们可以依然借助调试来看:
今天,我们就和大家来把sizeof和strlen的关系探讨清楚:
1、首先,sizeof是一个操作符(或者叫运算符);而strlen是库函数,使用时要引用头文件string.h
2、用途不同。sizeof是计算一个数组(或者其他类型)所占的空间大小,而strlen专门用于求字符串的长度,将’ '前面的字符串的长度计算出来。
3、算的方法机制不同。sizeof不会受到' '等字符的影响,关注的是空间的大小,有多少空间就计算出多少空间。而strlen是遇到' '的时候才会停止,关注的是字符串的长度。并且也是在第一次遇到' '的时候就停止了。如果没有遇到' ',那么它将会是一个随机值(因为它会一直往后找,直到碰到' ')
它是另外一种数组初始化的方式。
需要注意的是,这种语法形式同样是在C99编译器之后才支持的,而我们前面说过,VS对C99的标准支持的不是那么好,所以这种初始化的方式在vs编译器下依然会报错。所以我们等会的举例在vscode的gcc环境中进行。
那么这种初始化是什么呢?
在传统的初始化数组中,必须要初始前一个元素,才能初始化下面的元素。
举个例子:
比如说int a[10];
就比如说,如果你想初始化a[5]为1,你必须先初始化a[5]前面的元素。
就是说,必须这样
int a[10] = { 0, 0, 0, 0, 0, 1};//传统是初始方法
那么如果指定初始化器来初始化这一个数组,那么我们可以这样:
int a[10] = {[5] = 1};//C99提供的初始化方法
可以看到,这样的初始化方式还有两个特性:
1、 如果初始化容器后面有更多的值,那么这些值将用于初始化指定元素后面的值。以上面的代码为例,[4]=31,30,31,那么第五个元素是31,后面的元素30,31就会默认放在第六个和第七个位置作为a[5]和a[6]的初始化的值。
2、如果再次初始化指定的元素,那么最后初始化的将取代之前的初始化。比如上面的[1]=29,那么会将原先第二个位置上的元素28改变成29。
那么这样如果这样初始化,会发生什么?
所以,这样的数组是可读可写的。
那如果有一天,我想让我的数组变成只可读的,就是说其他人不能够修改我的数组里的值,这个时候,我们可以怎么办呢?
这个时候,我们就会用到我们之前所说的一个关键字:const
const是什么?
我们在这里刚好可以和大家谈谈。我们如果一个变量int a=10;我们可以对变量a进行加减乘除的运算。因为这里的a是一个变量。但是,我们一旦加上了const修饰,那么就变成了只可读但是不可以写的变量了。
所以,总结一下上面所说的就是:如果一个变量被const修饰,它就变成了只读的属性。
有了上面的知识基础,我们就可以这样创建一个数组:
const int a[3] = { 0, 1, 2};
可以看出,它是一个随机值。
因为当数组访问越界的时候,它其实只是内存中的一个随机的一块区域的值,而这一块区域我们并没有去使用它的权限,所以它实际上是内存上某一块区域的一个随机值。
那为什么会出现这样的情况?编译器会什么会让数组越界的情况发生而不会报错?
原因很简单,就是C对于程序员是足够信任的。所以,它相信程序员不会写出越界的数组,因此,就不再设置专门的步骤来检测数组是否越界。这样的好处是使得程序变得更加优化。不用每一次都去检验数组是否越界,从而会增加程序运行的速度。
Part 5 一维数组在内存中的存储
这是为什么呢?
我们需要知道这样一个事情:数组在内存中的存储是连续的。而一个字节给一个地址编号。
所以,它们的地址是挨着的。
那为啥每相邻的元素的地址相差的是4,而不是1?
原因很简单,因为我们在数组中存储的每一个元素是int类型。每个int类型的空间占4个字节。
并且随着数组下标的增长,地址由小到大。
Part 6 二维数组的创建、初始化、使用及内存存储
实际上,一维数组弄懂了,二维就很简单了,基本上都是”依此类推“。
我们如果创建了这样一个数组:
int a[3][4];
最老实的一种,就是把每一个元素都一一列举出来。那么在创建这个数组的时候,就会默认先把一行布满,然后接着下一行去排列。
第二种,就是不完全初始化:
类比一维数组,二维数组也一样,如果在大括号里面所列举的元素的个数小于[ ]中的元素的个数,那么就在后面默认补0;
第三种初始化的方式,可以这样:
这里的a[i][j]表示的正是访问第i行第j列的元素
还以刚刚上面的数组举例,我们将它们的地址依次打印出来:
那么如果这样说的话,我们的二维数组就可以这样来理解:
(我们逻辑上的二维数组)
这个a[3][4]数组中,可以拆分为a[0],a[1],a[2]三个一维数组,然后每个一维数组中有4个int类型的元素
(实际空间中存储的二维数组)
Part 7 章节回顾
本节我们主要将了数组的有关概念及用法。
主要讲了:
1、一维数组的创建、初始化、用法,以及在内存中的存储情况,即其在内存中存储是连续的;
2、在初始化中我们还讲了一个C99的初始化容器初始化的方法;
3、讲了多维数组的创建、初始化、用法等。
好啦,本节内容就到这里啦~
如果觉得干货满满,就点个关注呗hhhhh
下节内容,我们讲详细地给大家介绍运算符的相关知识~~~
也可以加一下村长的wx呀~
发表评论