C++指针(这是一篇正经知识总结)
Reference
- 《C++ Primer》(第5版)
- 《明解C++》
前言背景
C++语言中有几种复合类型:引用、指针、...
复合类型指的是基于其他类型定义的类型。
变量定义:类型说明符(type specifier) 一个或者多个变量名组成的列表。
通俗来说变量声明就是一个基本数据类型和紧随其后的一个声明符列表组成。
其中变量名由逗号分割,以分号结束。
// 声明符就是变量名 变量的类型就是声明的基本数据类型
int sum = 0, value, units_sold = 0;
Sales_item item;
string s("abcdefs");
复杂的声明符是基于基本数据类型得到的更复杂的类型,并把它指定给变量。
指针用于间接的操作对象、高效的处理数组、动态创建对象等。
1、C++基础中复合类型的指针
定义
指针(pointer)是"指向(point to)"另外一种类型的复合类型。
定义指针类型的方法将声明符写成*d的形式,其中d是变量名。在一条语句内声明多个指针对象必须每个变量名前都有*。
int *ip1, *ip2; // ip1和ip2都是指向int类型对象的指针
double dp, *dp2; // dp2是指向double型对象的指针, dp是double型对象
获取对象的地址 (取址运算符 &)
指针存放某个对象的地址,需要用取地址符(操作符 &)获取地址。
符号 & 的作用根据上下文而不同:
- 作为一元运算符的取址运算符:表达式 &x ()
- 作为二元运算符的按位与运算符:表达式 x & y
- 用于声明引用的分隔符:声明 int &ref;
// 正确示例
double dval;
double *pd = &dval;
double *pd2 = pd;
// 错误示例
int *pi = pd; // 错误:指针pi的类型和pd的类型不匹配
pi = &dval; // 错误:试图把double型对象的地址赋给int型指针
// 说明不同类型的指针之间不能赋值
指针值
指针的值(即地址)有4种状态:
- (1)指向一个对象
- (2)指向紧邻对象所占空间的下一个位置
- (3)空指针,意味着指针没有指向任何对象
- (4)无效指针
容易发现错误的情况就是拷贝或以其他方式访问无效指针,编译器不负责检查此类错误,此问题类似试图使用没有初始化的变量。访问无效指针的后果很严重。
利用指针访问对象 (解引用运算符 *)
如果指针指向了一个对象,则允许使用解引用符(操作符 *)来访问对象。
对指针解引用会得到所指的对象,如果给解引用的结果赋值,就是给指针所指的对象赋值。
解引用运算符 (*)的操作数所指的也可以不是对象,而是函数。
符号 * 根据上下文有以下作用:
- 作为一元运算符的解引用运算符:表达式 *p
- 作为二元运算符的乘法运算符:表达式 x * y
- 用来声明指针的分隔符:声明 int *p;
int ival = 42;
int *p = &ival; // p存放着ival的地址 或者说 p是指向变量ival的指针
cout << *p; // *p得到指针p指向的对象,输出42
/* 给解引用的结果赋值 */
*p = 0; // 由符号 * 得到指针p所指的对象,即可经由p为变量ival赋值
cout << *p; // 输出
测试代码
#include <iostream>
#include <typeinfo>
using namespace std;
int main() {
// 对象和地址
int n;
double x;
cout << "n的地址:" << &n << endl;
cout << "x的地址:" << &x << endl;
// 对象占用的内存空间大小
cout << "n的内存空间大小:" << sizeof (n) << endl;
cout << "x的内存空间大小:" << sizeof (x) << endl;
// 对象的类型
cout << "n的类型名:" << typeid(n).name() << endl;
cout << "x的类型名:" << typeid(x).name() << endl;
// 指针的基础
int num = 35;
int *p = #
cout << "num:" << num << endl;
cout << "地址 &num:" << &num << endl;
cout << "解引用 *p:" << *p << endl;
cout << "取地址 &p:" << &p << endl;
cout << "p:" << p << endl;
// 指针的类型
int *p1, *p2;
double dp, *dp2;
cout << "p1的类型名:" << typeid(p1).name() << endl;
cout << "p2的类型名:"<< typeid(p2).name() << endl;
cout << "dp的类型名:" << typeid(dp).name() << endl;
cout << "dp2的类型名:" << typeid(dp2).name() << endl;
// 指针的大小
cout << "p1的内存空间大小:" << sizeof(p1) << endl;
cout << "dp2的内存空间大小:" << sizeof(dp2) << endl;
cout << "int* 的大小:" << sizeof(int*) << endl;
return 0;
结果:
空指针
空指针不指向任何对象,在试图使用一个指针前可以先检查指针是否为空。
得到空指针最直接的办法就是用字面值nullptr来初始化指针。
直接把int变量赋值给指针是错误的操作,即使int变量的值恰好等于0也不行。
// 空指针的创建方法
int *p1 = nullptr;
int *p2 = 0;
// 需要导入 #include cstdlib
#include <cstdlib>
int *p3 = NULL; // 等价于 int *p3 = 0;
// 不能直接将int变量赋值给指针
int zero = 0;
pi = zero; //错误:不能直接赋值!
赋值和指针
指针和引用都能提供对其他对象的间接访问。
一条赋值语句到底是改变了指针的值还是改变了指针所指对象的值?
- 记住赋值永远改变的是等号左侧的对象
int i = 42;
int *pi = 0;
int *pi2 = &i;
int *pi3;
pi3 = pi2; // pi3和pi2指向同一个对象i
pi2 = 0; // 指针赋值为0表示指针变为空指针
// 如何判别是改变了指针,还是指针所指的对象
pi = &ival; // 指针pi被改变,指向ival的地址
*pi = 0; // *pi表示解引用 改变的是pi所指对象的值
// 赋值语句
int test2 = 521;
int *num2 = 0;
cout << "num2初始化为空指针:" << num2 << endl;
cout << "test2 : " << test2 << endl;
num2 = &test2;
cout << "num2赋值test2 : " << num2 << endl;
cout << "test2 : " << test2 << endl;
*num2 = 0;
cout << "num2指向的对象赋值为0:" << num2 << endl;
cout << "test2 : " << test2 << endl;
结果展示:
其他指针操作 (P105)
将指针用在条件表达式中
- 类似采用算术值作为条件遵循的规则:如果指针的值是0,条件取false;任何非0指针对应的条件值都是true
- 两个类型相同的合法指针,可以用相等操作符(==)或者不相等操作符(!=)进行比较,比较结果是布尔类型的。如果两个指针存放的地址值相同,则其相等;反之,不相等。指针相等的三种可能:(1)都为空 (2)都指向同一对象 (3)都指向了同一对象的下一地址。
- 需要注意可能存在的情况:一个指针指向某对象,同时另一指针指向另外对象的下一地址,此时也有可能出现这两个指针值相同的情况,即指针相等。
void* 指针
void* 是一种特殊的指针类型,可用于存放任意对象的地址。一个void* 指针存放着一个地址,但是该地址中存放的是什么类型的对象并不透明。
void*指针的功能有限:可以作为函数的输入或输出,或者赋给另外一个void*指针;不能直接操作void*指针所指的对象。
获取void*指针所存地址的方法(P144)
指针的转换:
- 数组转换成指针:大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针
int ia[10]; // 含有10个整数的数组
int *ip = ia; // ia转换成指向数组元素的指针
// 当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid等运算符的运算对象时,上述转换不会发生。
- C++还规定了几种其他的指针转换方式:常量整数值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void*;指向任意对象的指针能转换成 const void*。
- (P530)在有继承关系的类型间的指针转换方式:
以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象(P726)
指向指针的指针
一般来说,声明符中修饰符的个数没有限制,当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。
指针是内存中的对象,有自己的地址,允许把指针的地址再存放到另一个指针中。
通过*的个数可以区分指针的级别,即为 **表示指向指针的指针,***表示指向指针的指针的指针。
int ival = 1024;
int *p = &ival; // p指向一个int型的数
int **pp = &p; // pp指向一个int型的指针
// 解引用 int型指针会得到一个int型的数,解引用指向指针的指针会得到一个指针。
为了访问原始对象,需要对指向指针的指针进行解引用,得到指针,然后对指针进行解引用,获取指针指向的对象。
// 指向指针的指针
int ival = 1024;
int *p1 = &ival;
int **pp1 = &p1;
cout << "ival = " << ival << endl;
cout << "*p = " << *p1 << endl;
cout << "**pp = " << **pp1 << endl;
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。
但是指针是对象,所以存在对指针的引用。
从右向左阅读变量的类型,*&r表示一个指针的引用
&r表示是一个引用
*(&r)表示r引用的是一个指针
// 指向指针的引用
int i = 42;
int *p;
int *&r = p; // 正确:引用的是一个指针
int &*r = p; // 错误:引用不是对象,因此不能定义指向引用的指针
r = &i;
*r = 0;
2、指针和const
const也是一个值得单开一个模块的知识点!下次更新!
可以让指针指向常量或者非常量
指向常量的指针 (pointer to const)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。
通常意义上讲,指针的类型必须与其所指对象的类型一致,存在两个例外,
- (1)允许令一个指向常量的指针指向一个非常量对象,此情形下,非常量对象能够被修改,但是不能通过指针修改。
- (2)
// 指针和const
const double pi = 3.14; // pi是常量,值不能改变
double *ptr = π // 错误:ptr是一个普通指针
const double *cptr = π // 正确:cptr可以指向一个双精度常量
*cptr = 42; // 错误:不能给*cptr赋值
double dval = 3.14; // dval是一个双精度浮点数,它的值可以改变
cptr = &dval; // 正确:但是不能通过cptr改变dval的值 可以通过其他途径修改dval的值
const指针
指针本身就是对象,因此允许指针和其他对象类型一样,允许把指针本身定为常量,称为 常量指针(const pointer) 必须初始化,而且指针一旦初始化,指针的值(及存放在指针中的地址)就不允许改变。
声明方式:
int *const ptr = #
将*放在const的前面,强调不变的指针本身(及地址)而不是地址中存放的对象(地址中的值)。
还是从右向左读这个声明,离ptr近的是const,意味着ptr本身是常量对象,对象的类型由声明符的其余部分确定。
// const指针
int errNumb = 0;
int *const curErr = &errNumb;
const double pi = 3.14159;
const double *const piptr = π // piptr是一个常量double型的常量指针
// piptr指针存放的地址不能改变,同时地址中存放的值也不允许改变
// *piptr = 2.72; // 错误:不允许改变
// curErr指针的地址不会变,但是值可以变
cout << "curErr修改前地址:" << &curErr << endl;
cout << "curErr修改前值:" << *curErr << endl;
*curErr = 34;
cout << "curErr修改后地址:" << &curErr << endl;
cout << "curErr修改后值:" << *curErr << endl;
结果展示:
3、指针和数组
C++语言中,指针和数组关系密切。
使用取地址符&可以获取某个对象的指针,取地址符可以用于任何对象。
数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。对数组的元素使用取地址符能得到指向该元素的指针。
数组还有一个特性:在用到数组名字的地方, 很多时候 编译器会自动地把数组替换为一个指向数组首元素的指针。(数组名被解释为指向该数组的第一个元素的指针)
数组名不被解释为指向第一个元素的指针的情况:
- 当作为sizeof运算符及typeid运算符的操作数时
- 当作为取地址符的操作数时 ("&数组名" 是指向数组整体的指针,而不是指向"指向第一个元素的指针"的指针。)
- 当使用decltype关键字时,返回的类型是数组而非指针。
// 指针和数组
string nums[] = {"one", "two", "three"};
string *p = &nums[0];
string *p2 = nums;
cout << "p中存放的地址:" << p << endl;
cout << "p2中存放的地址:" << p2 << endl;
cout << "p的解引用:" << *p << endl;
cout << "p2的解引用:" << *p2 << endl;
// auto推断和decltype
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto ia2(ia);
cout << "ia2的类型:" << typeid(ia2).name() << endl;
// ia2 = 42;
*ia2 = 42;
auto ia3(&ia[0]);
cout << "ia2中存放的地址:" << ia2 << endl;
cout << "ia3中存放的地址:" << ia3 << endl;
cout << "ia2的解引用:" << *ia2 << endl;
cout << "ia3的解引用:" << *ia3 << endl;
decltype(ia) ia4 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// ia4 = p; // 错误:不能用整形指针给数组赋值
cout << "ia4的类型:" << typeid(ia4).name() << endl;
结果展示:
在大多数表达式中使用数组类型的对象其实是使用一个指向该数组首元素的指针。
指针也是迭代器
vector和string的迭代器支持的运算,数组的指针全部支持。
允许使用递增运算符将指向数组元素的指针向前移动到下一个位置上。
使用指针可以遍历数组中的元素。指针遍历数组的前提:获取指向数组的第一个元素的指针和指向数组尾元素的下一位置的指针。通过数组名字或者数组中首元素的地址都能得到指向首元素的指针;获取数组尾后指针可以利用数组的特殊性质,获取数组尾元素之后的并不存在的元素的地址,这个不存在的元素唯一的作用就是提供地址初始化尾后指针。尾后指针不能进行解引用或者递增操作。
对指向数组元素的指针p + i使用解引用运算符:p + i 是指向p所指元素向后i个位置的元素的指针,因此解引用运算符的表达式*(p + i)就是该元素的别名。当p指向a[0]时,表达式*(p + i)表示a[i]。
// 指针也是迭代器
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = arr; // p指向arr的第一个元素
cout << "p中存放的地址:" << p << endl;
cout << "p中地址的值:" << *p << endl;
++p; // p指向arr[1]
cout << "++p中存放的地址:" << p << endl;
cout << "++p中地址的值:" << *p << endl;
int *e = &arr[10]; // 指向arr尾元素的下一位置的指针
// 利用指针遍历数组
for(int *b = arr; b != e; ++b) {
cout << *b << endl;
结果展示:
标准库函数begin和end
尾后指针比较容易失误,C++11引入了begin和end函数,这两个函数和容器中的两个同名成员函数功能类似,但是数组不是类类型,因此这两个函数不是成员函数。使用方法如下:
// begin和end函数
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia); // 指向ia的首元素的指针
int *last = end(ia); // 指向arr尾元素的下一位置的指针
cout << "ia的首元素:" << ia[0] << " ia的首元素的地址:" << &ia[0] << endl;
cout << "beg指向元素:" << *beg << " beg中存的地址:" << beg << endl;
int arr[] = {6, 7, 8, 9, -5, -4, 0, 23, 23, 34, 56};
int *pbeg = begin(arr), *pend = end(arr);
while (pbeg != pend && *pbeg >= 0) {
++pbeg;
cout << "第一个负数:" << *pbeg << endl;
结果展示:
指针运算
指向数组元素的指针可以执行下面两个表中列出的所有关于迭代器运算。
解引用、递增、比较、与整数相加、两个指针相减等。
指针和整数可以相加, 指针和指针之间不可以相加 ,指针之间可以进行减法运算。
指针加上或者减去某个整数值,结果仍是指针。新指针指向的元素与原来的指针相比前进或者后退了该整数值个位置。
注意:如果指针加上某个整数产生了溢出,这种错误编译器一般检查不出来。
两个指针相减的结果是两指针之间的距离,参与运算的指针必须指向同一数组当中的元素。两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型,同时因为差值可能为负,所以ptrdiff_t是一种带符号类型。
关于比较,如果两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。如果两个指针指向不相关的对象,则不能比较它们。
同时,这些指针运算同样适用于空指针和所指对象并非数组的指针。在指针所指对象并非数组的情况下,两个指针必须指向同一个对象或该对象的下一位置。如果p是空指针,允许给p加上或者减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果为0。
// 指针运算
constexpr size_t sz = 5;
int arr[sz] = {1, 2, 3, 4, 5};
int *ip = arr; // 等价于int *ip = &arr[0]
int *ip2 = ip + 4; // ip2指向arr的尾元素 arr[4]
int *p = arr + sz;
int *p2 = arr + 10; // 错误:arr只有5个元素,p2的值未定义
auto n = end(arr) - begin(arr); // n的值为5
cout << "n的值:" << n << " n的类型:" << typeid(n).name() << endl;
// 两个指针指向同一数组的元素,利用关系运算符对其进行比较
int *b = arr, *e = arr + sz;
while(b < e) {
// 如果两个指针分别指向不相关的对象,则不能比较它们:
int i = 0, sz = 42;
int *p = &i, *e = &sz;
while (p < e)
解引用和指针运算的交互
指针加上一个整数所得的结果还是一个指针。如果结果指针指向了一个元素,允许解引用该结果指针。
表达式:*(ia + 4)计算ia前进4个元素后的新地址,解引用该结果指针的效果等价于表达式ia[4]。
表达式:(*ia) + 4 或者 *ia + 4 等价于 ia[0] + 4 表示元素增加4
表达式:*p++表示指针p的地址加1
表达式:(*p)++表示把p指针解引用之后的值加1
下标和指针
很多情况下,使用数组的名字其实用的是一个指向数组首元素的指针。
4、函数调用和指针&函数指针(函数和指针相关的内容)
返回数组指针(6.3.3 返回数组指针)
因为数组不能拷贝,所以函数不能返回数组。函数可以返回数组的指针或者引用。( 数组的两个特殊性质! )
定义一个返回数组的指针或引用的函数比较麻烦,可以使用类型别名简化任务。
// 返回数组的指针
typedef int arrT[10]; // arr是一个含有10个整数的数组的类型别名
using arrT = int[10]; // arrT的等价声明
arrT* func(int i); // func返回一个指向含有10个整数的数组的指针
声明一个返回数组指针的函数
在声明函数时,不使用类型别名,就需要记住被定义的名字后面数组的维度:
//声明一个返回数组指针的函数
int arr[10];
int *p1[10]; // 含有10个指针的数组
int (*p2)[10] = &arr;
和数组和指针数组的声明一样,定义一个返回数组指针的函数,数组的维度必须跟在函数名字后面。
函数的形参列表也跟在函数名字后面且形参列表应该先于数组的维度。
返回数组指针的函数形式如下:
Type (*function(parameter_list)[dimension]
- Type表示元素的类型
- dimension表示数组的大小
- (*function(parameter_list)两端的括号必须存在,如果没有括号,函数返回的类型将是指针的数组。
举例:int (*func1(int i))[10];
- func1(int i)表示调用func1函数时需要一个int类型的实参
- (*func1(int i))意味着我们可以对函数调用的结果执行解引用操作
- (*func1(int i))[10]表示解引用func1的调用将得到一个大小事10的数组
- int (*func1(int i))[10]表示数组中的元素是int类型
使用尾置返回类型
在C++11中支持使用 尾置返回类型(tailing return type)。 任何函数的定义都能使用尾置返回,这种形式对一返回类型比较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用。尾置返回类型在形参列表后面以一个 -> 符号开头,并在前面本应该出现返回类型的地方放一个auto。
// 使用尾置返回类型
auto func2(int i) -> int(*)[10];
可以清楚的看到func2函数返回的是一个指针,而且是一个指向含有10个整数的数组的指针。
使用decltype
如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。
arrPtr使用关键字decltype表示它的返回类型是个指针,并且该指针所指的对象与odd的类型一致。
因为odd是数组,所以arrPtr返回一个指向含有5个整数的数组的指针。有一个地方需要注意:decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是一个数组,需要在arrPtr前面添加*符号,arrPtr才能表示为返回指针。
// 使用decltype
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
// 返回指针,该指针指向含有5个整数的数组
decltype(odd) *arrPtr(int i) {
return (i % 2) ? &odd : &even; // 返回一个指向数组的指针
}
指针形参
函数的参数传递中形参初始化的机理和变量初始化一样。形参的类型决定了形参和实参的交互方式,如果形参是引用类型,它将绑定到实参上;否则,将实参的值拷贝后赋给形参。
当形参是引用类型时,称其对应的实参被引用传递(passed by reference)或者函数被传引用调用(called by reference)。和其他引用一样,引用形参也是它绑定的对象的别名,即为引用形参是它对应的实参的别名。
当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。将拷贝值得实参称为被值传递(passed by value)或者函数被传值调用(called by value)。
指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针可以间接地访问其所指的对象,所以通过指针可以修改其所指对象的值。
C++中建议使用引用类型的形参替代指针。
// 指针形参
int n = 0, i = 42;
int *p = &n, *q = &i;
*p = 42;
p = q;
void reset(int *ip) {
*ip = 0;
ip = 0;
reset(&i);
cout << "i = " << i << endl;
数组形参(数组会转换成指针)
数组的两个特殊性质:
- 不允许拷贝数组
- 使用数组时(通常)会将数组转换为指针。
因为不能拷贝数组,所以不能使用值传递的方式使用数组参数;因为使用数组时数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
可以在定义函数的时候将数组的形参写成类似数组的形式:
// 数组形参
void print(const int*);
void print(const int[]);
void prin(const int [10]);
// 上面三个函数是等价的,每个函数的唯一形参都是const int*类型的。
int i = 0, j[2] = {0, 1};
print(&i); // 正确:&i的类型是int*
print(j); // 正确:j转换成int*并指向j[0]
和其他使用数组的代码一样,以数组作为形参的函数必须确保使用数组的时候不会越界。
因为数组是以指针的形式传递给函数的,所以数组的大小并不明确,管理指针形参通常有三种常用的方法:
- 使用标记指定数组长度
- 使用标准库规范
- 显式传递一个表示数组大小的形参
数组引用形参
C++允许将变量定义成数组的引用,因此形参也可以是数组的引用。引用形参绑定到对应的实参上,也就是绑定到对应的实参上,即数组上。
// 正确:形参是数组的引用,维度是类型的一部分
void print(int (&arr)[10]) {
for(auto elem : arr)
cout << elem << endl;
// &arr两端的括号必不可少
f(int &arr[10]); // 错误:将arr声明成了引用的数组
f(int (&arr)[10]); // 正确:arr是具有10个整数的整型数组的引用
传递多维数组
在C++中没有真正的多维数组,多维数组的本质是数组的数组。
和所有的数组一样,将多维数组传递给函数时,真正传递的是指向数组首元素的指针。(P115)
因为多维数组是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。
数组的第二维(以及后面所有维度)的大小都是数组类型的一部分,不可省略。
// 多维数组
void print(int (*matrix)[10], int rowSize) { /*...*/ };
// matrix指向数组的首元素,该数组的元素是由10个整数构成的数组,这里将matrix声明成指向含有10个整数的数组的指针
// *matrix 两端的括号必不可少:
int *matrix[10]; // 10个指针构成的数组
int (*matrix)[10]; // 指向含有10个整数的数组的指针
// 等价定义
void print(int matrix[][10], int rowSize) { /*...*/ };
// matrix的声明看起来像一个二维数组,实际上形参是指向含有10个整数的数组的指针
函数指针(6.7 函数指针)
函数指针指向的是函数而非对象。(函数是一个命名了的代码块,一个典型的函数定义包括:返回类型(return type)、函数名字、0-n个形参、函数体)
和其他指针一样,函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
// 函数指针
bool lengthCompare(const string &, const string &); // lengthCompare函数的类型为bool 用指针替换函数名就能声明一个执行该函数的指针
bool (*pf)(const string &, const string &); // pf指向一个函数,该函数的参数是两个const string的引用。返回值是bool类型
bool *pf1(const string &, const string &); // 声明一个名为pf1的函数,该函数返回bool*
// 使用函数指针
pf = lengthCompare;
pf = &lengthCompare;
// 直接使用指向函数的指针调用该函数,无须提前解引用指针
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
pf = 0; // 正确:pf不指向任何函数
pf = sumLength; // 错误:返回类型不匹配
pf = cstringCompare; // 错误:形参类型不匹配