0基础C语言积跬步之数组
首先声明,此博客不照搬抄袭,存在自己的见解,我所使用的编译器是VS2022
目录
1. 数组的概念
2. 一维数组的创建和初始化
3.数组的类型
4. 一维数组的使用
5. 一维数组在内存中的存储
6. sizeof计算数组元素个数
7. 二维数组的创建和初始化
8. 二维数组的使用
9. 二维数组在内存中的存储
10. C99中的变长数组
一、数组的概念
数组就是一组相同类型元素的集合
注意:
(1)数组的元素个数不能为0,如int arr[0],这样写是不成立的
(2)数组的初始化int arr[1]={};如果什么都不写,在C99以及之后,会自动将数组里的元素全部初始化为0
(3)数组中的所以元素,它们的类型都是相同的
分为一维数组和多维数组(常见的是二维数组)
二、 一维数组的创建和初始化
语法形式:
数据类型+数组名[常量表达式]={初始化}; 例如:int arr[3]={1,2,3};数据类型:可以是char、short、int、float、double等,也可以是自定义类型,如结构体类型
数组名:可以任意取,但不能取C语言已经存在的关键字,其他的有意义就行
常量表达式:选取实际需要合适的常量值来设定大小就好了
初始化:用大括号括起来
//完全初始化 int arr1[3] = { 1,2,3 }; //不完全初始化,arr[0]存的是1,剩下元素自动初始化为0 int arr2[3] = { 1 }; ////错误的初始化 - 初始化项太多,编译器会直接报错 int arr3[3] = { 1,2,3,4 };一维数组的初始化[ ]内也可以不写常量,比如int arr[ ]={1,2,3};这样初始化也是可以的,编译器会根据你大括号内所写的元素个数来给数组arr设定合适的大小
三、数组的类型
数组去掉数组名留下的就是数组的类型,例如:
int arr[10]; //类型为 int [10]
char ch[5]; //类型为char [5]
int arr1[3][5]; //类型为int [3][5]
算是一种自定义类型
四、一维数组的使用
1.数组下标
int arr [n];n为常量表达式
int arr[10]={1,2,3,4,5,6,7,8,9,10};
数组每个元素分别都对应着一个下标,从0开始到n-1,分别对应着一个数
[ ]:下标引用操作符,用于对数组的元素进行访问,它是双目操作符,有两个操作数,分别是数组名和元素下标,例如我们想访问数组里的10这个数,我们先找10对应的下标,是9,数组名是arr,所以对应的引用就是arr[9],就能找到10。
也可以写9[arr],两个操作数交换位置不影响访问结果
2.数组元素的打印
#include <stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }运行截图:
注意:直接写printf("%d ",arr)是无法打印出整个数组的,只会打印出一串地址数,因为单独的arr数组名所代表的含义是:数组首元素的地址,所以arr=&arr[0]。而且因为是用%d打印,所以会把二进制位的地址数强行转化成一串十进制的数打印出来
所以我们在打印整型数组时应该使用循环,分别找到每个数对应的下标,依次引用访问打印
例如下方情况:
#include <stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } printf("\n"); printf("%d ", arr); return 0; }运行截图:
但是如果是字符串数组,例如char ch={"abcde"},那就可以用printf(“%s”,ch)直接来打印数组,而不需要使用循环,例如下方代码:
#include <stdio.h> int main() { char ch[10] = {"abcde"}; printf("%s\n", ch); return 0; }运行截图:
这是因为printf中使用%s打印字符串的规则,就是找到字符串首元素的地址后,会依次从低地址向高地址处打印,直到遇到字符'\0',才会停下来,而ch正是字符串首元素的地址,字符串末尾会隐藏一个'\0',所以不会存在打印出错,打印abcde后遇到'\0',就停止了打印
3.数组元素的输入
#include <stdio.h> int main() { int arr[10] = { 0 }; int i = 0; //输入数组元素 for (i = 0; i < 10; i++) { scanf("%d", &arr[i]); } //打印数组元素 for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }整型数字数组元素的输入也需要用循环,输入数值一个个给每个数组的元素
但对于字符数组的输入,不需要使用循环
运行截图:
下面演示字符数组输入的代码:
#include <stdio.h> int main() { char ch[10] = { 0 }; int i = 0; scanf("%s", ch); printf("%s\n", ch); return 0; }运行截图:
如图所示,输入“abcde”,可以正常打印,但输入Hello bit时,却只打印了Hello
这里我们就要扩展一个知识点:%s在scanf中遇到空白字符会自动截止,不读取空白字符,只读取空白字符之前的字符,并且读完自动在末尾加 \0,所以只读取了Hello
想要解决这个问题我们要用到一个新的占位符:%[^\n]
扩展:%[^\n]
%[^\n]意思是:除非遇到\n换行,\n之前的内容全部读取,是【仅属于 scanf】的专属格式占位符!
#include <stdio.h> int main() { char ch[10] = { 0 }; int i = 0; scanf("%[^\n]", ch); printf("%s\n", ch); return 0; }运行截图:
五、一维数组在内存中的存储
数组在内存中的存储是连续存放的,并且随着小标的增长,地址也从小到大(从低到高)增长
我们可以来验证一下:%p用于打印地址
#include <stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; for (i = 0; i < 10; i++) { printf("&arr[%d] = %p\n ", i, &arr[i]); } return 0; }运行截图:
可以看到,整型数组的每一个元素都为int类型,占四个字节的地址内存,图中arr[1]的内存地址正好比arr[0]的高四个字节的内存单元地址,刚好是一个整型,下方的也都一样,都间隔四个四节的内存单元地址,由此可说明数组在内存中的存储是连续的,一个地址占一个内存单元,单位为字节
六、 sizeof计算数组元素个数
sizeof操作符是一个可用于查询计算数据类型所占总空间的字节大小,它的查询原理是在编译阶段就查询了C语言规定的各个数组类型的定义表格,比如sizeof(int),编译时sizeof就查询int类型,C语言规定是4个字节,所以sizeof(int)结果就为4
sizeof(int [10])计算时,其实是在编译阶段查询了int为4个字节,然后已知这个数组中有10个元素,就会直接计算4×10=40来获得数组所占总空间字节的大小
#include <stdio.h> int main() { int arr[10] = { 0 }; printf("数组类型所占总字节大小=%d\n", sizeof(int [10])); printf("数组空间所占总字节大小=%d\n", sizeof(arr)); printf("数组元素所占字节的大小=%d\n", sizeof(arr[0])); printf("数组中元素的个数=%d\n", sizeof(arr)/sizeof(arr[0])); return 0; }运行截图:
sizeof(arr):数组空间所占总字节大小
sizeof(arr[0]):数组元素所占字节的大小
我们用sizeof(arr)/sizeof(arr[0])来计算出数组元素的个数
七、 二维数组的创建和初始化
1.二维数组的概念
二维数组其实就是把一维数组作为了数组的元素,就称之为二维数组,亦知,如果把二维数组作为数组的元素,那么这个数组就被称为三维数组
2.二维数组的创建
数据类型+数组名[常量表达式][常量表达式]={初始化}; 例如:int arr[3][5]={0};第一个常量表达式表示 行 ,第二个常量表达式表示 列
int arr[3][5];表示一个3行5列的整型数组,如下图所示
3.二维数组的初始化
//完全初始化 int arr[3][3] = { {1,2,3} ,{ 2,3,4 } ,{ 3,4,5 } }; //完全初始化 int arr1[3][3] = {1,2,3,2,3,4,3,4,5}; //不完全初始化,只初始化的arr[0][0]为3,剩下的元素自动初始化为0 int arr3[3][3] = {3}; //不完全初始化,只初始化每一行的前两个元素,剩下的元素自动初始化为0 int arr4[3][3] = {{1,2},{2,3},{3,4}};int arr[3][3]表示数组有3行3列,初始化每一行都用一个括号括起来,这样看起来更加清晰明了,不加也没事,就像上面的int arr1[3][3]的初始化那样也可以
注意!!!
一维数组初始化时,[ ]内部的常量可以省略,编译器通过元素的个数来设定数组大小
但int arr[]={};当你没有给数组给定大小时,就不能不初始化,不然编译器无法算出数组的大小,会显示错误
二维数组初始化时,int arr[行][列],行可以省略,但列不能省
八、二维数组的使用
1.数组下标
二维数组的行下标是从0开始的,列下标也是从0开始的
例如int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}}
我们可以把二维数组看成一个个一维数组的集合,如上图二维数组,我们可以看成三个一维数组分别是,int arr[0],int arr[1],int arr[2],这三个分别是它们的数组名,然后我们再访问里面的元素,如想访问第0行第0列的元素,就使用int arr[0][0]
2.数组元素的输入和打印
#include <stdio.h> int main() { int arr[3][5] = { 0 }; int i = 0; //数组元素的输入 for (i = 0; i < 3; i++) //产⽣⾏号 { int j = 0; for (j = 0; j < 5; j++) //产⽣列号 { scanf("%d", &arr[i][j]); //输⼊数据 } } //数组元素的打印 for (i = 0; i < 3; i++) { int k = 0; for (k = 0; k < 5; k++) { printf("%d ", arr[i][k]); } printf("\n"); } return 0; }运行截图:
九、 二维数组在内存中的存储
二维数组在内存中的存储也是连续的,我们可以打印出来看看:
#include <stdio.h> int main() { int arr[3][5] = { {1,2,3,4,5} ,{ 2,3,4,5,6 } ,{ 3,4,5,6,7} }; int i = 0; int j = 0; for (i = 0; i < 3; i++) { for (j = 0; j < 5; j++) { printf("&arr[%d][%d] = %d 地址为:%p\n", i, j,arr[i][j],& arr[i][j]); } } return 0; }运行截图:
可以看到,按照下标的顺序,地址都是从小到大排列,每个间隔四个字节,刚好是一个数组元素的大小,这说明二维数组中的元素在内存中的存储也确实都是连续的
十、C99中的变长数组
变长数组的好处就是,我们在之前使用int arr[常量]={初始化}初始化数组时,我们数组元素的大小都被固定了,而有了变长数组之后,我们之前存放数组的初始化大小的常量,就可以变成存放变量,我们在每次运行的时都可以重新设定数组的大小,不用再修改代码,解决了有时候数组大小太大占内存或者太小数组大小不够用的情况
要注意的是,由于变长数组的大小由于是在运行时创建的,所以变长数组不能初始化,因为一旦初始化,数组的大小就会被确定,和变量冲突,会直接报错
int n; scanf("%d", &n); // 运行时输入大小 int arr[n]; // 用变量n做大小在C99以及C99之后的编译器,基本都支持变长数组,但Vs编译器尚未添加变长数组这个语法,所以我用Devc++来验证实现变长数组
#include <stdio.h> int main() { int n = 0; printf("数组长度:"); scanf("%d", &n); int arr[n]; int i = 0; printf("元素:", n); for (i = 0; i < n; i++) { scanf("%d", &arr[i]); } printf("你输入的数组是:"); for (i = 0; i < n; i++) { printf("%d ", arr[i]); } return 0; }运行截图:
第一次修改数组长度为5
运行截图:
第二次修改数组长度为10
程序员不需要在再创建数组时给其一个估计的大小,而是可以在运行后再给其分配所需的精确长度,做到不浪费占用多余的内存,内存利用率更高