本文已收录至《C语言》专栏
欢迎多多浏览,点赞,收藏️+关注
目录
前言
正文
1. 一维数组
1.1 一维数组的创建
1.2 一维数组的初始化
1.3 一维数组的使用
1.4 一维数组在内存中的存储状态
2. 二维数组
2.1 二维数组的创建
2.2 二维数组的初始化
2.3 二维数组的使用
2.4 二维数组在内存中的存储状态
3. 数组越界访问
4. 作参数的数组
4.1 参数的传递
4.2 数组的应用示例
总结
在使用C语言进行编程时,我们可能有时候需要很多相同类型的变量帮助我们进行数据处理,但是如果我们逐一去定义变量会导致代码凌乱且难以更改,代码维护性会降低,所以这本次我们介绍数组,帮助大家解决此方面的难题。
数组分为一位数组,二维数组和多维数组,这里我们先介绍一维和二维数组。
1.1 一维数组的创建
格式:
值类型 变量名[数组大小(整型)]; //示例 int arr[10]; char ch[10]; float ft[10]; //......
例如第一个示例,创建了一个int型的变量arr数组,有十个空间。
可能有人会问,[ ]中能不能放变量,例如:
int sum; int arr[sum];
这里要作说明的是,这种写法是“变长数组”,小编目前使用的VS2022编译器暂时不支持这个语法;如果想要这样使用,编译器必须支持C99标准,在C99标准之前的都不支持该语法,只能在[ ]中放置常量。C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
1.2 一维数组的初始化
数组的初始化是指在创建数组的同时给数组的内容一些合理初始值(初始化),放入的值类型必须与数组类型相同且不能超过数组容量!
初始化示例:
int arr1[3] = { 1,2,3 };//初始化使用全部空间 int arr2[10] = { 1,2,3 };//初始化使用一部分空间 int arr3[] = { 1,2,3 };//初始化让编译器自动分配空间 char ch1[3] = { 'A','B','C' };//单字符初始化字符型数组 char ch2[3] = { 'A',32,'C' }; char ch3[] = "ABC";//字符串初始化字符型数组
如果数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
但是对于下面的代码要区分其在内存中是如何分配的。
当我们指定数组大小,那么编译器会按初始化顺序将值放入数组。
当我们初始化的值数量小于数组容量时,多余的空间会被初始化为0。
当我们不指定数组容量时,编译器会根据我们初始化的值分配对应的空间(对于不指定数组容量大小的数组,必须初始化,否则编译器会报错!)。
对于字符型数组
在字符型数组中,未被使用的空间会被初始化为‘\0’在ASCll表中的值为0。
1.3 一维数组的使用
对于使用数组,我们就要提到前面提到过的下标引用符“[ ]”,下标引用操作符。它其实就数组访问的操作符。
示例:
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i) { printf("arr[%d]=%d\n", i,arr[i]); } }
运行结果:
这里可以发现,我们定义了一个容量大小为10的数组,但是数组下标却是从0开始到9结束,这就是数组的语法定义,数组的下标从0开始到容量减1结束,而第10个位置并不是不存在,而是存放了一个“0或\0”作为数组结束的标志,这个标志在数组的使用中起到了非常重要的作用。
数组的大小我们也可以通过计算得到,那就是用sizeof,sizeof(arr)是整个数组的字节数,而sizeof(arr[0])是数组中一个元素的字节大小,用总字节数除以单个元素的字节数就能得到数组的容量大小。
1.4 一维数组在内存中的存储状态
了解数组在内存中的存储状态可以让我们使用数组更加得心应手。
我们对上面的示例代码使用%p输出每个数组元素在内存中的地址进行观察:
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) { printf("arr[%d]=%p\n", i, &arr[i]); } }
我们观察数组在内存中的存储空间是连续的,因为int型是4字节所以每个元素之间的差距大小为4,随着数组下标的增长,元素的地址,也在有规律的递增。
“所以我们的结论是:数组在内存中是连续存放的”。
而且我们对数组名取地址,发现数组名的地址解释数组的第一个元素的地址。
我们使用内存时,都是从低地址向高地址进行使用,使用完后根据情况归还内存空间。
2.1 二维数组的创建
格式:
数据类型 数组名[ 行大小 ][ 列大小 ]; 格式上与前面创建一维数组大同小异,但是还是有一定区别;
int arr[3][4] char ch[2][4]
对于一维数组我们可以理解为一行相同类型的元素,二维数组在此基础上增加了列的概念,形成了行列,我们可以理解为类似x,y坐标一样。
2.2 二维数组的初始化
对于二维数组的初始化,有一些语法规则我们需要知道:
int arr1[3][4]={{1,2,3},{4,5},{0}}; int arr2[][4]={{1,2,3},{4,5},{0}};
对于一维数组,我们可以说容量大小让编译器自己判断和分配,但是二维数组,“行”大小可以省略,“列”大小不能省略!
在初始化时,对于二维数组,我们可以通过使用大括号{ }中嵌套大括号{ { } }对每行进行精确赋值,具体入示例代码所示。
对于二维数组,在内存中的存放:
每个数组中存有三行四列,每一行相当于一个一维数组有四个空间。
在二维数组中,如果不使用多个大括号精确指定每个值赋在那个位置,则编译器会按顺序进行存放,以行开始存放,第一行存放满了再向第二行进行存放。
2.3 二维数组的使用
对于二维数组的使用,我们依然是使用下标引用符“[ ]”,但是对于二维数组,需要指定行和列,所以使用为“[ ][ ]”。
示例:
int main() { int arr1[3][4] = { {1,2,3,4},{5,6,7,8},{9,10} }; int arr2[3][4] = { 1,2,3,4,5,6,7,8,9,10 }; for (int i = 0; i < 3; i++) { for (int k = 0; k < 4; ++k) { printf("arr1[%d][%d]= %d\n",i,k,arr1[i][k]); } } printf("\n"); for (int i = 0; i < 3; i++) { for (int k = 0; k < 4; ++k) { printf("arr2[%d][%d]= %d\n", i, k, arr2[i][k]); } } return 0; }
运行结果:
2.4 二维数组在内存中的存储状态
像一维数组一样,这里我们尝试打印二维数组的每个元素。
int main() { int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10} }; for (int i = 0; i < 3; i++) { for (int k = 0; k < 4; ++k) { printf("arr1[%d][%d]= %p\n",i,k,&arr[i][k]); } } printf("\n"); printf("&arr=%p",&arr); return 0; }
我们可以发现,虽然二维数组划分了行和列,但其地址也是连续的,而且数组名存放的也是第一行第一列的元素的首地址,也就是第一个元素地址。
具体的,我们画图表示:
数组的下标是有范围限制的。 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的, 所以程序员写代码时,最好自己做越界的检查。
//错误示例1 int main() { int arr[5]={1,2,3,4,5,6}; }
对于给定大小的数组,如果赋值数量超过空间大小,编译器会报错!
对于非法越界访问:
//错误示例2 int main() { int arr[5] = { 1,2,3,4,5}; printf("%d\n",arr[5]); }
编译器在编译阶段不会报错。
甚至可以运行。
但是运行越界访问的值是乱码,实际上这已经类似于野指针问题了,且运行后编译器也发现错误进行警告了,详细的说明了我们的访问超出了范围。
注意:在二维数组的行和列也可能存在越界。
4.1 参数的传递
往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法 思想)函数 将一个整形数组排序。
我们先了解数组传递的是什么。
示例:
void man1(int *arr) { printf("%d\n",sizeof(arr)); } void man2(int arr[10]) { printf("%d\n", sizeof(arr)); } int main() { int arr[10]; man1(arr); man2(arr); return 0; }
我们对传入的参数求字节大小,发现无论是指针还是对应的数组变量,哪怕是指定了数组大小,其字节大小也为一个整型变量的大小,我们前面提到过,数组名只是存储首个元素的首地址,因为数组中的元素是顺序排列的,所以沿着顺序访问就能访问完所有数组元素。
这里要提到的是,前面我们使用数组名和单数组元素求出了数组的容量大小,如果是数组作为参数传递,在形参阶段,不能用sizeof求数组大小,因为此时这个数组参数只是一个指针,而对应类型指针的字节大小就是这个类型的字节大小。
4.2 数组的应用示例
冒泡排序代码示例:
void sort(int arr[], int sz)//参数接收数组元素个数 { for (int i = 0; i < sz-1; ++i) { for (int k = 0; k < sz-1; ++k) { if (arr[k] > arr[k + 1]) { int tmp = 0; tmp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = tmp; } } } } int main() { int arr[] = { 3,1,7,5,8,9,0,2,4,6 }; int sz = sizeof(arr) / sizeof(arr[0]); sort(arr, sz); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
本次我们介绍了数组的知识,数组这种存储方式对于我们编程非常重要,我们以后的很多地方例如手机信息存储或者图书馆信息管理等系统都会用到数组,合理的利用数组可以提高代码的整洁度和执行效率,所以学会数组的使用是必须的!
那么本次函数的知识分享就暂时先到这里啦,喜欢的读者可以多多点赞收藏关注。
如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
其他文章本栏推荐
C语言入门<操作符>_ARMCSKGT的博客-CSDN博客
C语言入门<分支语句>_ARMCSKGT的博客-CSDN博客
C语言入门<循环语句>_ARMCSKGT的博客-CSDN博客
欢迎读者多多浏览多多支持!