C++ 学习笔记六-函数
一、函数的定义:函数由函数名以及一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。函数执行的运算在一个称为函数体的块语句中定义。每一个函数都有一个相关联的返回类型。
1.C++ 语言使用调用操作符(即一对圆括号)实现函数的调用。正如其他操作符一样,调用操作符需要操作数并产生一个结果。调用操作符的操作数是函数名和一组(有可能是空的)由逗号分隔的实参。函数调用的结果类型就是函数返回值的类型,该运算的结果本身就是函数的返回值。
2.函数体是一个作用域:函数体是一个语句块,定义了函数的具体操作。通常,这个块语句包含在一对花括号中,形成了一个新的作用域。和其他的块语句一样,在函数体中可以定义变量。在函数体内定义的变量只在该函数中才可以访问。这种变量称为局部变量,它们相对于定义它们的函数而言是“局部”的,其名字只能在该函数的作用域中可见。这种变量只在函数运行时存在.
3.形参和实参:类似于局部变量,函数的形参为函数提供了已命名的局部存储空间。它们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递函数的实参初始化。实参则是一个表达式。它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。在调用函数时,所传递的实参个数必须与函数的形参个数完全相同。与初始化式的类型必须与初始化对象的类型匹配一样,实参的类型也必须与其对应形参的类型完全匹配:实参必须具有与形参类型相同、或者能隐式转换为形参类型的数据类型。
4.函数返回类型:函数的返回类型可以是内置类型(如 int 或者 double)、类类型或复合类型(如 int& 或 string*),还可以是 void 类型,表示该函数不返回任何值。函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针.
5.函数形参表:函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。例如,下面关于 process 的声明是等价的:
void process() { /* ... */ } // implicit void parameter list void process(void){ /* ... */ } // equivalent declaration形参表由一系列用逗号分隔的参数类型和(可选的)参数名组成。如果两个参数具有相同的类型,则其类型必须重复声明。
二、参数传递:每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。
1.非引用形参:普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
2.指针形参:函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。
3.const 形参:在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参.尽管函数的形参是 const,但是编译器却将 fcn 的定义视为其形码被声明为普通的 int 型:
void fcn(const int i) { /* fcn can read but not write to i */ } void fcn(int i) { /* ... */ } // error: redefines fcn(int)4.引用形参:使用引用形参,函数可以直接访问实参对象,而无须复制它。如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。如果函数具有普通的非 const 引用形参,则显然不能通过 const 对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的 const 特性。但比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的.应该将不需要修改的引用形参定义为 const 引用。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。
5.vector 和其他容器类型的形参:通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用 vector 形参的函数将会复制 vector 的每一个元素。从避免复制 vector 的角度出发,应考虑将形参声明为引用类型。事实上,C++ 程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器.
6.数组形参:数组有两个特殊的性质,影响我们定义和使用作用在数组上的函数:一是不能复制数组; 二是使用数组名字时,数组名会自动转化为指向其第一个元素的指针。因为数组不能复制,所以无法编写使用数组类型形参的函数。因为数组会被自动转化为指针,所以处理数组的函数通常通过操纵指向数组指向数组中的元素的指针来处理数组。
A.数组形参的定义:如果要编写一个函数,输出 int 型数组的内容,可用下面三种方式指定数组形参:
// three equivalent definitions of printValues void printValues(int*) { /* ... */ } void printValues(int[]) { /* ... */ } void printValues(int[10]) { /* ... */ }虽然不能直接传递数组,但是函数的形参可以写成数组的形式。虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。上面的三种定义是等价的,形参类型都是 int*。
B.通常,将数组形参直接定义为指针要比使用数组语法定义更好。这样就明确地表示,函数操纵的是指向数组元素的指针,而不是数组本身。由于忽略了数组长度,形参定义中如果包含了数组长度则特别容易引起误解。编译器忽略为任何数组形参指定的长度。当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。
7.数组实参:和其他类型一样,数组形参可定义为引用或非引用类型。大部分情况下,数组以普通的非引用类型传递,此时数组会悄悄地转换为指针。一般来说,非引用类型的形参会初始化为其相应实参的副本。而在传递数组时,实参是指向数组第一个元素的指针,形参复制的是这个指针的值,而不是数组元素本身。函数操纵的是指针的副本,因此不会修改实参指针的值。然而,函数可通过该指针改变它所指向的数组元素的值。通过指针形参做的任何改变都在修改数组元素本身。
8.通过引用传递数组:和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配.
9.多维数组的传递:所谓多维数组实际是指数组的数组。
和其他数组一样,多维数组以指向 0 号元素的指针方式传递。多维数组的元素本身就是数组。除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定:
// first parameter is an array whose elements are arrays of 10 ints void printValues(int (matrix*)[10], int rowSize);10.传递给函数的数组的处理:非引用数组形参的类型检查只是确保实参是和数组元素具有同样类型的指针,而不会检查实参实际上是否指向指定大小的数组。任何处理数组的程序都要确保程序停留在数组的边界内。有三种常见的编程技巧确保函数的操作不超出数组实参的边界:A.在数组本身放置一个标记来检测数组的结束。B.传递指向数组第一个和最后一个元素的下一个位置的指针。这种编程风格由标准库所使用的技术启发而得.C.将第二个形参定义为表示数组的大小.11.C++ 中的省略符形参是为了编译使用了 varargs 的 C 语言程序。关于如何使用 varargs,请查阅所用 C 语言编译器的文档。对于 C++ 程序,只能将简单数据类型传递给含有省略符形参的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确地复制。在无法列举出传递给函数的所有实参的类型和数目时,可以使用省略符形参。省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以有 0 或多个实参,而实参的类型未知。省略符形参有下列两种形式:
void foo(parm_list, ...); void foo(...);第一种形式为特定数目的形参提供了声明。在这种情况下,当函数被调用时,对于与显示声明的形参相对应的实参进行类型检查,而对于与省略符对应的实参则暂停类型检查。在第一种形式中,形参声明后面的逗号是可选的。
三、return语句
1.return 语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。return 语句有两种形式:
return; return expression;不带返回值的 return 语句只能用于返回类型为 void 的函数。在返回类型为 void 的函数中,return 返回语句不是必需的,隐式的 return 发生在函数的最后一个语句完成时。
返回类型为 void 的函数通常不能使用第二种形式的 return 语句,但是,它可以返回另一个返回类型同样是 void 的函数的调用结果.
2.return 语句的第二种形式提供了函数的结果。任何返回类型不是 void 的函数必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型。
3.在含有 return 语句的循环后没有提供 return 语句是很危险的,因为大部分的编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的。
4.主函数 main 的返回值:
A.返回类型不是 void 的函数必须返回一个值,但此规则有一个例外情况:允许主函数 main 没有返回值就可结束。如果程序控制执行到主函数 main 的最后一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。
B.关于主函数 main 返回的另一个特别之处在于如何处理它的返回值。可将主函数 main 返回的值视为状态指示器。返回 0 表示程序运行成功,其他大部分返回值则表示失败。非 0 返回值的意义因机器不同而不同,为了使返回值独立于机器,cstdlib 头文件定义了两个预处理变量,分别用于表示程序运行成功和失败.
5.返回非引用类型:函数的返回值用于初始化在调用函数处创建的临时对象。在求解表达式时,如果需要一个地方储存其运算结果,编译器会创建一个没有命名的对象,这就是临时对象。在英语中,C++ 程序员通常用 temporary 这个术语来代替 temporary object。
int sum(int a,int b) { int s=a+b; return s; //把s的值保存到函数调用时创建的临时变量里面 }
6.return 语句不可返回指向“栈内存”(局部对象)的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
char * Func(void) //调用函数时,会创建一个临时对象,保存返回值。(建议看看:函数栈的分布) { char str[] = “hello world”; // str 的内存位于栈上 … return str; // 将导致错误 }
四、函数声明:如变量必须先声明后使用一样,函数也必须在被调用之前先声明。与变量的定义类似,函数的声明也可以和函数的定义分离;一个函数只能定义一次,但是可声明多次。
1.函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型,函数原型描述了函数的接口。
2.函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该用作辅助文档:
void print(int *array, int size);
3.把函数声明直接放到每个使用该函数的源文件中,这可能是大家希望的方式,而且也是合法的。但问题在于这种用法比较呆板而且容易出错。解决的方法是把函数声明放在头文件中,这样可以确保对于指定函数其所有声明保持一致。如果函数接口发生变化,则只要修改其唯一的声明即可。
4.将提供函数声明头文件包含在定义该函数的源文件中,可使编译器能检查该函数的定义和声明时是否一致。特别地,如果函数定义和函数声明的形参列表一致,但返回类型不一致,编译器会发出警告或出错信息来指出这种差异。
5.默认实参:默认实参是一种虽然并不普遍、但在多数情况下仍然适用的实参值。调用函数时,可以省略有默认值的实参。编译器会为我们省略的实参提供默认值.
A.默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。
B.调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。下面的函数 screenInit 的调用都是正确的:
string screen; screen = screenInit(); // equivalent to screenInit (24,80,' ') screen = screenInit(66); // equivalent to screenInit (66,80,' ') screen = screenInit(66, 256); // screenInit(66,256,' ') screen = screenInit(66, 256, '#');
C.函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。例如,如果要给 background 提供实参,那么也必须给 height 和 width 提供实参:
screen = screenInit(, , '?'); // error, can omit only trailing argumentsD.设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。
6.默认实参的初始化式:默认实参可以是任何适当类型的表达式.
7.既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。下面的例子是错误的:
// ff.h int ff(int = 0); // ff.cc #include "ff.h" int ff(int i = 0) { /* ... */ } // error通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。
五、局部对象:在 C++ 语言中,每个名字都有作用域,而每个对象都有生命期。要弄清楚函数是怎么运行的,理解这两个概念十分重要。名字的作用域指的是知道该名字的程序文本区。对象的生命期则是在程序执行过程中对象存在的时间。
在函数中定义的形参和变量的名字只位于函数的作用域中:这些名字只在函数体中可见。通常,变量名从声明或定义的地方开始到包围它的作用域结束处都是可用的。
1.自动对象:默认情况下,局部变量的生命期局限于所在函数的每次执行期间。只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。
A.局部变量所对应的自动对象在函数控制经过变量定义语句时创建。如果在定义时提供了初始化式,那么每次创建对象时,对象都会被赋予指定的初值。对于未初始化的内置类型局部变量,其初值不确定。当函数调用结束时,自动对象就会撤销。
B.形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。
C.自动对象,包括形参,都在定义它们的块语句结束时撤销。形参在函数块中定义,因此当函数的执行结束时撤销。当函数结束时,会释放它的局部存储空间。在函数结束后,自动对象和形参的值都不能再访问了。
2.静态局部对象:一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。
六、内联函数:inline 函数避免函数调用的开销
将函数指定为 inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。假设我们将 shorterString 定义为内联函数,则调用:
cout << shorterString(s1, s2) << endl;在编译时将展开为:
cout << (s1.size() < s2.size() ? s1 : s2) << endl;
A.inline 说明对于编译器来说只是一个建议,编译器可以选择忽略这个。一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。大多数的编译器都不支持递归函数的内联。一个 1200 行的函数也不太可能在调用点内联展开。
B.内联函数应该在头文件中定义,这一点不同于其他函数。inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。
七、类的成员函数:成员函数的定义与普通函数的定义类似。和任何函数一样,成员函数也包含下面四个部分:
函数返回类型,函数名、用逗号隔开的形参表(也可能是空的)、包含在一对花括号里面的函数体。正如我们知道的,前面三部分组成函数原型。函数原型定义了所有和函数相关的类型信息:函数返回类型是什么、函数的名字、应该给这个函数传递什么类型的实参。函数原型必须在类中定义。但是,函数体则既可以在类中也可以在类外定义。
1.定义成员函数的函数体:类的所有成员都必须在类定义的花括号里面声明,此后,就不能再为类增加任何成员。类的成员函数必须如声明的一般定义。类的成员函数既可以在类的定义内也可以在类的定义外定义。 编译器隐式地将在类内定义的成员函数当作内联函数.类内定义一般为内联函数--要求函数体一般不多于5行(不固定)、没有循环、递归等。
普通成员函数均在类外定义。
2.成员函数含有额外的、隐含的形参:调用成员函数时,实际上是使用对象来调用的。每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。
3.this 指针的引入:每个成员函数(除了static 成员函数外)都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。
4.const 成员函数的引入:
class Sales_item { public: // operations on Sales_item objects double avg_price() const; //const成员函数 bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } // private members as before private: std::string isbn; unsigned units_sold; double revenue; };在 Sales_item 成员函数声明的形参表后面的 const 所起的作用了:const 改变了隐含的 this 形参的类型。在调用 total.same_isbn(trans) 时,隐含的 this 形参将是一个指向 total 对象的 const Sales_Item* 类型的指针。
用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。因此,函数 avg_price 和函数 same_isbn 只能读取而不能修改调用它们的对象的数据成员。
5.this 指针的使用:在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用:
bool same_isbn(const Sales_item &rhs) const
6.在类外定义成员函数:
在类的定义外面定义成员函数必须指明它们是类的成员:
//使用作用域操作符指明函数 avg_price 是在类 Sales_item 的作用域范围内定义的。
double Sales_item::avg_price() const { if (units_sold) return revenue/units_sold; else return 0; }7.构造函数是特殊的成员函数:与其他成员函数不同,构造函数和类同名,而且没有返回类型。而与其他成员函数相同的是,构造函数也有形参表(可能为空)和函数体。一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。A.构造函数的形参指定了创建类类型对象时使用的初始化式。通常,这些初始化式会用于初始化新创建对象的数据成员。构造函数通常应确保其每个数据成员都完成了初始化。
B.没有形参的构造函数为默认构造函数。默认构造函数说明当定义对象却没有为它提供(显式的)初始化式时应该怎么办:
vectorvi; // default constructor: empty vector string s; // default constructor: empty string
C.构造函数和初始化列表:在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。
class Sales_item { public: // operations on Sales_item objects double avg_price() const; bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } // default constructor needed to initialize members of built-in type Sales_item(): units_sold(0), revenue(0.0) { } // private members as before private: std::string isbn; unsigned units_sold; double revenue; };上述例题的初始化列表表明 units_sold 和 revenue 成员都应初始化为 0。每当创建 Sales_item 对象时,它的这两个成员都以初值 0 出现。而 isbn 成员可以不必准确指明其初值。除非在初始化列表中有其他表述,否则具有类类型的成员皆被其默认构造函数自动初始化。于是,isbn 由 string 类的默认构造函数初始化为空串。当然,如果有必要的话,也可以在初始化列表中指明 isbn 的默认初值。
8.合成的默认构造函数:如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。
由编译器创建的默认构造函数通常称为合成的默认构造函数,它将依据如同变量初始化的规则初始化类中所有成员。对于具有类类型的成员,如 isbn,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖于对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。如果对象在局部作用域中定义,则这些成员没有初始化。除了给它们赋值之外,出于其他任何目的对未初始化成员的使用都没有定义。
合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员。
9.类代码文件的组织:通常将类的声明放置在头文件中。大多数情况下,在类外定义的成员函数则置于源文件中。C++ 程序员习惯使用一些简单的规则给头文件及其关联的类定义代码命名。类定义应置于名为 type.h 或 type.H 的文件中,type 指在该文件中定义的类的名字。成员函数的定义则一般存储在与类同名的源文件中。依照这些规则,我们将类 Sales_item 放在名为 Sales_item.h 的文件中定义。任何需使用这个类的程序,都必须包含这个头文件。而 Sales_item 的成员函数的定义则应该放在名为 Sales_item.cc 的文件中。这个文件同样也必须包含 Sales_item.h 头文件。
八、重载函数:出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
1.函数重载和重复声明的区别:如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的.函数不能仅仅基于不同的返回类型而实现重载。
2.在函数中局部声明的名字将屏蔽在全局作用域内声明的同名名字。这个关于变量名字的性质对于函数名同样成立:
/* Program for illustration purposes only: * It is bad style for a function to define a local variable * with the same name as a global name it wants to use */ string init(); // the name init has global scope void fcn() { int init = 0; // init is local and hides global init string s = init(); // error: global init is hidden }
3.一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明.在 C++ 中,名字查找发生在类型检查之前。
4.仅当形参是引用或指针时,形参是否为 const 才有影响。
5. 为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
1.精确匹配。实参与形参类型相同。
2.通过类型提升实现的匹配
3.通过标准转换实现的匹配
4.通过类类型转换实现的匹配
内置类型的提升和转换可能会使函数匹配产生意想不到的结果。
九、函数指针:函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关:
// pf points to function returning bool that takes two const string references bool (*pf)(const string &, const string &);*pf 两侧的圆括号是必需的:
// declares a function named pf that returns a bool* bool *pf(const string &, const string &);1.函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。将函数指针初始化为 0,表示该指针不指向任何函数。指向不同函数类型的指针之间不存在转换. 2.通过指针调用函数:指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数:cmpFcn pf = lengthCompare; lengthCompare("hi", "bye"); // direct call pf("hi", "bye"); // equivalent call: pf1 implicitly dereferenced (*pf)("hi", "bye"); // equivalent call: pf1 explicitly dereferenced3.函数指针形参:函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:void useBigger(const string &, const string &, bool(const string &, const string &)); // equivalent declaration: explicitly define the parameter as a pointer to function void useBigger(const string &, const string &, bool (*)(const string &, const string &));4.返回指向函数的指针:阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解。具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的是函数时,同样的转换操作则无法实现:
// func is a function type, not a pointer to function! typedef int func(int*, int); void f1(func); // ok: f1 has a parameter of function type func f2(int); // error: f2 has a return type of function type func *f3(int); // ok: f3 returns a pointer to function type
5.指向重载函数的指针:指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误