前几日用C编写DSP程序时,遇到一个问题:如何向C函数中传递指向二维数组的指针参数。初接触以为很简单,直接声明一个二维数组,然后把数组名传进去。但是一经编译便报错。后来仔细想了一下,并查找了一些相关资料,发现二维数组在概念上远比一维数组复杂,或者说二维数组以一种晦涩的方式构建在一维数组之上。
先来回顾一下一维数组。一维数组的数组名即为指向该数组的指针,该指针值保存了数组存放在内存中的一块连续区域的起始地址;数组的下标表示了这片内存区域的某存储区相对于起始地址的偏移量。简单来讲就是:指向一维数组的指针,指向数据存放区域的起始位置。
事实上,计算机系统的多维数组其实最终还是以一维数组的形式实现的。就N x M的二维数组来讲,设其数组名为array。指针array 指向一个数组,该数组存放的是一系列指针,这些指针分别指向相应的一维数组,而这些数组中存放的才是我们的数据。
array -> [一维数组指针1] -> [ 一维数组,M长]
[一维数组指针2] -> [ 一维数组,M长]
…… ……
[一维数组指针N] -> [ 一维数组,M长]
由此array是第i个指针变量地址,array[j]则表示相对于第i个指针变量偏移j*sizeof(数组类型)。系统通过这种机制访问了该二维数组的第i行,第j列的内容。
有上述可知,指向二维数组的指针其实是指向“指针变量地址”的指针变量。所以在声明指向二维数组的指针时,用int ** array的形式。
有以下两种方式来对二维数组分配内存:
///// 方法一
#include
int ** array = malloc( N * sizeof(int *) );
for (int k=0;k array[k] = malloc( M * sizeof(int) ); ///// 方法二 #include int ** array = malloc( N * sizeof(int *) ); array[0] = malloc( M * sizeof(int) ); for (int k=1;k array[k] = array[0]+M*k; 上述两种方法的区别在于:前者在内存中分配的区域有可能是不连续的;而后者则在内存中的一片连续区域为该数组分配空间。 我们还可以通过一维数组模拟二维数组。在这中间要进行下标转换。如对于模拟的NxM数组,访问其第i行,第j列元素时,在一维数组中对应的位置是i*M+j。当然为了更简捷,我们可以把这个数组下标转换过程定义为一个宏,交由编译系统来处理。 #define Arr2 ( array_name, row,col ) array_name[row*M+col] 定义该宏后,访问Arr2( array, i, j)等价于访问array[i*M+j]。 今天写程序的时候要用到二维数组作参数传给一个函数,我发现 将二维数组作参数进行 传递还不是想象得那么简单里,但是最后我也解决了遇到的问 题,所以这篇文章主要介绍 如何处理二维数组当作参数传递的情况,希望大家不至于再在 这上面浪费时间。 正文: 首先,我引用了谭浩强先生编著的《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 址的,它的 地址为: 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); 要用指针处理二维数组,首先要解决从存储的角度对二维数组的认识问题。我们知道,一个二维数组在计算机中存储时,是按照先行后列的顺序依次存储的,当把每一行看作一个整体,即视为一个大的数组元素时,这个存储的二维数组也就变成了一个一维数组了。而每个大数组元素对应二维数组的一行,我们就称之为行数组元素,显然每个行数组元素都是一个一维数组 下面我们讨论指针和二维数组元素的对应关系,清楚了二者之间的关系,就能用指针处理二维数组了。 设p是指向数组a的指针变量,若有: p=a[0]; 则p+j将指向a[0]数组中的元素a[0][j]。 由于a[0]、a[1]┅a[M-1]等各个行数组依次连续存储,则对于a数组中的任一元素a[i][j],指针的一般形式如下: p+i*N+j 元素a[i][j]相应的指针表示为: *( p+i*N+j) 同样,a[i][j]也可使用指针下标法表示,如下: p[i*N+j] 例如,有如下定义: int a[3][4]={{10,20,30,40,},{50,60,70,80},{90,91,92,93}}; 则数组a有3个元素,分别为a[0]、a[1]、a[2]。而每个元素都是一个一维数组,各包含4个元素,如a[1]的4个元素是a[1][0]、a[1][1]、a[1]2]、a[1][3]。 若有: int *p=a[0]; 则数组a的元素a[1][2]对应的指针为:p+1*4+2 元素a[1][2]也就可以表示为:*( p+1*4+2) 用下标表示法,a[1][2]表示为:p[1*4+2] 特别说明: 对上述二维数组a,虽然a[0]、a都是数组首地址,但二者指向的对象不同,a[0]是一维数组的名字,它指向的是a[0]数组的首元素,对其进行“*”运算,得到的是一个数组元素值,即a[0]数组首元素值,*a等价于a[0] a[0]等价于&a[0][0],因此,*a[0]与a[0][0]是同一个值; C++指针函数习题 一、选择题 1.以下程序的运行结果是()。 sub(int x, int y, int *z) { *z=y-x; } void main() { int a,b; sub(10,5,&a); sub(7,a,&b); cout< #include<> 方法 指针函数和函数指针的区别 关于函数指针数组的定义 为函数指针数组赋值 函数指针的声明方法为: 数据类型标志符 (指针变量名) (形参列表); 注1:“函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如: int func(int x); /* 声明一个函数 */ int (*f) (int x); /* 声明一个函数指针 */ f=func; /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 注2:函数括号中的形参可有可无,视情况而定。 下面的程序说明了函数指针调用函数的方法: 例一、 #include 在开发工业以太网项目的时候经常遇到一些小细节问题,在建立数据报进行传输的过程中传递txbuf缓冲区的地址的时候就遇到类似下面的问题。 一.简单说明1 定义一个2X3的int型的二维数组int array[2][3];并且给这个二维数组赋值1,2,3,4,5,6;array[0][0]=1 array[0][1]=2 array[0][2]=3 array[1][0]=4 array[1][1]=5 array[1][2]=6 输出结果 1 2 3 4 5 6 array[0]表示第一行的首地址,也就是第一行第一个数的地址,也就是&array[0][0] So array[0]==&array[0][0];其实&array[0]还==array[0]==&array[0][0],都表示第一行的首地址。 array[1]是第二行的首地址,也就是第二行第一个数的地址,也就是&array[1][0] so array[1]=&array[1][0];试试&array[1]还==array[1]==&array[1][0] 定义一个指针变量int *p;将第一行的首地址赋给p有3种方式。 1. p=array[0]; 2. p=&array[0]; 3. p=&array[0][0]; p[0]就等同于array[0][0],也就是p[0]==1;(为了形象记忆,可以用替换的角度去记忆和理解。因为之前说过p=array[0], so, p[0]就把p换成array[0]再加上[0]就是arary[0][0]) p[1]等于array[0][1]等于2 p[2]等于array[0][2]等于3 二维数组作为函数参数传递在实际中的应用 周立功教授数年之心血之作《程序设计与数据结构》,电子版已无偿性分享到电子工程师与高校群体,在公众号回复【程序设计】即可在线阅读。书本内容公开后,在电子行业掀起一片学习热潮。经周立功教授授权,本公众号特对本书内容进行连载,愿共勉之。第一章为程序设计基础,本文为1.7.3将二维数组作为函数参数。>>>> 1.7.3 将二维数组作为函数参数>>> 1. 函数原型int data[3][2] = {{1, 2}, {3, 4}, {5, 6}};int sum(int (*pDdata)[2], int size); int sum(int data[3][2], int size); int sum(int data[][2], int size);int sum(int (*pData)[2], int size); int sum(int data[3][], int size); int data[80][3]; int iMax(int *pData, size_t numData) largest = iMax(data, row*col);largest = iMax(data[0], row*col); 1 #includeint working_calc_salary(working_time[month]);int calc_salary(int *working_time);>>> 2. 二维数组的行1 int sum(int (*pData)[2], int size)int (*pData)[2] = data; for(int *ptr = ptr ptr = ptr = data[i]; int data[row][col]; largest = iMax(data[i], col); >>> 3. 二维数组的列int data[row][col], (*pData)[col], i; for(pData = pData 在这里,将pData声明为指向长度为col的整型数组的指针,pData++将pData移到下一行的开始位置。在表达式(*pData)[i]中,*pData代表data的一整行,因此(*pData)[i]选中了该行第i列的那个元素。注意,*pData必须使用括号,否则编译器会认为pData是指针数组,而不是指向数组的指针。 由此可见,只要抓住“变量的三要素(即变量的类型、变量的值和变量的地址)”并贯穿始终,则一切问题将迎刃而解。 指向二维数组的指针 一. 二维数组元素的地址 为了说明问题, 我们定义以下二维数组: int a[3][4]={{0,1,2,3}, {4,5,6,7}, {8,9,10,11}}; a为二维数组名, 此数组有3行4列, 共12个元素。但也可这样来理解, 数组a由三个元素组成: a[0], a[1], a[2]。而它中每个元素又是一个一维数组, 且都含有4个元素(相当于4列), 例如, a[0]所代表的一维数组所包含的4 个元素为a[0][0], a[0][1], a[0][2], a[0][3]。如图5.所示: ┏━━━━┓┏━┳━┳━┳━┓ a─→┃a[0] ┃─→┃0 ┃1 ┃2 ┃3 ┃ ┣━━━━┫┣━╋━╋━╋━┫ ┃a[1] ┃─→┃4 ┃5 ┃6 ┃7 ┃ ┣━━━━┫┣━╋━╋━╋━┫ ┃a[2] ┃─→┃8 ┃9 ┃10┃11┃ ┗━━━━┛┗━┻━┻━┻━┛ 图5. 但从二维数组的角度来看, a代表二维数组的首地址, 当然也可看成是二维数组第0行的首地址。a+1就代表第1行的首地址, a+2就代表第2行的首地址。如果此二维数组的首地址为1000, 由于第0行有4个整型元素, 所以a+1为1008, a+2 也就为1016。如图6.所示 a[3][4] a ┏━┳━┳━┳━┓ (1000)─→┃0 ┃1 ┃2 ┃3 ┃ a+1 ┣━╋━╋━╋━┫ (1008)─→┃4 ┃5 ┃6 ┃7 ┃ a+2 ┣━╋━╋━╋━┫ (1016)─→┃8 ┃9 ┃10┃11┃ ┗━┻━┻━┻━┛ 图6. 既然我们把a[0], a[1], a[2]看成是一维数组名, 可以认为它们分别代表它们所对应的数组的首地址, 也就是讲, a[0]代表第0 行中第0 列元素的地址, 即&a[0][0], a[1]是第1行中第0列元素的地址, 即&a[1][0], 根据地址运算规则, a[0]+1即代表第0行第1列元素的地址, 即&a[0][1], 一般而言, a[i]+j即代表第i行第j列元素的地址, 即&a[i][j]。 另外, 在二维数组中, 我们还可用指针的形式来表示各元素的地址。如前所述, a[0]与*(a+0)等价, a[1]与*(a+1)等价, 因此a[i]+j就与*(a+i)+j等价, 它表示数组元素a[i][j]的地址。 因此, 二维数组元素a[i][j]可表示成*(a[i]+j)或*(*(a+i)+j), 它们都与a[i][j]等价, 或者还可写成(*(a+i))[j]。 另外, 要补充说明一下, 如果你编写一个程序输出打印a和*a, 你可发现它们的值是相同的, 这是为什么呢? 我们可这样来理解: 首先, 为了说明问题, 我们把二维数组人为地看成由三个数组元素a[0], a[1], a[2]组成, 将a[0], a[1], a[2]看成是数组名它们又分别是由4个元素组成的一维数组。因此, a表示数组第0行的地址, 而*a即为a[0], 它是数组名, 当然还是地址, 它就是数组第0 行第0 列元素的地址。 在C++中,参数传递的方式是“实虚结合”。 ?按值传递(pass by value) ?地址传递(pass by pointer) ?引用传递(pass by reference) 按值传递的过程为:首先计算出实参表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的,然后把以求出的实参表达式的值一一存入到形参变量分配的存储空间中,成为形参变量的初值,供被调用函数执行时使用。这种传递是把实参表达式的值传送给对应的形参变量,故称这种传递方式为“按值传递”。 使用这种方式,调用函数本省不对实参进行操作,也就是说,即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。 [cpp]view plaincopy 1./* 2. pass By value 3.*/ 4.#include 如果在函数定义时将形参说明成指针,对这样的函数进行调用时就需要指定地址值形式的实参。这时的参数传递方式就是地址传递方式。 地址传递与按值传递的不同在于,它把实参的存储地址传送给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。 [cpp]view plaincopy 1.#include 二维数组和指针
C指针函数习题
函数指针
关于二维数组地址和指针之间的赋值
二维数组作为函数参数传递在实际中的应用
指向二维数组的指针
C++中函数调用时的三种参数传递方式
指向函数的指针