1. 基础知识learn
集合:由一个或多个元素构成的整体。就是将一组事物组合在一起。
- 类型可以不一样
- 没有顺序要求
列表:又叫线性列表。按照一定顺序排列而成的有限序列。
- 有序的
- 长度可变
- 元素类型可以不同
1.1 数组
数组:是列表的实现方式之一。
- c/c++/java中数组元素类型必须一致,但是python中可以不同,python中数组叫list。
- 数组有下标索引,但是列表没有
- 元素连续存储,每个元素占据相同大小的内存空间。列表的可以连续也可以不连续。
1.1.1 数组名的含义
数组名的含义:首元素的地址。但是有两个特例:1)sizeof(数组名):表示整个数组所占内存的大小;2)&数组名表示整个数组的地址, + 1会指向数组的尾后地址
数组名是首元素的地址,针对一维int arr[]和二维数组int arr[][]的区别:
- 一维数组名:因为一维数组的首元素是int,所以一维数组名的类型就是int*,所以使用一维数组做函数参数传递时,实参通常写成int*,即display(int * arr, int size);
- 二维数组名:因为二维数组首元素是一个一维数组int[column],所以二维数组名的类型就是一个指向一维数组的指针:int (*p_arr)[column]。也就是说二维数组名实际不是一维数组,所以不能模仿一维数组的函数传递形式,如果非要模仿就需要把二维数组先转换成真正的一维数组:即,转换成一个一维数组,其元素应该又是一个一维数组,因为函数传递时,一维数组被自动转换成了首元素的地址即int*,所以,二维数组转换后的一维数组的元素的类型应该也是int*, 所以最终二维数组被转换成了int* arr[row]类型,然后这个类型就可以作为参数直接传递给二级指针的形参,但是这个转换编译器并没有帮我们实现,需要程序员自己实现。在函数内部就可以当成二级数组进行操作了。
数组的相关操作:
基于数组的在计算机内存连续存储,有下标索引,且每个元素占内存空间大小相等的特点,来设计数组元素的操作方法。
- 访问元素“D”的流程:由于我们只保存了数组首元素的地址,所以必须从首元素开始:先找到索引为0的地址2008,再加上索引值2008 + 2 * sizeof(元素类型) =2010,最后间接访问改地址存的元素就是“D”。
- 插入元素:插入元素需要先腾出空间,然后才可以插入:所以需要先把插入位置的元素及其后面的元素都先向右移动一位,这样子空出来的位置就可以把新元素插入进来
- 删除元素:删掉后会有一个空位,所以需要把要删元素后面的所有元素都向左移动一位即可。
二维数组:本质上还是一维数组,只是这个一维数组的每个元素又是一维数组。 用来处理矩阵相关的实际问题。
- 逻辑上是矩阵,由行列逻辑分布,但是在物理内存空间是连续存储的,所以实际跟一个大的一维数组的存储完全相同,所以无果不显示指出column的大小,编译器无法知道每行的数组(一维)的大小。
- 访问元素:matrix[i][j] = *(*(matrix + i) + j); 更接近计算机语言的理解,仅仅是理解,不一定就是这样子 = matrix[0][0]的地址 + i * (column * sizeof(type)) + j * sizeof(type)。所以计算机必须要知道行列式的列数,才能找到对应的元素,所以,二维数组作为参数传递时,必须要指明列数。
a[i][j] = a[ (i) * COLNUM + j ]
- 二维数组作为参数传递:必须指定二维数组的列数,否则函数无法勾画出二维数组的组织形式。只有有了列长度,通过下标a[i][j]时才能得到正确的下标地址。 二维数组作为参数时,是不能直接使用二级指针作为形参的,因为实际的类型并不匹配,会编译报错。原因如下:二维数组名,实际是一个指向数组的指针,跟真正意义的二级指针还是有些区别的。所以必须严格按照一维数组的方式来改造:即把二维数组改写成一维数组(因为一维数组会被编译器自动转换成指针),该一维数组的元素类型是指向一维数组的指针,所以应该把二维数组名改造成:int * arr[row]。这样子就可以真正的模仿一维数组用一级指针,二维数组用二级指针了。当然了,实际编码时,也可以不用二维数组来表示元素,而是直接使用int * arr[row]形式来初始化二维数组。
形参 实参 一维数组 display(int * arr, int size) display(数组名,元素个数) 二维数组 display(int ** arr, int size) display(int *arr[row], 总元素个数)
int *arr[row]:arr是一个数组,数组里的每个元素都是int*的指针,所以每个元素都可以指向一个一维数组。
而二维数组名实际是int (*p)[column]的一个指针,该指针又指向了第0行一维数组。所以二维数组名是不可以直接作为二级指针来传递的。
二维数组形参形式 二维数组实参形式 解释说明 display_1(int m[][4], int size) {
// 等价于:(int m[][4], int size_row, int size_column)
调用方式:直接用二维数组名作为形参display_1(matrix, 12);
形参就是二维数组,所以实参可以直接使用二维数组名。 display_2(int (*p)[4], int size)
调用方式:直接用二维数组名作为形参display_1(matrix, 12);
形参实际是一个指针,指向了一个一维数组int[4]。所以形参类型跟二维数组名的类型完全匹配,所以实参可以直接用二维数组名。 displayMatrix(int ** m, int row_size, int column_size)
需要先把二维数组改造成一维数组,其元素时一个指向一维数组的指针: int *arr[row]; for (int i = 0; i < row; i++) { arr[i] = matrix[i]; }
形参是一个二级指针,二维数组名虽然可以看成二级指针,但是并不能直接传递给形参,需要先把二维数组完全模仿成一维数组才可以传递给二级指针:即,把二维数组改写成一维数组,该一维数组的元素类型是指向一维数组的指针:一共有row行,所以就有row个指针:int * p[row]
2. 代码demo
2.1 二维数组demo
//date: 20240404
//author: tianqiang
//二维数组基础 + 作为函数参数
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
//个人觉得,这个并不是很推荐
void displayMatrix(int ** m, int row_size, int column_size) {
for (int i = 0; i < row_size; i++) {
for(int j = 0; j < column_size; j++) {
printf("%d ,", m[i][j]);
}
printf("\n");
}
printf("\n");
}
//必须提供列数,和元素的总个数
//调用方式:display_1(matrix, 12);
void display_1(int m[][4], int size) {// 等价于:(int m[][4], int size_row, int size_column)
for (int i = 0; i < size / 4; i++) {
for(int j = 0; j < 4; j ++) {
printf("%d ,", m[i][j]);
}
}
printf("\n");
}
//注意:*p一定要用括号括起来,不然编译报错的
//调用方式:display_2(matrix, 12);
void display_2(int (*p)[4], int size) {
for (int i = 0; i < size / 4; i++) {
for(int j = 0; j < 4; j ++) {
printf("%d ,", p[i][j]);
}
}
printf("\n");
}
int main() {
int matrix[3][4] = {
{1, 2, 3, 13},
{4, 5, 6, 16},
{7, 8, 9, 19}
};
display_1(matrix, 12);
display_2(matrix, 12);
printf("matrix[0][1] = %d \n", matrix[0][1]);
printf("&matrix[0][1] = %p \n", &matrix[0][1]);
printf("matrix[0][1] = %d \n", *(*(matrix + 0)) + 1);
int *arr[3]; //注意:这里arr是数组,数组的元素是指针。
for (int i = 0; i < 3; i++) {
arr[i] = matrix[i];
}
//所以可以直接传递arr了,因为这里才是真正意义的把二维数组当成了一维数组来思考的
displayMatrix(arr, 3, 4);
return 0;
}