c++ primer 6th 函数

一、函数基础
  1. 即使两个形参的类型一样,也必须把两个类型都写出来。

  2. 局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<map>
    #include<bits/stdc++.h>
    using namespace std;
    size_t count_calls()
    {
        static size_t ctr = 0;
        return ++ctr;
    }
    int main()
    {
        for(size_t i = 0;i != 10; ++i)
        {
            cout << count_calls() << endl;
        }
        return 0;
    }
    

    如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0。

二、参数传递
  1. 使用引用避免拷贝

    如果函数无需改变引用形参的值,最好将其声明为常量引用。

  2. const形参和实参

    当用实参初始化形参时会忽略掉顶层const。

    void fcn(const int i);
    void fcn(int i);//错误重复定义了fcn(int)
    
  3. 数组引用实参

    #include<stdio.h>
    #include<iostream>
    #include<string.h>
    #include<iostream>
    using namespace std;
    void print(int (&arr)[10])
    {
            for(auto elem : arr)
            {
                    cout << elem << endl;
            }
    }
    int main()
    {
            int a[10] = {1,2,3,4,5};
        	int i = 0;
        	int j[2] = {0,1};
        	print(&i);//不是10个整数的数组
        	print(j);//不是10个整数的数组
            print(a);    
    }
    
    &arr两端的括号必不可少
    f(int &arr[10])//将arr声明成了引用的数组
    f(int (&arr)[10])//arr是具有10个整数的整数数组的引用
    指针同理
    
  4. 含有可变形参的函数

    #include<stdio.h>
    #include<iostream>
    #include<string.h>
    #include<iostream>
    using namespace std;
    void error_msg(initializer_list<string> il)
    {
            for(auto beg = il.begin();beg != il.end();++beg)
            {
                    cout << *beg << " ";
            }
            cout << endl;
    }
    int main()
    {
            int a[10] = {1,2,3,4,5};
            error_msg({"hello","hello","hello"});       
    }
    
    initializer_list lst; 默认初始化:T类型元素的空列表
    initializer_list lst{a,b,c...}; lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const
    lst2(lst) 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素
    lst.size() 列表中的元素数量
    lst.begin() 返回指向lst中首元素的指针
    lst.end() 返回指向lst中首元素的指针
    lst2 = lst 原始列表和副本共享元素
三、返回类型和return语句
  1. 值是如何被返回的

    string make_plural(size_t ctr,const string &word,const string &ending)
    {
    	return (ctr > 1)?word + ending : word;
    }
    

    该函数的返回类型时string,意味着返回值将被拷贝到调用点。因此,该函数将返回word的副本或者一个未命名的临时string对象,该对象的内容是word和ending的和。

    const string &shorterString(const string &s1,const string &s2)
    {
    	return s1.size() <= s2.size() ? s1 : s2;
    }
    

    其中形参和返回类型都是const string的引用,不管是调用函数还有返回结果都不会真正拷贝string对象

  2. 引用返回左值

    #include<stdio.h>
    #include<iostream>
    #include<string.h>
    #include<iostream>
    using namespace std;
    char &get_val(string &str,string::size_type ix)
    {
            return str[ix];
    }
    int main()
    {
            string s("a value");
            cout << s << endl;
            get_val(s,0) = 'A';
            cout << s << endl;
            return 0;
            //shorterString("hi","bye") = "X"; //错误:返回值是个常量
    }
    
  3. 主函数main的返回值

    如果没有return 0;编译器将隐式地插入一条返回0的return语句

  4. 不要返回局部对象的引用或指针

    #include<stdio.h>
    #include<iostream>
    #include<string.h>
    #include<iostream>
    #include<cstdlib>
    using namespace std;
    const string &manip()
    {
            return "hello";
    }
    int main()
    {
            string a = manip();
            cout << a << endl;//无法运行
    }
    

    当函数结束时临时对象占用的空间也就随之释放掉了,所以两条return语句都指向了不再可用的内存空间。

  5. 使用尾置返回类型

    //func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
    auto func(int i) -> int(*) [10]
    {        
    }
    
    
  6. 使用decltype

    int odd[] = {1,3,5,7,9};
    int even[] = {0,2,4,6,8};
    decltype(odd) *arrPtr(int i)
    {
    	return (i % 2)? &odd : & even
    }
    
    

    decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示arrPtr返回指针还必须在函数声明时加一个*符号。

四、函数重载
  1. 判断两个形参类型是否相异

    Record lookup(const Account &acct);
    Record lookup(const Account&); //省略了形参的名字
    typedef Phone Telno;
    Record lookup(const Phone&);
    Record lookup(cosnt Telno&);//Telno和Phone的类型相同
    
    

    main函数不能重载

  2. 重载和const形参

    顶层const不影响传入函数的对象。

    Record lookup(Phone);
    Record lookup(const Phone); //重复声明
    
    Record lookup(Phone*)
    Record lookup(Phone* const);//重复声明 
    

    非常量可以转换成const

    如果形参时是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:

    Record lookup(Account&);
    Record lookup(const Account&); //新函数,作用与常量引用
    
    Record lookup(Account*)
    Record lookup(const Account*);//新函数,作用域指向常量的指针
    

    但是当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。

  3. const_cast和重载

    const string &shorterString(const string &s1,const string &s2)
    {
    	return s1.size() <= s2.size()?s1:s2;
    }
    string &shorterString(string &s1,string &s2)
    {
    	auto &r = shorterString(const_cast<const string&>(s1),const_cast<const string&>(s2));
    	return const_cast<string&>(r);
    }
    

五、特殊用途语言特性

  1. 默认实参

    typedef string::size_type sz;
    string screen(sz ht = 24,sz wid = 80,char backgrnd = ' ');
    string window;
    window = screen(, , '?');//错误:只能省略尾部的实参
    window = screen('?') //调用screen('?',80,' ')
    
  2. 默认实参声明

    string screen(sz,sz,char = ' ');
    //我们不能修改一个已经存在的默认值
    string screen(sz,sz,char = '*'); //错误:重复声明
    string screen(sz = 24,sz = 80,char); //正确
    
    
  3. 默认实参初始值

    sz wd = 80;
    char def = ' ';
    sz ht();
    string screen(sz = ht(),sz = wd,char = def);
    string window = screen();//调用screen(ht(),80,' ')
    
    //调用时:
    void f2()
    {
    	def = "*";//改变默认实参的值
    	sz wd = 100;//隐藏了
    	window = screen();//调用screen(ht(),80,'*');
    }
    
  4. 内联函数

    内联函数可避免函数调用的开销

    内联机制用于优化规模较小、流程直接、频繁调用的函数。

  5. constexpr函数

    能用于常量表达式的函数,函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。

    #include <stdio.h>
    #include <iostream>
    #include <string.h>
    #include <vector>
    #include <algorithm>
    using namespace std;
    constexpr int add()
    {
            return 5;
    }
    constexpr size_t scale(size_t cnt)
    {
            return add() * cnt;
    }
    int main()
    {
            cout << scale(2) << endl;
    }
    

    把内联函数和constexpr函数放在头文件内

  6. 调试帮助

    assert是一种预处理宏,定义在cassert头文件中,所以使用时无需std::,也不需要提供using声明。

    assert(expr);
    

    NDEBUG预处理变量

    assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NODEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。

五、函数匹配

候选函数:函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数成为候选函数。候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见。

可行函数:考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,而是每个实参的类型与对应的 形参类型相同,或者能转换成形参的类型。

含有多个形参的函数匹配

f(int,int);
f(double,double);

f(42,3.5);会出现二义性。

六、函数指针
  1. 使用函数指针

    #include <stdio.h>
    #include <iostream>
    #include <string.h>
    #include <vector>
    #include <algorithm>
    #include <cassert>
    using namespace std;
    bool lengthCompare(const string &s1, const string &s2)
    {
            return s1.size() > s2.size();
    }
    bool (*pf)(const string &, const string &);
    
    int main()
    {
            pf = lengthCompare;
            cout << pf("hello", "goodbye") << endl;
            cout << (*pf)("hello", "goodbye") << endl;
            cout << lengthCompare("hello", "goodbye") << endl;
            pf = 0; // pf不指向任何函数
            string :: size_type sumLength(const string&,const string &); //返回类型不匹配
            pf = sumLength;
    }
    
  2. 重载函数的指针

    void ff(int*);
    void ff(unsigned int);
    void (*pf)(unsigned int) = ff;//pf1指向ff(unsigned)
    void (*pf2)(int) = ff;//没有和一个ff与该形参列表匹配
    double (*pf3)(int *) = ff;//ff和pf3的返回类型不匹配
    
  3. 函数指针形参

    //第三个形参是函数类型,它会自动地转换成指向函数的指针
    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 &));
    //自动将函数lengthCompare转换成指向该函数的指针
    useBigger(s1,s2,lengthCompare);
    
    //Func和Func2是函数类型
    typedef bool Func(const  string &,const string&);
    typedef decltype(lengthCompare) Func2;
    //FuncP和FuncP2是指向函数的指针
    typedef bool(*FuncP)(const  string &,const string&);
    typedef decltype(lengthCompare) *Func2;
    
    void useBigger(const string&, const string& ,Func);//自动转换成指针
    void useBigger(const string&,const string&,FuncP2);
    
  4. 返回指向函数的指针

    using F = int(int*,int);//函数类型不是指针
    using PF = int(*)(int*,int);//指针类型
    
    PF f1(int);
    F f1(int);//F是函数类型,f1不能返回一个函数
    F *f1(int);
    
    int (*f1(int))(int *,int);
    auto f1(int)-> int (*)(int*,int);
    
  5. 将auto和decltype用于函数指针类型

    decltype(sumLength) *getFcn(const string &);//切记decltype返回函数类型了不返回指针类型
    
posted @ 2019-08-01 17:42  冷眼旁观你的泪  阅读(166)  评论(0编辑  收藏  举报