前言
在上一章中,我们已经详细地介绍了有关函数的相关内容,学习了函数是什么、C语言中函数的分类、函数的参数、调用、函数的嵌套调用和链式访问、函数的声明和定义、函数递归。
而在这一章,小编将带领大家进行数组的学习,虽然数组的知识点比较小,但是我们还是要进行好好学习,从标题中,我们能看出要详细地学习数组的知识,希望大家看的开心!
一、一维数组的创建与初始化
小编在之前就比较迷数组,第一就是数组的下标是从0开始的,但是不要害怕,紧跟小编的步伐,来进行学习吧!
1.1 数组的创建
数组是一组相同类型元素的集合,一定要记住,因为后面我们要学习结构体,结构体可以存放不同类型元素的集合。
数组的创建方式:
type_t arr_name [const_n]
type_t 是指数组的元素类型
const_n 是一个常量表达式,用来指定数组的大小
在了解完数组的创建方式后,下面我们来进行数组创建的实例:
//代码1 int arrq[10]; //代码2 int count = 10; int arr2[count]; //数组这样可以正常创建吗? //代码3 char arr3[10]; float arr4[10]; double arr5[10];
代码2这种情况在C语言(C99之前)中是不能使用的,因为数组创建在C99标准之前,[ ]中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小是可以使用变量指定的,但一定要切记变长数组是不能初始化的。
1.2 数组的初始化
为啥要讲数组的初始化呢?
因为局部变量不初始化的话,局部变量里放的是随机值;全局变量不初始化的话,全局变量里放的是0。所以说初始化很重要。
数组初始化是指:在创建数组的同时给数组的内容一些合理初始值(初始化)。
数组的初始化分为:完全初始化和非完全初始化。
看代码:
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"; //不完全初始化
数组的完全初始化是指:数组创建时在给定数字时将数组填满;而数组的非完全初始化是指:数组创建时在给定数字时填不满数组,则剩下的元素默认初始化为0;下面,我们来进行调试验证。
数组在创建的时候如果想不指定数组的确定大小就得初始化。数组的元素个数根据初始化的内容来确定。
1.3 一维数组的使用
对于数组的使用,我们之前介绍了一个操作符:[ ],下标引用操作符。他其实就是数组访问的操作符。下面,我们来看一下代码吧!
#include <stdio.h> int main() { int arr[10] = { 0 };//数组的不完全初始化 //计算数组的元素个数 int sz = sizeof(arr) / sizeof(arr[0]); //对数组内容赋值,数组是使用下标来访问的,下标从0开始,所以: int i = 0; //做下标 for (i = 0; i < sz; i++) { arr[i] = i; } //输出内容数据 for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
在这里,小编我要提醒大家:要记住数组的长度怎样计算?但有一种情况大家不要使用这种方法进行计算(代码在下面):这种方式会在sizeof(arr)算的是整个数组的长度,而不是光输入的数组长度,其余情况倒是可以随便用。
const int N = 10; int arr[100]; for(int i = 0; i < N; i++) { ... } int sz = sizeof(arr)/sizeof(arr[0]);
总结:
1)数组是使用下标来访问的,下标是从0开始的,依次增加1;
2)数组的大小可以通过计算得到的。
1.4 一维数组在内存中的存储
当讨论完一维数组的使用后,接下来我们来讨论数组在内存中的存储。 下面,我们来看一下代码吧!(在这里提一嘴,打印地址的占位符是:%p;为了便于观察,我们将环境改为X86。)
#include <stdio.h> int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 0; i < sz; i++) { arr[i] = i; } for (i = 0; i < sz; i++) { printf("&arr[%d] = %p\n", i, &arr[i]); } printf("\n"); return 0; }
输出的结果如下:
仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址也是有规律的递增。由此可以得出结论:数组在内存中是连续存放的。(方便内存区使用数组)
二、二维数组的创建与初始化
2.1 二维数组的创建
在小编看来,二维数组与一维数组的不同在于:二维数组增加了行和列的关系。接下来,看二维数组的创建。
//数组创建 int arr[3][4]; char arr[3][5]; double arr[2][4];
2.2 二维数组的初始化
小编记得二维数组的初始化要比一维数组的初始化要难,下面也有好几种情况:
int arr1[3][4] = { 1,2,3,4 }; int arr2[3][4] = { {1,2},{3,4} };//如果二维数组没有放满,则补0 int arr3[][4] = { {1,2},{5,4} };//二维数组如果有初始化,行可以省略,列不能省略
下面在监视窗口中可以验证上述所说:
为什么在二维数组初始化中可以有大括号呢?
我们可以将二维数组的每一行想像为一个一维数组。
2.3 二维数组的使用
二维数组的使用也是通过下标的方式,在假想中,二维数组是一个矩阵,所以二维数组有自己的行号和列号,我们可以通过行号和列号进行使用一个元素,就如同坐标系一样去访问。下面举个例子:
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
下面看代码:
int main() { int arr[3][5] = { 0 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { arr[i][j] = i * 4 + j; } } for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } return 0; }
2.4 二维数组在内存中的存储
在经历了上面对二维数组的学习后,大家可能会发现其实二维数组与一维数组基本类似。像一维数组一样,这里我们尝试打印二维数组的每一个元素地址。下面,我们来看一下代码吧!(在这里再提一嘴,打印地址的占位符是:%p;为了便于观察,我们将环境改为X86。)
int main() { int arr[3][5] = { 0 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { arr[i][j] = i * 4 + j; } } for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]); } printf("\n"); } return 0; }
输出的结果如下:
通过结果我们可以分析得到,其实二维数组在内存中也是连续存储的,这里能看出二维数组是一维数组的数组,证实了这一点。
三、数组越界
数组的下标是有范围限制的;数组的下标规定是从0开始的,如果数组有N个元素,最后一个元素的下标就是N-1。所以数组的下标如果小于0,或者大于N-1,就是数组越界访问了,超出了数组合法空间的访问。二维数组的行和列也可能存在越界。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的。
但如果有赋值操作的话,程序就会崩溃。
所以程序员写代码时,最好自己做好越界的检查。
四、数组作为函数参数
往往我们写代码的时候,会将数组作为参数传入函数,接下来我们借着举例子的时候,为大家介绍一下排序算法中最简单的排序——冒泡排序。
冒泡排序的算法思想:两两相邻的元素进行比较,将较大(较小)的沉入最下面。
4.1 冒泡排序函数的错误设计
我们接下来看代码:
void bubble_sort(int arr[]) { int sz = sizeof(arr) / sizeof(arr[0]); for (int i = sz - 1; i >= 0; i++) { for (int j = 0; j < i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } int main() { int arr[] = { 3,5,7,8,0,2,1,4,6,9 }; bubble_sort(arr); for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
这个代码出现问题,那我们来找一下问题:经过调试之后,可以看到bubble_sort函数内部的sz为2,而不是我们想要的值。难道数组作为函数参数的时候,不是把整个数组传递过去吗?
4.2 数组名是什么?
为了解决上述问题,我们进行探究数组名的内容:看下面的代码:
结论:数组名就是地址。通常来说,数组名是数组首元素的地址。
如果数组名是首元素地址的话,那么我们之前学过计算数组的长度和这个情况不同。看代码:
所以数组名表示的并不一定都是首元素地址,有两个例外:
sizeof(数组名),计算整个数组的大小,sizeof内部单独放入一个数组名,数组名表示整个数组,单位是字节。
&数组名,取出的是数组的地址。&数组名。数组名表示整个数组。
4.3 冒泡排序函数的正确设计
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。所以即使在函数参数部分写成数组的形式:int arr[ ] 表示的依然是一个指针: int* arr。那么这也回答了4.1中的问题:sizeof(arr)的结果是8。
那我们该怎么进行更改函数设计呢?下面看代码:
void bubble_sort(int arr[], int sz)//参数接受元素个数 { //代码同上面函数 } int main() { int arr[] = { 3,5,7,8,0,2,1,4,6,9 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
总结
在这一部分,小编详细地编写了有关数组的一篇博客。希望大家看完以后,进行点评,谢谢大家!