4.2 二维数组初始化
完成二维数组的定义后,需要对二维数组进行初始化,初始化二维数组的方式有四种,具体如下:
4.2.1 按行给二维数组赋初值
int a[2][3] = {{1,2,3},{4,5,6}}; //我们也可以写成格式比较清晰的形式 int a[2][3] = { {1,2,3}, {4,5,6} };
在上述代码中,等号后面有一对大括号,大括号中的第一对括号代表的是第一行的数组元素,第二对括号代表的是第二行的数组元素。
4.2.2 将所有的数组元素按行顺序写在一个大括号内
int a[2][3] = {1,2,3,4,5,6};
在上述代码中,二维数组a共有两行,每行有三个元素,其中,第一行的元素依次为1、2、3,第二行元素依次为4、5、6。这是由编译器自动区分判断的。
4.2.3 对部分数组元素赋初值
int b[3][4] = {{1},{4,3},{2,1,2}}; //我们也可以写成格式比较清晰的形式 int a[3][4] = { {1}, {4,3}, {2,1,2} };
在上述代码中,只为数组b中的部分元素进行了赋值,对于没有赋值的元素,系统会自动赋值为0,数组b中元素的存储方式如图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-huowvBAI-1668092325247)(D:/typora-image/wps3.png)]
4.2.4 如果对全部数组元素置初值,则二维数组的第一个下标可省略,但第二个下标不能省略
int a[2][3] = {1,2,3,4,5,6};
可以写为
int a[][3] = {1,2,3,4,5,6};
系统会根据固定的列数,将后边的数值进行划分,自动将行数定为 2。
4.3 二维数组的引用
二维数组的引用方式同一维数组的引用方式一样,也是通过数组名和下标的方式来引用数组元素,其语法格式如下:
数组名[下标][下标];
在上述语法格式中,下标值应该在已定义的数组的大小范围内,例如下面这种情况是错误的。
int a[3][4]; // 定义a为3行4列的二维数组 a[3][4]=3; // 对数组a第3行第4列元素赋值,出错
在上述代码中,数组a可用的行下标范围是[0,2],列下标是[0,3],a[3][4]超出了数组的下标范围。
📝 示例演示
#include<stdio.h> int main() { //声明并初始化数组 int array[3][4] = { {1,2,3,4 }, {5,6,7,8}, {9,10,11,12} }; for (int i = 0; i < 3; i++) //循环遍历行 { for (int j = 0; j < 4; j++) //循环遍历列 { printf("[%d][%d]: %d ", i, j, array[i][j]); } printf("\n");//每一行的末尾添加换行符 } }
4.4 二维数组的长度
二维数组的长度是 数组定义时的行下标
,例如 int arr[4][5],该二维数组的长度是 4,准确来说,二维数组的每一个元素都是一个一维数组。
无论是几维数组,数组的长度都可以通过 sizeof(arr)/sizeof(arr[0])
得到。
5.多维数组
在计算机中,除一维数组和二维数组外,还有三维,四维,……等多维数组,它们用在某些特定程序开发中,多维数组的定义与二维数组类似,其语法格式具体如下:
数组类型修饰符 数组名 [n1][n2]…[nn];
定义一个三维数组的示例代码如下:
int x[3][4][5];
上面的这个例子,定义了一个三维数组,数组的名字是x,数组的长度为3,每个数组的元素又是一个二维数组,这个二维数组的长度是4,并且这个二维数组中的每个元素又是一个一维数组,这个一维数组的长度是5,元素类型是int。
多维数组在实际的工作中使用不多,并且使用方法与二维数组相似,这里不再做详细地讲解,有兴趣的可以自己学习。
6.数组名
6.1 一维数组的数组名
#include <stdio.h> int main() { int arr[] = { 1,2,3,4,5,6,7 }; printf("%p", arr); // %p:求 arr 在内存中的地址 printf("\n\n"); printf("%p", &arr[0]); // 求数组第一个元素在内存中的地址 return 0; }
数据的地址以16进制形式存放于内存中。
说明一维数组中:数组名的值是数组首元素的地址
6.2 二维数组的数组名
#include <stdio.h> int main() { int arr[3][4]; printf("%p", arr); // %p:求 arr 在内存中的地址 printf("\n\n"); printf("%p", arr[0]); // 求数组第一个元素在内存中的地址 return 0; }
说明二维数组中:数组名的值是数组首元素的地址
6.3 总结
同理,在多维数组也是如此 —— 数组名是数组首元素的地址。
7.数组在内存里的存储
7.1 一维数组
#include <stdio.h> int main() { int arr[10] = { 0 }; int i = 0; for (i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) { printf("&arr[%d] = %p\n", i, &arr[i]); } return 0; }
为什么差4位呢?
原因:因为是int类型的数组,一个int类型的数据占4个字节。
7.2 二维数组
#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; }
7.3 总结
- 数组在内存中是连续存放的。因为数组是连续的,知道了首元素的地址,就知道了数组每一个元素的地址,数组名就是首元素的地址,这也就解释了为什么我们有了数组名,就可以得到和操作该数组的每一个元素。同理,我们知道每一个数据的内存地址,可以直接找到给定地址的数据。
- 随着数组下标的增长,地址是由低到高变化的。
8.数组未初始化注意事项
8.1 未初始化
8.1.1 全局数组,被编译器初始化为0
#include <stdio.h> int arr[5];//全局数组 int main() { for (int i = 0; i < 5; i++) { printf("arr[%d] = %d\n", i, arr[i]); } return 0; }
8.1.2 局部数组,被编译器初始化为随机数
#include <stdio.h> int main() { int arr[5];//局部数组 for (int i = 0; i < 5; i++) { printf("arr[%d] = %d\n", i, arr[i]); } return 0; }
8.2 不完全初始化
局部数组和全局数组中未赋值的数组元素都会被赋值为0。
#include <stdio.h> int arr2[5] = { 1,2,3 }; int main() { int arr1[5] = { 1,2,3 }; int i = 0; printf("局部数组:\n"); for (i = 0; i < 5; i++) { printf("arr1[%d] = %d\n", i, arr1[i]); } printf("\n"); printf("全局数组:\n"); for (i = 0; i < 5; i++) { printf("arr2[%d] = %d\n", i, arr2[i]); } printf("\n"); return 0; }
9.键盘输入给数组赋值的方式
9.1 一维数组
#include <stdio.h> int main() { int arr[3]; //一维数组每一个元素的地位相当于普通变量 //即 temp 与 arr[i] 地位 相当 //因此对于普通变量 temp ,取地址是 &temp //那么对于数组元素 arr[i] ,取地址是 &arr[i] for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { scanf_s("%d", &arr[i]); } //打印结果 printf("\n\n结果为:\n"); for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("arr[%d] = %d\n", i,arr[i]); } return 0; }
9.2 二维数组
#include <stdio.h> int main() { int arr[2][3]; //二维数组每一个元素是一维数组,一维数组的每一个元素的地位相当于普通变量 //即 temp 与 arr[i][j] 地位 相同 //因此对于普通变量 temp ,取地址是 &temp //那么对于数组元素 arr[i][j] ,取地址是 &arr[i][j] for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { for (int j = 0; j < sizeof(arr[0])/sizeof(arr[0][0]); j++) { scanf_s("%d", &arr[i][j]); } } //打印结果 printf("\n\n结果为:\n"); for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { for (int j = 0; j < sizeof(arr[0]) / sizeof(arr[0][0]); j++) { printf("arr[%d][%d] = %d\n", i,j, arr[i][j]); } } return 0; }
同理,多维数组也是同样的方式。
10.进阶:三个经典问题
10.1 打印直角状杨辉三角
10.1.1 题目描述
目标打印结果:
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1
10.1.2 思路分析
- 建立一个二维数组,初始化每一个元素值为0
- 可以很容易观察到,每一行第一个元素的值总是为1,因此我们可以先给每一行的第一个元素赋值为1
- 然后其它位置的每一个元素
arr[i][j]
的值总是等于该元素位置的上一行同一列位置值
+上一行同一列左边一个位置值
的和。
即 arr[ i ][ j ] = arr[ i-1 ][ j ] + arr[ i-1 ][ j-1 ] - 我们就可以从第二行开始按此规律进行赋值
10.1.3 代码实现
#include <stdio.h> int main() { //定义一个合适的二维数组大小 //未完全初始化 -> 该数组元素值全部默认为 0 int arr[20][20] = { 0 }; //这里我们只打印 7 行数据 int row = 7; //给每一行第一个元素赋值为1 for (int i = 0; i < row; i++) { arr[i][0] = 1; } //从第二行开始赋值,对应 arr[1][j] 开始 for (int i = 1; i < row; i++) { // 每一行最大的索引值是 i,这是找规律看出来的 for (int j = 1; j <= i; j++) { /* 其它位置的每一个元素 arr[i][j] 的值总是等于该元素位置的 上一行同一列位置值 + 上一行同一列左边一个位置值 的和。 这是核心代码 */ arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1]; } } //打印结果 for (int i = 0; i < row; i++) { // 每一行最大的索引值是 i,这是找规律看出来的 for (int j = 0; j <= i; j++) { printf("%-6d", arr[i][j]); } printf("\n"); } return 0; }
10.2 筛数法求素数
10.2.1 题目描述
求 2 ~ 10000内的素数有哪些
10.2.2 筛数法求素数的基本思想
把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。
10.2.3 代码实现
#include<stdio.h> //定义一个常量 N #define N 10001 int main() { /* 建立一个数组,每个索引 Index 就代表一个数 num 规定 arr[num] 的值为 0 或 1 如果 arr[num] 为 0,则表示 num 是素数 如果 arr[num] 为 1,则表示 num 是非素数 */ int arr[N] = { 0 }; int temp; //从 2 开始依次遍历 [2,10000] 范围内的所有数 for (int num = 2; num < N; num++) { //C语言中 0 表示 false,非0数表示true if(!arr[num]) { //说明 arr[num] = 0,则表明arr[num]是素数 //那么num的倍数均不为素数,我们就 num所有的倍数 对应数组索引位置的元素值 设置为 1 for (temp = 2 * num; temp < N; temp += num) { // num为素数,我们将它的倍数设置为1,不为素数 arr[temp] = 1; } } } //输出素数,就是取出数组中索引对应值为 0 的数据 for (int num = 2; num < N; num++) { if (!arr[num]) { printf("%d\n", num); } } return; }
上述代码其实还可以再做优化,比如说2的倍数和3的倍数肯定有重复,那就会进行重复的赋值操作。那我们应该怎么去优化呢?这个问题就交给小伙伴们自己去认真思考一下。因为我们都是这世上孤独的灵魂,没有人会永远的陪你与疼你,这让我想起遗憾的是她只是我的一个朋友。
10.3 约瑟夫问题
10.3.1 问题描述
设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数 到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由 此产生一个出队编号的序列。
10.3.2 思路分析
- 假设 10 个人参与游戏,对应数组索引 [1,10],从编号为3的人开始从1报数,报到 4 的人出局,下一个未出局的人重新从1开始报数,直到全部出局。
- 数组 每一个元素值为 0 或 1, 0代表未出局,1代表出局。游戏刚开始默认值都为0。
- 每次报数前先判断编号k对应的数组索引的元素值 arr[k] 是否为0:
- 为0说明未出局,则判断此人是否报数报到 4
- 报到4则将对应数组索引的元素值 arr[k] 置为 1,表示出局 ,当前游戏剩余人数 count --,并将 报数 num 置为 1 ,表示下一个人从1开始报数。
- 没有报到 4 则将 num++,得到下一个人应该报的数。
- 为1说明出局,不进行处理
- 无论是否出局,报数还得继续,则移至下一个人
- 如果当前编号已经是10,即已经到了最后一个编号,则将 k = 1 ,表示从头开始,回到
步骤3
- 如果当前编号不是10,那就直接 k++ ,移动到下一个人,回到
步骤3
- 直到 count 为 0游戏结束
10.3.3 代码实现
#include<stdio.h> //参与游戏的总人数 #define N 10 int main() { /* 这里举一个具体例子: n:总人数 - 10 k:从该编号开始报数 - 3 m:数到 m 的人淘汰出局 - 4 */ /* 数组索引[1, 10] 对应编号[1, 10]的人 使用不完全初始化,则数组元素值均为0 0 表示未出局 1 表示已出局 则默认都是还未出局 */ int arr[N+1] = { 0 }; //当前游戏剩余人数初始值为 10 int count = N; //从编号为 3 的人从1开始报数 int k = 3; //记录当前数到的数为 num,默认从1开始报数 int num = 1; //数到4的人淘汰出局 int m = 4; //count为0即为false,while循环结束代表游戏结束 while (count) { //如果出局则arr[k]为 1 则 !arr[k] 为false,不会进入if,就不用管了,直接忽略 if (!arr[k]) { //说明未出局,则进行判断编号为k的人是否数到了m if (num != m) { //说明没有数到m,则得到下一个人应该报的数:num++ 即可 num++; } else { //如果报到了这个数 num,则将该人编号对应数组索引的元素值置为 1 表示出局 arr[k] = 1; //并保证下一个人从1重新开始报数 num = 1; //打印一下出局人的编号 printf("出局人编号:%d\n", k); } } //无论有没有出局,我们在处理完上述后都应该移动该编号的下一个位置 if (k != N) { //如果没有到 最后一个人,直接 往后移动 k++; } else { //如果到了最后一个人,则从编号为1的人开始报数,这样就构成了一群人围成一个圈进行报数的模式 k = 1; } } return 0; }