~~~
int *p1[6]; //指针数组
int *(p2[6]); //指针数组,和上面的形式等价
int (*p3)[6]; //二维数组指针
int (*p4)(int, int); //函数指针
~~~
C语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。对,从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!
对于初学者,有几种运算符的优先级非常容易混淆,它们的优先级从高到低依次是:
* 定义中被括号`( )`括起来的那部分。
* 后缀操作符:括号`( )`表示这是一个函数,方括号`[ ]`表示这是一个数组。
* 前缀操作符:星号`*`表示“指向xxx的指针”。
### 1) int \*p1\[6\];
从 p1 开始理解,它的左边是 \*,右边是 \[ \],\[ \] 的优先级高于 \*,所以编译器先解析`p1[6]`,p1 首先是一个拥有 6 个元素的数组,然后再解析`int *`,它用来说明数组元素的类型。从整体上讲,p1 是一个拥有 6 个 int \* 元素的数组,也即指针数组。
### 2) int (\*p3)\[6\];
从 p3 开始理解,( ) 的优先级最高,编译器先解析`(*p3)`,p3 首先是一个指针,剩下的`int [6]`是 p3 指向的数据的类型,它是一个拥有 6 个元素的一维数组。从整体上讲,p3 是一个指向拥有 6 个 int 元素数组的指针,也即二维数组指针。
> 为了能够通过指针来遍历数组元素,在定义数组指针时需要进行降维处理,例如三维数组指针实际指向的数据类型是二维数组,二维数组指针实际指向的数据类型是一维数组,一维数组指针实际指向的是一个基本类型;在表达式中,数组名也会进行同样的转换(下降一维)。
### 3) int (\*p4)(int, int);
从 p4 开始理解,( ) 的优先级最高,编译器先解析`(*p4)`,p4 首先是一个指针,它后边的 ( ) 说明 p4 指向的是一个函数,括号中的`int, int`是参数列表,开头的`int`用来说明函数的返回值类型。整体来看,p4 是一个指向原型为`int func(int, int);`的函数的指针。
### 4) char \*(\* c\[10\])(int \*\*p);
这个定义有两个名字,分别是 c 和 p,乍看起来 p 是指针变量的名字,不过很遗憾这是错误的。如果 p 是指针变量名,`c[10]`这种写法就又定义了一个新的名字,这让人匪夷所思。
以 c 作为变量的名字,先来看括号内部(绿色粗体):
char \* **(\* c\[10\]) **(int \*\*p);
[\] 的优先级高于 \*,编译器先解析`c[10]`,c 首先是一个数组,它前面的`*`表明每个数组元素都是一个指针,只是还不知道指向什么类型的数据。整体上来看,`(* c[10])`说明 c 是一个指针数组,只是指针指向的数据类型尚未确定。
跳出括号,根据优先级规则(() 的优先级高于 \*)应该先看右边(红色粗体):
char \* **(\* c\[10\]) (int \*\*p)**;
`( )`说明是一个函数,`int **p`是函数参数。
再看左边(橘黄色粗体):
**char \*** **(\* c\[10\]) (int \*\*p)**;
`char *`是函数的返回值类型。
从整体上看,我们可以将定义分成两部分:
**char \*** **(\* c\[10\]) (int \*\*p)**;
绿色粗体表明 c 是一个指针数组,红色粗体表明指针指向的数据类型,合起来就是:c 是一个拥有 10 个元素的指针数组,每个指针指向一个原型为`char *func(int **p);`的函数。
### 5) int (\*(\*(\*pfunc)(int \*))\[5\])(int \*);
从 pfunc 开始理解,先看括号内部(绿色粗体):
int (\*(\***(\*pfunc)**(int \*))\[5\])(int \*);
pfunc 是一个指针。
跳出括号,看它的两边(红色粗体):
int (\*(\***(\*pfunc)(int \*)**)\[5\])(int \*);
根据优先级规则应该先看右边的`(int *)`,它表明这是一个函数,`int *`是参数列表。再看左边的`*`,它表明函数的返回值是一个指针,只是指针指向的数据类型尚未确定。
将上面的两部分合成一个整体,如下面的蓝色粗体所示,它表明 pfunc 是一个指向函数的指针,现在函数的参数列表确定了,也知道返回值是一个指针了(只是不知道它指向什么类型的数据)。
int (\* **(\*(\*pfunc)(int \*))** \[5\])(int \*);
蓝色粗体以外的部分,就用来说明函数返回什么类型的指针。
我们接着分析,再向外跳一层括号(红色粗体):
int (**\*** **(\*(\*pfunc)(int \*))** **\[5\]**)(int \*);
\[ \] 的优先级高于 \*,先看右边,\[5\] 表示这是一个数组,再看左边,\* 表示数组的每个元素都是指针。也就是说,\* \[5\] 是一个指针数组,函数返回的指针就指向这样一个数组。
那么,指针数组中的指针又指向什么类型的数据呢?再向外跳一层括号(橘黄色粗体):
**int****(\*** **(\*(\*pfunc)(int \*))** **\[5\])****(int \*)**;
先看橘黄色部分的右边,它是一个函数,再看左边,它是函数的返回值类型。也就是说,指针数组中的指针指向原型为`int func(int *);`的函数。
将上面的三部分合起来就是:pfunc 是一个函数指针(蓝色部分),该函数的返回值是一个指针,它指向一个指针数组(红色部分),指针数组中的指针指向原型为`int func(int *);`的函数(橘黄色部分)。
- c语言
- 基础知识
- 变量和常量
- 宏定义和预处理
- 随机数
- register变量
- errno全局变量
- 静态变量
- 类型
- 数组
- 类型转换
- vs中c4996错误
- 数据类型和长度
- 二进制数,八进制数和十六进制数
- 位域
- typedef定义类型
- 函数和编译
- 函数调用惯例
- 函数进栈和出栈
- 函数
- 编译
- sizeof
- main函数接收参数
- 宏函数
- 目标文件和可执行文件有什么
- 强符号和弱符号
- 什么是链接
- 符号
- 强引用和弱引用
- 字符串处理函数
- sscanf
- 查找子字符串
- 字符串指针
- qt
- MFC
- 指针
- 简介
- 指针详解
- 案例
- 指针数组
- 偏移量
- 间接赋值
- 易错点
- 二级指针
- 结构体指针
- 字节对齐
- 函数指针
- 指针例子
- main接收用户输入
- 内存布局
- 内存分区
- 空间开辟和释放
- 堆空间操作字符串
- 内存处理函数
- 内存分页
- 内存模型
- 栈
- 栈溢出攻击
- 内存泄露
- 大小端存储法
- 寄存器
- 结构体
- 共用体
- 枚举
- 文件操作
- 文件到底是什么
- 文件打开和关闭
- 文件的顺序读写
- 文件的随机读写
- 文件复制
- FILE和缓冲区
- 文件大小
- 插入,删除,更改文件内容
- typeid
- 内部链接和外部链接
- 动态库
- 调试器
- 调试的概念
- vs调试
- 多文件编程
- extern关键字
- 头文件规范
- 标准库以及标准头文件
- 头文件只包含一次
- static
- 多线程
- 简介
- 创建线程threads.h
- 创建线程pthread
- gdb
- 简介
- mac使用gdb
- setjump和longjump
- 零拷贝
- gc
- 调试器原理
- c++
- c++简介
- c++对c的扩展
- ::作用域运算符
- 名字控制
- cpp对c的增强
- const
- 变量定义数组
- 尽量以const替换#define
- 引用
- 内联函数
- 函数默认参数
- 函数占位参数
- 函数重载
- extern "C"
- 类和对象
- 类封装
- 构造和析构
- 深浅拷贝
- explicit关键字
- 动态对象创建
- 静态成员
- 对象模型
- this
- 友元
- 单例
- 继承
- 多态
- 运算符重载
- 赋值重载
- 指针运算符(*,->)重载
- 前置和后置++
- 左移<<运算符重载
- 函数调用符重载
- 总结
- bool重载
- 模板
- 简介
- 普通函数和模板函数调用
- 模板的局限性
- 类模板
- 复数的模板类
- 类模板作为参数
- 类模板继承
- 类模板类内和类外实现
- 类模板和友元函数
- 类模板实现数组
- 类型转换
- 异常
- 异常基本语法
- 异常的接口声明
- 异常的栈解旋
- 异常的多态
- 标准异常库
- 自定义异常
- io
- 流的概念和类库结构
- 标准io流
- 标准输入流
- 标准输出流
- 文件读写
- STL
- 简介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair对组
- 深浅拷贝问题
- 使用时机
- 常用算法
- 函数对象
- 谓词
- 内建函数对象
- 函数对象适配器
- 空间适配器
- 常用遍历算法
- 查找算法
- 排序算法
- 拷贝和替换算法
- 算术生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各种插件
- utf8编码
- 制作安装项目
- 编译模式
- 内存对齐
- 快捷键
- 自动补全
- 查看c++类内存布局
- FFmpeg
- ffmpeg架构
- 命令的基本格式
- 分解与复用
- 处理原始数据
- 录屏和音
- 滤镜
- 水印
- 音视频的拼接与裁剪
- 视频图片转换
- 直播
- ffplay
- 常见问题
- 多媒体文件处理
- ffmpeg代码结构
- 日志系统
- 处理流数据
- linux
- 系统调用
- 常用IO函数
- 文件操作函数
- 文件描述符复制
- 目录相关操作
- 时间相关函数
- 进程
- valgrind
- 进程通信
- 信号
- 信号产生函数
- 信号集
- 信号捕捉
- SIGCHLD信号
- 不可重入函数和可重入函数
- 进程组
- 会话
- 守护进程
- 线程
- 线程属性
- 互斥锁
- 读写锁
- 条件变量
- 信号量
- 网络
- 分层模型
- 协议格式
- TCP协议
- socket
- socket概念
- 网络字节序
- ip地址转换函数
- sockaddr数据结构
- 网络套接字函数
- socket模型创建流程图
- socket函数
- bind函数
- listen函数
- accept函数
- connect函数
- C/S模型-TCP
- 出错处理封装函数
- 多进程并发服务器
- 多线程并发服务器
- 多路I/O复用服务器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反应堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函数
- libevent
- libevent简介