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

时间: 2024-10-05 23:27:07

c++ primer 5th 笔记:第六章的相关文章

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

第九章:顺序容器 笔记 1. 一个容器就是一些特定类型对象的集合.顺序容器(sequential container)为程序员提供了控制元素存储和访问顺序的能力. 2. 容器分为有序容器和无序容器:访问分为顺序访问和随机访问. 3. forward_list和array是新C++标准增加的类型.与内置数组相比,array是一种更安全.更容易使用的数组类型.与内置数组类似,array对象的大小是固定的. 4. 通常,使用vector是最好的选择,除非你有很好的理由选择其他容器. 5. 容器操作中类

C++PRIMER 阅读笔记 第三章

本章主要介绍 string vector 和 bitset, 不能贪多,现在本文主要介绍 string 与 vector 头文件中最好不要使用namespace std, 因为头文件会直接被预处理器放置到C中 std::string 的构造方式: string s1; string s2(s1); string s3("value");string s4(n,'c');           第三种构造方式可有"\0"? cout 与 cin 可以直接操作string

MySQL cookbook读书笔记第六章

1,修改MySQL中的日期的格式 在显示一个日期值时,如果没有特别指定,MySQL按照ISO格式显示日期即(CCYY-MM0DD).如果不希望按照MySQL的默认格式输出时间和日期值,可以使用date_format()或者time_format()函数按照用户期望的格式重写日期或者时间值 date_format(),time_format()和str_to_date()三个函数都接受格式化串作为参数: 2,设置客户端时区 如果客户端和服务器处在不同的时区,那么客户端在服务器上保存timestam

《Linux Shell脚本攻略》 笔记 第六章:打包压缩

<Linux Shell脚本攻略> 笔记 第六章:打包压缩 //1.打包.解包 [[email protected] program_test]# tar -cf output.tar 11.txt 22.txt 33.txt [[email protected] program_test]# tar -xf output.tar -C ./tar-file/  //-C指定要提取到哪个路径? //列举出归档文件中的内容 [[email protected] program_test]# ta

构建之法阅读笔记05-第六章

阅读笔记 第六章:敏捷流程 第六章敏捷流程主要介绍了什么是敏捷流程及其原则,还有什么时候可以选择敏捷的开发方法,什么时候选择其他方法. 敏捷的流程是指一系列价值观和方法论的集合.介绍了一些敏捷开发原则,比如,经常发布可用的软件,业务人员和开发人员在项目开发过程中应该每天共同工作,面对面的交流始终是最有效的沟通方式,不断关注技术和设计,保持简明,团队要学会自我管理,时时总结如何提高团队效率,并付诸行动. 敏捷流程的方法论---Scrum方法论.首先第一步需要找出完成产品需要做的事情,然后决定当前的

《Microsoft Sql server 2008 Internals》读书笔记--第六章Indexes:Internals and Management(1)

<Microsoft Sql server 2008 Internals>索引目录: <Microsoft Sql server 2008 Internals>读书笔记--目录索引 在第五章主要学习了table的内部存储结构,第七章<Special storage>中将继续深入学习存储机制,那将是本书最难理解的一章.第六章主要介绍index的基础知识,第八章是<query Optimizer>,下面我们先来看看第六章:Indexes:Internals an

深入理解 C 指针阅读笔记 -- 第六章

Chapter6.h #ifndef __CHAPTER_6_ #define __CHAPTER_6_ /*<深入理解C指针>学习笔记 -- 第六章*/ typedef struct __person { char* name; char* title; unsigned int age; }person; /*结构体内存的释放问题*/ void __struct_memory_test(); #endif Chapter6.cpp #include "Chapter6.h&quo

APUE读书笔记-第六章 系统数据文件和信息

昨天看完了,今天来看看第六章.感觉第六章的内容不是非常重要.简单看看吧 6.2 口令文件 口令文件其实就是/etc文件夹下的passwd文件,但处于安全性的考虑,我们无法直接读取它.就是通过直接限制权限的方式对其进行保护,passwd文件具体权限如下: -rw-r--r-- 1 root root 可以看到只有root用户具有读写权限,与root同组的用户与其他用户仅具有读权限. 不过为了解决以上问题,Linux中给出了一系列数据结构与函数帮助我们操纵口令文件,首先是关键数据结构,定义位于/in

《从0到1》笔记 第六章 成功不是中彩票

第六章 成功不是中彩票----长期规划及其重要 初创公司的创立者只有在对公司没有确切规划的时候才会主动卖掉公司.2006年7月,当雅虎公司出价10亿要收购Facebook时,我认为如果是我们,至少会考虑一下.但是马克.扎克伯格在会议上宣布:“好了,伙计们,这个会议只是走个程序,10分钟也不用,我们显然不会把Facebook卖掉”马克清楚他能够领导公司开创出怎样的未来,而雅虎不清楚. 初创企业是你可以明确掌握最大努力的机会.在这里,你不只拥有自己生命的代理权,还拥有这个世界上某个重要角落的代理权.