目录

  • 1 C++语言导论
    • 1.1 C++语言与问题求解
    • 1.2 人类如何求解问题
      • 1.2.1 过程化思维
      • 1.2.2 面向对象思维
      • 1.2.3 泛型思维
    • 1.3 计算机的工作原理
      • 1.3.1 计算机组成
      • 1.3.2 计算机如何执行程序
      • 1.3.3 计算机如何存储程序和数据
      • 1.3.4 关于存储的约定
    • 1.4 从人机需求到计算机语言
      • 1.4.1 机器语言(只考虑计算机的需求)
      • 1.4.2 汇编语言(考虑一点点人的需求)
      • 1.4.3 高级语言(兼顾人机需求)
    • 1.5 从人机需求到C++语言
      • 1.5.1 过程化编程
      • 1.5.2 面向对象编程
      • 1.5.3 泛型编程
  • 2 动手写个小程序
    • 2.1 求解素数判断问题
    • 2.2 程序的一般结构
    • 2.3 程序的编译和链接
    • 2.4 C++程序是字符序列
    • 2.5 C++程序是单词序列
    • 2.6 C++程序是语句序列
    • 2.7 C++程序是函数集合
    • 2.8 C++程序是文件集合
  • 3 C++过程化编程
    • 3.1 过程化编程的主要工作
    • 3.2 描述简单数据
      • 3.2.1 C++语言的数据类型
        • 3.2.1.1 C++语言的内置数据类型
        • 3.2.1.2 C++语言的复合数据类型
        • 3.2.1.3 定义自己的数据类型
      • 3.2.2 描述字面量
      • 3.2.3 描述变量
    • 3.3 描述数据处理过程
      • 3.3.1 数据的基本运算处理
      • 3.3.2 控制数据处理流程
      • 3.3.3 流程控制实验练习
      • 3.3.4 输入输出处理
        • 3.3.4.1 键盘输入
        • 3.3.4.2 屏幕输出
        • 3.3.4.3 控制输入输出格式
        • 3.3.4.4 文件输入输出处理
    • 3.4 制造数据处理机器:函数
      • 3.4.1 定义函数
      • 3.4.2 调用函数
      • 3.4.3 函数调用的形参生成
      • 3.4.4 函数嵌套和递归调用
      • 3.4.5 函数内联
      • 3.4.6 函数重载
      • 3.4.7 函数的默认形参值
    • 3.5 结构化编程与程序组织
      • 3.5.1 函数声明的组织方式
      • 3.5.2 程序的多文件组织
      • 3.5.3 作用域和生存期
        • 3.5.3.1 作用域
        • 3.5.3.2 生存期
      • 3.5.4 函数和变量的共享:链接性
      • 3.5.5 名字空间域
    • 3.6 描述复杂数据:复合数据类型
      • 3.6.1 常变量
      • 3.6.2 数组变量
      • 3.6.3 指针变量
        • 3.6.3.1 定义指针变量
        • 3.6.3.2 访问指针变量
        • 3.6.3.3 指针与动态变量
        • 3.6.3.4 动态数组
      • 3.6.4 描述字符串变量
      • 3.6.5 引用:给变量取个别名
      • 3.6.6 嵌套复合的数据类型
        • 3.6.6.1 常量数组与枚举类型
        • 3.6.6.2 指针数组与数组指针
        • 3.6.6.3 常量指针与指针常量
        • 3.6.6.4 多维数组和多级指针
        • 3.6.6.5 常变量、指针、数组的引用
        • 3.6.6.6 简化嵌套复合声明:typedef
      • 3.6.7 复合数据类型与函数
        • 3.6.7.1 数组与函数
        • 3.6.7.2 指针与函数
        • 3.6.7.3 引用与函数
  • 4 面向对象编程
    • 4.1 面向对象编程的主要工作
    • 4.2 描述需要什么样的对象
      • 4.2.1 定义类
      • 4.2.2 产生对象
      • 4.2.3 访问对象的成员
      • 4.2.4 描述对象的成员
    • 4.3 约定对象生死时刻的行为
      • 4.3.1 必须编写析构函数
      • 4.3.2 浅拷贝和深拷贝
      • 4.3.3 构造和析构函数的调用时机
    • 4.4 约定对象运算的行为
      • 4.4.1 友元函数和友元类
      • 4.4.2 两种重载操作符的方式比较
      • 4.4.3 重载赋值运算符
      • 4.4.4 深拷贝的赋值运算符
    • 4.5 约定类成员的常量性
      • 4.5.1 常量数据成员
      • 4.5.2 常成员函数
      • 4.5.3 常对象和常成员函数
      • 4.5.4 mutable成员
      • 4.5.5 volatile成员
    • 4.6 约定类成员的静态性
      • 4.6.1 静态数据成员
      • 4.6.2 静态成员函数
      • 4.6.3 const和static成员小结
    • 4.7 对象组合
    • 4.8 对象继承与派生
      • 4.8.1 选择继承方式
      • 4.8.2 改造基类成员
      • 4.8.3 重写构造和析构函数
      • 4.8.4 派生类与基类的赋值兼容
        • 4.8.4.1 派生类对象与基类对象的兼容性
        • 4.8.4.2 派生类对象与基类引用/指针的兼容性
        • 4.8.4.3 针对基类引用/指针进行通用编程
    • 4.9 对象行为的多态:重载和虚函数
      • 4.9.1 编译期多态:重载
      • 4.9.2 运行期多态:虚函数
        • 4.9.2.1 在类层次中定义虚函数
        • 4.9.2.2 使用虚函数实现通用输出操作符
        • 4.9.2.3 虚函数运行期绑定原理
        • 4.9.2.4 运行期绑定的优势与不足
        • 4.9.2.5 虚析构函数
        • 4.9.2.6 不产生对象的类:抽象类
        • 4.9.2.7 只有纯虚函数的类:接口类
    • 4.10 运行期类型识别RTTI
    • 4.11 异常处理机制
  • 5 泛型编程
运行期类型识别RTTI

运行期类型识别RTTI

继承的赋值兼容规则告诉我们,基类引用/指针可以指向派生类对象,那么,给定一个基类引用/指针,能否获得该引用/指针实际所指对象的数据类型呢?解决这一问题的语法机制就是运行期时类型识别Run-time Type IdentificationRTTI),是指在程序中能够使用基类引用或者指针来检索该引用或指针所指对象的实际派生类型。

C++语言通过typeiddynamic_cast两个操作符支持运行期类型识别。其中,

1typeid:返回引用或指针所指对象的实际数据类型;

2dynamic_cast:将基类引用/指针安全地转换为派生类引用/指针。

操作符typeid

操作符typeid,获得对象或者类型名的类型信息,结果存放在一个type_info类对象中。注意,使用 type_info 类需要包含标准库头文件 typeinfo。例如,

class Vehicle{ … … };

class Bus: virtual public Vehicle { … … };

class Ship: virtual public Vehicle { … … };

class Amphibus: public Bus, public Ship { …… };

int main() {

       Busbus(10);  Vehicle * pvc = &bus;

       consttype_info &ti = typeid(*pvc);      //运行期类型识别

       cout<<ti.name()<<endl;

       inta;

       cout<<typeid(a).name()<<endl;           //编译期类型识别

       return0;

}

这里,由于typeid的操作数*pvc是一个类对象且类中包含虚函数,所以,在运行期识别类型信息,得出其实际数据类型是Bus;如果操作数不是类类型或者是不包含虚函数的类,则在编译期即可确定操作数的类型信息,即编译期类型识别。

操作符dynamic_cast

操作符dynamic_cast,称为动态转换,用于进行基类和派生类之间进行指针或引用的类型转换。对于从派生类到基类的上行转换(upcasting),dynamic_caststatic_cast是等效的;但是,对于从基类到派生类的下行转换(downcasting),dynamic_cast会进行运行期的类型检查,此时dynamic_caststatic_cast更为安全。例如,

int main() {

    Busbus(10);      Vehicle * pvc = &bus;

    Bus*pbus = dynamic_cast<Bus*>(pvc);            //转换成功

    pbus->print();

    Vehicle &rvc = bus;

    Ship &rship = dynamic_cast<Ship&>(rvc);//转换失败

    rship.print();

    return0;

}

这里,指针pvc实际指向的就是一个Bus类对象bus,将pvc指针转换为Bus类型的指针是合理的,因此,第一次dynamic_cast会成功地实现类型转换。

而引用rvc实际引用的是一个Bus类对象,我们却要将其转换为Ship类引用,这就是指鹿为马,因此,第二次dynamic_cast会转换失败。如果不对转换失败做进一步的处理,那么,这种失败会导致程序执行出错并且退出,错误提示为:

abnormal program termination

动辄就死的程序,性情太过刚烈,没有人敢用。所谓好死不如赖活着,健壮的程序不应该遇到一点问题就崩溃,因此,我们应该对转换失败的dynamic_cast做一些特殊处理。实际上,转换失败的dynamic_cast会给出一些失败的线索,

1dynamic_cast为指针,则转换失败的指针值为0

2dynamic_cast为引用,则转换失败会抛出异常bad_cast

显然,我们可以检查转换结果指针是否为0来避免程序执行崩溃,例如,

int main() {

    Busbus(10);  Vehicle * pvc = &bus;

    //方法1:通过检查结果指针是否为0来避免程序崩溃

    if ( Bus *pbus = dynamic_cast<Bus*>(pvc)) {

        pbus->print();

    } else {

        cout<<”动态类型转换失败”<<endl;

    }

       return0;

}

此外,转换为引用的失败会抛出bad_cast异常,我们还可以通过捕获bad_cast异常来避免程序执行崩溃。

int main() {

    //方法2:通过捕获处理bad_cast异常来避免程序崩溃

    Vehicle &rvc = bus;

    try {

        Ship& pship = dynamic_cast<Ship&>(rvc);

        pship->print();

    } catch (bad_cast) {

        cout<<”动态类型转换失败”<<endl;

    }

       return0;

}

通过返回值检查和异常捕获,我们可以处理程序执行时出现的诸多突发问题,编写出更加健壮可靠的程序。