目录
3.3.4 初始化创建数组时:行数能省略,列数不能省略(要点)
1. 前言概述——数组
数组的概念:数组是⼀组相同类型元素的集合。
特点:
- 数组中存放的是1个或者多个数据,但是数组元素个数不能为0。
- 数组中存放的多个数据,类型是相同的。
- 数组多是⼀维数组和多维数组,超过2维的多维数组用不上。
2. 一维数组
2.1 普通创建格式
⼀维数组创建的基本语法如下:
1. type arr_name[常量值];
2.//元素类型 数组名[数组大小]
存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。
例如:int math[7] 、char ch[8] 、float score[10]
2.2 数组的初始化
有时候,数组在创建的时候,我们需要给定⼀些初始值值,这种就称为初始化的。而初始化的数据要放在大括号里。
2.2.1 完全初始化
概念:创建数组的同时,把所有元素都初始化赋值了。
比如:
1. int arr[5] = {5,4,3,2,1}; //数组arr的5个元素都被赋值
2. char ch[3] = {'a', 'c', 'f' }; //数组ch的3个元素都被赋值
2.2.2 不完全初始化
概念:创建数组的同时,至少一个元素被初始化赋值。
特点:剩下没被赋值的元素全都被赋值为0。
比如:
1. int a[5] = {1}; //只有a[0]是1,a[1],a[2],a[3],a[4]都是0
2. char ch[3] = {'w'}; //只有ch[0]是'w',ch[1], ch[2]都是'\0'(其实就是0,但不是'0')。
利用这个特点我们可以简单地把数组初始化为0,如int arr[10] = {0};
2.2.1 初始化创建数组时:元素个数可省略(要点)
如果创建的同时还初始化,那么创建时的数组大小可以省略。但是你初始化多少个数据,你的数组大小就是多少。
例如:
int arr[ ] = {1,2,3,4,5,6}; //数组arr的大小为6。
char ch[ ] = {'h', 'i'}; //数组ch的大小为2。
值得注意的是:无论是创建时规定数组大小,还是由初始化来规定数组大小,数组大小一旦被规定,则后续操作都不能修改数组的大小。
2.3 数组下标
1. 数组的序号顺序
假设数组有n个元素,那么下标是从0开始的,最后⼀个元素的下标是n-1。
下标就相当于数组元素的编号,对于int arr[10] = {1,2,3,4,5,6,7,8,9,10}有:
2. 下标引用操作符
在C语⾔中数组的访问提供了⼀个操作符 [ ] ,这个操作符叫:下标引⽤操作符。
有了下标访问操作符,我们就可以轻松的访问到数组的元素了
比如:我们想访问第8个的元素,我们就可以使⽤ arr[7] ;想要访问第4个的元素,就可以使⽤ arr[3] ;想访问第1个元素,就使用 arr[0] 。
3. 一维数组的特定初始化(强化理解)
对于不完全初始化,(完全初始化也行,只是没必要),其实我们也可以用下标进行初始话。
i.创建时规定数组大小的情况:
例如:
int a[10] = { [3]=2, [5]=6 }; //此时a[3]等于2,a[5]等于6,其他元素都为0
ii.由初始化来规定数组大小的情况:
数组的大小 == 最大的特定下标序数 + 1 + 该序数后面的初始化数据的个数
int main()
{
int arr[] = {[3]=3, [5]=7, 9, 1, 2};
return 0;
}
这段代码中,arr的大小是9个int型的数据,其中a[3]、a[5]、a[6]、a[7]、a[8]被初始化赋值,其他元素的数据均为0。
2.4 一维数组数据类型(需知)
序言:网上很多用十几分钟、甚至几分钟的视频来讲解数组的,他们大多数都没讲清楚数组的数据类型是什么,导致很多人以为int arr[5]的类型是int,float arr[3]的类型是float。其实不是这样的,数组本身就是一种数据类型。
数组也是有类型的,数组算是⼀种⾃定义类型,去掉数组名留下的就是数组的类型。
例如:
int arr1[10],arr1数组的类型是 int [10];
int arr2[12],arr2数组的类型是 int[12];
char ch[5],ch 数组的类型是 char [5]。
2.5 一维数组的遍历输入
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);
}
return 0;
}
用一层循环的 [i] 来访问数组的所有元素,遍历打印数组也是差不多这样。
2.6 一维数组在内存中的存储(底层)
有了前⾯的知识,我们其实使⽤数组基本没有什么障碍了,如果我们要深⼊了解数组,我们最好能了 解⼀下数组在内存中的存储。
依次打印数组元素的地址:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 10; i++)
{
printf("&arr[%d] = %p\n ", i, &arr[i]);
}
return 0;
}
数组随着下标的增⻓,地址是由小到大变化的,并且我们发现每两个相邻的元素之间相差4(因为⼀个int型是4个字节)。所以我们得出结论:数组在内存中是连续存放的。这就为后期我们使⽤指针访问数组奠定了基础(在讲指针的时候我们在讲,这⾥暂且记住就行)。
3. 二维数组
前⾯学习的数组被称为⼀维数组,数组的元素都是内置类型的,如果我们把⼀维数组做为数组的元 素,这时候就是⼆维数组。⼆维数组以上的数组统称为多维数组。
3.1 普通创建格式
那我们如何定义⼆维数组呢?语法如下:
1. type arr_name[常量值1][常量值2];
2. //元素类型 数组名[行数][列数];
例如:int arr[3][5]; double data[2][8];
3.2 二维数组在内存中的存储(底层)
int main()
{
int arr[3][5] = { 0 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
}
}
return 0;
}
每⼀⾏内部的每个元素都是相邻的,地址之间相差4个字节,跨⾏位置处的两个元素(如:arr[0][4]和arr[1][0])之间也是差4个字节,所以二维数组中的每个元素也都是连续存放的。
真正的排列图:
3.3 初始化方式
无论是一维还是多维数组,它们都是有完全初始化与不完全初始化,这里只讨论不完全初始化的方式。同样的,不完全初始化剩余的元素统一赋值为0。
3.3.1 单括号初始化
单个括号初始化时,都是默认从数组第一个元素开始,按内存顺序把初始化的值赋完。
如: int arr[3][5] = {1,2,3,4,5, 2,3,4};
从arr[0][0]开始,一直初始化数据到arr[1][2],共8个元素。剩下的元素(arr[1][3] ~ arr[2][4])都被赋值为0。
3.3.2 双重括号初始化:按照行初始化
按照行初始化时,默认第一个大括号被当做第一行,以此类推。
如:int arr[3][5] = { {1,2},{3,4},{5,6} };
第一行arr[0][0]、arr[0][1]被初始化,第二行arr[1][0]、arr[1][1]被初始化,第三行arr[2][0]、arr[2][1]被初始化。剩下的元素为0。
3.3.3 下标特定初始化(深化理解)
i. 特定元素
连用两个下标引用操作符 (形如“ [常量1][常量2] = 值 ”),可以对单个元素进行特定初始化。
例如:
int main()
{
int a[3][5] = { [1] [2] = 4,[2][1] = 3 };
return 0;
}
这里只对a[1][2]、a[2][1]初始化,其他元素为0。
ii. 特定行
只用一个下标引用操作符与大括号组合使用 (形如“ [常量] = { } ”),可以对特定行进行初始化,默认从该行的首元素开始赋值。
例如:
int main()
{
int b[5][3] = { [1]={1,2}, [3]={3,4} };
return 0;
}
这里只有第2行的前两个元素 以及 第4行的前两个元素被初始化,其他为0。
iii. 特定行中的特定元素
大括号中再使用下标引用操作符(形如“ [常量1] = { [常量2]=值 } ”),可以对该行的特定元素进行初始化。
例如:
int main()
{
int c[2][8] = { [1] = { [3] = 3,[6] = 7 } };
return 0;
}
这里只有第2行的c[1][3]和c[1][6]被初始化,其他为0。
3.3.4 初始化创建数组时:行数能省略,列数不能省略(要点)
例如:
int arr5[ ][5] = {1,2,3}; //只有1行,每行5个元素
int arr6[ ][5] = {1,2,3,4,5,6,7}; //有2行,每行5个元素
int arr7[ ][5] = { {1,2}, {3,4}, {5,6} }; //有3行,每行5个元素
简单来说:创建变量不明确大小时,系统只知道要分配一块连续的内存空间给你,依靠输入的数据来推断要给出多大的空间,这又跟二维数组的内存寻址公式有关。最终我们可以省略行,但不能省略列数。
3.4 二维数组数据类型(需知)
与一维数组一样,去掉数组名留下的就是数组的类型。
例如:
int arr1[2][10],arr1数组的类型是 int [2][10];
float arr2[4][4],arr2数组的类型是 float [4][4];
char ch[5][7],ch 数组的类型是 char [5][7]。
3.5 二维数组的遍历输入
int main()
{
int arr[3][5] = {0};
for (int i = 0; i < 3; i++) //产⽣⾏号
{
for (int j = 0; j < 5; j++) //产⽣列号
{
scanf("%d", &arr[i][j]); //输⼊数据
}
}
return 0;
}
用两层循环的 [ i ][ j ] 就能把每行每列都访问完,遍历输出也是如此。
4. sizeof计算元素个数(必考点)
在遍历数组的时候,我们经常想知道数组的元素个数,那C语⾔中有办法使⽤程序计算数组元素个数吗? —————— 答案是有的,可以使⽤sizeof关键字。
首先要明确一点,sizeof 计算的结果永远是数据类型的大小。即使运算的对象是变量名或是数组名,本质上是计算它们对应的数据类型大小。
一维数组的计算方式:
int sz = sizeof( arr ) / sizeof( arr[0] );
二维数组的计算方式:
int sz = sizeof( arr ) / sizeof( arr[0][0] );
补充:接收变量sz最好是创建为 size_t 类型的,因为 sizeof 的返回类型是size_t,这是一种无符号整型,可以用%zd的格式打印。(用int接收也没影响,系统会进行隐式类型转换)
以一个二维数组举例,让我们看看是不是真的能行:
int main()
{
int arr[3][5] = {0};
printf("首元素的字节大小:%zd\n", sizeof(arr[0][0]) );
printf("整个数组的字节大小:%zd\n", sizeof(arr) );
printf("数组元素个数:%zd\n", sizeof(arr) / sizeof(arr[0][0]) );
return 0;
}
结果:
int型的大小是4个字节,首元素的数据类型是int型,所以sizeof(arr[0][0])的结果是4;二维数组arr共有3*5等于15个元素,每个都是int型,总共60个字节;60除以4等于15,最终计算出来数组的元素共15个。
所以sizeof计算元素个数的本质是:数组的类型大小 / 单个元素的类型大小。在这个例子表现为int[3][5] / int == 3*5 == 15。
5. C99中的“变长数组”
在C99标准之前:C语⾔在创建数组、主动规定数组大小的时候只能使⽤常量(如:int arr1[10];)、常量表达式(如:int arr2[3+5];) 。
C99标准之后:C99中给出了名叫变长数组的新特性,允许我们可以使⽤变量规定数组大小。
例如:
1. int n = 0;
2. scanf (" %d ", &n);
3. int arr[n];
遗憾的是:VS2022虽然⽀持⼤部分C99的语法,却不⽀持C99中的变⻓数组特性。如果想测试这一特性,建议用gcc编译器或者手机上的c语言编译器验证。
变长数组特性的本质:c99之前,数组的大小是在编译时确定的;c99之后,数组的大小是在运行时确定的。( 顺序:编译器编译—》链接器链接接——》CPU运行 )
由于C语言在设计时采用了静态内存分配机制,数组作为基本数据结构,其空间需要在程序运行前预先分配好。C89要求所有数组的大小在编译时就已经固定,这主要是为了确保数组所占用的内存大小在程序运行前就已确定,便于静态分配内存。C99允许内存可以动态分配,当变量n的值要等到程序运行起来后,用户输入之后n的值确定了,就能确定数组的大小。
注意:“变长数组”只是一种特性,这种特性为我们提供了一种新的主动规定数组大小的方法。不是说数组的大小可以随时随意改变,无论是编译时规定,还是运行时规定,数组的大小都不能被改变! 但如果你是malloc申请来的一块空间,并由指针变量对这块空间进行管理的“伪数组”,这时你才能对“伪数组”进行扩充和缩减。
5.0 常变量的冷知识
由const关键字修饰的变量称为常变量,常变量的值确定后不能被改变。
如:const int n = 3; //n现在是常变量,而且n的值只能为3不可被改变。
先说结论:在c语言中,常变量属于变量;在c++中,常变量属于常量。
前面说过,vs2022不支持变长数组。比较方法:创建数组时用常变量n来决定数组大小,如果报错则说明常变量是变量,如果不报错则说明常变量是常量。
在.c文件中:
报错了,说明常变量在c语言是变量。如果我们把.c文件改成c++文件,又会怎么用呢:
此时没有报错,说明常变量在c++是常量。