前言

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

指向指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

#include <stdio.h>int main ()
{int  V;int  *Pt1;int  **Pt2;V = 100;/* 获取 V 的地址 */Pt1 = &V;/* 使用运算符 & 获取 Pt1 的地址 */Pt2 = &Pt1;/* 使用 pptr 获取值 */printf("var = %d\n", V );printf("Pt1 = %p\n", Pt1 );printf("*Pt1 = %d\n", *Pt1 );printf("Pt2 = %p\n", Pt2 );printf("**Pt2 = %d\n", **Pt2);return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

var = 100
Pt1 = 0x7ffee2d5e8d8
*Pt1 = 100
Pt2 = 0x7ffee2d5e8d0
**Pt2 = 100

传递指针给函数

C 语言允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。

下面的实例中,我们传递一个无符号的 long 型指针给函数,并在函数内改变这个值:

#include <stdio.h>
#include <time.h>void getSeconds(unsigned long *par);int main ()
{unsigned long sec;getSeconds( &sec );/* 输出实际值 */printf("Number of seconds: %ld\n", sec );return 0;
}void getSeconds(unsigned long *par)
{/* 获取当前的秒数 */*par = time( NULL );return;
}

当上面的代码被编译和执行时,它会产生下列结果:

Number of seconds :1294450468

指针和函数的关系

1、函数指针(指向函数的指针)

一个函数在编译之后,会占据一部分内存,而它的函数名,就是这段函数的首地址。

可以把一个指针声明成为一个指向函数的指针。

C 语言规定函数名会被转换为指向这个函数的指针,除非这个函数名作为 & 操作符或 sizeof 操作符的操作数(注意:函数名用于 sizeof 的操作数是非法的)。也就是说 f = test; 中 test 被自动转换为 &test,而 f = &test; 中已经显示使用了 &test,所以 test 就不会再发生转换了。因此直接引用函数名等效于在函数名上应用 & 运算符,两种方法都会得到指向该函数的指针。

指向函数的指针必须初始化,或者具有 0 值,才能在函数调用中使用。

与数组一样:

(1)禁止对指向函数的指针进行自增运算++
(2)禁止对函数名赋值,函数名也不能用于进行算术运算。
示例1:

int fun1(int,int);
int fun1(int a, int b){ return a+b;
}
int main(){int (*pfun1)(int,int); pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓 int a=(*pfun1)(5,7); //通过函数指针调用函数。
}

示例2:

#include <stdio.h>
#include <stdlib.h>
int Max(int x, int y)  //定义Max函数
{int z;if (x > y) {z = x;}else { z = y;}return z;
}
int main() {//定义一个函数指针int(*p)(int, int);int a, b, c;//把函数Max赋给指针变量p, 使p指向Max函数p = Max;printf("please enter a and b:");scanf("%d%d", &a, &b);//通过函数指针调用Max函数c = (*p)(a, b);printf("a = %d\nb = %d\nmax = %d\n", a, b, c);system("pause");return 0;
}​

示例2:

#include <stdio.h>​ ​
void test( )​{​printf("test called!/n");​
}​ ​
int main( )​{​   void (*f) ( );​   f = test; ​    f ( );​ (*f)( );​    //test++;             // error,标准禁止对指向函数的指针进行自增运算​          //test = test + 2;    // error,不能对函数名赋值,函数名也不能用于进行算术运算​          printf("%p/n", test);​          printf("%p/n", &test);​          printf("%p/n", *test);​  return 0;
​}

运行结果为:

test called!
​test called!
​004013EE​004013EE​004013EE

这里的玄学就是 *test 为什么能和上面两个之前介绍过的输出一样的值。

首先来看函数名 test,是一个符号用来标识一个函数的入口地址,在使用中函数名会被转换为指向这个函数的指针,指针的值就是函数的入口地址,&test 在前面已经说了:显示获取函数的地址。*test 可以认为由于 test 已经被转换成了函数指针, 指向这个函数,所以 *test 就是取这个指针所指向的函数名,而又根据函数名会被转换指向该函数的指针的规则,这个函数也转变成了一个指针,所以 *test 最终也是一个指向函数 test 的指针。也就是说:*test --> *(&test) --> test --> &test。

上述关系十分重要!

为了更加明确,把示例 1 做补充:

#include <stdio.h>
int fun1(int,int);
int fun1(int a, int b){return a+b;
}
/* 要调用上面定义函数的主函数 */
int main (){int (*pfun1)(int,int);pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓int a=(*pfun1)(5,7); //通过函数指针调用函数。printf("%d\n",a);int e = fun1(5,7);printf("%d\n",d)int b = (&fun1)(5,7);printf("%d\n",b);int c = (*fun1)(5,7);printf("%d",c);return 0;
}
//根据关系 *fun1==*&fun1==fun1==&fun1 可知,以上的运行结果会得到4个5+7。
//因此在下面的函数指针数组实例中,action[2]()就相当于这里的(&fun1(5,7)),这点务必搞清楚。

2、指针函数(返回值为指针的函数)

所谓指针函数,就是返回指针的函数。在前面笔记中“从函数返回数组”中已经介绍。

C 语言的库函数中有很多都是指针函数,比如字符串处理函数,下面给出一些函数原型:

char *strcat( char *dest, const char *src );​
char *strcpy( char *dest, const char *src );
​char *strchr( const char *s, int c );​
char *strstr( const char *src, const char *sub );

3、两者混用(不常用)

注意函数的返回值不仅仅局限于指向变量的指针,也可以是指向函数的指针。

首先来看这个声明:*int (*function(int)) (double*, char);要了解此声明的含义,首先来看 function(int),将 function 声明为一个函数,它带有一个 int 型的形式参数,这个函数的返回值为一个指针,正是函数指针 int () (double, char); 这个指针指向一个函数,此函数返回 int 型并带有两个分别是 double* 型和 char 型的形参。

如果使用typedef可以将这个声明简化:(没看懂。。。。之后的结构体再补充)

typedef int (*ptf) (double*, char);​
ptf function( int );
另一个例子:

void (*signal (int sig, void (*func) (int siga)) ) ( int siga );
现在要分析的是 signal,因为紧邻 signal 的是优先级最高的括号,首先与括号结合,所以 signal 为一个函数,括号内为 signal 的两个形参,一个为int型,一个为指向函数的指针。接下来从向左看,* 表示指向某对象的指针,它所处的位置表明它是 signal 的返回值类型,现在可以把已经分析过的 signal 整体去掉,得到 void (*) ( int siga )。又是一个函数指针,这个指针与 signal 形参表中的第二个参数类型一样,都是指向接受一个 int 型形参且不返回任何值的函数的指针。

用 typedef 可以将这个声明简化:

typedef int (*p_sig) (double*, char);​
p_sig signal(int sig, p_sig func);
这个 signal 函数是 C 语言的库函数,在 signal.h 中定义,用来处理系统中产生的信号。

4、函数指针数组

假设现在有一个文件处理程序,通过一个菜单按钮来选择相应的操作(打开文件,读文件,写文件,关闭文件)。这些操作都实现为函数且类型相同,分别为:

void open();
void read();
void write();
void close();

现在定义一个函数指针类型的别名PF:

typedef void (*PF) ( );
把以上 4 种操作取地址放入一个数组中,得到:

PF file_options[ ] = {
&open,
&read,
&write,
&close
};

如果不使用 typedef,那么分析起来就会比较复杂,结果是 void (*file_options[ ]) ( );

这个数组中的元素都是指向不接受参数且不返回任何值的函数的指针,因此这是一个函数指针数组。接下来,定义一个函数指针类型的指针action并初始化为函数指针数组的第一个元素:PF* action = file_options;,如果不好理解,可以类比一下:

int ia[4] = {0, 1, 2, 3};
int *ip = ia;,

这里 PF 相当于 int,这样应该比较好懂了。

复习:

int ia[4] = {0, 1, 2, 3};
int *ip = ia; //ia就是&ia[0],因此ip指向ia[0]。与此同时ip[1]的含义又和*(ip+1)一样。
printf("%p\n",ip);
printf("%p\n",ip+1);
printf("%d\n",ip[1]);
printf("%d\n",*(ip+1));​/*输出结果0x7ffee4cca9b00x7ffee4cca9b411*/

通过对指针 action 进行下标操作可以调用数组中的任一操作,如:action2 会调用 write 操作,以此类推。在实际中,指针 action 可以和鼠标或者其他 GUI 对象相关联,以达到相应的目的。

5、函数与指针的复杂声明(不做要求,一般用 typedef 代替它)

只举一个例子:

int *(*(*fp)(int)) [10];
阅读步骤:

1.从未定义的变量名开始阅读 -------------------------------------------- fp
2.往右看,什么也没有,遇到了),因此往左看,遇到一个* ------ 一个指向某对象的指针
3.跳出括号,遇到了(int) ----------------------------------- 一个带一个int参数的函数
4.向左看,发现一个* --------------------------------------- (函数)返回一个指向某对象的指针
5.跳出括号,向右看,遇到[10] ------------------------------ 一个10元素的数组
6.向左看,发现一个* --------------------------------------- 一个指向某对象指针
7.向左看,发现int ----------------------------------------- int类型

所以 fp 是指向函数的指针(函数指针), 该函数返回一个指向数组的指针,此数组有 10 个 int* 型的元素。

int *(*(*fp)(int)) [10]; (*fp)(int)是一个指向函数的指针ptr*(*fp)(int)相当于一个指针ptr1(指针函数的返回值)最后剩下int *ptr1[10],可以理解。

【C语言学习笔记】26. 指针(3)指向指针的指针、传递指针给函数相关推荐

  1. C语言学习笔记---结构体中的字符数组和字符指针

      在结构体中可以使用字符数组来存储字符串,也可以使用字符指针来存储字符串.比如: struct str{char s1[5];char s2[5];};struct str str1= {" ...

  2. c语言学习笔记【结构体02】结构体指针变量与结构体变量的函数参数,C语言学习笔记结构体02结构体指针变量与结构体变量的函数参数.docx...

    C 语言学习笔记[结构体02]结构体指针变量与结构体变量 的函数参数 C 语言学习笔记之结构体指针变量一提指针,那可 是 C 语言的核心了,有多少学子曾拜倒在指针的脚下.单纯的说指针,其实并不难,但是 ...

  3. C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)

    C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址.   取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符   地址的大小 ...

  4. 梓益C语言学习笔记之指针

    梓益C语言学习笔记之指针 一.32位平台下,地址是32位,所以指针变量占32位,共4个字节 二.内存单元的地址即为指针,存放指针的变量称为指针变量,故:"指针"是指地址,是常量,& ...

  5. c语言中void arrout,c语言学习笔记(数组、函数

    <c语言学习笔记(数组.函数>由会员分享,可在线阅读,更多相关<c语言学习笔记(数组.函数(53页珍藏版)>请在人人文库网上搜索. 1.数组2010-3-29 22:40一维数 ...

  6. C语言学习笔记06-占位符格式、C基本类型及逃逸字符一些细节(附介绍BCD码)

    主要整理有关占位符格式与逃逸字符的一些细节 朋友们,看栗子--"BCD解码" (文末附BCD码介绍) 一个BCD数的十六进制是0x12(对应二进制表示:0001 0010),它表达 ...

  7. 嵌入式C语言——学习笔记

    嵌入式C语言--学习笔记 计算机程序语言的学习思路? GCC的使用及其常用选项介绍 gcc概述 C语言编译过程 C语言常见的错误 预处理的使用 宏展开下的 #.## C语言常用关键字及运算符操作 关键 ...

  8. 梓益C语言学习笔记之链表&动态内存&文件

    梓益C语言学习笔记之链表&动态内存&文件 一.定义: 链表是一种物理存储上非连续,通过指针链接次序,实现的一种线性存储结构. 二.特点: 链表由一系列节点(链表中每一个元素称为节点)组 ...

  9. 6.方法(go语言学习笔记)

    6.方法(go语言学习笔记) 目录 定义 匿名字段 方法集 表达式 1. 定义 方法是与对象实例绑定的特殊函数. 方法是面向对象编程的基本概念,用于维护和展示对象的自身状态.对象是内敛的,每个实例对象 ...

  10. go get 拉取指定版本_go语言学习笔记-基础知识-3

    相关文档 go语言学习笔记-目录 1.简介 1.1 什么是GO Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易.Go是从2007年末由Robert Griesemer, Rob ...

最新文章

  1. struts2中s:select标签的使用
  2. GPS计算司机行车时长,深圳交警开启疲劳驾驶午间整治
  3. 三个对CS最大的谬误
  4. python中可迭代对象拆包时、怎么赋值给占位符_python3-数据结构和算法 » 1.2 解压可迭代对象赋值给多个变量...
  5. 数据结构实验之图论七:驴友计划
  6. [转]Oracle SQL 日期的應用
  7. S32K MCAL02-FlexCAN 时钟模块【理论部分】
  8. 趣学python教孩子学编程pdf免费下载_《趣学Python——教孩子学编程》——导读-阿里云开发者社区...
  9. 基于hilbert变换的数字信号_基于Hilbert变换处理绝对重力仪测量数据
  10. 深度学习大神都推荐入门必须读完这9篇论文
  11. 一个超级实用的单片机调试技巧!DWT组件
  12. Android 实现搜索历史(1)
  13. Mongodb备份和还原
  14. Android-蓝牙的网络共享与连接分析
  15. 如何保证代码的健壮性和可读性
  16. 分布式事务中的三种解决方案详解(转载)
  17. Gauss消元法(特解与通解)
  18. 《请停止无效的努力》读书笔记
  19. 2010年小学生学习全能托管
  20. java mail 是什么_JavaMail是什么意思

热门文章

  1. 欧奈尔RPS指标选股!本地数据源快速遍历全市场!股票量化分析工具QTYX-V2.3.1...
  2. Centos7 系统登录密码忘记解决方法
  3. QT 大作业实现对图片与视频的处理
  4. 超像素(slic算法)特征提取(颜色,纹理)——个人梳理
  5. 【浏览器】360浏览器默认用极速模式打开页面
  6. 新建SVN仓库并上传项目
  7. docker启动和关闭命令
  8. Python Socket网络编程(一)初识Socket和Socket初步使用
  9. 2021年安全员-B证(广西省-2021版)考试题库及安全员-B证(广西省-2021版)考试内容
  10. 怎么选择国际短信平台?