大家好,又见面了,我是你们的朋友全栈君。
在没接触数组之前,同学们用的都是定义一个一个变量来存放数据,但是这样就有一个缺陷,如果数据量很大的时候,比如有50个学生的成绩需要录入进去,那么定义50一个变量将会非常耗费时间,而且用scanf()函数输入数据的时候也很麻烦。
int stu1, stu2, stu3, ..., stu50;
scanf("%d %d %d %d ...",&stu1, &stu2, &stu3, ..., &stu50);
那么在C语言中有没有一种东西可以处理上面的数据呢?
当然有啦,数组这时候就出现了。
数组
数组是数据结构(我们大一下学期会专门学习这一章节),它可以存储一个固定大小的相同类型元素的顺序集合。<摘自百度>
有几个关键字要注意一下:
1:固定大小,
2:相同类型,
3:顺序集合。
要理解数组就得理解这三个关键字,我接下来一个一个对这个关键字进行讲解。
一:数组.固定大小
我们定义一个数组的时候,都必须事先告诉编译器这个数组的长度是多少,好让编译器给我们分配长度大小的内存空间,用来存放数据。
比如第一个例子,我想存放 50 个学生的成绩,或者存放一年每个月的销售额. 那么数组的定义是这样的:
double ArrStu[50]; //Array:数组
double Sales[12]; //一年十二个月,所以长度是12
观察下上面的两个数组,可以注意到数组(在这里先是一维数组)定义的基本格式是:
DataType ArrName[ size ];
//datatype 数据类型,如 int, long, float, double...
//ArrName 数组的名字,这里起名的方式跟变量名字的起法是一样的。
//size 数组的大小,这里的大小是固定的。
//[] 下标运算符,如我们要索引第2个元素,那么就是 Arr[1];
二:数组.数据类型
这里的数据类型就是上面提到的 datatype 。 一旦你确定了数组是何种类型的,那么你存放的数据就应该是这种类型的。
你不可以定义了 int 类型的数组,却用来存放浮点数,虽然可以编译通过,但是会得不到我们想要的结果。
如:
int arr[2]; //定义一个长度为2的int类型的数组
arr[0] = 12.5; //赋值
arr[1] = 14.8;
运行结果如图:
int型,以%d的格式控制符输出,就会只保留整数部分,小数点后面的全部截断,所以输出的结果是12 14.
三:数组.顺序集合
假如我们定义了一个长度为 10 的数据,操作系统就会为其分配连续的十个内存地址。
这些地址用来存放地址,每一个地址所占的字节是数组的数据类型所决定的。
如int类型的每一个地址占据着4个字节,double类型的8个。
这里我用了取地址符将数组每一个元素的地址给显现出来,可以注意到各个元素之间的地址相差了4,为啥是4而不是别的呢?这是因为一个我一开始定义的数据类型是int类型的。
这里补充下内存地址的理解:
1:内存地址只是一个编号,代表一个内存空间。
2:内存地址也是内存当中存储数据的一个标识,并不是数据本身,通过内存地址可以找到内存当中存储的数据。<相当于通过你身份证上的地址信息,可以找到你的家乡一样.>
3:你也可以把计算机内存想象成一条长街上一间间房子,每间房子上面都有且只有一个唯一的编号,房子可以存放数据。
如这里的首元素的内存编号是 5240768,第二个元素的内存编号是 5240772,
这里也需要知道一点,这里的编号,只是该数据存放的首地址,只需要知道首地址就可以获取整个地址的值。
其他:
一 : 数组定义时候的方括号 [] 和 花括号里面的常量
上面我介绍了数组的定义方式和例子,如: int arr[10]
这里的10表示整个数组长度为常量10,[ ]也叫做下标运算符,如上面介绍的那样,你要索引哪个元素,就直接写该元素所在的位置即可。
这里要强调一点,数组的下标(index) 的范围是 0 ~ size – 1
下标下界是0,上界是 size – 1 如果应用不当,就会出现越界的错误。
在现在的学习阶段,方括号里面的内容必须是一个常量,而不能出现像
int n;
int arr[n];
二:数组的初始化
数组的初始化是在其定义的时候就应该执行的,如,为5个已经知道的整形数据进行排序,那么:
//正确
int ArrNum[5] = {
43, 65, 32, 774, 899 };
//而不能用下面这种方式
int ArrNum[5];
ArrNum[5] = {
43, 65, 32, 774, 899 };
因为对于 ArrNum[5] = 来说,这是一个赋值操作,将右值赋值给左值,一切常数、字符和字符串都是右值。在这里 { 43, 65, 32, 774, 899 }; 并不是右值的一种,所以这是错误的。
另一个错误是,ArrNum[5] 下标为 5 这个元素实际上并不存在的。原因上面 “其他,第一点”有讲述,这也属于数组的越界。
有数字类型的数组初始化,也有字符类型数组的初始化。 例如
//方式一,单个单个元素的赋值,用单引号引起来每一个字符
char Name1[9] = {
'H','y','d','r','o','g','e','n'}; //H2
//方式二,直接用双引号,将字符串赋给变量Name2
char Name2[9] = "Hydrogen";
char Name2[9] = {
"Hydrogen"}; //
三:数组的越界
这里讲的数组长度存在一个上界,一旦超过了这个界限会如何?
前面讲述到了,一旦数组定义完毕,系统就会为其分配它长度大小的空间地址。
而一旦超过了这个大小,就会发生一些未知的错误,也就是所谓的越界
这里用一个例子来说明下越界后数组内部的值的情况:
由运行结果可以知道,当数组的下标超过了上界后,其后面的值都是不确定的。
以上是数组的三个要素和一些补充,既然有数组了,我们如何为其赋值呢?总不可能采取:
scanf("%d %d %d...", &arr[0], &arr[1], &arr[2]...);
这样冗长的表达式吧。
考虑到数组当中,如果要对数组其中的某一个元素赋值的话,我们可以利用对应的下标索引出。即:
arr[0] = 23;
arr[1] = 44;
arr[2+3] = 412; //方括号里面可以有加减操作
arr[2*3] = 32; //也可以有乘除
arr[n--] = i; //也可以是变量(变量的值对于数组来说有意义。)
...
arr[size-1] = 90;
在结合前面学习到的循环结构,是否可以将两者结合起来呢?
答案当时是可以的。
C语言中,循环有三种:
for( 表达式1; 表达式2; 表达式3) {
语句块; }
while(表达式){
语句块; }
do {
语句块; }while(表达式);
每一个循环结构都需要一个循环变量来对其进行控制,如 i, k, j 每一个循环体,
对于循环变量来说:
1:其值都需要提前指定其大小(循环从哪里开始)
2:循环变量的上限(也就是循环到什么时候结束)
3:循环变量是如何改变的(如每次执行完循环体后,循环变量自增1,或是自增2…)
对于循环结构的 for 和 while 来说,执行第三步,都是在执行循环体后在执行的。
对于do-while()结构来说,无条件的执行一次。
讲到这里,很自然的就可以将循环结构和数组联系起来了。
对于数组的赋值,由于其下标可以用任意小于其上界的数字进行索引,那么我就可以借助一个循环变量 i , 来对其进行元素的索引。
可以这样理解:一个数组定义好了,在内存中已经分配了连续的空间地址,这个相当于一条街上连续的几户人家定了同一个公司的牛奶,然后每次配送员,只需要携带定的数量牛奶,一个接着一个送过去就可以了。
用代码写出来如下:
这里的循环变量 i 从 0 开始,也就是索引数组的第一个元素,即其下标为0的元素。
循环体的内容是将数据写入对应下标,每次执行完循环体后,循环变量自增1,即转到数组的下一个下标。这样循环执行,直到循环结束位置。
那对于字符数组呢?
字符数组有三种输入方式
一:用循环结构一个字符一个字符输入
二:调用gets()函数
三:调用scanf()函数
这里注释掉的两种输出方式都没啥问题,但是有个前提是有结束符号。
细心的人可能注意到了我这里第一种方式多了一行
arr[i] = 'arr[i] = '\0';
';
‘\0’是啥?有啥作用?
这个就是我上面提到的结束符号,输出的时候告诉编译器我这里结束啦,不可以再往后结束啦。
对于gets(), scanf(); 两个函数,在你输入字符串结束后,会自动在字符串结尾加上’\0’,这个是编译器帮你做到的,无需担心。<缓冲区和scanf的缺陷参见上一篇内容>
但是对于getchar();函数来说,却没有这个功能,它仅仅只是从缓冲区读取字符给你,也就是说,在最后你需要自己加上一个结束标记。
以上是一维数组的一些基本知识,以及一些补充。
对于二维数组来说,它的定义比一维的多了一个方括号:
int Arr[4][4];
一维数组像一条线一样,只有长度;二维数组有行,有列,可以看成有长和宽的矩形一样。
数据大小就是LH,如上面的二维数组,长度就是44=16。
在内存分配上面,是否也是按照二维的样式来分配呢?答案是否定的,它分配内存也是开辟了连续字节的。
这里首内存地址编号是:9828620
尾内存地址编号是: 9828680 < 9828620 + 15 * 4 = 9828680)(减去首地址这个元素)
可以看到这也是连续分配的。
值得注意的是,在输入,输出二维数组的时候,需要用到双重循环。
一维数组需要一层循环,二维两层,三维三层。
对于二维数组的理解,可以结合一维来。(二维比一维多了“行” 这个元素)。
在后面的学习中,可以将数组和指针联系起来,在更后续的学习中,可以联系到数据结构里面,这里以后学习到了自然会明白。
(如有错误,欢迎指出)
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/157321.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...