函数传参:二维数组(续...)

本文探讨了C语言中二维数组作为函数参数的不同方法,包括指定第二维长度、使用指向数组的指针及指针的指针等方式,并通过实例演示了具体应用。

   [原文开始] 

     可以用二维数组名作为实参或者形参,在被调用函数中对形参数组定义时可以指定所有维数的大小,也可以省略第一维的大小说明,如: 

     void Func(int array[3][10]); 

     void Func(int array[][10]); 

     二者都是合法而且等价,但是不能把第二维或者更高维的大小省略,如下面的定义是不合法的: 

     void Func(int array[][]); 

     因为从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),而并不区分行和列,如果在形参中不说明列数,则系统无法决定应为多少行多少列,不能只指定一维而不指定第二维,下面写法是错误的: 

     void Func(int array[3][]);实参数组维数可以大于形参数组,例如形参数组定义为: 

     void Func(int array[3][10]); 

     而实参数组定义为: 

     int array[5][10]; 

     这时形参数组只取实参数组的一部分,其余部分不起作用。 

   [原文结束] 

   大家可以看到,将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维的,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。大家在学编译原理这么课程的时候知道编译器是这样处理数组的: 

   对于数组 int p[m][n]; 

   如果要取p[i][j]的值(i>=0 && i<m && 0<=j && j < n),编译器是这样寻址的,它的地址为: 

   p + i*n + j; 

   从以上可以看出,如果我们省略了第二维或者更高维的大小,编译器将不知道如何正确的寻址。但是我们在编写程序的时候却需要用到各个维数都不固定的二维数组作为参数,这就难办了,编译器不能识别阿,怎么办呢?不要着急,编译器虽然不能识别,但是我们完全可以不把它当作一个二维数组,而是把它当作一个普通的指针,再另外加上两个参数指明各个维数,然后我们为二维数组手工寻址,这样就达到了将二维数组作为函数的参数传递的目的,根据这个思想,我们可以把维数固定的参数变为维数随即的参数,例如: 


     void Func(int array[3][10]); 

     void Func(int array[][10]); 

   变为: 

     void Func(int **array, int m, int n); 


   在转变后的函数中,array[i][j]这样的式子是不对的(不信,大家可以试一下),因为编译器不能正确的为它寻址,所以我们需要模仿编译器的行为把array[i][j]这样的式子手工转变为: 

     *((int*)array + n*i + j); 

     在调用这样的函数的时候,需要注意一下,如下面的例子: 

     int a[3][3] = 

     { 

       {1, 1, 1}, 

       {2, 2, 2}, 

       {3, 3, 3} 

     }; 

     Func(a, 3, 3); 


   根据不同编译器不同的设置,可能出现warning 或者error,可以进行强制转换如下调用:   

     Func((int**)a, 3, 3); 

   其实多维数组和二维数组原理是一样的,大家可以自己扩充的多维数组。下面是一 个完整的例子程序,这个例子程序的主要功能是求一个图中某个顶点到其他顶点的最短路经,图是以邻接矩阵的形式存放的(也就是一个二维数组),其实这个函数也是挺有用的,但是我们这篇文章的重点在于将二维数组作为函数的参数传递。

 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

C语言中函数间传递二维数组的方法 

方法一, 形参给出第二维的长度。

例如:

#include <stdio.h>
void func(int n, char   str[ ][5] )
{
    int i;
    for(i = 0; i < n; i++)
        printf("\nstr[%d] = %s\n", i, str[i]);
}

void main()
{
    char* p[3];
    char str[][5] = {"abc","def","ghi"};
    func(3, str);
}

方法二,形参声明为指向数组的指针。

例如:

#include <stdio.h>
void func(int n, char  (*str)[5] )
{
    int i;
    for(i = 0; i < n; i++)
        printf("\nstr[%d] = %s\n", i, str[i]);
}

void main()
{
    char* p[3];
    char str[][5] = {"abc","def","ghi"};
    func(3, str);
}

方法三,形参声明为指针的指针。

例如:

#include <stdio.h>
void func(int n, char **str)
{
    int i;
    for(i = 0; i < n; i++)
        printf("\nstr[%d] = %s\n", i, str[i]);
}
void main()
{
    char* p[3];
    char str[][5] = {"abc","def","ghi"};
    p[0] = &str[0][0];
    p[1] = str[1];
    p[2] = str[2];
    func(3, p);
}

 

#include <stdio.h> 

void fun(int **ppN, int N1, int N2); 

int main(void) 
{ 

    int **ppTmp; 
    int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 

    ppTmp = (int **)a; 

    fun(ppTmp, 3, 4); 
    return 0; 
} 

void fun(int **ppN, int N1, int N2) 
{ 
    int i=0, j=0; 
    for(i=0; i<3; i++) 
    for(j=0; j<4; j++) 
    printf("ppTmp[%d][%d]:%d\n", i, j, *(ppN+i*N2+j)); 
}  

 

转载于:https://my.oschina.net/chaenomeles/blog/909506

<think> 我们讨论的是在C语言中,如何通过函数传递二维数组指针(即数组指针)来操作二维数组。根据引用[1][2][3],我们知道传递二维数组时,实际上传递的是指向第一行(即第一个子数组)的指针。因此,函数参数应该定义为指向一维数组的指针(数组指针),并且需要指定列数(第二维的大小)。 ### 方法总结 1. **明确列数**:函数参数中必须指定二维数组的列数(第二维大小),因为编译器需要知道每一行有多少个元素以便正确计算地址。 2. **使用数组指针**:形参类型为`int (*p)[COLS]`,其中`COLS`是列数。这样`p`就是一个指向包含`COLS`个整数的一维数组的指针。 3. **下标访问**:在函数内部可以使用双下标`p[i][j]`来访问元素,因为`p`是指向数组的指针,所以支持数组下标语法。 ### 示例代码 以下示例展示如何定义函数并使用二维数组指针传参: ```c #include <stdio.h> // 函数定义:使用二维数组指针作为参数 // 参数1: 指向包含5个整数的一维数组的指针(即二维数组指针) // 参数2: 行数 void print_matrix(int (*mat)[5], int rows) { for (int i = 0; i < rows; i++) { for (int j = 0; j < 5; j++) { // 使用双下标访问元素,等价于 *(*(mat + i) + j) printf("%d ", mat[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11,12,13,14,15} }; // 调用函数,传递二维数组名(自动转换为指向第一行的指针) print_matrix(arr, 3); return 0; } ``` ### 关键点解析 1. **参数类型**:`int (*mat)[5]`表示`mat`是一个指针,指向包含5个整数的数组。这与二维数组`arr`的类型兼容(`arr`也会退化为指向其第一行的指针)[^1][^3]。 2. **内存访问**:`mat[i][j]`的地址计算方式为: - `mat + i`:跳过`i`行,每行大小为`5 * sizeof(int)` - `*(mat + i)`:得到第`i`行数组的首地址(类型为`int[5]`,再退化为`int*`) - `*(mat + i) + j`:得到第`i`行第`j`个元素的地址 - `*(*(mat + i) + j)`:访问该地址的值(等价于`mat[i][j]`)[^3] 3. **行数传递**:函数需要知道行数(第一维大小),因为指针本身不包含行数信息,所以通过第二个参数`rows`传递。 ### 注意事项 1. **列数必须匹配**:函数声明中的列数(本例中为5)必须与实际二维数组的列数一致,否则会导致内存访问错误[^1][^2]。 2. **静态大小限制**:这种方法只适用于列数固定的二维数组。如果列数动态变化,需改用其他方法(如指针数组或一维数组模拟)。 3. **类型安全**: - 避免将二维数组指针强制转换为`int**`,因为内存布局不同(二维数组是连存储,而`int**`通常指向指针数组)[^5]。 - 正确的类型转换:二维数组名退化为`int (*)[COLS]`类型,而非`int**`。 ### 替代写法 也可使用数组语法定义形参(引用[1]方法1),但编译器仍会将其视为指针: ```c void print_matrix(int mat[][5], int rows) { // 函数体相同 } ``` 这里`mat[][5]`与`int (*mat)[5]`完全等价,编译器自动转换。 ### 动态二维数组处理 若二维数组是动态分配的(行数、列数可能变化),则需使用不同的参数传递方式: 1. **一维数组模拟**:分配连内存(`malloc(rows * cols * sizeof(int))`),传递`int*`和行列数。 2. **指针数组**:分配行指针数组,每行再分配列数组(即`int**`方式),传递`int**`和行列数。 ### 总结 使用二维数组指针传参的关键在于: - 函数形参声明为指向固定长度数组的指针(如`int (*p)[5]`) - 传递数组名时自动转换为指向第一行的指针 - 函数内通过双下标直接访问元素 - 必须传递行数,且列数在编译时已知 §§相关问题§§ 1. 如何传递动态分配(运行时确定行列)的二维数组函数? 2. 二维数组与指针数组(`int**`)在内存布局上有什么区别? 3.函数中修改通过二维数组指针传递的数组会影响原数组吗? </think> ### 函数使用二维数组指针传参的方法与注意事项 在C语言中,直接使用二维数组指针(数组指针)进行函数传参是最规范的操作方式,其核心是使用**指向数组的指针**(`int (*p)[N]`)。以下是详细说明: #### 正确传参方法 ```c #include <stdio.h> // 方法1: 使用数组指针 void process_matrix(int (*p)[5], int rows) { for(int i = 0; i < rows; i++) { for(int j = 0; j < 5; j++) { printf("%d ", p[i][j]); // 等价于 *(*(p+i)+j) } printf("\n"); } } // 方法2: 显式指定二维数组列数(编译器自动转为指针) void process_matrix_v2(int arr[][5], int rows) { // 访问方式相同 } int main() { int matrix[3][5] = { {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15} }; process_matrix(matrix, 3); // 传递二维数组名 return 0; } ``` #### 关键注意事项 1. **列数必须明确指定** - 函数参数必须包含列维度(如`int (*p)[5]`或`int arr[][5]`) - 编译器需要列数来计算内存偏移量: $$address = base + i \times \text{cols} \times \text{sizeof(int)} + j \times \text{sizeof(int)}$$ - ✓ 正确:`void func(int (*p)[5])` ✗ 错误:`void func(int **p)` (内存布局不匹配)[^3] 2. **指针运算的工作原理** ```c // 解引用过程分解: p // 指向第0行的指针(类型:int(*)[5]) p + i // 指向第i行的指针 *(p + i) // 获取第i行首地址(类型:int*) *(p + i) + j // 第i行第j个元素的地址 *(*(p + i) + j) // 实际元素值(等价于p[i][j])[^3] ``` 3. **行数作为单独参数传递** - 二维数组名传递时会退化为指向第一行的指针 - 原数组的行数信息丢失,需额外传递行数参数[^2] 4. **内存布局一致性** - 真实二维数组内存是连的(行优先) - 函数内可通过指针算术直接访问: ```c // 计算元素[i][j]的地址 int* element = (int*)p + i * 5 + j; ``` #### 常见错误示例 ```c // 错误1:错误使用二级指针 void wrong_func(int **p, int rows, int cols) { /* 访问p[i][j]会导致段错误 */ // 因为实际传入的是int(*)[5],不是int** } // 错误2:省略列数 void wrong_func(int p[][], int rows) { /* 编译错误:数组类型不完整 */ } ``` #### 动态二维数组的特殊处理 对于运行时确定维度的二维数组,需改用指针数组: ```c void process_dynamic_matrix(int **p, int rows, int cols) { for(int i=0; i<rows; i++) for(int j=0; j<cols; j++) p[i][j] = i*j; } // 调用前需分配内存: int **matrix = malloc(rows * sizeof(int*)); for(int i=0; i<rows; i++) matrix[i] = malloc(cols * sizeof(int)); ``` ### 总结要点 1. 固定列数二维数组传参必须使用数组指针类型`int (*p)[N]` 2. 函数内访问元素推荐使用`p[i][j]`语法(编译器自动转换为指针运算) 3. 必须传递列数(编译时确定)和行数(运行时参数) 4. 动态数组需改用`int**`+单独分配每行内存[^5]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值