• 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)。

  • 相关阅读:
    毕业考试
    相机标定
    深度相机
    怎么选工业相机
    Python Socket 编程
    Canoe 过滤Trace中报文
    Canoe 使用Replay Block CAN回放报文
    安装Jupyter Notebook
    Altium Designer PCB 画板框
    EMQX 取消匿名登录和添加、删除用户
  • 原文地址:https://www.cnblogs.com/wzhe/p/6009741.html
Copyright © 2020-2023  润新知