二维数组传参数时需要注意的是告诉编译器传递的是一个数组指针。有3种写法
写法1:传递int (*array)[3]
含有3个元素的一维数组指针地址
在C语言中,虽然不能直接传递二维数组作为指针的指针(即int**类型),但可以通过传递指向数组第一维的指针和行数来实现对二维数组的操作。由于二维数组实际上是按行连续存储的一维数组,因此可以通过一维指针间接操作整个二维数组。
下面是一个通过“指针的指针”方式模拟二维数组传参的示例,但实际上内部处理的是一维数组:
#include <stdio.h>
// 定义一个处理二维数组的函数原型,这里使用一级指针数组表示二维数组
void process_2d_array(int (*array)[3], int rows) {
// 通过(*array)[j]的方式访问元素,实际上等同于array[i][j]
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < 3; ++j) {
printf("Element at [%d][%d]: %d\n", i, j, (*array)[i * 3 + j]);
}
}
}
int main() {
// 定义一个3x3的二维数组
int my_array[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 虽然不直接传递二级指针,但我们可以通过类型转换将二维数组名视为指向一维数组的指针,并传递给函数
process_2d_array((int(*)[3])my_array, sizeof(my_array) / sizeof(my_array[0]));
return 0;
}
在这段代码中,我们并没有真正传递指针的指针,而是将二维数组名强制类型转换为指向具有固定列数的一维数组的指针,并将其传递给函数。注意这种方式仅适用于列数固定的二维数组。对于列数可变的情况,通常需要传递额外的信息以描述二维数组的结构。
写法2 传二维数组进来,int array[][3]
在C语言中,传递二维数组给函数时,通常需要通过指针来完成。这里是一个二维数组传参的简单示例:
#include <stdio.h>
// 定义一个处理二维数组的函数原型
void process_2d_array(int array[][3], int rows) {
// 遍历二维数组并打印元素
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < 3; ++j) {
printf("Element at [%d][%d]: %d\n", i, j, array[i][j]);
}
}
}
int main() {
// 定义一个3x3的二维数组
int my_array[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 调用函数,传递二维数组和行数
process_2d_array(my_array, sizeof(my_array) / sizeof(my_array[0]));
return 0;
}
在这个例子中,process_2d_array 函数接收一个指向整型数组的指针(在这里是二维数组的第一维),以及二维数组的行数。由于C语言编译器能够根据数组定义推断出数组第一维的大小(在这种情况下每一行有3个元素),所以在函数内部可以使用这个信息来遍历整个二维数组。
注意:如果数组的列数不固定,则必须作为参数传递给函数。上述代码中的二维数组列数固定为3,因此不需要额外传递列数参数。若列数动态可变,则可以将函数声明修改为接受行数和每行元素数量两个参数。例如:
void process_2d_array(int array[][COLS], int rows, int cols) {
// ...
}
这里的 COLS 应该在调用函数前已知,并作为实参传递进去。
写法3 传递数组指针进来,int *arr
此时只能使用arr[0~11]来取值。因为不是一维数组
#include <stdio.h>
int main(){
int my_array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
//int (*p)[3] = my_array;
int (*p) = my_array;
//printf("p[0][1]=%d\n", p[0][1]);
printf("p[0]=%d\n p[3]=%d\n", p[0], p[4]);
}
区分int arr[3] 和 int(arr)[3]
是的,编译器对 int *arr[3] 和 int (*arr)[3] 的解析确实不同:
- int *arr[3]:
- 编译器将其解析为一个包含三个元素的数组(arr),而每个元素都是指向整型变量的指针。
- 类型可以理解为“指针数组”,即数组中的每一个元素都是
int*类型。 - 示例声明等同于有三个指针变量,分别可以存储整型变量的地址。
- int (*arr)[3]:
- 编译器将其解析为一个指向具有三个整型元素的一维数组的指针(arr)。
- 类型可以理解为“指向数组的指针”,即这个指针指向的是一个大小为3的整数数组。
- 示例声明相当于一个指针,该指针可以指向二维数组的第一行或者其他任何含有三个连续
int类型数据的内存区域。
总结来说,两者的主要区别在于:
int *arr[3]是一个数组,其中每个元素是一个指针。int (*arr)[3]是一个指针,它指向一个含有三个整型元素的数组。
语法解析
在C语言中,编译器解析声明时通常遵循一定的优先级和约定俗成的“右结合性”规则。对于复杂类型的声明,编译器会从左向右逐步分析,并根据遇到的关键字调整解析路径。以下是一个一般的解析顺序:
-
基本类型:首先识别基本类型关键字,如
int、float、char等。 -
指针与数组:当遇到星号
*时,知道这是一个指针声明;当遇到方括号[]时,开始处理数组部分。这里有个特殊规则是,数组维度可以出现在类型名后,也可以紧跟在变量名后(例如int arr[3]和int [3] arr在效果上是相同的)。-
对于
int *arr[3],编译器首先看到int,知道这是整型,然后看到星号*,理解为指针,最后是方括号[],由于数组维度跟在了变量名之后,因此整个表达式被解释为一个包含三个元素的数组,每个元素都是一个指向整型的指针。 -
对于
int (*arr)[3],编译器同样首先看到int,然后立即遇到括号(*arr),其中星号说明它是指针,但因为括号的存在,使得数组维度[3]结合到这个指针类型上,所以整体被解释为一个指向含有三个整数元素的一维数组的指针。
-
-
函数指针:如果在声明中遇到了圆括号
(),则编译器会将其视为函数指针声明的一部分,先解析函数参数列表,然后再处理返回类型和其他修饰符。 -
其他类型修饰符:例如
const、volatile、restrict等,它们通常位于类型关键字和指针符号之间,用来描述该类型的属性。 -
结构体、联合体与枚举:编译器还会处理
struct、union、enum关键字及其定义的内容,以及它们可能构成的指针或数组类型。
总的来说,在处理复杂的类型声明时,编译器会按照上述步骤逐渐构建类型信息,最终确定完整的数据类型定义。在某些情况下,为了清晰起见,人们可能会选择使用类型别名 (typedef) 或者将复杂的类型定义放在另一个地方并通过指针来间接声明变量,这样可以使代码更易读。


