c++ primer 5th 笔记:第六章

第六章:函数

  笔记

    1. 通过调用运算符(call operator)来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是一个函数或指向函数的指针

    2. 在c++语言中,名字有作用域,对象有生命周期。

       a. 名字的作用域是程序文本的一部分,名字在其中可见。

       b. 对象的生命周期是程序执行过程中该对象存在的一段时间。

    3. 函数体是一个语句块,块构成一个新的作用域。

    4. 形参和函数体内部定义的变量统称为局部变量(local variable)。

    5. 局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。

    6. 函数只能定义一次,但可以声明多次。函数的声明也称作函数原型

    7. 每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。

    8. 在c++语言中,建议使用引用类型的形参替代指针

    9. 使用引用避免拷贝。拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作

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

    // 不良设计:第一个形参的类型应该是const string&
    string::size_type find_char(string &s, char c,
                                             string::size_type &occurs);
    // 只能将find_char函数作用于string对象。

    find_char("Hello World", 'o', ctr);    // 错误
    // 尽量使用常量引用

    11. 可以使用非常量初始化一个底层cosnt对象,但是反过来不行。

    12. 数组的两个特殊性质对我们定义和使用作用在数组上的函数由影响:a. 不允许拷贝数组;b. 使用数组时通常会将其转换成指针。

    13. c++语言允许将变量定义成数组的引用,形参也可以是数组的应用。

    // 正确:形参是数组的应用,维度是类型的一部分
    void print(int (&arr)[10])   // &arr两端的括号必不可少
    {
        for (auto elem : arr)
            cout << elem << endl;
    }

    14. 传递多维数组。数组的第二维的大小都是数组类型的一部分,不能省略

    // matrix 指向数组的首元素,该数组的元素是由10个整数构成的数组
    void print(int (*matrix)[10], int rowSize)      { /*  ...  */}
    //  再次强调, *matrix两端的括号必不可少
    //  int *matrix[10];        10个指针构成的数组
    //  int (*matrix)[10];      指向含有10个整数的数组的指针

    // 等价定义
    void print(int matrix[][10], int rowSize) { /*  ...  */}
    // matrix 的声明看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针

    15. main:处理命令行选项:

    int main(int argc, char *argv[])
    // 第二个形参argv是一个一维数组,它的元素是指向C风格字符串的指针

    // 等价定义
    int main(int argc, char **argv)    { ... }

    16. C++新标准提供了两种主要方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型。和vector不一样的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。

    17. return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。

      返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果

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

    // 严重错误:这个函数试图返回局部对象的引用
    const string &manip()
    {
        string ret;
        //  以某种方式改变一下ret
        if (!ret.empty())
            return ret;            //  错误:返回局部对象的引用
        else
            return "Empty";   //  错误:"Empty"是一个局部临时量
    }
    // 上面的两条return语句都将返回未定义的值,也就是,试图使用manip函数的返回值将
    //  发生未定义的行为。对于第一条return语句来说,显然它返回的是局部对象的引用。在
    // 第二条return语句中,字符串字面值转换成一个局部临时string对象,对于manip来说
    // 该对象和ret一样都是局部的。当函数结束时临时对象占用的空间也就随之释放掉了,所
    // 以两条return语句都指向了不再可用的内存空间。

    19. 引用返回左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。

    // 我们能为返回类型是非常量引用的函数
    char &get_val(string &str, string::size_type ix)
    {
        return str[ix];
    }

    int main()
    {
        string s("a value");
        cout << s << endl;           // 输出a value
        get_val(s, 0) = 'A';            // 将s[0]的值改为A
        cout << s << endl;
        return 0;
    }
    // 如果返回类型是常量引用,我们不能给调用的结果赋值

    20. C++新标准规定,函数可以返回花括号包围的值得列表,类似于其他返回结果,此处的列表也用来表示返回的临时量进行初始化。

    vector<string> process()
    {
        // ...
        // expected 和 actual是string对象
        if (expected.empty())
            return {};
        else if (expected == actual)
            return { "functionX", "okay"};
        else
        return { "functionX", expected, actual };
    }
    // 如果函数返回的是类类型,由类本身定义初始化值如何定义

    21. 在c++11新标准中可以使用尾置返回类型。

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

    22. 使用decltype

      注意:decltype并不负责把数组类型转换成对应的指针,所以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;
    }

    23. 函数匹配(function matching)是指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫做函数确定(overload resolution)

    24. 一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值

    25. 当设计含有默认实参的函数时,其中一项任务是合理设置形参的顺序,尽量让不怎么使用的默认值的形参出现在前面,而让那些经常使用默认值的形参出现在后面。

    26. 内联函数可以避免函数调用的开销。将函数指定为内联函数,通常就是将它们在每个调用点上"内联地"展开。另外,内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。

    27. 把内联函数和constexpr函数放在头文件内,毕竟,编译器要想展开函数仅有函数的声明是不够的,还需要函数的定义。

    28. 调试帮助:assert预处理宏。所谓预处理宏其实就是一个预处理变量。 assert的行为依赖于一个名为NDEBUG的预处理变量的状态。我们可以使用一个#define语句定义NDEBUG,从而关闭调试状态。

  重点知识点总结:

    函数重载:

      如果同一作用域的几个函数名字相同但形参列表不同,我们称之为重载(overloaded)函数

      一. 重载和const形参:顶层const不影响传入函数对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来:

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

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

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

    // 对于接受引用或指针的函数来说,对象是常量还是非常量对应的形参不同
    // 定义了4个独立的重载函数
    Record lookup(Account&);            // 函数作用于Account的引用
    Record lookup(const Account&);   // 新函数,作用于常量引用

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

    分析:上面的例子中,编译器可以通过实参是否是常量来推断应该调用哪个函数。因为const不能转换成其他类型,所以我们只能把const对象(或指向const的指针)传递给cosnt形参。相反的,因为非常量可以转换成const,所以上面的四个函数都能作用于非常量或者指向非常量的指针。不过,当我们传递非常量对象或者非常量对象的指针时,编译器会优先选择非常量版本的函数

      二. const_cast和重载

      const_cast 在重载函数的情景中最有用。例如:

    // 比较两个string对象的长度,返回较短的那个引用
    const string &shorterString(const string &s1, const string &s2)
    {
        return s1.size() <= s2.size() ? s1 : s2;
    }
    // 上面的返回结果仍然是const stirng的引用,因此我们需要一种新的shorterString函数
    // ,当它的实参不是常量时,得到的结果是一个普通的引用,使用const_cast可以做到这一点
    string &shorterString(string &s1, string &s2)
    {
        auto &r = shorterString(const_cast<const string&>(s1),
                                                const_cast<const string&>(s2));
        return const_cast<string&>(r);
    }
    // 这个引用事实上绑定在了某个初始的非常量的实参上,所以,我们可以再将其转换回一个
    // 普通的stirng&,这显然是安全的

       三. constexpr函数

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

        const int new_sz()  { return 42; }
        const int foo = new_sz();    // 正确:foo是一个常量表达式

          编译器把对constexpr函数的调用替换成其结果值。

      四.函数匹配(p217)(待写)

      五.函数指针(p221)(待写)

  术语

    调用运算符(call operator)、自动对象(automatic object)、局部静态对象(local static object)、

    函数原型(function prototype)、引用传递(passed by reference)、值传递(passed by value)、

    递归循环(recursion loop)、最佳匹配(best match)、预处理宏(preprocessor marco)、可行函数(viable function)、

    二义性调用(ambiguous call)。

posted @ 2016-10-29 10:56  西伯利亚狼zz  阅读(352)  评论(0编辑  收藏  举报