C语言数组

1、简单概述

首先需要说明一点的是,数组是数据结构的一种,数据结构描述了特定的数据类型以及数据类型之间的关系。

数组是同种数据类型组成的集合,是同种数据类型的集合。数组是集合的一种,我们获取得到集合,就可以来操作集合中的元素。
习惯上,将数组中的每个变量叫做元素。

它们之间的关系是:数组的内存空间地址是连续的,数据类型必须都相同,如果不相同就不符合定义。

2、操作

首先先定义一个数组,然后进行初始化。数组的初始化分为完全初始化和部分初始化以及不初始化

// 完全初始化
int a[5] = {1,2,3,4,5};
// 部分初始化
int b[5]= {1,2};
// 不初始化,不进行初始化,里面全部都是垃圾值。
int c[5];

完全初始化和部分初始化的区别就在于:是否将数组中的每个元素都进行了赋值?
就拿最后一个不初始化来举例子:

# include <stdio.h>
int main(void){
	
	int a[4];
	for(int i = 0;i<4;i++){
		printf("对应的值是:%d\n",a[i]);
	}
	return 0;
}

下面进行正常的测试:

# include <stdio.h>
int main(void){
	
	int a[4] = {1,2,3};
	// int[3] b = {3,4,5};
	printf("a对应的值是:%d\n",a);
	printf("a[0]对应的值是:%d\n",a[0]);
	printf("a[1]对应的值是:%d\n",a[1]);
	printf("a[2]对应的值是:%d\n",a[2]);
	printf("a[3]对应的值是:%d\n",a[3]);
	printf("a[4]对应的值是:%d\n",a[4]);
	return 0;
}

直接看控制台输出:

a对应的值是:6487568
a[0]对应的值是:1
a[1]对应的值是:2
a[2]对应的值是:3
a[3]对应的值是:0
a[4]对应的值是:10687360

--------------------------------
Process exited after 0.1377 seconds with return value 0
请按任意键继续. . .

可以看到的是对数组进行部分初始化,没有初始化的元素的默认值是0

通过上面的案例,提出问题:
问题1:a是什么?

通过控制台输出,控制台出现了一个很诡异的现象,a输出的整型值是6487568,那么这个a是什么?

问题2:定义一个数组,长度是4,里面有4个元素,从下表0开始,a[0]、a[1]、a[2]、a[3]这四个元素,但是a[4]又是什么?

解决第一个问题:

通常我们会这里理解,因为a前面是int,但是[]代表的是什么意思?
我们误以为首先a代表的是一个变量,而且在初始化的时候给变量赋值了初始值。
但是实际上数组名a,代表的是一个常量。但是是我们实际使用中,编译器会将其隐式的编译成指向内存中数组的第一个元素的首地址

数组名字a指向这个数组中第一个元素的地址,而不是这个地址里面存储的值。这个可以验证:

# include <stdio.h>
int main(void){
    // 我这里只是给数组中的三个元素进行了赋值,而没有对后面的元素进行赋值,那么默认初始化就是0
	int a[4] = {1,2,3};
	// int[3] b = {3,4,5};
	printf("a对应的值是:%d\n",a);
	printf("a[0]对应的值是:%d\n",&a[0]);
	return 0;
}

控制台输出:

a对应的值是:6487568
a[0]对应的值是:6487568

--------------------------------
Process exited after 0.1527 seconds with return value 0
请按任意键继续. . .

对a[0]取地址,可以看到a[0]当前所处的内存空间的地址值和a是一样的,那么就是说a和a[0]的地址是同一块空间。
总结:这个数组的名字变量a是一个指针常量。关于这个问题,后面还回来进行详细的描述。

解决第二个问题:

a[4]是什么?首先在分配内存的时候,操作系统给这个集合分配了一块内存空间,每个元素是int类型的,长度是4,元素只有:a[0]、a[1]、a[3]、a[3]这四个元素,对于a[4]来说,看看下面的实验:

# include <stdio.h>
int main(void){
	
	int a[4] = {1,2,3};
	// int[3] b = {3,4,5};
	printf("a对应的值是:%d\n",a);
	printf("a[0]对应的值是:%d\n",&a[0]);
	printf("a[1]对应的值是:%d\n",&a[1]);
	printf("a[2]对应的值是:%d\n",&a[2]);
	printf("a[3]对应的值是:%d\n",&a[3]);
	printf("a[4]对应的值是:%d\n",&a[4]);
    printf("a[4]对应的值是:%d\n",a[4]);
	return 0;
}

控制台输出:

a对应的值是:6487568
a[0]对应的值是:6487568
a[1]对应的值是:6487572
a[2]对应的值是:6487576
a[3]对应的值是:6487580
a[4]对应的值是:6487584
a[4]对应的值是:1774464

--------------------------------
Process exited after 0.1567 seconds with return value 0
请按任意键继续. . .

可以惊奇的发现,原来对于a[0]、a[1]、a[2]来说,地址也是对应的内存地址首单元+数据类型所占用的字节数。但是a[4]这块内存空间尽管存在,但是对应的是垃圾值,而不是默认值。

因为对于a[4]这个元素代表的空间不属于声明的数组的之内,所以这里操作系统没有进行初始化,但是我们却可以访问到。但是可以访问到并不代表可以来进行操作它。

3、错误示例

错误示例一

# include <stdio.h>
int main(void){
	
	int a[4];
	a[4]= {1,2,3};
	return 0;
}

编译都不会通过,因为int a[4]进行了数组的声明定义,声明在内存空间中创建四个连续的int类型的空间,然后再进行赋值的时候,发现没有a[4],也可以说a[4]对应的内存空间里存放的是垃圾值。

数组中根本就不存在着a[4],更何况是赋值。代码演示如下:

# include <stdio.h>
int main(void){
	
	int a[4];
	a[4]= 2;
	printf("a[4]对应的值是:%d\n",a[4]);
	return 0;
}

控制台直接输出错误,这里和上面对比,可以发现,尽管可以访问到a[4],但是却无法来对a[4]这块空间来进行赋值。因为此时这个数组就只有a[0]、a[1]、a[2]这三个元素。

所以从这里可以看出来,数组在C语言中就已经知道了数组的长度,因为在定义的时候就已经指明了。

错误示例二

最经典的一种方式就是数组进行赋值,如:将数组a赋值给数组b,也就是说将a数组中的值都赋值给b数组中的元素;

# include <stdio.h>
int main(void){
	
	int a[4] ={1,2};
	int b[] = {};
	b = a;
	for(int i = 0; i<4;i++){
		printf("b中元素的值是:%d\n",b[i]);
	}
	return 0;
}

编译不会通过,是因为数组名是一个指针常量。常量一旦进行了初始化值之后,就无法再次来进行赋值。所以这段代码就是错误的。
如果想要来对数组b来进行赋值,那么需要使用下一个方式:

# include <stdio.h>
int main(void){
	
	int a[4] ={1,2};
	int b[] = {};
	// b = a;
	for(int i = 0; i<4;i++){
		b[i] = a[i];
		printf("b中元素的值是:%d\n",b[i]);
	}
	return 0;
}

查看控制台输出

b中元素的值是:1
b中元素的值是:2
b中元素的值是:0
b中元素的值是:0

--------------------------------
Process exited after 0.4152 seconds with return value 0
请按任意键继续. . .

赋值成功。

4、数组操作

数组中的操作:添加、删除、移动、遍历、最值问题,但是其他语言实现的时候,都会有对应的工具包来进行操作这些数组。我们只需要来操作这些工具类即可来进行操作数组了。

5、二维数组

先甩出来一个二维数组:int a【3】【4】,那么对应的数据就有3*4=12个,有十二个数据

a[0][0] a[0][1] a[0][2] a[][3]
a[1][0] a[1][1] a[1][2] a[1][3] 
a[2][0] a[2][1] a[2][2] a[2][3] 
a[3][0] a[3][1] a[3][2] a[3][3]     

所以从上面观察可以看出来,a【m】【n】代表的是第(m+1)行第(n+1)列的值;

那么int a【m】【n】这个定义说明了,二维数组的最后一个值是a【m-1】【n-1】

二维数组的空间结构:

我觉得就是一个一维数组中嵌套了一个一维数组,行或者是列中存储的是一个数组的首地址的值。

二维数组的初始化:

int a[3][4]={1,2,3,4,5,6};

int b[2][3]={
    	{1,2,3},
        {4,5,6},
        {7,8,9},
};

6、对数组名的理解

参考博客:https://blog.csdn.net/chuhe163/article/details/80795728
首先先举个例子:先定义一个一维数组

int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

上面已经说过了,数组名是一个常量,这个是规定。不然为什么会多带了一个[]来进行说明呢?

这个数组名被编译器隐式的编译成为了一个指向数组中第一个元素首地址的的常量。

但是有两种情况下,编译器不会将其编译成指向首地址的常量:

第一种:对数组名使用sizeof运算符

sizeof(a)

这将会得到整个数组所占的内存大小,a是长度为10的int(4字节)数组,运算结果是40个字节

第二种:对数组名取地址

&a

运算结果是数组的地址。注意,数组的地址和数组首元素的地址是不同的概念,尽管二者的值是相同的。

# include <stdio.h>
int main(void){
	
	int a[4]={1,2,3};
	printf("%d\n",a);
	printf("%d\n",&a);
	printf("%d\n",&a[0]);
	return 0;
}

在我当前的计算机中:三者输出的都是一样的值,所以这就非常的奇怪了。

因为在我们以前的认知中,首先a和&a不应该是一样的值。那么有没有可能是巧合?再试一次,结果还是一样的结果。

除了上面说的两种例外,其他情况下编译器都将数组名隐式转换成指针常量。比如使用下标引用数组元素:

a[3]        // 编译器自动转换成下面的表达式
*(a + 3)

a的值被转换成指针常量,指向第一个元素,向右移动3 * sizeof(int)个字节,然后解引用,便得到了第4个元素的内容。
因为第一种写法会自动转换成第二种,这个过程需要一些开销,所以我们说第二种写法通常效率会高一些。

数组的类型:

以数组a为例,a的类型是:

int *

数组的类型取决于数组元素的类型:如果它们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型就是“指向其他类型的常量指针”。(出自《C和指针》第141页)
这里需要补充两点,&a的类型和二维数组名的类型。
在接下来的第四点会详细解释&a的含义,这里先给出结论,&a是指向数组的指针,而&a的类型是int (*)[10]
然后二维数组的类型同样取决于数组元素的类型,假设有二维数组int b[10][20]
因为C语言的多维数组实际上是一维数组,二维数组实际上只是一个一维数组,只不过里面每个元素又是一个一维数组而已。所以b的类型是int (*)[20],而&b的类型是int (*)[10][20]

一个有趣的事实是,a&a 的值是相同的。
a 的值是数组首元素的地址,它并不是一个指针。
*“取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针”*(出自《C和指针》第142页)。

总感觉这句话说的怪怪的,也就说,对数组名字取地址得到的是指向内存空间的一个指针,这个指针指向数组中的第一个元素的首地址。

通过四条语句可以更好地理解它们的关系:
我知道%p用来输出地址,但为了方便观察我改用%d以整数形式输出

# include <stdio.h>
int main(void){
	
	int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    printf("a      = %d\n", a);
    printf("a + 1  = %d\n", a + 1);
    printf("&a     = %d\n", &a);
    printf("&a + 1 = %d\n", &a + 1);
	return 0;
}

查看控制台输出:

a      = 6487536
a + 1  = 6487540
&a     = 6487536
&a + 1 = 6487576

可以很直观地看出a&a的区别了。(根据上面的推断,这些都是死的知识点,记住了就OK)
a指向首元素,右移一位,地址增加了4字节,也就是一个int的长度;
&a指向数组,右移一位,地址增加了40字节,相当于指向了下一个数组(实际上并不存在),或者说指向了数组a最后一个元素的下一个元素,这在C++里称为尾后指针。

posted @ 2021-08-05 18:28  写的代码很烂  阅读(391)  评论(0编辑  收藏  举报