1. 二维数组
二维数组的本质,也是一维数组,一维数组中的每个元素,又是一个一维数组
声明/定义:int[4] array[3] => int array[3][4];
int main()
{
int a[3][4];
printf("&arr[0][0] = %p\n", &a[0][0]); // 0x16f38b2e8
printf("&arr[0] = %p\n", &a[0]); // 0x16f38b2e8
printf("&a = %p\n", &a); // 0x16f38b2e8
printf("arr[0] = %p\n", a[0]); // 0x16f38b2e8
printf("arr = %p, arr+1 = %p\n", arr, arr + 1); // 加的是4*4=16个字节
printf("arr[0] = %p arr[0] + 1 = %p\n", arr[0], arr[0] + 1); // 加的是4个字节
return 0;
}
打印的地址都是相同的,可以打印二维数组中任何单个元素的值,但当打印数组名(如 a 或 a[0])时,您得到的是地址,因为数组名在大多数情况下会被转换为指向数组首元素的指针。
1.1. 二维数组的定义中:行可以省,列不可以省,列里面有类型信息
int main()
{
int a[][4] = {{1, 2, 3, 4}, {1, 2, 3, 4}, {1, 2, 3, 4}, {1, 2, 3, 4}};
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
for (int j = 0; j < 4; j++)
{
printf("a[%d][%d] = %4d\t", i, j, a[i][j]);
}
}
return 0;
}
1.2. 存储
二维数组是行列矩阵,他在内存中存储的是一维的,按行顺序存储
1.3. 数组的三要素
1.4. 联系
1. 主对角线与次对角线输出
输入一个4*4的二维数组,并输出该数组的主对角线和次对角线上的元素
int main()
{
int array[4][4] = {{1, 1, 1, 1}, {2, 2, 2, 2}, {3, 3, 3, 3}, {4, 4, 4, 4}};
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (i == j)
{
printf("%d\t", array[i][j]);
}
}
putchar(10);
}
for (int j = 3; j >= 0; j--)
{
printf("%d\t", array[3 - j][j]);
}
return 0;
}
主对角线规律:行和列的索引值是相同的i==j
次对角线规律:行的索引值+列的索引值=3
2. 逆置一个二维数组
将一个矩阵4*4进行转置处理,要求初始化原始矩阵,输出原矩阵和转置后的矩阵
int main()
{
char arr[4][4] = {'a','b','b','b','c','a','b','b','c','c','a','b','c','c','c','a'};
char t;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%2c\t", arr[i][j]);
}
putchar(10);
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// printf("%d%d\t", i, j);
if (i > j)
{
t = arr[i][j];
arr[i][j] = arr[j][i];
arr[j][i] = t;
}
}
putchar(10);
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%2c\t", arr[i][j]);
}
putchar(10);
}
return 0;
}
思路:由于转置后的矩阵是对称的,所以只有下三角部分的元素需要交换。因此,通过判断i > j来确定是否需要交换元素。交换操作是通过一个临时变量t来完成的。
3. 天生棋局
生成一个10*10的棋局,要求,初始化为零。随机置入10颗棋子,棋子处置为1,并打印。
若在棋子中出现连续三个棋子在一起,包含横行和竖行,则输出好棋,否则,输出臭棋
int main()
{
int chess[10][10] = {0};
srand(time(NULL));
int x, y;
// int count = 10;
// while (count--)
// {
// // 问题:如果有相同的值,则不满10个数
// x = rand() % 10;
// y = rand() % 10;
// chess[x][y] = 1;
// }
int count = 0;
// 可
// while (1)
// {
// x = rand() % 10;
// y = rand() % 10;
// if (chess[x][y] != 1)
// {
// chess[x][y] = 1;
// count++;
// if (count == 10)
// break;
// }
// }
// 亦可
while (count < 20)
{
x = rand() % 10;
y = rand() % 10;
if (chess[x][y] == 1)
continue;
chess[x][y] = 1;
count++;
}
// 打印出棋盘
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
printf("%d ", chess[i][j]);
}
putchar(10);
}
count = 0;
int flagGoodChess = 0;
// 判断好棋:逐行逐列扫描
for (int i = 0; i < 10; i++) // 横行扫描
{
count = 0;
for (int j = 0; j < 10; j++)
{
if (chess[i][j] == 1)
{
count++;
if (count == 3)
{
flagGoodChess = 1;
break;
}
}
else
count = 0;
}
if (flagGoodChess == 1)
break;
count = 0;
for (int j = 0; j < 10; j++) // 竖行扫描
{
if (chess[j][i] == 1)
{
count++;
if (count == 3)
{
flagGoodChess = 1;
break;
}
}
else
count = 0;
}
if (flagGoodChess == 1)
break;
}
if (flagGoodChess == 1)
printf("好棋\n");
else
printf("臭棋\n");
return 0;
}
思路:
- 棋子放置:在一个无限循环中,随机选择一个位置,如果该位置没有棋子,则放置一个棋子,并将计数器加1。当放置了10个棋子后,跳出循环。这种方法确保了每个棋子都是唯一放置的。
- 检查好棋:
- 代码通过两个嵌套循环检查每一行和每一列,看是否有连续三个位置都被标记为1。
- 如果在某一行或列找到了连续三个1,则将flagGoodChess标记为1,并跳出循环。
- 最后,根据flagGoodChess的值打印出结果:如果是1,则打印“好棋”;否则打印“不好棋”。
4. 有序数组归并
合并两个已经有序的数组A[M],B[M],到另外一个数组C[M+N]中,使用另外一个数组依然有序。其中M和N均是宏常量。
#define M 5
#define N 3
int main8()
{
int A[M] = {1, 3, 5, 7, 9};
int B[N] = {2, 4, 6};
int C[M + N];
int i = 0, j = 0, k = 0;
while (i < M && j < N)
{
if (A[i] < B[j])
C[k++] = A[i++]; // 被放进去的,索引也要往前走
else
C[k++] = B[j++];
}
// A数组走完了
while (i < 5)
C[k++] = A[i++];
// B数组走完了
while (j < 3)
C[k++] = B[j++];
for (int i = 0; i < M + N; i++)
{
printf("%d\n", C[i]);
}
return 0;
}
思路:
- 合并过程:使用了一个while循环来比较数组A和B中的元素。如果A中的当前元素小于B中的当前元素,则将A的元素添加到C中,并将i和k的值增加;否则,将B的元素添加到C中,并将j和k的值增加。这个过程一直持续到其中一个数组的元素全部被添加到C中。
- 处理剩余元素:在while循环结束后,可能会有一个数组还有剩余元素没有被添加到C中。两个单独的while循环,检查哪个数组已经遍历完毕,然后继续将另一个数组的剩余元素添加到C中。这两个循环分别在i < M和j < N的情况下将剩余元素添加到C中。
2. 数组名的二义性
数组名,是数组的唯一标识符,既表示一种构造数据类型的大小,也表示访问 组中的成员的首地址,用来访问数据成员使用
2.1. 一维数组名
一维数组名,他是一种构造类型,同时他又承担了访问每个数组元素的责任,所以数组名就有两重性。
// 数组名是数组的唯一标识符
// 数组名充当一种构造类型
// 数组名,充当访问数据成员的首地址
int main9()
{
int arr[10] = {2};
printf("sizeof(arr) = %d\n", sizeof(arr)); // 40
printf("sizeof(int[10] = %d\n", sizeof(int[10])); // 40
printf("arr = %p\n", arr); // 0x16fcc3330
printf("arr = %p\n", &arr[0]); // 0x16fcc3330
printf("arr[0] = %d\n", *(arr + 0)); // 2
return 0;
}
2.2. 二维数组名
二维数组本质是一种嵌套关系,本质是一种一维数组,每个一维数组成员又是一个一维数组,
int main()
{
int array[3][4];
printf("sizeof(array) = %d sizeof(int[3][4]) = %d\n", sizeof(array), sizeof(int[3][4])); // 48 48
printf("sizeof int[4] = %d sizeof(array[0] = %d\n", sizeof(int[4]), sizeof(array[0])); // 16 16
printf("array = %p &array[0] = %p\n", array, &array[0]); // 0x16f2632e8 0x16f2632e8
printf("array + 1 = %p &array[0] + 1 = %p\n", array + 1, &array[0] + 1); // 0x16f2632f8 0x16f2632f8
printf("array[0] = %p &array[0][0] = %p\n", array[0], &array[0][0]); // 0x16f2632e8 0x16f2632e8
printf("array[0] + 1 = %p &array[0][0] + 1 = %p\n", array[0] + 1, &array[0][0] + 1); // 0x16f2632ec 0x16f2632ec
return 0;
}
printf(“array + 1 = %p &array[0] + 1 = %p\n”, array + 1, &array[0] + 1); 都是指向下一行的起始地址
printf(“array[0] + 1 = %p &array[0][0] + 1 = %p\n”, array[0] + 1, &array[0][0] + 1); 都是指向第一行的第二个元素。
// 一维数组的逻辑和存储是一致的,均是线性的
// 二维数组的逻辑是二维的,但其存储是线性的
// 数组在内存中是一段连续的存储空间
// 存储是线性的是由内存物理特性决定的
int main()
{
int a[3][4] = {1, 2, 3};
printf("sizeof(a) = %d\n", sizeof(a)); // 48
printf("sizeof(int[3][4]) = %d\n", sizeof(int[3][4])); // 48
printf("a = %p\n", a); // 0x16d3ef2e8
printf("&a = %p\n", &a[0]); // 0x16d3ef2e8
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d \t", *(a[i] + j)); // 1 2 3 0
// 0 0 0 0
// 0 0 0 0
}
putchar(10);
}
return 0;
}
- *操作符:
- 解引用:用于获取指针指向的地址处的值(例如:*ptr)。
- 声明指针:用于声明一个指针变量(例如:*int ptr)。
- &操作符:
- 取地址:用于获取变量的地址(例如:&x)。
- 获取表达式结果的地址:例如获取数组元素的地址(例如:&a[0])。