数组 基础知识 和 冷知识(超详细总结)

目录

1. 前言概述——数组

2. 一维数组

2.1 普通创建格式

2.2 数组的初始化

2.2.1 完全初始化

2.2.2 不完全初始化

2.2.1 初始化创建数组时:元素个数可省略(要点)

2.3 数组下标

2.4 一维数组数据类型(需知)

2.5 一维数组的遍历输入

2.6 一维数组在内存中的存储(底层)

3. 二维数组

3.1 普通创建格式

3.2 二维数组在内存中的存储(底层)  

3.3 初始化方式

3.3.1 单括号初始化

3.3.2 双重括号初始化:按照行初始化

3.3.3 下标特定初始化(深化理解)

3.3.4 初始化创建数组时:行数能省略,列数不能省略(要点)

3.4 二维数组数据类型(需知)

3.5 二维数组的遍历输入

4. sizeof计算元素个数(必考点)

5. C99中的“变长数组”

5.0 常变量的冷知识


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++是常量。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值