C++ Primer 4 第七章 函数

本文介绍了函数的基本概念,包括函数的定义、参数传递机制、返回值处理等内容。详细讲解了不同类型的参数传递方式及其应用场景,以及如何正确使用内联函数和重载函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

第七章 函数

1. 函数的定义

函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调用函数。主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。

与初始化式的类型必须与初始化对象的类型匹配一样,实参的类型也必须与其对应形参的类型完全匹配:实参必须具有与形参类型相同、或者能隐式转换为形参类型的数据类型。

函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针.

在定义或声明函数时,没有显式指定返回类型是不合法的.

没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。例如,下面关于 process 的声明是等价的:

     void process() { /* ... */ }      // implicit void parameter list

     void process(void){ /* ... */ }  // equivalent declaration

2. 参数传递

形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。

非引用形参表示对应实参的局部副本。对这类形参的修改仅仅改变了局部副本的值。一旦函数执行结束,这些局部变量的值也就没有了。

指针形参: 函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变

如果函数形参是非 const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值. 如果保护指针指向的值,则形参需定义为指向 const 对象的指针:

     void use_ptr(const int *p)

     {

          // use_ptr may read but not write to *p

     }

可以将指向 const 对象的指针初始化为指向非 const 对象,但不可以让指向非 const 对象的指针向 const 对象。

const 形参: 在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参。这种行为源于 const 对象的标准初始化规则。因为初始化复制了初始化式的值,所以可用 const 对象初始化非 const 对象,反之亦然。

如果将形参定义为非引用的 const 类型:

     void fcn(const int i) { /* fcn can read but not write to i */ }

则在函数中,不可以改变实参的局部副本。由于实参仍然是以副本的形式传递,因此传递给 fcn 的既可以是 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)

这种用法是为了支持对 C 语言的兼容,因为在 C 语言中,具有 const 形参或非 const 形参的函数并无区别。

复制实参的局限性: 复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:

         1) 当需要在函数中修改实参的值时。

         2) 当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过大。

         3) 当没有办法实现对象的复制时。

对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。

引用形参: 引用形参直接关联到其所绑定的实参,而并非这些对象的副本。

引用形参的另一种用法是向主调函数返回额外的结果:函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。

如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用:

        bool isShorter(const string &s1, const string &s2)

     {

         return s1.size() < s2.size();

     }

其每一个形参都是 const string 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。

更灵活的指向 const 的引用:如果函数具有普通的非 const 引用形参,则显然不能通过 const 对象进行调用。毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的 const 特性。但比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的:

     // function takes a non-const reference parameter

     int incr(int &val)

     {

         return ++val;

     }

     int main()

     {

         short v1 = 0;

         const int v2 = 42;

         int v3 = incr(v1);   // error: v1 is not an int

         v3 = incr(v2);       // error: v2 is const

         v3 = incr(0);        // error: literals are not lvalues

         v3 = incr(v1 + v2);  // error: addition doesn't yield an lvalue

         int v4 = incr(v3);   // ok: v3 is a non const object type int

     }

问题的关键是非 const 引用形参只能与完全同类型的非 const 对象关联。

应该将不需要修改的引用形参定义为 const 引用。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。

传递指向指针的引用:

         void ptrswap(int *&v1, int *&v2)

     {

         int *tmp = v2;

         v2 = v1;

         v1 = tmp;

     }

形参int *&v1的定义应从右至左理解:v1 是一个引用,与指向 int 型对象的指针相关联。也就是说,v1 只是传递进 ptrswap 函数的任意指针的别名。

vector 和其他容器类型的形参:通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用 vector 形参的函数将会复制 vector 的每一个元素。

从避免复制 vector 的角度出发,应考虑将形参声明为引用类型。然而事实上,C++ 程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器:

     // pass iterators to the first and one past the last element to print

     void print(vector<int>::const_iterator beg, vector<int>::const_iterator end)

     {

         while (beg != end) {

             cout << *beg++;

             if (beg != end) cout << " "; // no space after last element

         }

         cout << endl;

     }

数组形参:数组有两个特殊的性质,影响我们定义和使用作用在数组上的函数:一是不能复制数组;二是使用数组名字时,数组名会自动转化为指向其第一个元素的指针。因为数组不能复制,所以无法编写使用数组类型形参的函数。因为数组会被自动转化为指针,所以处理数组的函数通常通过操纵指向数组指向数组中的元素的指针来处理数组。

如果要编写一个函数,输出 int 型数组的内容,可用下面三种方式指定数组形参:

     void printValues(int*) { /* ... */ }

     void printValues(int[]) { /* ... */ }

     void printValues(int[10]) { /* ... */ }

虽然不能直接传递数组,但是函数的形参可以写成数组的形式。虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。上面的三种定义是等价的,形参类型都是 int*。

编译器忽略为任何数组形参指定的长度:当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。

如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:

          void printValues(int (&arr)[10]) { /* ... */ }

     int main()

     {

         int i = 0, j[2] = {0, 1};

         int k[10] = {0,1,2,3,4,5,6,7,8,9};

         printValues(&i); // error: argument is not an array of 10 ints

         printValues(j);  // error: argument is not an array of 10 ints

         printValues(k);  // ok: argument is an array of 10 ints

         return 0;

     }

         &arr 两边的圆括号是必需的,因为下标操作符具有更高的优先级:

             f(int &arr[10])     // error: arr is an array of references

              f(int (&arr)[10]) // ok: arr is a reference to an array of 10 ints

多维数组的传递:除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定:

     void printValues(int (matrix*)[10], int rowSize);

上面的语句将 matrix 声明为指向含有 10 个 int 型元素的数组的指针。

也可以用数组语法定义多维数组。与一维数组一样,编译器忽略第一维的长度,所以最好不要把它包括在形参表内:

     void printValues(int matrix[][10], int rowSize);

这条语句把 matrix 声明为二维数组的形式。实际上,形参是一个指针,指向数组的数组中的元素。数组中的每个元素本身就是含有 10 个 int 型对象的数组。

有三种常见的编程技巧确保函数的操作不超出数组实参的边界:

         第一种方法是在数组本身放置一个标记来检测数组的结束。C 风格字符串就是采用这种方法的一个例子,它是一种字符数组,并且以空字符 null 作为结束的标记。处理 C 风格字符串的程序就是使用这个标记停止数组元素的处理。

         第二种方法是传递指向数组第一个和最后一个元素的下一个位置的指针。

         第三种方法是将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。

含有可变形参的函数:在无法列举出传递给函数的所有实参的类型和数目时,可以使用省略符形参。省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以有 0 或多个实参,而实参的类型未知。省略符形参有下列两种形式:

     void foo(parm_list, ...);

     void foo(...);

         第一种形式为特定数目的形参提供了声明。在这种情况下,当函数被调用时,对于与显示声明的形参相对应的实参进行类型检查,而对于与省略符对应的实参则暂停类型检查。在第一种形式中,形参声明后面的逗号是可选的。

3. return 语句

return 语句有两种形式:

     return;

     return expression;

不带返回值的 return 语句只能用于返回类型为 void 的函数。在返回类型为 void 的函数中,return 返回语句不是必需的,隐式的 return 发生在函数的最后一个语句完成时。

return 语句的第二种形式提供了函数的结果。任何返回类型不是 void 的函数必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型。

返回类型不是 void 的函数必须返回一个值,但此规则有一个例外情况:允许主函数 main 没有返回值就可结束。如果程序控制执行到主函数 main 的最后一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。非 0 返回值的意义因机器不同而不同,为了使返回值独立于机器,cstdlib 头文件定义了两个预处理变量,分别用于表示程序运行成功和失败:

     #include <cstdlib>

     int main()

     {

         if (some_failure)

             return EXIT_FAILURE;

         else

             return EXIT_SUCCESS;

     }

返回引用:当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。

         const string &shorterString(const string &s1, const string &s2)

     {

         return s1.size() < s2.size() ? s1 : s2;

     }

形参和返回类型都是指向 const string 对象的引用,调用函数和返回结果时,都没有复制这些 string 对象。

理解返回引用至关重要的是:千万不能返回局部变量的引用。当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存

返回引用的函数返回一个左值。因此,这样的函数可用于任何要求使用左值的地方。

如果不希望引用返回值被修改,返回值应该声明为 const:

     const char &get_val(...

返回指向局部对象的指针也是错误的。一旦函数结束,局部对象被释放,返回的指针就变成了指向不再存在的对象的悬垂指针。

4. 函数声明

将提供函数声明头文件包含在定义该函数的源文件中,可使编译器能检查该函数的定义和声明时是否一致。特别地,如果函数定义和函数声明的形参列表一致,但返回类型不一致,编译器会发出警告或出错信息来指出这种差异。

默认实参:默认实参是一种虽然并不普遍、但在多数情况下仍然适用的实参值。调用函数时,可以省略有默认值的实参。编译器会为我们省略的实参提供默认值。默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。

调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。

函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。

设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。

既可以在函数声明也可以在函数定义中指定默认实参。但是,只能为一个形参指定默认实参一次。

通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。

5. 局部对象

静态局部对象:一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值。

6. 内联函数

inline 函数避免函数调用的开销:

         inline const string & shorterString(const string &s1, const string &s2)

     {

             return s1.size() < s2.size() ? s1 : s2;

     }

将函数指定为 inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。假设我们将 shorterString 定义为内联函数,则调用:

         cout << shorterString(s1, s2) << endl;

在编译时将展开为:

         cout << (s1.size() < s2.size() ? s1 : s2) << endl;

从而消除了把 shorterString 写成函数的额外执行开销。

inline 说明对于编译器来说只是一个建议,编译器可以选择忽略这个。

内联函数应该在头文件中定义:inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。把 inline 函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见。

在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。

7. 类的成员函数

函数原型必须在类中定义。但是,函数体则既可以在类中也可以在类外定义。

类的所有成员都必须在类定义的花括号里面声明,此后,就不能再为类增加任何成员。类的成员函数必须如声明的一般定义。类的成员函数既可以在类的定义内也可以在类的定义外定义。

编译器隐式地将在类内定义的成员函数当作内联函数。

成员函数含有额外的、隐含的形参:调用成员函数时,实际上是使用对象来调用的。

每个成员函数(除了static 成员函数外)都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。

const 成员函数:const 改变了隐含的 this 形参的类型。在调用 total.same_isbn(trans) 时,隐含的 this 形参将是一个指向 total 对象的 const Sales_Item* 类型的指针。就像如下编写 same_isbn 的函数体一样:

     bool Sales_item::same_isbn(const Sales_item *const this, const Sales_item &rhs) const

     { return (this->isbn == rhs.isbn); }

用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。

在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用。由于 this 指针是隐式定义的,因此不需要在函数的形参表中包含 this 指针,实际上,这样做也是非法的。但是,在函数体中可以显式地使用 this 指针。

构造函数的初始化列表:在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。

默认构造函数:如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。它将依据如同变量初始化的规则初始化类中所有成员。对于具有类类型的成员,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖于对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。如果对象在局部作用域中定义,则这些成员没有初始化。

8. 重载函数

出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。main 函数不能重载。

函数不能仅仅基于不同的返回类型而实现重载。

如果两个形参的差别只是一个使用 typedef 定义的类型名,而另一个使用 typedef 对应的原类型名,则这两个形参并无不同。

默认实参并没有改变形参的个数。

如果一对函数的区别仅在于是否将形参定义为 const,则仍为重复定义。值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。

如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。

重载确定的三个步骤:

         第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,并且在调用点上,它的声明可见。

         第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。如果函数具有默认实参,则调用该函数时,所用的实参可能比实际需要的少。默认实参也是实参,在函数匹配过程中,它的处理方式与其他实参一样。

         第三步是确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。这里所谓“最佳”的原则是实参类型与形参类型越接近则匹配越佳。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。

含有多个形参的重载确定:

         已有重载函数:f(int, int)和f(double, double),调用f(42, 2.56);

         编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:

         1)其每个实参的匹配都不劣于其他可行函数需要的匹配。

         2)至少有一个实参的匹配优于其他可行函数提供的匹配。

         如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。

         首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个 int 型形参的函数比带有两个 double 型形参的函数匹配更佳。

         但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。

         因此,这个调用有二义性:每个可行函数都对函数调用的一个实参实现更好的匹配。编译器将产生错误。解决这样的二义性,可通过显式的强制类型转换强制函数匹配:

     f(static_cast<double>(42), 2.56);  // calls f(double, double)

     f(42, static_cast<int>(2.56));     // calls f(int, int)

实参类型转换:为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:

         1)精确匹配。实参与形参类型相同。

         2)通过类型提升实现的匹配

         3)通过标准转换实现的匹配

         4)通过类类型转换实现的匹配

需要类型提升或转换的匹配:

         1)通过类型提升实现的转换优于其他标准转换。假设有两个函数,一个的形参为 int 型,另一个的形参则是 short 型。对于任意整型的实参值,int 型版本都是优于 short 型版本的较佳匹配,即使从形式上看 short 型版本的匹配较佳:

     void ff(int);

     void ff(short);

     ff('a');    // char promotes to int, so matches f(int)

字符字面值是 char 类型,char 类型可提升为 int 型。提升后的类型与函数 ff(int) 的形参类型匹配。char 类型同样也可转换为 short 型,但需要类型转换的匹配“劣于”需要类型提升的匹配。结果应将该调用解释为对 ff (int) 的调用。

         2)没有哪个标准转换比其他标准转换具有更高的优先级。

                  extern void manip(long);

              extern void manip(float);

              manip(3.14);  // error: ambiguous call

         字面值常量 3.14 的类型为 double。这种类型既可转为 long 型也可转为 float 型。由于两者都是可行的标准转换,因此该调用具有二义性。

参数匹配和枚举类型:整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。

          enum Tokens {INLINE = 128, VIRTUAL = 129};

     void ff(Tokens);

     void ff(int);

     int main() {

         Tokens curTok = INLINE;

         ff(128);    // exactly matches ff(int)

         ff(INLINE); // exactly matches ff(Tokens)

         ff(curTok); // exactly matches ff(Tokens)

         return 0;

     }

虽然无法将整型值传递给枚举类型的形参,但可以将枚举值传递给整型形参。此时,枚举值被提升为 int 型或更大的整型。具体的提升类型取决于枚举成员的值。如果是重载函数,枚举值提升后的类型将决定调用哪个函数。

重载和 const 形参:仅当形参是引用或指针时,形参是否为 const 才有影响。可基于函数的引用形参是指向 const 对象还是指向非 const 对象,实现函数重载。将引用形参定义为 const 来重载函数是合法的,因为编译器可以根据实参是否为 const 确定调用哪一个函数:

          Record lookup(Account&);
          Record lookup(const Account&); // new function
          const Account a(0);
          Account b;
          lookup(a);   // calls lookup(const Account&)
          lookup(b);   // calls lookup(Account&)

         非 const 对象既可用于初始化 const 引用,也可用于初始化非 const 引用。但是,将 const 引用初始化为非 const 对象,需通过转换来实现,而非 const 形参的初始化则是精确匹配。

         可将 const 对象的地址值只传递给带有指向 const 对象的指针形参的函数。也可将指向非 const 对象的指针传递给函数的 const 或非 const 类型的指针形参。如果两个函数仅在指针形参时是否指向 const 对象上不同,则指向非 const 对象的指针形参对于指向非 const 对象的指针(实参)来说是更佳的匹配。重复强调,编译器可以判断:如果实参是 const 对象,则调用带有 const* 类型形参的函数;否则,如果实参不是 const 对象,将调用带有普通指针形参的函数。

         注意不能基于指针本身是否为 const 来实现函数的重载:

     f(int *);

     f(int *const); // redeclaration

9. 指向函数的指针

函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关

用 typedef 简化函数指针的定义:

         typedef bool (*cmpFcn)(const string &, const string &);

函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。此时,直接引用函数名等效于在函数名上应用取地址操作符。指向不同函数类型的指针之间不存在转换:

通过指针调用函数:指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数:

          cmpFcn pf = lengthCompare; // 等价:cmpFcn pf = &lengthCompare;

     lengthCompare("hi", "bye"); // direct call

     pf("hi", "bye");            // equivalent call: pf1 implicitly dereferenced

     (*pf)("hi", "bye");         // equivalent call: pf1 explicitly dereferenced

函数指针形参:函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:

         // third parameter is a function type and is automatically treated as a pointer to function

     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 &));

返回指向函数的指针:

         // ff is a function taking an int and returning a function pointer the function pointed to returns an int and takes an int* and an int

     int (*ff(int))(int*, int);

ff(int) 将 ff 声明为一个函数,它带有一个 int 型的形参。该函数返回int (*)(int*, int); 它是一个指向函数的指针,所指向的函数返回 int 型并带有两个分别是 int* 型和 int 型的形参。

使用 typedef 可使该定义更简明易懂:

     // PF is a pointer to a function returning an int, taking an int* and an int

     typedef int (*PF)(int*, int);

     PF ff(int);  // ff returns a pointer to function

允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的是函数时,同样的转换操作则无法实现:

     // 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

指向重载函数的指针:

         C++ 语言允许使用函数指针指向重载的函数:

     extern void ff(vector<double>);

     extern void ff(unsigned int);

     // which function does pf1 refer to?

     void (*pf1)(unsigned int) = &ff; // ff(unsigned)

指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误:

     // error: no match: invalid parameter list

     void (*pf2)(int) = &ff;

     // error: no match: invalid return type

     double (*pf3)(vector<double>);

     pf3 = &ff;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值