C++ Primer Plus第6版18个重点笔记

下面是我看《C++ Primer Plus》第6版这本书后所做的笔记,作为备忘录便于以后复习。

笔记部分

  1. C++的const比C语言#define更好的原因?

    首先,它能够明确指定类型,有类型检查功能。
    其次,可以使用C++的作用域规则将定义限制在特定的函数或文件中。
    第三,可以将const用于更复杂的类型,比如数组和结构。

    C语言中也有const,其与C++中const的区别是:一是作用域规则不同;另一个是,在C++中可以用const值来声明数组长度。

  2. 不能简单地将整数赋给指针,如下所示:
    int *ptr;
    ptr = 0xB8000000;  // type mismatch

    在这里,左边是指向int的指针,因此可以把它赋给地址,但右边是一个整数。您可能知道,0xB8000000是老式计算机系统中视频内存的组合段偏移地址,但这条语句并没有告诉程序,这个数字就是一个地址。在C99标准发布之前,C语言允许这样赋值。但C++在类型一致方面的要求更严格,编译器将显示一条错误消息,通告类型不匹配。要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:

    int *ptr;
    ptr = (int *) 0xB8000000;  // type now match

    这样,赋值语句的两边都是整数的地址,因此这样赋值有效。
    注意,pt是int值的地址并不意味着pt本身的类型是int。例如,在有些平台中,int类型是个2字节值,而地址是个4字节值。

  3. 为什么说前缀++/--比后缀++/--的效率高?

    对于内置类型和当代的编译器而言,这看似不是什么问题。然而,C++允许您针对类定义这些运算符,在这种情况下,用户这样定义前缀函数:将值加1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。因此,对于类而言,前缀版本的效率比后缀版本高。
    总之,对于内置类型,采用哪种格式不会有差别,但对于用户定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高。

  4. 逗号运算符
    到目前为止,逗号运算符最常见的用途是将两个或更多的表达式放到一个for循环表达式中。逗号运算符的特性有下面几个:
    • 它确保先计算第一个表达式,然后计算第二个表达式;
      i = 20, j = 2 * i; // i set to 20, then j set to 40
    • 逗号表达式的值是第二部分的值。例如,上面表达式的值为40。
    • 在所有运算符中,逗号运算符的优先级是最低的。例如:
      cats = 17, 240;
      被解释我:
      (cats = 17), 240;
      也就是说,将cats设置为17,后面的240不起作用。如果是cats = (17, 240);那么cats就是240了。
  5. 有用的字符函数库cctype
    从C语言继承而来,老式格式是ctype.h,常用的有:
  6. 快排中中值的选取:
    将元素每5个一组,分别取中值。在n/5个中值里面找到中值,作为partition的pivot。
    为什么*不每3个一组?保证pivot左边右边至少3n/10个元素,这样最差O(n)。
  7. C++存储方案:C++三种,C++11四种
    这些方案的区别就在于数据保留在内存中的时间。

    自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
    静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
    线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。本书不探讨并行编程。
    动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。

  8. 自己写string类注意事项:
    • 关于记录已有对象数object_count
      不要在类声明(即头文件)中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象组成部分。请注意,初始化语句指出了类型int(不可缺少),并使用了作用域运算符,但没有使用关键字static。
      初始化是在方法文件中,而不是在类声明文件中进行的,这是因为类声明位于头文件中,可能被包含多次,这样若在头文件中进行初始化静态成员,将出现多个初始化语句副本,从而引发错误。
      对于不能在类声明中初始化静态成员的一种例外情况是:静态数据成员为整型或枚举型const。即如果静态数据成员是整型或枚举型,则可以在类声明中初始化。
    • 注意重写拷贝构造函数和赋值运算符,其中赋值运算符的原型为:
      Class_name & Class_name::operator=(const Class_name &);
      它接受并返回一个指向类对象的引用,目的应该是方便串联使用。
  9. 何时调用拷贝(复制)构造函数:
    StringBad ditto (motto);
    StringBad metoo = motto;
    StringBad also = StringBad(motto);
    StringBad * pStringBad = new StringBad (motto);

    以上4中方式都将调用:StringBad(const StringBad &)

    • 其中中间两种声明可能会使用复制构造函数直接创建metoo和also对象,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,这取决于具体的实现。最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pStringBad指针。
    • 每当程序生成了对象副本时,编译器都将使用复制构造函数。具体的说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。记住,按值传递意味着创建原始变量的一个副本。
    • 编译器生成临时对象时,也将使用复制构造函数。例如,将3个Vector对象相加时,编译器可能生成临时的Vector对象来保存中间的结果。
    • 另外,String sailor = sports;等价于String sailor = (String)sports;因此调用的是拷贝构造函数
  10. 何时调用赋值运算符:
    • 已有的对象赋给另一个对象时,将调用重载的赋值运算符。
    • 初始化对象时,并不一定会使用赋值操作符:
      StringBad metoo=knot;   // use copy constructor, possibly assignment, too

      这里,metoo是一个新创建的对象,被初始化为knot的值,因此使用赋值构造函数。不过,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值操作符将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=操作符时也可能调用赋值构造函数。

    与复制构造函数相似,赋值运算符的隐式实现也对成员进行逐个复制。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。

  11. 赋值运算符和拷贝构造函数在实现上的区别:
    • 由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。
    • 函数应当避免将对象赋给自身;否则给对象重新赋值前,释放内存操作可能删除对象的内容。
    • 函数返回一个指向调用对象的引用(方便串联使用),而拷贝构造函数没有返回值。

    下面的代码说明了如何为StringBad类编写赋值操作符:

    StringBad & StringBad::operator=(const StringBad & st)
    {
     if(this == & st)
        return * this;
     delete [] str;
     len = st.len;
     str = new char [len + 1];
     strcpy(str,st.str);
     return *this;
    }

    代码首先检查自我复制,这是通过查看赋值操作符右边的地址(&s)是否与接收对象(this)的地址相同来完成的,如果相同,程序将返回*this,然后结束。
    如果不同,释放str指向的内存,这是因为稍后将把一个新字符串的地址赋给str。如果不首先使用delete操作符,则上述字符串将保留在内存中。由于程序程序不再包含指向字符串的指针,一次这些内存被浪费掉。
    接下来的操作与复制构造函数相似,即为新字符串分配足够的内存空间,然后复制字符串。
    赋值操作并不创建新的对象,因此不需要调整静态数据成员num_strings的值。

  12. 重载运算符最好声明为友元
    比如将比较函数作为友元,有助于将String对象与常规的C字符串进行比较。例如,假设answer是String对象,则下面的代码:
    if("love" == answer)
    将被转换为:
    if(operator == ("love", answer))
    然后,编译器将使用某个构造函数将代码转换为:
    if(operator == (String("love"), answer))
    这与原型是相匹配的。
  13. 在重写string类时使用中括号访问字符时注意:
    (1)为什么重载的[]返回值是个char &而不是char?
    (2)为什么有两个重载[]的版本,另一个是const版本?

    解答(1):
    将返回类制声明为char &,便可以给特定元素陚值。例如,可以编写这样的代码:
    String means ("might");
    means [9] = ‘ r‘;
    第二条语句将被转换为一个重载运算符函数调用:
    means.operator[][0] = ‘r‘;
    这里将r陚给方法的返回值,而函数返回的是指向means.str[0]的引用,因此上述代码等同于下面的代码:
    means.str[0] = ‘r‘;
    代码的最后一行访问的是私有数据,但由于operator 是类的一个方法,因此能够修改数组的内容。 最终的结果是“might”被改为“right”。

    解答(2):
    假设有下面的常量对象:
    const String answer("futile");
    如果只有上述operator定义,则下面的代码将出错:
    cout << answer[1]; // compile-time error
    原因是answer是常量,而上述方法无法确保不修改数据(实际上,有时该方法的工作就是修改数据, 因此无法确保不修改数据)。
    但在重载时,C++将区分常量和非常量函数的特征标,因此可以提供另一个仅供const String对象使用 的 operator版本:
    // for use with const String objects
    const char & string::operator const {
    return str[i];
    }
    有了上述定义后,就可以读/写常规String对象了 :而对于const Siring对象,则只能读取其数据。

  14. 静态成员函数在类声明外定义实现时不能再加static关键字,与静态成员变量一样。
  15. 实现has-a关系的两种方法:
    • 组合(或包含)方式。这是我们通常采用的方法。
    • c++还有另一种实现has-a关系的途径—私有继承。使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员。这意味着基类方法将不会称为派生对象公有接口的一部分,但可以派生类的成员函数中使用它们。而使用公有继承,基类的公有方法将称为派生类的公有方法。简言之,派生类将继承基类的接口:这是is-a关系的一部分。使用私有继承,基类的公有方法将称为派生类的私有方法,即派生类不继承基类的接口。正如从被包含对象中看到的,这种不完全继承是has-a关系的一部分。
      使用私有继承,类将继承实现。例如,如果从String类派生出Student类,后者将有一个String类组件,可用于保存字符串。另外,Student方法可以使用String方法类访问String组件。

      包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。我们使用术语子对象来表示同继承或包含添加的对象。
      因此,私有继承提供的特性与包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系

    • 使用包含还是使用私有继承?
      由于既可以使用包含,也可以使用私有继承来建立has-a关系,那么应使用何种方式呢?大多数C++程序员倾向于使用包含。
      • 首先,它易于理解。类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。
      • 其次,继承会引起很多问题,尤其从多个基类继承时,可能必须处理很多问题,如包含同名方法的独立的基类或共亨祖先的独立基类。
        总之,使用包含不太可能遇到这样的麻烦。
      • 另外,包含能够包括多个同类的子对象。如果某个类需要3个string对象,可以使用包含声明3个独立的string成员。而继承则只能使用一个这样的对象(当对象都没有名称时,将难以区分)。

      然而,私有继承所提供的特性确实比包含多。例如,假设类包含保护成员(可以是数据成员,也可以是成员函数),则这样的成员在派生类中足可用的,但在继承层次结构外是不可用的。如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承得到的将是派生类,因此它能够访问保护成员。
      另—种需要使用私有继承的情况是需要重新定义虛函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将只能在类中使用,而不是公有的。

    • 通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义 虚函數,则应使用私有继承。
  16. 关于保护继承
    保护继承是私有继承的变体,保护继承在列出基类时使用关键字protected;
    class Student : protected std::string,
                    protected std::valarray<double>
    {
    ...
    }

    使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员,和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类的时,私有继承和保护继承
    之间的主要区别便呈现出来了。使用私有继承时,第三代将不能使用基类的接口,这是因为基类的共有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将编程呢个受保护的,因此第三代派生类可以使用它们。

    下表总结了公有、私有和保护继承。隐式向上转换意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象。

  17. 智能指针相关
    请参考:C++智能指针简单剖析,推荐必看。
  18. C++中的容器种类:
    • 序列容器(7个)

      • vector:提供了自动内存管理功能(采用了STL普遍的内存管理器allocator),可以动态改变对象长度,提供随机访问。在尾部添加和删除元素的时间是常数的,但在头部或中间就是线性时间。
      • deque:双端队列(double-ended queue),支持随机访问,与vector类似,主要区别在于,从deque对象的开始位置插入和删除元素的时间也是常数的,所以若多数操作发生在序列的起始和结尾处,则应考虑使用deque数据结构。为实现在deque两端执行插入和删除操作的时间为常数时间这一目的,deque对象的设计比vector更为复杂,因此,尽管二者都提供对元素的随机访问和在序列中部执行线性时间的插入和删除操作,但vector容器执行这些操作时速度更快些。
      • list:双向链表(是循环的)。目的是实现快速插入和删除。
      • forward_list(C++11):实现了单链表,不可反转。相比于list,forward_list更简单,更紧凑,但功能也更少。
      • queue:是一个适配器类。queue模板让底层类(默认是deque)展示典型的队列接口。queue模板的限制比deque更多,它不仅不允许随机访问队列元素,甚至不允许遍历队列。与队列相同,只能将元素添加到队尾、从队首删除元素、查看队首和队尾的值、检查元素数目和测试队列是否为空。
      • priority_queue:是另一个适配器类,支持的操作与queue相同。
        priority_queue模板类是另一个适配器类,它支持的操作与queue相同。两者之间的主要区别在于,在priority_queue中,最大的元素被移到对首。内部区别在于,默认的底层类是vector。可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造函数参数:
        priority_queue<int> pq1;                     // default version
        priority_queue<int> pg2(greater<int>);       // use greater<int> to order
        greater<>函数是一个预定义的函数对象。
      • stack:与queue相似,stack也是一个适配器类,它给底层类(默认情况下为vector)提供了典型的栈接口。
    • 关联容器
      • 4种有序关联容器:set、multiset、map和multimap,底层基于树结构
      • C++11又增加了4种无序关联容器:unordered_set、unordered_multiset、unordered_map和unordered_multimap,底层基于hash。
时间: 2024-10-16 15:56:04

C++ Primer Plus第6版18个重点笔记的相关文章

C++ Primer Plus 第六版 第16章 string类和标准模板库

1.string实际上是模板具体化basic_string<char> 的一个typedef,有默认参数,所以省略了初始化参数 2.size_type是一个依赖于实现的整形 string将string::npos定义为字符串的最大长度 3.string类的构造函数P656 4.对于c-风格字符串,3种输入方法:cin>>   cin.getline(),cin.get 对于string   ,2种输入方法:cin>>,getline(cin,string对象) 5.st

c++ primer(第五版)学习笔记及习题答案代码版(第十四章)重载运算与类型转换

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.h 和.cc 中,需要演示某一题直接修改 #define NUM****, 如运行14.30题为#define NUM1430: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful f

读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++]

读书笔记之:C++ Primer (第4版)及习题(ch12-ch18) [++++] 第12章 类 1. 类的声明与定义:前向声明,不完全类型 2. 从const函数返回*this 3. 可变数据成员mutable 4. 用于const对象的构造函数:构造函数不能声明为const 5. 构造函数初始化式 构造函数的执行分为两个阶段:初始化阶段和普通的计算阶段 6. 构造函数初始化列表 7. 默认实参与构造函数 8. 类通常定义一个默认构造函数,不然的话使用起来会很麻烦. 9. 使用默认构造函数

c++ primer(第五版)学习笔记及习题答案代码版(第十一章)关联容器

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,包含Chapter7.h头文件,读入文件包括./test ./rules .需要演示某一题直接修改 #define NUM****, 如运行11.23题为#define NUM1123: chapter 11 1.  关联容器不支持顺序容器的位置相关的操作,例如push_front或push_back.原因是关联容器中元素是根据关键字存储的,这些操作对 关联容器没有意义.而且关联容器也不支持构造函数或插入操作这些接收一个元素值和

c++ primer(第五版)学习笔记及习题答案代码版(第六章)函数

笔记较为零散,都是自己不熟悉的知识点. 习题答案至于一个.cc 中,编译需要包含Chapter6.h头文件. 需要演示某一题直接修改 #define NUM***, 如运行6.23题为#define NUM623: chapter 6 1. 形参初始化的机理与变量初始化一样. 当形参是引用类型时,它对应的实参被引用传递或者函数被传引用调用. 2. const和实参 void fcn(const int i){ /*fcn能够读取i,但是不能向i写值*/} void fcn(int i){ /*.

C++ Primer中文版 (第5版)pdf

下载地址:网盘下载 C++ Primer中文版 (第5版)是久负盛名的 C 经典教程,时隔八年之久,终于迎来重大升级.除令全球无数程序员从中受益,甚至为之迷醉的--C 大师 Stanley B. Lippman 的丰富实践经验,C 标准委员会原负责人 Josée Lajoie 对C 标准的深入理解,以及C 先驱 Barbara E. Moo 在 C 教学方面的真知灼见外,更是基于全新的 C 11标准进行了全面而彻底的内容更新.非常难能可贵的是,<C Primer 中文版(第5版)>所有示例均全

C++ Primer【第五版】习题参考答案——第六章(函数)

本系列文章会不断更新,但是时间不能保证.另外基本上都是自己做的答案,仅供参考,如果有疑问欢迎交流. #include <iostream> #include <initializer_list> using namespace std; int test_Ex_6_27(std::initializer_list<int> li); int main() { cout << test_Ex_6_27({23,78,89,76,90}) << en

快速学习C++ primer(第四版)第一天

//1.17 //遍历数组a,计算其中负数的个数 int amount=0; for(int i=0;i<strlen(a);i++) if(a[i]<0) ++amount; //1.19 //每隔输10个值 for(int val=lower,count=1;val<=upper;++val,++count) { cout<<val<<" "; if(count%10==0) cout<<endl; } 快速学习C++ prim

c++ primer plus(第6版)中文版 第九章编程练习答案

首先,说明下环境: linux:fedora14: IDE:eclipse: python:python2.7 python框架:django web服务器:apache web服务器的python模块:mod_wsgi 写在前面: 之前用的windows下面的xampp,写的php后台,现在想转向linux下面的python,跟以前一样,选择apache和eclipse作为自己的开发工具. eclipse的python配置, 参见之前的博客:http://blog.csdn.net/zy416