函数

学习目标:

函数


学习内容:

一、函数基础

  • 函数就是一段命了名的代码块。一个函数通常包括四个部分:返回类型,函数名字,0个或多个形参列表以及函数体。

    int fact(int val){
        int ret = 1;
        while(val > 1){
            ret *= val--;
        }
        return ret;
    }
    
  • 调用函数的执行流程:1、使用实参初始化函数对应的形参;2、主调函数被中断,执行被调函数;3、函数执行完毕,返回return后的值;4、程序控制权从被调函数转移回主调函数。

    int main(){
        int j = fact(5);  
        cout << "5! is " << j << endl;
        return 0;
    }
    
  • 实参初始化形参的规则:1、形参和实参的个数必须一致;2、每个形参和实参的类型必须一致(允许隐式转换);

    fact("hello");  // string和int类型不一致
    fact();         // 实参太少
    fact(42, 10);   // 实参太多
    fact(3.14);     // 正确 double可以隐式转换为int
    
  • 形参定义的规则:1、允许形参列表为空,通常用void;2、形参之间用逗号隔开,每个形参都必须有一个声明符,即使两个形参类型相同,也要把两个形参的类型都写出来;3、任意两个形参不能同名,函数内的变量名也不能和形参同名;

  • 返回类型规则:1、允许不返回任何值,void;2、大部分类型都可以作为返回值;3、返回类型不能为数组或者函数类型,但是允许是指向数组或函数的指针;

  • 局部变量:定义在形参和函数体内部的变量;

  • 局部变量可以分为自动对象和局部静态对象;
    i. 自动对象:只存在于块执行期间的对象,当执行函数时经过变量定义语句时创建对象,在函数执行结束时销毁它(形参属于自动对象);
    ii. 局部静态对象(static局部变量):生命周期贯穿函数调用及之后的时间,当第一次执行函数经过变量定义时创建对象,直到主程序终止才销毁(对象所在函数执行结束,对象仍然存在)

    int count(){
        static int cnt = 0;
        return ++cnt;
    }
    
    int main(){
        // 静态局部变量 输出1~10
        // 自动对象     输出10个1
        for(int i = 0; i != 10; ++i){
            cout << count() << endl;
        }
        
        return 0;
    }
    
  • 函数声明
    i. 函数声明和函数定义的唯一区别是函数声明没有函数体,直接用一个分号结束;
    ii. 从函数声明中可以看出函数的三要素:返回类型、函数名、形参类型,函数声明描述了函数的接口;
    iii. 函数声明也称为函数原型;
    iv. 函数声明一般放在头文件,函数定义一般放在源文件;

  • 分离式编译

    // 把main.cpp和fact.cpp一起编译成main_fact.exe
    g++ -g main.cpp fact.cpp -o main_fact
    

二、参数传递

  • 实参初始化形参和变量的初始化方式一样,本质就是将实参的值拷贝给形参。
  • 按照形参类型,可以将传参方式分为值传递和引用传递两种方式。
    i. 引用传递:形参类型是引用类型,引用形参是它对应的实参的别名;
    ii. 值传递:形参类型不是引用类型,指实参的值通过拷贝传递给形参;

1. 传值参数

  • 形参是实参的副本,函数对形参做的所有操作不会影响实参本身;
    i. 当形参是普通值类型:把实参(变量值)拷贝给形参

    int fact(int val){
        int ret = 1;
        while(val > 1)
            ret *= val--;
        return ret;
    }
    
    int main(){
        int a = 5;
        // fact(a)  把实参a的值拷贝给形参val
        cout << "5! is " << fact(a) << endl;
        // fact函数中改变了形参val的值,但是并不会影响实参a
        cout << a << endl;  // 5  
    return 0;
    }
    

    ii. 当形参是指针类型:把实参(变量的地址)拷贝给实参

    void reset(int *p){
        // 修改形参p  并不回影响实参(i的地址)    
        *p = 0;  // i = 0
        p = 0;   // 形参p指向0空指针   
    }
    
    int main(){
        int i = 42;  // &i = 0x61fe1c
        reset(&i);  // 把实参(i的地址)拷贝给形参p  即形参p指向i
        cout << i << endl;  // &i = 0x61fe1c
        return 0;
    }
    

    iii. C++中建议使用引用类型的形参代替指针形参,指针形参最好不要用;

2.传引用参数

  • 引用形参其实就是实参的一个别名,对形参改变,也会改变实参

    void reset(int &p){
        p = 0;
    }
    
    int main(){
        int i = 42;  
        // p是i的一个别名  对形参p操作 也会改变实参i
        reset(i);  
        cout << i << endl;  // 0
        return 0;
    }
    
  • 引用形参可以避免大拷贝:当传入的实参比较大,那么如果用普通的形参类型,它会将这个大实参拷贝给形参,效率比较低。相反使用引用的话,不需要拷贝,只是给这个大实参起了个别名而言,效率很高;

  • 如果在函数内不需要修改引用形参的值,那么最好把它声明为常量引用const string &s;

  • 使用引用形参还可以返回额外信息:一般一个函数只能返回一个值,如果需要返回多个值,那么就可以以引用的形式返回;

  • 当实参是一个右值时,形参不能为引用类型;

    // return返回s中第一次出现c的idx  引用形参times返回s中c出现的次数
    // s可能很长,所以使用& 可以避免拷贝 不改变s所以是const
    // times需要返回给函数外 所以使用&
    // c 因为可能直接传入'a'字符 即实参可能是一个右值 所以不能用&
    int reset(const string &s, char c, int &times){
        int idx = s.size();
        for(int i = 0; i != s.size(); ++i){
            if(s[i] == c){
                if(idx == s.size())
                    idx = i;
                ++times;
            }
        }
        return idx;
    }
    
    int main(){
        int times = 0;
        string s = "Hello World";
        char c = 'l'; 
        int idx = reset(s, c, times);
        cout << idx << " " << times << endl;  // 2 3
        return 0;
    }
    

3. const形参和实参

  • 顶层const: 对象本身是一个const;底层const:指针指向的内容是一个const;

  • 当执行对象的拷贝操作时,顶层const可以忽略,底层const必须一致;

    const int ci = 42;  // 顶层const
    int i = ci;  // 正确  顶层const可以忽略
    int *const p = &i;  
    p = 0;  // 错误  p(i的地址)是一个const 不能修改
    *p = 0;  // 正确 p不可修改 但是*P表示p指向的内容可以修改
    
  • 而恰好实参初始化形参本质上就是将实参拷贝给形参;

    // 这种函数重载是错误的,const是顶层const 可以忽略 所以两个函数是一样的
    void fcn(const int i){}
    void fcn(int i){}
    
  • 引用的const是底层const;

  • 顶层const + & -> 底层const;

  • 常量引用右边可以是任意东西(常量可以引用非常量):非常量/字面量/表达式/不同类型变量;

  • 非常量不可以引用常量;

    void reset(int *p){}          // 1
    void reset(int &p){}          // 2
    void reset(const int *p){}    // 3
    void reset(const int &p){}    // 4
    string::size_type find_char(string &s. char c, string::size_type &occurs);    // 5
    string::size_type find_char(const string &s. char c, string::size_type &occurs);   // 6
    
    int i = 0;
    const int ci = i;  // 顶层
    string::size_type ctr = 0;  // unsigned int
    reset(&i);     // 1  int *p = &i;
    reset(&ci);    // 3  const int *p = &ci;  &(顶层const) -> 底层const 所以不能选1
    reset(i);      // 2  int &p = i;
    reset(ci);     // 4  const int &p = ci;  引用const是底层const 所以不能选2
    reset(42);     // 4  const int &p = 42;  常量引用右边可以是字面量
    reset(ctr);    // 4  常量引用右边可以是不太类型的变量  不能选2  类型不一样且无符号数无法自动转换为带符号数
    find_char("hello world", 'l', ctr);   // 6  字面值不是变量 不能对它取别名  但是常量引用右边却可以是一个字面值
    
  • 对于函数中不会修改的形参要尽量设计成常量引用:常量引用右边接受的实参类型更多(字面值/常量/表达式/不太类型对象),不容易出错;

4.数组形参

  • 不需要修改数组元素时,通常都将这个数组形参定义为const;

  • 将数组形参定义为指向数组首元素的指针
    i. 因为不能拷贝数组,所以无法通过值传递方式使用数组参数;
    ii. 但是使用数组的时候,编译器会自动将其转换为指向数组首元素的指针,所以可以把形参写成类似数组的形式(指针)传参;

    // 这三个函数都是相同的
    void print(const int*);
    void print(const int[]);
    void print(const int[10]);  // 这个10是形参  实参并不一定是10  
    
    int i = 0, j[2] = {1, 2};
    print(&i);  // 正确  传入指向i的指针
    print(j);   // 正确  传入指向j[0]的指针
    

    iii. 因为数组形参传入的是指向首元素的地址,函数内部其实是不知道数组的大小,所以在遍历这个数组的时候要小心数组越界问题;通常有3种方式解决这个问题(推荐使用前两种方式,第三种局限性很大):

    1. 使用规范库规范:传递指向数组首元素和尾后元素的指针;
    void print(const int *beg, const int *end){
        while(beg != end){
            cout << *beg++ << endl;
        }
    }
    
    1. 传入指向数组首元素的指针和该数组size;
    // size = end(a) - begin(j);  或者  sizeof(a) / sizeof(a[0])
    void print(const int a[], size_t size){
        for(size_t i = 0; i != size; ++i){
            cout << a[i] << endl;
        }
    }
    
    1. 标记结尾元素为0(局限性很大);
    void print(const int *p){
        if(p){
            while(*p)
                cout << *p++ << endl;
        }
    }
    
  • 将数组形参定义为数组的引用,引用形参会直接绑定在;

    // int (&arr)[10]: 定义了一个引用,引用的对象是一个大小为10的int数组
    // int &arr[10]: 定义了一个数组,数组中每个元素都是int型的引用,错误 引用不是对象 不能称为数组中的元素
    void print(int (&arr)[10]){
        for(auto elem : arr)
            cout << elem << endl;
    }
    int a[10] = {0};
    int b[2] = {0, 1};
    int c = 0;
    print(a);  // 对
    // 注意引用即别名,实参必须和形参类型一致,所以实参必须也是大小为10的int数组
    print(b);  // 错
    print(c); // 错
    
  • 形参是多维数组:

    // int (*matrix)[10]:定义了一个指针,指向一个大小为10的int数组
    // int *matrix[10]: 定义了一个大小为10的数组,每个元素都是int型的指针
    void print(int (*matrix)[10], int row_size){}
    // 第一个维度不用定义 写了也没用  第二个维度写死了就是10列
    void print(int matrix[][10], int row_size){}
    

5. main函数:处理命令行选项

  • 带参数main函数一般形式:

    int main(int argc, char *argv[]){}
    int main(int argc, char **argv[]){}  // 推荐用这个
    

    argc表示输入参数的个数,argv为一个数组,存放的是从m命令行输入的所有char字符串; 例子:

    int main(int argc, char **argv){
        string s = "";
        for(int i = 1; i != argc; ++i){
            s += string(argv[i]) + " ";
        }
        cout << s << endl;
        return 0;
    }
    
    // 命令行输入: hello world
    // 命令行输出: hello world
    

6. 含有可变形参的函数

  • 如果形参类型全部相同:使用标准库类型initializer_list 示例:

    void error_msg(initializer_list<string> il){
        for(auto beg = begin(il) ; beg != end(il); ++beg)
            cout << *beg << " ";
        cout << endl;
    }
    
    int main(int argc, char **argv){
        // lst可以是任意个数string类型
        initializer_list<string> lst = {"error", "hello"};
        error_msg(lst);
        return 0;
    }
    
  • 实参类型不同,可以使用可变参数模板(后续介绍)

  • 省略形参符: …,便于C++访问某些C代码,这些C代码使用了 varargs的C标准功能;

三、返回类型和return语句

  • return语句有两种形式

    return;  // 只能用在返回类型是void的函数
    return expression;
    
  • 无返回值函数:声明void,无需返回值,但是如果需要跳过函数某些语句直接退出函数,可以直接return;

  • 有返回值函数
    i. 返回值类型必须和函数的返回类型相同,或者能隐式转换成函数的返回类型;
    ii. 值是怎么返回的:如果是返回值类型如string,则返回的是一个变量的副本或者一个未命名的临时变量;如果返回值类型是引用类型&,则返回的是返回的对象的一个别名;

    string get_return(string a){
        return a;         // 返回a变量的副本
        return "hello";   // 返回未命名的临时变量
    }
    
    string& get_return(string a){
        return a;         // 返回a变量的一个别名
    }
    

    iii. 不要返回局部变量对象的引用或者指针:函数的局部对象在函数执行结束后会被销毁,返回一个已经消耗的对象的引用或指针都是没有意义的;

    string& get_return(string a){
        string b = "world";
        return a;        // 对
        return b;        // 不报错 但是执行的时候报错
        return "hello";  // 直接报错          
    }
    
  • 调用运算符()和点运算符优先级相同,遵循左结合律,所以如果返回类类型可以在后面直接接点运算符调用类对象的属性;

    auto sz = shorterString(s1, s2).size();
    
  • 返回类型是非常量引用类型如char&,则这个返回值是一个左值,可以直接为它赋值;

    char& get_val(string &str, string::size_type idx){
        return str[idx];
    }
    int main(){
        string s = "hello world";
        get_val(s, 0) = 'H';
        cout << s << endl;  // Hello world
        return 0;
    }
    
  • c++11规定,函数可以返回花括号{}的值列表,此时返回的是未命名的临时变量;

    vector<string> process(){
        return {"hello", "world", "h", "k"};
    }
    
  • 主函数main的返回值可以没有return,系统会隐式的插入一条返回0的return语句;

  • 返回数组指针:数组不能被拷贝,所以不能直接返回数组,但是可以返回数组的指针或者引用;四种方法,尾置法最好,最简单,可读性也最好;
    i. 返回数组的指针/引用看起来比较繁使,可以使用类型别名:

    typedef int arrT[10];
    using arrT = int[10];
    arrT* func(int i);  // 返回一个指针 指向int[10]
    arrT& func(int i);  // 返回一个引用 它是一个int[10]数组的别名
    

    ii. 但是如果想不使用类型别名,数组的维度就必须定义再数组名之后(读起来从内向外,从右往左):

    Type (*func(param list))[dim];
    int (*func(int i))[10];   // 返回一个指针 指向int[10];
    int (&fun())[10];         // 返回一个引用 它是一个int[10]数组的别名
    
  • c++11允许使用尾置返回类型:

    auto func(int i) -> int(*)[10];  // 返回一个指针 指向int[10];
    auto func(int i) -> int(&)[10];  // 返回一个引用 它是一个int[10]数组的别名
    
  • 当明确知道返回的指针指向哪个数组时,可以使用decltype关键字声明返回类型:

    int odd[] = {1, 2, 3};
    decltype(odd) *func(i);  // 返回一个指针 指向int[3];
    decltype(odd) &func(i);  // 返回一个引用 它是一个int[10]数组的别名
    

四、函数重载

  • 函数重载:函数名字相同,但是参数列表不同(和返回值类型无关),一般只重载那些操作相似的函数;

    void print(int a);
    int print(int b);      // 错 只有返回值类型不同 不是函数重载
    void print(string b);  // 对
    void print(const string b);   // 错  顶层const不算重载
    
  • main函数不能重载;

  • 实参给形参初始化本质上也是变量赋值,所以如果const形参是顶层的,可以忽略;

    void print(string b){cout << "1" << endl;}     
    void print(const string b){cout << "2" << endl;}   // 错  顶层const不算重载
    
    void print(string *b){cout << "3" << endl;}
    void print(string* const b){cout << "4" << endl;}  // 错  顶层const不算重载
    void print(const string* b){cout << "5" << endl;} // 对  底层const 算重载 
    
    void print(string &b){cout << "6" << endl;}
    void print(const string &b){cout << "7" << endl;}   // 底层const  算重载 
    
  • 当函数的作用相同时,不需要再重写函数,只需使用const_cast调用已经存在的重载函数即可:

    const string& shorterString(const string& s1, const string& s2){
        cout << "1" << endl;
        return s1.size() <= s2.size() ? s1 : s2;
    }
    
    string& shorterString(string& s1, string& s2){
        cout << "2" << endl;
        auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
        return const_cast<string&>(r);
    }
    
    
    int main(){
        string s1 = "Hello";
        string s2 = "jr";
        cout << shorterString(s1, s2) << endl;
        return 0;
    }
    
  • 函数重载与作用域:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名;

    string read();
    void print(const string&);
    void print(double);
    
    
    int main(){
        bool read = false;
        string s = read();   // 错  内层同名  外层的read函数就隐藏了
        void print(int);     // 1
        print("Hello");      // 错  内层同名  外层的read函数就隐藏了
        print(3);            // 对  调用1
        print(3.14);         // 对  隐式转换  调用1
        return 0;
    }
    

五、三个函数相关的语言特性
1. 默认实参

  • 一旦某些形参被赋予了默认值,那么它后面的所有形参都必须要有默认值;

    string screen(sz h = 24, sz w = 80, char backgrnd = ' ');
    string screen(sz h = 24, sz w = 80, char backbrnd);  // 错
    string screen(sz h, sz w = 80, char backgrnd = ' ')  // 对
    
  • 默认实参只能省略尾部的实参;

    string window;
    window = screen();                // screen(24, 80, ' ')
    window = screen(66);              // screen(66, 80, ' ')
    window = screen(66, 256);         // screen(66, 256, ' ')
    window = screen(66, 256, '#');    // screen(66, 256, '#')
    window = screen(, , '?);          // 错
    window = screen('?');             // screen('?', 256, ' ');
    
  • 可以多次声明同一个函数;

    string screen(sz, sz, char = ' ');
    string screen(sz, sz, char = '?');   // 错 重复声明
    string screen(sz = 24, sz = 80, char);
    

2. 内联函数和constexpr函数

  • 内联函数inline可以避免函数调用的开销,可以让编译器在调用点内联地展开该函数;

  • 内联函数适合优化函数规模小但需要频繁调用的函数;

    inline const string& shorterString(const string &s1, const string &s2){
        return s1.size() <= s2.size() ? s1 : s2;
    }
    cout << shorterString(s1, s2) << endl;
    
    // 等价于调用时直接在调用点展开该函数
    cout << (s1.size() <= s2.size() ? s1 : s2) << endl;
    
  • 内联函数一般都在头文件中定义;

  • “常量表达式函数”指能作用于常量表达式的函数;

  • 一个常量表达式必须满足下面两个条件:
    i. 整个函数体中除了using、typedef、static_assert以外,有且只有一条return语句;
    ii. 函数的返回值和所有形参都必须是字面值类型;

    constexpr int new_sz(){return 42;};  // 常量表达式函数
    constexpr int foo = new_sz();        // foo是常量表达式
    
  • constexpr函数会被隐式的指定为内联函数;

  • constexpr函数返回类型并不一定为常量表达式:

    constexpr int display(int x){
        return x;
    }
    int main(){
        int x = 1;
        int a[display(x)];    // 错误  此时函数返回的不是常量表达式
        int a[display(1)];    // 对    此时函数返回的是常量表达式
        return 0;
    }
    

3. 调试帮助

  • assert预处理宏:assert(exp);打开调试状态,通常用来检查不能发生的条件;
    i. 如果exp表达式为假,assert输出信息并终止程序; 反之assert什么也不做;
    ii. #include < cassert>

    // 假设要求输入的word长度必须要大于阈值  那么就可以用这句话来判断
    assert(word.size() > threshold);
    
  • NDEBUG预处理变量:通常和assert联用,关闭调试状态,跳过assert语句,放在#include < cassert>前面;

    #define NDEBUG
    #include <cassert>
    using namespace std;
    
    int main(){
        int x = 0;
        assert(x);  // 关闭NDEBUG 输出 expression: x     打开 跳过这句话
        return 0;
    }
    
  • NDEBUG除了和assert联用外,还可以单独使用编写调试代码;
    _ func _ :输出当前函数名称
    _ FILE _ :输出当前文件名称
    _ LINE _ :输出当前行号
    _ DATE _ :输出文件编译日期
    _ TIME _ :输出文件编译时间

    void print(){
        #ifndef NDEBUG
            cerr << __func__ << "..." << endl;
        #endif
    }
    

六、重载函数匹配

  • 重载函数匹配三个步骤:1、寻找候选函数;2、寻找可行函数;3、寻找最佳匹配
    候选函数:所有重载函数集;
    可行函数:可以被当前这组实参调用的函数(可以隐式转换);
    最佳匹配:形参和实参类型越接近,匹配的越好;

    void f();  // 1
    void f(int);  // 2
    void f(int, int);  // 3
    void f(double, double = 3.14);  // 4
    f(5.6);  // 候选:1234  可行:24  最佳:4
    f(42, 2.56);  // 候选:1234  可行:34  最佳:没有最佳 34匹配度一样  报错
    
  • 匹配分级,可以分为5个等级:
    i. 精准匹配;
    实参和形参类型完全相同;
    实参从数组类型或函数类型转换为对应的指针类型;
    向实参添加顶层const或者从实参删除顶层const;
    ii. 通过const转换实现的匹配;
    iii. 通过类型提升实现的匹配(bool、char、short、int、long);
    iv. 通过算术类型转换或指针转换实现的匹配;
    v. 通过类类型转换实现的匹配;

    void ff(int);
    void ff(short);
    ff('a');  // char->int->3  char->short->4  匹配ff(int)
    
    void mainp(long);
    void mainp(float);
    mainp(3.14);  // double->long double->float都是4  级别一样 错误
    
    Record lookup(Account&);
    Record lookup(const Account&);
    const Account a;
    Account b;
    lookup(a);  // 可行=最佳=lookup(const Account&);
    lookup(b);  // 两个都可行  但是最佳lookup(Account&);
    

七、函数指针

  • 函数指针:依然是一个指针,指向的是一个函数,而非一个对象;
    声明函数指针:

    bool lengthCompare(const string&, const string&);
    // 定义一个指针pf,指向一个函数 函数返回类型是bool,形参是两个const string引用
    bool (*pf)(const string&, const string&); 
    // 定义一个函数 函数名是pf,返回类型是bool*,形参是两个const string引用
    bool *pf(const string&, const string&); 
    

    使用函数指针:

    // 定义一个指针pf,指向一个函数 函数返回类型是bool,形参是两个const string引用
    bool (*pf)(const string&, const string&); 
    // pf指向lengthCompare函数
    pf = lengthCompare;   // 等价 函数名退化为函数指针
    pf = &lengthCompare;  
    // 通过函数指针调用该函数
    bool flag1 = pf("hello", "goodbye");  // 等价
    bool flag2 = (*pf)("hello", "goodbye");  
    
  • 指针在指向函数的时候不存在任何的转换规则,返回类型和形参类型必须完全一致;不过可以指向0,即指针不指向任何函数;

    bool lengthCompare(const string&, const string&);
    string::size_type sumLength(const string&, const string&);
    bool cstringCompare(const char*, const char*);
    
    int main(){
        // 定义一个指针pf,指向一个函数 函数返回类型是bool,形参是两个const string引用
        bool (*pf)(const string&, const string&); 
        pf = 0;   // pf不指向任何函数
        pf = sumLength;  // 返回类型不匹配
        pf = cstringCompare;  // 形参类型不匹配
        pf = lengthCompare;  // 对
        return 0;
    }
    
  • 重载函数的指针也一样,在指针指向函数的时候,返回值类型和形参类型必须完全匹配;

    void ff(int*);
    void ff(unsigned int);
    
    int main(){
        void (*pf1)(unsigned int) = ff;  // 对 指向ff(unsigned int)函数
        void (*pf2)(int) = ff;           // 错 形参不匹配
        double (*pf3)(int*) = ff;        // 错 返回值类型不匹配
        return 0;
    }
    
  • 函数形参是函数指针类型
    i. 函数指针还可以作为另一个函数的形参,形参可以是函数本身(会自动转换为指向函数的指针),也可以是指向函数的指针

    bool lengthCompare(const string&, const string&);
    // 声明 两个等价
    void useBigger(const string &s1, const string &s2, bool pf(const string&, const string&));
    void useBigger(const string &s1, const string &s2, bool (*pf)(const string&, const string&));
    
    int main(){
        // 调用
        string s1 = "hello", s2 = "goodbye";
        useBigger(s1, s2, lengthCompare);
        return 0;
    }
    

    ii. 可以用类型别名typedef和decltype简化代码

    typedef bool Func(const string&, const string&);
    typedef decltype(lengthCompare) Func2;  // Func = Func2 都是函数类型
    typedef vool (*FuncP)(const string&, const string&);
    typedef decltype(lengthCompare) *Func2P;  // FuncP = Func2P  都是函数指针类型
    void useBigger(const string&, const string&, Func);
    void useBigger(const string&, const string&, FuncP);  // 等价 形参可以是函数类型也可以是函数指针类型
    
  • 返回值是函数指针类型
    必须返回指向函数的指针,不能返回函数本身

    using F = int(int*, int);
    using PF = int(*)(int*, int);
    PF f1(int);  // 正确 可以返回函数指针类型
    F f1(int);   // 错误 不能直接返回函数类型
    F *f1(int);  // 正确 返回函数指针类型
    

    可以使用尾置返回类型

    auto f1(int) -> int(*)(int*, int);
    

    可以使用类型声明decltype指明返回类型

    string::size_type sumLength(const string&, const string&);
    // 定义genFcn函数的返回类型是指向sumLength的指针类型
    decltype(sunLength) *genFcn(const string*);
    

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值