2.3 二维数组的使用
二维数组的使用也是通过下标的方式(访问)。
说明:
1、行和列的下标也是从0开始的。
2、确定行和列的下标就能找到元素了。
3、注:数组元素只能逐个引用,而不能一次引用整个数组。(for嵌套)
代码实例:
#include<stdio.h> int main() { int arr[3][4] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; //打印3行4列的数组 int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } printf("\n"); } return 0; }
2.4 二维数组在内存中的存储
像一维数组一样,这里我们打印二维数组的每一个元素的地址看看:
#include<stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; //打印二维数组每一个元素的地址 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; }
结论:二维数组在内存也是连续存储的。
理解:二维数组是连续的。
1、看成连续的一维数组。
#include<stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; //1、下标法打印 int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } } printf("\n"); //2、指针法 int* p = &arr[0][0]; int k = 0; for (k = 0; k < 12; k++) { printf("%d ", *(p + k)); } printf("\n"); return 0; }
运行结果:
2、 把每一行看成一维数组(即:二维数组也是一维数组的数组(集合))
应用:
计算行数:sizeof(arr)/sizeof(arr[0])
计算列数:sizeof(arr[0])/sizeof(arr[0][0])
总结:知道数组在内存中是连续存放的,我们就可以用指针访问数组元素。
3. 数组的越界
数组的下标是有范围限制的。
数组的下标规定是从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 }; //下标的范围0-9 int i = 0; for (i = 0; i <= 10; i++)//下标大于9了 { printf("%d ", arr[i]); } printf("\n"); return 0; }
这个时候我们发现当i=10时,数组越界了,但是编译器不报错!
运行结果:
当我们打印数组元素,发现有不是数组范围的值,经验告诉我们很可能数组越界了。
当出现以下错误,一定是数组越界了。
注:二维数组的行和列也可能存在越界。(越界就如:生活中没抓到的小偷,就不是罪犯吗?)
4. 数组作为函数参数
我们在写代码的时候,会将数组作为参数传给函数。
将一个整形数组排序。
排序:冒泡、选择、插入、快速...
在这我先讲个简单的冒泡排序
冒泡排序:
4.1 冒泡排序函数的错误设计
#include<stdio.h> void Sort(int arr[]) { int sz = sizeof(arr) / sizeof(arr[0]);//能计算出数组的个数吗? 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[10] = { 3,6,9,0,8,5,2,1,4,7 }; //写一个函数对数组进行排序 Sort(arr);//能否正确排序? int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
运行结果:
我们发现数组并未排序成功,这是为什么呢?
通过调试,我们发现在函数内部sz=1。为什么呢——难道数组作为函数参数的时候,不是把整个数组传递过去?
接下来理解数组名。
4.2 数组名是什么?
#include<stdio.h> int main() { int arr[10] = { 0 }; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0; }
运行结果:
结论:数组名是数组首元素的地址。
但是有两个例外:
1、sizeof(数组名),这里的数组名是表示整个数组,计算的是整个数组的大小,单位是字节。
2、&数组名,这里的数组名是表示整个数组,&数组名取出的是数组的地址。
#include<stdio.h> int main() { int arr[10] = { 0 }; printf("%p\n", arr); printf("%p\n", arr+1); printf("%p\n", &arr); printf("%p\n", &arr + 1); return 0; }
运行结果:
虽然arr和&arr值一样,但是含义是不同的!
4.3 冒泡排序函数的正确设计
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。所以即使在函数参数部分写成数组的形式:int arr[]的本质是一个指针:int * arr。(所以在sort函数内部sz=1,因为在函数内部的sizeof(arr)的结果是4)
改正:
#include<stdio.h> void 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 t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t; } } } } int main() { int arr[] = { 3,6,9,0,7,2,1,5,8,4 }; int sz = sizeof(arr) / sizeof(arr[0]); Sort(arr, sz); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
运行结果:
补充:
arr[i] <---->*(arr+i)
&arr[i] <----->arr+i