c语言 - 数组

c语言 - 数组,数组定义,二维数组,静态数组,变长数组,动态数组,字符串数组,数组和指针,数组其实也是一种类型

一维数组

定义数组,数组名为a,数组元素的数据类型为int整型,数组长度为5,数组是一个整体,它的内存是连续的

给每个元素赋值,index索引从0开始

也可以在定义的同时进行赋值,数组初始化

访问数组元素,下标的取值范围为 0 ≤ index < length,过大或过小都会越界,导致数组溢出,发生不可预测的情况

二维数组

所谓二维数组,就是数组的每个元素都是数组(实际上还是一维数组,应理解为逻辑上的二维数组)。
假设我们要存放 5 个同学的语数英分数,就可以使用一个 5 行 3 列的二维数组,如下所示:

如果是给全部元素赋值,也可以省略{},结果是一样的

元素查询

对于无序数组,我们只能通过遍历整个数组进行查询
比如,我们输入一个数字,如果该数字在数组中,则打印其下标

对于有序数组,并不需要遍历所有元素,只需要遍历部分元素就可以判断给出的数字是否在数组中

字符数组

由于c语言没有字符串数据类型,只能使用字符数组或者字符串指针来表示字符串string

录入字符串、打印字符串:

字符串字面量总是以'\0'结尾,因此字符数组长度总是比字符串长度大 1,puts()printf()扫描到'\0'就结束输出了。

string.h

静态数组

在C语言中,数组一旦定义之后,其占用的内存空间就是固定的,不能增加或删除元素,只能修改元素的值,这样的数组称之为静态数组
当使用下标访问数组元素时,如果下标大于数组长度或者小于0,就会发生越界(out of bounds),小于0发生下限越界(off normal lower),大于数组长度发生上限越界(off normal upper)
C语言为了提高效率,并不会对数组越界进行检查,即使越界了,也能通过编译,只有在运行期间才可能会发生问题

我们也不知道数组前后的是什么数据,上面的程序貌似没有出现什么大问题,还能正常运行,但是,如果不小心访问到了其他程序的内存或者系统保护的内存等等,程序就会直接蹦了

发生段错误,因为访问了无法访问的内存,程序就被系统直接kill了
数组溢出:当赋予数组的元素个数超过数组长度时,就会发生溢出(overflow)

变长数组

目前主要有三个c语言标准:C89/ANSI C、C99、C11
对于C89,基本上所有的编译器都支持,如微软的VC/VS,GNU的gcc
对于C99,老版本的VC/VS并不支持,而gcc很早就支持了

在C89,必须使用数值常量指明数组长度,不能包含变量
而对于C99,数组长度可以为变量,也就是可以在运行时确定长度,这种就叫做变长数组

注意,变长数组仍属于静态数组,一旦确定长度,就不可再次更改

注意:变长数组不能在定义的同时进行初始化!

动态数组

静态数组由于要在创建数组的时候就确定长度,并且长度length还不能为变量,只能是常数,有时候我们并不知道我们到底需要多长的数组,并且很多时候数组要能动态扩容的,这时候就需要动态数组
所谓动态数组,其实就是在(heap)中创建的数组,通过执行代码为其分配内存,我们自己负责内存的释放

也可使用指针的方式赋值和取值

数组小结

数组(Array)是一系列相同类型的数据的集合,可以是一维的、二维的、多维的,最常用的是一维数组二维数组,多维数组较少用到。

数组的定义格式为:
type array[length](此时分配内存)
type为数据类型,array为数组名,length为数组长度
数组长度 length 最好是整数或者常量表达式,例如 10、20*4 等,这样在所有编译器下都能运行通过
数组在内存中占用一段连续的空间,数组名表示的是这段内存空间的首地址

访问数组中某个元素的格式为:
array[index]
0 <= index <= length-1

数组赋值

排序和查找
简单应用,给一个数组,计算其最大和最小值

数组与指针

数组名的本意是表示一组数据的集合,和普通变量一样,都用来指代一块内存
但在使用过程中,数组名有时候会转换成指向第0个元素的指针

当数组名作为数组定义的标识符(也就是定义或声明数组时)sizeof& 的操作数时,它才表示整个数组本身
在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)

再谈数组下标[]
数组的下标与指针的偏移量相同,都是1个元素占用的字节数,如:
int a[] = {1, 2, 3, 4, 5}, *p, i = 2;
可以通过下面任何一种方式访问a[i]
p = a; p[i];p = a; *(p + i);p = a + i; *p;

对数组的引用a[i]在编译时总是被编译器改写成*(a+i)的形式,C语言标准也要求编译器必须具备这种行为
所以,对于数组a,如果要访问第3个元素的值,可以用a[3],也可以用3[a],都会改写为*(a+3)*(3+a),这并没有任何区别

数组作为函数参数
C语言标准规定,作为类型的数组的形参应该调整为类型的指针
在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第 0 个元素的指针形式
编译器只向函数传递数组的地址,而不是整个数组的拷贝

这种隐式的转换,意味着下面的三种方式完全是等价的

在函数内部,arr 会被转换成一个指针变量,编译器为 arr 分配 4 个字节(32位平台)的内存
用 sizeof(arr) 求得的是指针变量的长度,而不是数组长度
要想在函数内部获得数组长度必须额外增加一个参数,在调用函数之前求得数组长度

数组和指针绝不等价
数组和指针不等价的一个典型案例就是求数组的长度,这个时候只能使用数组名,不能使用数组指针
因为这时候数组名代表的是一组数据的集合,而求数组指针的长度其实只是求一个指针变量的长度

数组是一系列数据的集合,没有开始和结束标志
p 仅仅是一个指向 int 类型的指针,编译器不知道它指向的是一个整数还是一堆整数
对 p 使用 sizeof 求得的是指针变量本身的长度
也就是说,编译器并没有把 p 和数组关联起来,p 仅仅是一个指针变量,不管它指向哪里,sizeof 求得的永远是它本身所占用的字节数

站在编译器的角度讲,变量名、数组名都是一种符号,它们最终都要和数据绑定起来。变量名用来指代一份数据,数组名用来指代一组数据(数据集合),它们都是有类型的,以便推断出所指代的数据的长度

没错,数组也是由类型的,就像基本数据类型一样,数组是一种由基本类型派生而来的稍复杂一些的类型,sizeof是根据数据的类型来计算长度的

对于数组int a[6] = {1, 2, 3, 4, 5, 6};,其类型为int [6],表示一个拥有6个int数据的集合
对于指针int *p;,其类型为int *,32位系统下是4字节,64位系统下是8字节

归根结底,a和p这两种符号的类型不同,指代的数据不同,自然用sizeof求得的长度不同

对于二维数组,也是同样的道理,int a[2][3] = {1, 2, 3, 4, 5, 6},其类型为int a[2][3]

编译器在编译过程中会创建一张专门的表格用来保存名字以及名字对应的数据类型、地址、作用域等信息
sizeof 是一个操作符,不是函数,使用 sizeof 时可以从这张表格中查询到符号的长度

与普通变量名相比,数组名既有一般性也有特殊性:
一般性表现在数组名也用来指代特定的内存块,也有类型和长度
特殊性表现在数组名有时候会转换为一个指针,而不是它所指代的数据本身的值