C++ primer 第六版 笔记(一)

第一、二、三章

Posted by LT on August 15, 2020

第一、二章 概述

  1. 编译C++程序时,编译器自动定义了一个预处理器名字__cplusplus,因此,我们可以根据它来判断该程序是否是C++程序,以便有条件地包含一些代码。例如:
      #ifdef __cplusplus 
         // 我们要编译 C++ 
         extern "C" 
         #endif 
         int min( int, int );
    
  2. C++库名字 C库名字 区别
     #include <cassert>
     using namespace std;
    

    #include <assert.h>相当于以上两行。

  3. 数组
    • C++对数组类型提供的内置支持,仅限于读写单个元素,不支持对整个数组的操作。
    • 数组不是C++语言的一等公民(first-class),它是从 C 语言中继承来的,反映了数据与对其进行操作的算法的分离。而这正是过程化程序设计的特征。
  4. 静态与动态内存分配,两个主要区别
    • 1.访问方式:直接/间接
      • 静态对象有名字的变量,可直接对其进行操作;
      • 动态对象没有名字的变量,惟一的访问方式是通过指针间接地访问。
    • 2.分配与释放
      • 静态对象的分配与释放由编译器自动处理,不需要做任何事情;
      • 动态对象的分配与释放必须由程序员显式地管理(通过new和delete)
  5. 内存泄漏(memory leak)
    • 如果动态分配的内存没有释放,程序就会在结束时出现内存泄漏:
    • 即,一块动态分配的内存,我们不再拥有指向这块内存的指针,因此无法将它返还给程序供以后重新使用(现在大多数系统提供识别内存泄漏的工具)
  6. private 和 public 控制对类成员的访问
    • 出现在类体中公有 public 部分的成员,在一般程序的任何地方都可以访问它们
    • 出现在私有 private 部分的成员,只能在该类的成员函数友元(friend)中被访问
  7. inline
    • 考虑下面例子
        IntArray array; 
        int array_size = array.size(); 
      
    • 还有 // 假设_size 是 public 的 int array_size = array._size;
    • 显然 第一个例子需要一个函数调用 而第二个只需直接访问内存就行了
      • 一般来说 函数调用比直接访问内存的开销要大得多 信息隐藏(类的公共接口与私有实现代码的分离)是否给程序的执行增加了额外负担呢?
      • 幸运的是,在一般情况下 回答是 不。
      • C++提供的方案是,size()作为非虚拟函数 由编译器处理并内联展开。
  8. 数据成员
    • 被声明为 static 的数据成员
      • 无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份.
      • 这是在类的所有对象之间共享数据的一种方式。
    • 保护protected,私有private
      • 类的保护区域内的数据/成员函数,不提供给一般的程序,只提供给派生类。
      • 基类的私有区域内的成员,只能供该类自己使用,连派生类都不能使用。
  9. 派生类对象的初始化过程 + 首先自动调用每个基类的构造函数来初始化相关的基类子对象 + 然后再执行派生类的构造函数
  10. 派生类 构造函数 是 必需的,原因: + 基类的构造函数 并没有 被派生类继承(析构函数和拷贝赋值操作符同样也没有) + 派生类 IntArrayRC 构造函数 提供了向 基类IntArray 构造函数 传递参数的接口
  11. 为什么 不需要 提供 派生类 拷贝构造函数? + 14、17 章 构造函数 讲解
  12. 继承过来的 需要析构的 IntArray 成员 都由 IntArray 析构函数 来处理
  13. 类模板 的成员函数会怎么样呢? + 不是 所有的成员函数 都能自动地随 类模板 的 实例化 而被实例化 + 只有真正被程序 使用到的成员函数 才会被实例化 + 这一般发生在程序生成过程中的一个独立阶段,16.8节将详细讨论
  14. 异常 + 不是在发生异常的地方恢复执行过程,而是在处理异常(catch子句)的地方恢复执行过程。 + 如果,异常机制按照函数被调用的顺序回查每个函数直到 main()函数 仍然没有找到处理代码, - 那么它将调用标准库函数 terminate() 。缺省情况下,terminate()函数结束程序。

第三章 C++数据类型

  1. 变量 和 文字常量 都有存储区,并且有相关的类型 区别在于: - 变量 可寻址(addressable) - 文字常量 不可寻址
  2. 声明(declaration)不是定义(比如 关键字 extern), 不会引起内存分配. - 常用做法: - 不是 在每个使用对象的文件中都提供一个单独的声明, - 而是在一个头文件中声明这个对象, 然后 在需要声明该对象的时候 include这个头文件
  3. 初始化
    • 如果一个变量是在 全局域 global scope 内定义的,那么系统提供初始值 0
    • 如果变量是在局部域 local scope 内定义的,或是通过new动态分配的,则这些对象是未初始化的,系统不会向它提供初始值 0。
    • 除了类对象,内置数据类型也支持构造函数语法,可将对象初始化为 0
      • 例如 int ival = int(); 设置 ival 为 0
  4. 空指针
    • void* 是 空类型指针,可以被 任何类型数据指针 的地址 赋值
    • 但 空指针 指向的 对象类型 未知,所以,不能 操作空类型指针 所指向的对象
    • 只能传送该地址值 或者 将它与其他地址值作比较
  5. 字符串类型:C风格字符串 和 string类型
    • “+” 可以完成 字符串(string类型)拼接
    • 初始化:
      • 将一个 C风格的字符串 赋给一个 string对象
          string s1; 
          const char *pc = "a character array"; 
          s1 = pc; // ok
        
      • string 👉 C风格字符串:const char *str = s1.c_str();
  6. const 限定修饰符
    • 只读(read- only)
      • 例子 const int bufSize = 512
    • 把变量定义为const常量 可以避免 意外 修改 对象
      • 例如 if ( bufsize = 0 )// 错误 企图写入 const 对象
    • 常量在定义后就不能被修改 所以它必须被初始化
      • 未初始化的常量定义,将导致编译错误:const double pi; // 错误: 未初始化的常量
  7. 指向常量的指针,
    • 往往被用作函数参数,例如 int strcmp( const char *str1, const char *str2 );
    • 一个 const指针,可以指向一个 const 或 非const 对象
      • 指向 非const对象const指针 curErr:
        • 不能赋给 curErr 其他的地址值,
        • 但可以修改 curErr 指向的值
      • 指向 const对象const指针
        • 指向对象的值,以及它的地址,都不能被改变
  8. 指针和引用 区别
    • 引用 在定义后不能修改,所以 引用 必须初始化.
    • 引用类型主要被用作函数的 形式参数
    • 一个引用给另一个引用赋值,改变的是引用对象的值,而不是引用。(而一个指针给指针赋值,改变的是指针。)
      • 假定有下列代码
        • int &ri = ival, &ri2 = ival2;
        • ri = ri2;
        • 改变的是 ival 而不是引用本身。赋值之后,两个引用仍然指向原来的对象
  9. const
    • const表达式:必须能在编译时刻计算出它的值。
    • 对于 非 const 对象,系统只能在运行时刻访问它的值
    • 字符串常量包含一个额外的终止空字符。比如:
        // 错误: "Daniel"是 7 个元素
        const char ch3[ 6 ] = "Daniel";