10.1 二级指针 10.2 指针与二维数组 10.3 指针的动态存储分配 10.4 函数指针 10.5 main函数的参数 第10章 指针提高篇 10.1 二级指针 10.2 指针与二维数组 10.3 指针的动态存储分配 10.4 函数指针 10.5 main函数的参数
10.1 二级指针 如果定义指针变量时,前面有两个*号,则属于二级指 针变量。二级指针也称为指向指针的指针,其所指存储 空间里存放的是地址。 二级指针变量的定义格式为: 数据类型 **指针变量名; 【例10-1】二级指针应用举例。 #include <stdio.h> int main( ) { int a, *p1, **p2; a = 10; p1 = &a; p2 = &p1; printf("%d %d %d\n", a, *p1, **p2); return 0; }
程序第4行定义了三个变量,a是数值型变量,p1是一级指针变量,p2是二级指针变量。第5~7行为赋值语句,p1指针指向变量a,p2指针指向变量p1。其关系如下图所示: 图 10‑2 例10-1中三个变量之间的关系示意图 由图可知,一级指针p1所指存储空间里存放整数值,二级指针p2所指存储空间里存放地址。因此,我们也可以这样理解,指向数值的指针是一级地址,指向地址的指针是二级指针。
图10‑3 二维数组中二级指针、一级指针、数组元素之间的关系图 10.2 指针与二维数组 10.2.1 二维数组中指针的概念 假设有定义 int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; 这是一个3行4列的二维数组,可以将它看做由3个一维数 组构成,二维数组中的每一行相当于一个一维数组。 图10‑3 二维数组中二级指针、一级指针、数组元素之间的关系图
引用二维数组元素有以下几种等价的方法: 方法一:a[i][j] 方法二:*(a[i] + j) 方法三:*(*(a + i) + j) 方法四:(*(a + i))[j] 对于这四种方法的理解,首先应该明白a[i]等价于 *(a + i),表示二维数组第i行的首地址,而“a[i]+j”或 “*(a+i)+j”表示二维数组第i行第j列元素的地址,于是 “*(a[i]+j)”或“*(*(a+i)+j)”则表示二维数组第i行第j列 的元素。
数据类型 (*行指针变量名)[常量表达式]; 10.2.2 行指针与二维数组 由前所述,我们知道,二维数组名是一个二级地址 常量,C语言规定,二维数组名为行指针常量。假设有 定义 int a[3][4];,则数组名a是一个基础类型为4个int型 的行指针,执行“a + i”表示在二维数组首地址的基础 上后移i行元素。因此,操作二维数组可以利用行指针变 量来实现。 行指针变量的定义格式为: 数据类型 (*行指针变量名)[常量表达式]; 假设有定义 int (*p)[4];,则p为行指针变量,该指针 的基础类型为4个int型。
【例10-2】定义一个3行4列的二维数组并初始化所有元素, 再定义一个行指针变量指向二维数组,利用行指针输出二维 数组所有元素的值。 #include <stdio.h> int main( ) { int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; int (*p)[4], i, j; p = a; printf("二维数组元素是:\n"); for(i=0; i<3; i++) for(j=0; j<4; j++) printf("%d\t", p[i][j]); } printf("\n"); return 0;
本题定义了3行4列的二维数组a,每行有4个int型的数据 ,因此定义指向该二维数组的行指针其基础类型也必须为4 个int型。程序第6行的语句p = a; 表示将行指针p指向二维数 组a的首行,如下图所示: 图10‑4 例10-2中行指针与二维数组的关系图 思考:如果二维数组定义为int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};,行指针变量定义为int (*p)[3];,能 否将这个行指针指向二维数组?
10.2.3 指针数组与二维数组 数据类型 *指针数组名[常量表达式]; 10.2.3 指针数组与二维数组 二维数组可以看作由多个一维数组构成,每行元素视为一 个一维数组。一个一维数组可以由一个一级指针指向,当 定义多个一级指针,每个指针指向二维数组的一行,这些 指针便构成了指针数组。 指针数组的定义格式为: 数据类型 *指针数组名[常量表达式]; 假设有定义 int *q[3];,则q为指针数组的数组名,该数组 中有3个数组元素q[0] ~ q[2],它们分别是3个整型的指针 变量。
【例10-3】定义一个3行4列的二维数组并初始化所有元素,再定义一个指针数组,编程使用指针数组输出二维数组所有元素的值。 #include <stdio.h> int main( ) { int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; int *q[3], i, j; for(i=0; i<3; i++) q[i] = a[i]; } printf("二维数组元素是:\n"); for(j=0; j<4; j++) printf("%d\t", q[i][j]); printf("\n"); return 0;
本题定义了3行4列的二维数组a,每行可以用一个指针 操作,因此指针数组定义大小为3。程序第6 ~ 9行的for循环 功能是将二维数组三行的首地址a[0] ~ a[2]依次赋值给指针 数组q中的三个指针变量q[0] ~ q[2],如下图所示: 图10‑5 例10-3中指针数组与二维数组的关系图 思考:如果二维数组定义为int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};,指针数组定义为int *q[2];,能否 用这个指针数组操作二维数组?
void* malloc(unsigned size) 10.3 指针的动态存储分配 所谓“动态存储分配”是指:调用如malloc()、calloc()等库函数向系统动态申请一定大小的存储空间,我们只需将该存储空间的首地址赋给指针变量,便可让指针指向该存储空间。当动态申请的存储空间使用完毕后,可以调用如free( )函数释放空间。 10.3.1 动态存储分配与释放 1、malloc( )函数 malloc( )函数的作用是在内存中分配由应用程序使用的 存储空间,并将此存储空间的首地址作为函数返回值返回 给调用处。malloc( )函数的原型为: void* malloc(unsigned size)
【例10-4】动态分配两个存储空间,分别为字符型、整型,然后用两个指针依次指向这两个存储空间,并向其中存入数值,最后输出存储空间里的内容。 #include <stdio.h> #include <stdlib.h> int main( ) { char *p1; int *p2; p1 = (char *)malloc(sizeof(char)); p2 = (int *)malloc(sizeof(int)); *p1 = 'A'; *p2 = 65; printf("p1, p2所指存储空间的首地址: %u, %u\n", p1, p2); printf("p1, p2所指存储空间里的内容: %c, %d\n", *p1, *p2); return 0; }
程序第7、8行为动态申请存储空间,指针p1、p2所指存储空间分别为1个字节、4个字节;程序第9、10行是向这两个存储空间分别存入字符、整数。观察第8行的语句“p2 = (int *)malloc(sizeof(int));”,其中“(int *)”是强制类型转换,“sizeof(int)”是计算需要动态申请的字节数,该语句动态申请了4个字节的存储空间,并将存储空间的首地址赋给指针p2。以上程序的存储示意图如下所示,图中每一个小方框代表一个字节。 图10‑6 例10-4的存储示意图
void* calloc(unsigned n, unsigned size) calloc( )函数的作用是在内存中分配由应用程序使用的 存储空间,并将此存储空间的首地址作为函数返回值返回 给调用处。其功能类似malloc( )函数,calloc( )函数的原型 为: void* calloc(unsigned n, unsigned size) 形参n用于指定所分配的存储空间的存储单元个数;形 参size用于指定每个存储单元的大小,单位为字节。函数返 回值是void类型指针(地址),根据需要可以显式转换为 其它类型。
void* realloc(void *p, unsigned newsize) realloc函数使已分配的存储空间改变大小,即重新分配 ,并将新存储空间的首地址作为函数返回值返回给调用处。 realloc( )函数的原型为: void* realloc(void *p, unsigned newsize) 形参p为原存储空间的地址,形参newsize为新分配的存 储空间的大小。可以使原来的存储空间扩大,也可以缩小。 4、free( )函数 free( )函数用来释放由malloc( )、calloc( )分配的存储空 间或由realloc( )函数重新分配的存储空间。free( )函数的原 型为: void free(void *p);
【例10-5】首先调用malloc( )函数动态申请一个int型的存储空间,并存入10。再调用realloc( )函数扩大存储空间为3个int型,再向里面存入20、30。 #include <stdio.h> #include <stdlib.h> int main( ) { int*p; p = (int *)malloc(sizeof(int)); printf("第一次分配的存储空间首地址: %u\n", p); p[0] = 10; p = (int*)realloc(p, 3 * sizeof(int)); printf("第二次分配的存储空间首地址: %u\n", p); p[1] = 20; p[2] = 30; printf("存储空间里存放的三个整数: %d, %d, %d\n", p[0], p[1],p[2]); free(p); return 0; }
10.3.2 一维动态数组 假设有定义int a[5];,表示系统分配了一个大小为5的静态一维数组a,其存储空间为20个字节。利用前面所述的动态分配函数,也可以实现一维动态数组,并将动态数组的首地址赋给指针变量,便可以利用指针变量引用数组元素。 【例10-6】调用malloc( )函数动态分配一个一维整型数组,该数组大小为5,向数组中存入5个整数,并输出所有数值。 #include <stdio.h> #include <stdlib.h> int main( ) { int *p, i; p = (int *)malloc(5 * sizeof(int)); p[0] = 10; p[1] = 20; p[2] = 30; p[3] = 40; p[4] = 50; printf("动态一维数组中的值是: \n");
该例中动态分配的存储空间示意图如图10-7所示: 续前 for(i=0; i<5; i++) { printf("%d\t", p[i]); } printf("\n"); free(p); return 0; 该例中动态分配的存储空间示意图如图10-7所示: 图10‑7 例10-6的存储示意图
10.3.3 二维动态数组 假设有定义int a[3][4];,表示系统分配了一个3行4列 的静态二维数组a,其存储空间为48个字节。我们也可以 调用动态分配函数实现二维动态数组,指向二维动态数 组的指针应该是二级指针变量。 【例10-7】调用calloc( )函数动态分配一个二维整型数组,该数组为3行4列,用于存放3名学生,每名学生4门课的成绩,成绩从键盘输入。 #include <stdio.h> #include <stdlib.h> #define M 2 #define N 3 int main( ) { int **pscore, *psum, i, j;
pscore = (int **)calloc(M, sizeof(int *)); for(i=0; i<M; i++) { pscore[i] = (int *)calloc(N, sizeof(int)); psum = (int *)calloc(M, sizeof(int)); } printf("请输入%d名学生%d门课的成绩: \n", M, N); psum[i] = 0; for(j=0; j<N; j++) scanf("%d", &pscore[i][j]); psum[i] += pscore[i][j]; printf("\n%d名学生的总分: \n", M); printf("%d\t", psum[i]); printf("\n"); return 0;
程序中定义了两个指针变量pscore和psum,其中pscore是二级指针,psum是一级指针。本题中利用pscore和psum指针实现的动态存储分配如下图所示: 图10‑8 例10-7中的动态指针
函数类型 (*函数指针)(形参类型列表); 10.4 函数指针 在C语言中,一个函数在内存里占用一段连续的存储 区,函数名代表了这段存储区的首地址,称为函数的入口 地址。可以把函数的入口首地址(即函数名)赋予一个指 针变量,这种指针称为“函数指针”(也称为指向函数的 指针变量),然后利用函数指针调用子函数。 1、函数指针的定义 函数指针的一般定义格式为: 函数类型 (*函数指针)(形参类型列表); 例如,“int (*fp)(float);”定义了一个函数指针fp,该函 数指针能够指向返回值为int型的函数,函数形参是float型。
2、函数指针的赋值 函数指针在定义之后必须赋值,使它指向一个确定的 函数,然后便可以利用函数指针调用其所指函数。函数指 针赋值的格式: 函数指针 = 函数名; 例如:int fun(float x); //声明一个函数fun( ) int (*fp)(float); //定义一个函数指针fp fp = fun; //将函数名fun赋给函数指针fp 注意:以下写法是错误的,“fp = fun( );”,因为写 成“fun( )”的形式是表示函数调用。
函数指针(实参列表); 3、利用函数指针调用函数 为函数指针赋值函数名后,可以利用函数指针调用子函 数。格式如下: 【例10-8】定义fun( )函数,其功能是求表达式“1+2+…+n”的和,然后定义一个函数指针指向fun( )函数,利用函数指针调用该函数。 #include <stdio.h> int fun(int n) { int i, sum = 0; for(i=1; i<=n; i++) sum += i; } return (sum);
续前 int main( ) { int n, sum; int (*p)( ); printf("请输入一个正整数:"); scanf("%d", &n); p = fun; sum = p(n); printf("1 + 2 + ... + %d = %d\n", n, sum); return 0; }
【例10-9】以下fun( )函数是用函数名作为实参,通过传送不同的函数名,求tan(x)和cot(x)的值。 4、函数名作实参 函数名可以作为函数实参,由于函数名代表函数的首 地址,因此当函数名作实参时,属于传地址方式,对应的 形参是函数指针,在子函数里,可以利用函数指针调用子 函数。 【例10-9】以下fun( )函数是用函数名作为实参,通过传送不同的函数名,求tan(x)和cot(x)的值。 #include <stdio.h> #include <math.h> double fun(double (*f1)(double), double (*f2)(double), double x) { return (*f1)(x) / (*f2)(x); } int main( ) double x, v, y;
续上: printf("请输入角度: "); scanf("%lf", &x); //输入角度到x中 v = x * 3.14159 / 180.0; //将角度x转换为弧度v y = fun(sin, cos, v); //求tan(v) printf("tan(%.1lf) = %.3f\n", x, y); y = fun(cos, sin, v); //求cot(v) printf("cot(%.1lf) = %.3f\n", x, y); return 0; }
void main(int argc, char *argv[ ]) void main(int argc, char **argv) 在前面编写的程序中,main后面总是跟一对空的圆括号,表示main函数无参。实际上,在运行C程序时,根据需要,main函数是可以有参数的,这些参数可以由操作系统(例如在DOS系统下)通过命令行传递给main,因此main函数的参数也称为命令行参数。 main函数只有定义为以下形式时,才可以接收命令行参数。系统规定接收命令行参数的main函数必须有两个形参,如下所示: void main(int argc, char *argv[ ]) 或者 void main(int argc, char **argv)
说明: (1)argv和argc是两个参数名,也可以由用户自己命名,但是参数类型固定。 (2)第一个参数argc必须是整型,它记录从命令行中输入的参数个数,参数是一个或者若干个字符串,根据输入的字符串的个数,系统自动为argc变量赋值。例如,从命令行输入了三个字符串(包括可执行文件名),则系统自动为形参argc赋值为3。 (3)第二个参数argv必须是一个字符型指针数组或者是二级指针变量,指针数组中含有多个字符型指针变量,分别指向命令行中的每一个字符串。 (4)从命令行输入字符串时应遵循以下规则:①第一个字符串必须是可执行文件名;②如果有多个字符串,则各个字符串之间用空格隔开。
【例10-10】新建一个源文件,路径为“C:\test\prog 【例10-10】新建一个源文件,路径为“C:\test\prog.c”,在源文件中定义一个带参数的main函数,代码如下,编译链接源文件生成可执行文件prog.exe。然后在Windows自带的DOS模拟器中运行prog.exe文件,方法是在命令提示符后面输入命令行,命令行由若干字符串组成,第一个字符串是"prog.exe",后续字符串是传递给main函数的参数。 #include <stdio.h> int main(int argc, char *argv[ ]) { int i; printf("main函数的参数: \n"); for(i=0; i<argc; i++) puts(argv[i]); } return 0;
图10‑9 main函数的参数argv与三个字符串参数之间的指向关系 在DOS模拟器下运行prog.exe文件的效果图: main函数的两个参数是“int argc, char *argv[ ]”,其中argc记录此时传递的字符串个数为3,argv中有3个指针argv[0]、argv[1]、argv[2],分别指向三个字符串的首地址,如图10-9所示: 图10‑9 main函数的参数argv与三个字符串参数之间的指向关系