一、一维数组的创建和初始化
1.数组的创建
数组是一组相同类型元素的集合。
数组的创建语法形式:
type_t array_name[const_n]
// type_t 是指数组的元素类型
//array_name 是指数组的数组名
//const_n 是一个常量表达式,用来指定数组的大小
数组创建的实例:
int main() { //code 1 int arr1[10]; char arr2[20]; float arr3[30]; double arr4[2+3]; //code 2 int count = 10; int arr5[count]; return 0; }
code1 我们成功创建完成, 但是关于code2的代码,我们需要注意,此时的count不属于常量表达式,而是一个变量,故而引入了变长数组(Variable-Length Array)的概念。
需要注意的是:
在C99标准之前,[ ]中的数组的大小必须是常量表达式指定。
在C99标准中,引入了变长数组的概念,允许数组的大小可以由变量指定,但是数组不能初始化。
一般编译器则没有C99标准中变长数组的概念。
2.数组的初始化
数组的初始化是指在创建数组的同时给数组的内容一些合理的初始值。
int main() { //code 1 int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; //完全初始化 int arr2[10] = { 1,2,3,4,5 }; //未完全初始化(剩余的默认初始化为0) //code 2 char ch1[5] = {'a','b',99};//打印abc c的ASCII为99 剩余两个元素为\0(0) char ch2[10] = "abcdef";//剩余三个元素默认为\0(0) //code3 char ch3[] = "abc"; //4个元素 ‘a’ ‘b’ ‘c’ ‘\0’ char ch4[] = { 'a','b','c' }; //3个元素 return 0; }
对于code3,数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数会根据初始化的内容来确定。
3.一维数组的使用
对于数组的使用,我们在初识C语言部分提到过一个操作符: [ ] 下标引用操作符,即数组访问的操作符,用来访问数组的元素。
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //数组下标 0 1 2 3 4 5 6 7 8 9 int sz = sizeof(arr) / sizeof(arr[0]); // sz计算的是数组arr的元素个数 //遍历数组 int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); // arr[i]的[] 就是下标引用操作符 } int a = 10; printf("%d\n", sizeof(a));// a的大小是4 printf("%d\n", sizeof(int)); //a的数据的大小是4 printf("%d\n", sizeof(arr));// arr的大小是40 printf("%d\n", sizeof(int [10])); // arr的类型大小是40 return 0; }
4.一维数组在内存中的存储
下面探讨数组在内存中如何存储的。
1.#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //数组下标 0 1 2 3 4 5 6 7 8 9 int sz = sizeof(arr) / sizeof(arr[0]); // sz计算的是数组arr的元素个数 int i = 0; for (i = 0; i < sz; i++) { printf("&arr[%d] = %p\n", i,&arr[i]); //&arr[i] 取出每一个元素的地址 } return 0; }
我们执行打印截图如下
观察这些地址(十六进制表现形式),010FFE0C、010FFE10、010FFE14……每个元素的地址之间都相差4个字节,即一个int类型的大小。且在内存中是连续存放的。
所以我们有以下结论:
1.数组在内存中是连续存放的;
2.随着下标的增长,地址是由低到高变化的。
二、二维数组的创建和初始化
1.二维数组的创建
二维数组与一维数组语法格式类似, type_t arr_name[x][y],x可以看做二维数组的行数,y可以看做二维数组的列数。
int main() { //code 1 int arr1[3][4]; char arr2[2][5]; double arr3[4][6]; return 0; }
2.二维数组的初始化
int main() { //code 1 int arr1[2][3] = { 1,2,3,4,5,6 }; //完全初始化 表示两行三列 // 等同于 int arr1[2][3] = {{1,2,3},{4,5,6}}; int arr2[][4] = { {1,2},{3,4} ,{5,6} }; //未完全初始化 //二维数组初始化,行是可以省略的,但是列不能省略。 //因为列决定了划分数组的范围 return 0; }
3.二维数组的使用
二维数组的使用也是通过下标的方式访问
#include <stdio.h> int main() { //code 1 int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; printf("%d\n", arr[1][2]); //7 //code 2 int i = 0; //用来定义行 0 1 2 for (i = 0; i < 3; i++) { int j = 0;//定义列 0 1 2 3 for (j = 0; j < 4; j++) { printf("%-2d ", arr[i][j]); } printf("\n"); // 每打印完一行后换行 } return 0; }
我们可以画图以方便观察arr的行列元素及下标。
4.二维数组在内存中的存储
像一维数组一样,我们试着打印二维数组的每个元素的地址
#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; }
我们打印结果,观察这些元素的地址。
我们会发现第一行元素都相差4个字节,但是第一行的最后一个元素 arr[0][3] 与第二行的首元素 arr[1][0] 也相差4个字节,第二行末元素与第三行首元素亦是如此……这说明了看似我们观察二维数组是多行存放的,但其实本质上,二维数组在内存中也是连续存放的!
三、数组越界
数组的下标是有范围限制的。
数组的下标规定是从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 ", arr[i]); // 当i等于10的时候数组越界访问了。 } return 0; }
四、数组作为函数参数的案例
我们在编写函数代码时,有时会将数组作为函数参数进行传参,那么数组是如何作为函数参数进行使用的呢? 我们以实现一个冒泡排序函数案例进行讲解。
冒泡排序(Bubble Sort)是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
实现步骤:
两两相邻的元素进行比较。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
案例演示:
1.冒泡排序函数的错误设计
#include <stdio.h> void bubble_sort(int arr[]) { int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); //计算数组元素的个数 //冒泡排序的趟数 for (i = 0; i < sz - 1; i++) { int j = 0; //每趟比较的过程 for (j = 0; j < sz - 1 - 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[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 }; bubble_sort(arr); int i; for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
运行此代码,我们发现结果还是原来数组的元素,并没有发生变化。我们通过调试可以看到bubble_sort函数内部的sz计算数组元素个数的结果为1,难道数组名作为函数参数的时候,不是把整个数组传递过去吗? 在这里,我们需要先讨论另一个问题,数组名是什么?
1.1数组名是什么?
我们先观察如下代码:
#include<stdio.h> int main() { //code 1 int arr[] = { 1,2,3,4,5 }; printf("%p\n", arr); printf("%p\n", &arr[0]); //code 2 printf("%d\n", sizeof(arr)); return 0; }
打印代码显示如下:
code1当中的arr的地址确实是等于数组首元素的地址。
code2打印的是20,而不是地址的大小(4/8),所以这里表示的是整个数组的大小。
我们在C语言初识部分就了解到数组名是数组首元素的地址,那么所有出现数组名的地方都是数组首元素的地址吗?其实不然,有两个例外。
1.sizeof(数组名),数组名如果单独放在sizeof内部,这里的数组名表示的是整个数组,计算的是整个数组的大小。
2.&数组名 ,这里的数组名加&取地址操作符,取出的是整个地址的大小。
除此之外,遇到的所有的数组名都是数组首元素的地址。
观察以下代码:
#include<stdio.h> int main() { //code 1 int arr[] = { 1,2,3,4,5 }; printf("%p\n", arr); printf("%p\n", &arr[0]); printf("%p\n", &arr); return 0; }
我们发现&arr和数组首元素的地址一样,这样表面看似不冲突,其实&arr和数组首元素地址有本质区别。
我们将&arr+1和数组首元素地址+1
我们发现数组首元素地址+1,加的是4个字节,为一个整型的大小。
&arr + 1,加的是20个字节,为一个数组的大小。
2.冒泡排序函数的正确设计
所以我们回头再看,bubble_sort(arr),当数组传参的时候,实际上只是把数组的首元素地址传递过去了,所以即使在函数参数部分写成数组的形式:int arr[ ] ,但它本质上是一个指针:int *arr
那么,函数内部的sizeof(arr)结果计算的是4,sz计算为1,for循环只循环一次,所以打印出来的元素没有变化。
此我们就应该考虑到数组元素的计算应该写到bubble_sort函数的外部。
将写完的代码编写如下:
#include <stdio.h> void bubble_sort(int arr[],int sz) { int i = 0; //冒泡排序的趟数 for (i = 0; i < sz - 1; i++) { int j = 0; //每趟比较的过程 for (j = 0; j < sz - 1 - 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[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr,sz); int i; for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
☃️ 最后,数组部分的讲解就到这里~,希望能帮助到各位,还请多多三连啦,若编写有误,请私信我纠正哟。