目录
前言
在编程世界中我们有时候需要把多次定义相同类型的变量来存储所对应的值,但是每出现需要存储的值就要定义一个变量这肯定是不方便的,因此,我们就需要用到数组。在我们学习数组前我们需要了解字符串是什么🔗:【C语言】字符串-CSDN博客
数组分为:
- 一维数组
- 二维数组
一、什么是数组
简单的来说数组是存放相同类型的,是一组相同类型的元素的集合,在内存中的存储顺序和字符串常量一样是连续存放的。
二、一维数组
2.1数组的创建
type_t arr_name [const_n];
- type_t 是指数组的元素类型,即是在这个连续空间中只能存储所对应的类型
- const_n 是一个常量表达式,用来指定数组的大小
举个栗子:
int arr1[10];
char arr2[10];
float arr3[1];
double arr4[20];
int n = 4;
int arr5[n]; //变长数组
数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数 组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
但是不是所有编译器都支持C99标准,所以在使用变长数组时一定要思考再三。而且使用变长数组后代码的可移植性就大大下降了。🙃
2.2一维数组的类型
我们知道整型类型是:int;字符类型是:char;单精度浮点类型是:float;等等...
但是一维数组的数组类型是:type_t [const_n]
例如:
int arr1[10] 的数组类型是:int [10];表示在内存中有10个整形连续存储的空间。
char arr2[3] 的数组类型是:char [3];表示在内存中有3个字符类型连续存储的空间。
float arr3[30] 的数组类型是:float3 [30];表示在内存中有30个单精度浮点类型连续存储的空间。
2.3数组的初始化
- 数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
- 关于初始化:不才建议创建数组时候就把数组初始化,因为在函数栈帧的时候,各空间都有默认值的,数组的只创建不初始化可能会导致有逻辑错误时候为debug增加困难
int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
以上初始化都可
数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
2.4、一维数组的使用
在此之前我们需要认识下标引用操作符(数组访问操作符):[],使用下标引用操作符就可以实现对数组的访问
- 数组是使用下标来访问的,下标是从0开始
- 数组的大小可以通过计算得到。
栗如:
#include <stdio.h>
int main()
{
int arr[10] = {0};//数组的不完全初始化
//计算数组的元素个数
int sz = sizeof(arr)/sizeof(arr[0]);//用整体大小除一个元素的大小 = 个数
//对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
int i = 0;//做下标
for(i=0; i<10; i++)
{
arr[i] = i;
}
//输出数组的内容
for(i=0; i<10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
2.5一维数组在内存中的存储
举个栗子
include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
- 我们可以看到的出的结果,结果虽然地址是16进制显示,但是我们还是可以看出每个数组元素都相差4个字节(一个整形),也就说明了数组在内存是连续存放的。
- 我们可以观察出数组随着数组下标的增长,元素的地址的大小在递增。即说明数组在内存中是由低地址向高地址存储的
数组arr在内存中的简易草图:
三、二维数组
- 二维数组和一维数组的创建、初始化、使用和内存存储都极其相似
- 但是二维数组和一维数组的逻辑理解上有出入
3.1二维数组的创建
- 以部分类型为例
//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
3.2 二维数组的类型
- 在我们创建的 int arr[3][4];其中int [3][4]是二维数组的类型,表示在内存中有3个int [4]的连续存储空间
- char arr[3][5]; 其中 char [3][5]是二维数组的类型,表示在内存中有3个char [5]的连续存储空间
这样我们就对二维数组有了全新的认知了
3.3 二维数组的初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};
- 在二维数组如果有初始化,行可以省略,列不能省略。👀
- 可以省略有多少个一位数组,因为编译器会自动分配缺少的空间,但是如果省略一维数组的类型,那编译器就不知道一个一维数组有多少空间,又有多少个一维数组。会报错
- 关于初始化:不才建议创建数组时候就把数组初始化,因为在函数栈帧的时候,各空间都有默认值的,数组的只创建不初始化可能会导致有逻辑错误时候为debug增加困难
int[3][4] = {1, 2, 3, 4}:在内存中为了方便理解不才画出的草图:
int arr[3][4] = {{1,2},{4,5}}:在内存中为了方便理解不才画出的草图:
3.4 二维数组的使用
- 二维数组的使用也是通过下标的方式调用的,与一维数组的逻辑一样
例:
#include <stdio.h>
int main()
{
int arr[3][4] = { 0 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
arr[i][j] = i * 4 + j;
}
}
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
}
return 0;
}
3.5二维数组在内存中的存储
#include <stdio.h>
int main()
{
int arr[3][4];
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
}
}
return 0;
}
得出的结果也是连续存储的也是数组随着数组下标的增长,元素的地址的大小在递增。不才画出二维数组在内存中的草图:
四、数组越界问题
- 数组的下标是有范围限制的。
- 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
- 数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
- C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的
- 越界访问后会带来意想不到的错误,这时候程序会出现和我们意想之外的结果。
- 二维数组的行和列也可能存在越界。
栗:
#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\n", arr[i]);//当i等于10的时候,越界访问了
}
return 0;
}
上面数组类型是:int [10];说明数组下标最大为9,当下标以10访问时,数组就形成了越界访问。
4.1越界访问的经典问题代码
判断下面程序的运行结果
#include <stdio.h>
int main() {
int arr[10] = {0};
int i = 0;
for (i = 0; i <= 18; i++) {
arr[i] = i;
}
for (i = 0; i <= 18; i++) {
printf("%d ", arr[i]);
}
return 0;
}
应该会有看官老爷说ez有🙌就行,说输出:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
如果对函数压栈和栈帧了解的看官就应该会明白,这个代码是有可能程序崩掉,也有可能死循环。
如果是先进的编程工具,程序就会自动崩掉,但是如果我们使用的老式编译工具,程序就会在第一个循环里死循环。
因为在函数的压栈中,我们对地址的使用顺序是先使用高地址,后使用低地址的,不才画个草图
因为main函数是在栈区进行的,所以不才就画了个栈区的草图,每一个格子代表4个字节
在上文中说到,数组是随着数组下标的增长,元素的地址的大小在递增,因为越界访问是会直接在内存中直接修改存储的数据,如果越界访问达到一定程度的时候,就会到达控制循环的i内存位置并且修改了变量 i 后,数组就会回到 i 被赋值的数组-1的下标,而且i也是永远都达不到跳出循环的限制,所以出现死循环。
五、数组名是什么
先说结论:数组名是数组首元素的地址。(有两个例外)
- sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组
- &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
除上面两种之外,所有的数组名都是首元素地址。
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", arr + 1 );
printf("%p\n", &arr[0]+1);
printf("%d\n", *arr);
return 0;
}
输出结果都是:
因为数组名现在是首元素地址,所以输出内容一致,但是在sizeof中
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };
printf("%u\n", sizeof(arr) );
printf("%u\n", sizeof(arr[0]) );
printf("%d\n", *arr);
return 0;
}
打印结果就完全不同了,sizeof(arr)计算的是数组的整体大小,sizeof(arr[0])计算的是arr[0]的元素大小。
以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖
ps:表情包来自网络,侵删🌹