读《C和C++代码精粹》——Chunk Allison著,董慧颖译

第1章 更好的C

  • C++是类型安全语言
  • 所有函数在第一次使用之前必须声明或定义
  • 引用参数直接支持引用调用语义
  • 模板允许创建通用函数
  • 内联函数将类似于函数的宏的高效与实际函数的安全性相结合
  • 声明可以出现在函数可以出现的任意位置

第2章 指针

  • C和C++仅仅与那些使用它们的人一样危险
  • 指针是地址
  • 可以将任何一个指针赋值成void*
  • 注意区分一个const指针和一个指向const的指针
  • p+n == (char*)p+n*sizeof(*p)
  • *(a+i) == a[i]
  • 除非在sizeof和&的上下文中,否则一个数组名即是指向它第一个元素的指针
  • 没有多维数组,只有数组的数组
  • 仅是指针的存在并不要求它所引用的类型的实现的有效性(这是一个不完全类型)

如果理解了这些概念,你就正在逐渐成为一名可信赖的C++程序员。

第3章 预处理器

  • 预处理器不能理解语言
  • 头不一定是文件?(不理解)
  • 彻底的记住带括号的宏
  • 宁可用内联函数而不用类函数的宏(除了字符串化和标记粘贴)
  • 宁可用常值而不要用类对象的宏
  • 用assert宏来捕捉不应该发生的概念错误
  • 有条件地用特殊的宏来编译头文件(来避免循环包含)
  • C和C++支持三字符组合以适应国际键盘,C++支持更多可读双字符和其他保留字

第4章 C标准库之一:面向合格的程序员

  • <ctype.h>字符处理,isupper(c),tolower(),…
  • <stdio.h>输入输出,包括格式化函数、字符I/O函数和文件函数
  • <stdlib.h>复杂的工具,含有div函数返回的结构div_t{quot商,rem余数},长整数是ldiv_t
  • <string.h>文本处理,复制、连结、比较和查找

第5章 C标准库之二:面向熟练的程序员

  • <assert.h>支持有保护的程序的断言
  • <limits.h>整数运算的系统参数
  • <stddef.h>通用类型和常量 ,包含宽字符类型wchar_t
  • <time.h>时间处理

第6章 C标准库之二:面向优秀的程序员

  • <errno.h>错误检测
  • <float.h>实数运算的系统参数
  • <locale.h>文化自适应
  • <math.h>数学函数
  • <setjmp.h>非局部分支
  • <signal.h>中断处理
  • <stdarg.h>可变长度参数表

第7章 抽象

  • 复杂性是生活中不可避免的事实
  • 抽象是处理复杂性的工具
  • 定义明确的抽象数据类型可把接口从实现中分离出来
  • C++类支持数据抽象
  • 成员访问控制限定词(private和protected)支持封装
  • 具体的类型应该有定义明确的构造函数、拷贝构造函数、赋值运算符合一个析构函数
  • 运算符重载可以使具体的类型的行为类似于内置类型
  • 模板支持类型抽象
  • 虚函数支持函数抽象

Date类

//date.h
#ifndef DATE_H
#define DATE_H

#include <iostream>
using std::ostream;

struct DateRep;

class Date
{
public:
    Date(int, int, int);
    ~Date();
    char* format(char*) const;
    int compare(const Date&) const;
    friend ostream& operator<<(ostream&, const Date&);

private:
    struct DateRep* drep;
    static const char* month_text[13];
};

#endif
//date.cpp
#include <stdio.h>
#include "date.h"

struct DateRep
{
    DateRep(int, int, int);
private:
    friend struct Date;
    friend ostream& operator<<(ostream&, const Date&);

    int month;
    int day;
    int year;
};

const char* Date::month_text[13] = 
    {"Bad month", "January", "February", "March", "April",
     "May", "June", "July", "August", "September",
     "October", "November", "December"};
     
DateRep::DateRep(int m, int d, int y)
:month(m),day(d),year(y)
{}

Date::Date(int m, int d, int y)
{
    drep = new DateRep(m,d,y);
}
Date::~Date()
{
    delete drep;
}

char* Date::format(char* buf) const
{
    sprintf(buf, "%s %d, %d",
        month_text[drep->month],
        drep->day,drep->year);
    return buf;
}

int Date::compare(const Date& dp2) const
{
    int result = drep->year - dp2.drep->year;
    if (result == 0)
        result = drep->month - dp2.drep->month;
    if (result == 0)
        result = drep->day - dp2.drep->day;
    return result;
}

ostream& operator<<(ostream& os, const Date& d)
{
    os << Date::month_text[d.drep->month]
       << ' ' << d.drep->day
       << ", " << d.drep->year;
    return os;
}
//tdate.cpp
#include <iostream>
#include "date.h"
using namespace std;

int main()
{
    Date d1(10,1,1951),d2(3,7,1995);
    
    cout << "d1=" << d1 << endl;
    cout << "d2=" << d2 << endl;
    
    return 0;
}

image

第9章 位操作

  • 位操作支持系统编程而且能降低内存需要
  • 按位运算符允许访问整数位的子集(但是要注意运算符的优先权)
  • bitset类模板支持有效的固定大小的位设置操作
  • vector<bool>模板规范支持动态大小的位字符串

第10章 类型转换盒强制类型转换

  • C和C++提供隐式类型转换以简化混合模式运算
  • 在运算中整数类型可以根据需要而升级
  • 浮点运算由标准的类型转换来“平衡”
  • 函数原型帮助编译器提供隐式类型转换
  • 有时候不希望用户定义类型的隐式类型转换
  • 当想屏蔽隐式类型转换时,可以显示地声明带有单个参数的构造函数
  • 强制类型转换使得类型转换变得清楚
  • 尽量使用函数风格的强制类型转换,而不是C风格的强制类型转换
  • 当适用时使用新风格的强制类型转换(即除了使用构造函数调用时,总是使用)
  • 当适用时尽量使用const
  • 首选语义常量而不是位逻辑常量
  • 可变的存储类使得在大多数情况下去掉const变得没有必要
  • RTTI允许安全的向下强制类型转换

第11章 可见性

  • 在C++文件里:extern “C” f();告诉编译器根据C的规则生成连接名
  • 标识符可以有外部连接(在多重翻译单元内可见)、内部连接(只在单独的翻译单元中可见)和没有连接
  • 有外部连接的对象必须在一个被命名的名字空间中定义(包括被秘密命名的全局名字空间)

第12章 控制结构

  • 用断言或注释显示不变的条件
  • break退出密封关闭的循环或分支
  • continue跳到最近关闭的循环的下一个迭代
  • 用setjmp/longjmp机制在异常条件下交替地返回
  • 用信号处理程序捕获同步信号和异步信号

第13章 异常

  • 错误处理方法
    1. 忽略错误
    2. 检查返回代码
    3. 用非局部跳转来使执行过程改变方向
    4. 使用异常
  • 异常处理是一个运行期机制
  • C++异常处理是围绕中断模式设计的
  • 标准异常
    • exception
      • logic_error
        • domain_error
        • invalid_argument
        • length_error
        • out_of_range
      • runtime_error
        • range_error
        • overflow_error
        • underflow_error
      • bad_alloc
      • bad_cast
      • bad_exception
      • bad_typeid
  • ”资源分配是初始化“原则
  • 向语言中加入异常的目的之一是为了弥补构造函数无返回值这一不足之处
  • 为不能就地处理的错误抛出异常,包括无效的参数
  • 不要在有构造函数和析构函数的对象前使用setjmp和longjmp。而是要用异常。当一次被抛出时,堆栈中所有的自动对象将被自动销毁
  • 异常允许独立的程序组件关于错误条件进行通信
  • 通过与抛出的对象的类型相匹配来选择处理程序
  • 根据被处理的类型从最特殊到最普通的顺序排列相邻的处理程序
  • 如果用标准库,准备好在标准继承结构里捕获异常(至少是exception)
  • 在自动对象中封装资源分配以确保在需要的时候调用析构函数
  • 在auto_ptr中封装new表达式的结果以确保析构函数可以被合适地调用
  • 可以对文档使用异常规范并加强函数所抛出的异常类型
  • 断言只用来防止你(快速的开发者)的错误
  • 为每一个组件定义一个从exception类(或runtime_error或logic_error,如果是和的话)派生出来的异常类

第14章 面向对象编程

  • 三要素:封装,继承,多态
  • 共有继承表示基类和派生类的“is-a”关系
  • 指向基类的指针还可以指向派生类对象,派生类对象总是可以代替基类对象
  • 多态是指有关联的实现共用一个单独的接口,实现可以根据需要随着所包含的对象类型变化而自动化。C++通过虚函数支持多态
  • 虚函数通过函数指针表来模拟动态绑定

第15章 算法

  • 算法种类:通用序列算法,有关排序的算法,数值算法。其中前两个算法包含在<algorithm>中,数值算法包含在<numeric>中
  • 算法是计算机科学的“要素”
  • C++标准库中的算法是函数模板,他们中的大多数都是处理由[begin,end)所限定范围的一系列对象
  • 函数对象是一个定义运算符的类的实例
  • 可改写的函数对象含有对它的参数和返回值类型的定义
  • 判断是一个返回bool类型的函数或者函数对象
  • 函数对象适配器是一个函数模板,它把一个或者多个函数对象以及其他可选的数值作为参数,并且返回一个新的函数对象
  • 通过把函数对象和函数对象适配器配合,可以自定义算法断言,通常没必要自己编写函数

第16章 容器和迭代器

  • 标准容器可以支持任何具体类型的对象,包括其他的容器。具体的类型是有值语义的类型,意味着它们可以被复制、赋值和比较相等或者不相等
  • 标准容器包括:基本序列容器、容器适配器和关联容器
  • 基本序列容器包括:向量、双端对列和表,它们之间的不同主要在于在哪允许元素的有效插入和删除,以及它们支持何种迭代器
  • 迭代器是指针的一般化,迭代器是用通用方式连接算法和容器的粘结剂
  • 迭代器分五种:输入迭代器,输出迭代器,前向迭代器,双向迭代器和随机访问迭代器
  • 不支持算法的迭代器的容器经常要求定义它们自己的板式(如,list<T>sort())
  • 容器适配器包括:栈,队列和优先权队列。容器适配器用更严格的接口来包装基本序列以实现更高级的数据抽象
  • 关联容器是包括:集合,多重集合,图和多重图。关联容器集合和图仅存储唯一的关键词
  • 特殊用途的容器bitset和valarray不支持其他标准容器所支持的容器——迭代器——算法主题

第17&&18章 文本处理

  • 在交互式应用程序中每次读入一行,然后在必要的时候解析每一行这是一个很好的习惯
  • 标准C++字符串类最重要的特征就是内存管理
  • 标准C++库通过模板basic_string支持任何“类字符型”的字符串
  • 如果标准库不能满足文件处理的需要,试着用POSIX文件操作。如果还不行,把依赖平台的代码分隔在独立的模块中以利于端口进程
  • 总是考虑用C++来封装低级别的概念和操作

第19章 时间和日期处理

  • 利用<time.h>中的函数,如localtime、strftime和difftime,进行简单的时间和日期操作
  • 确信知道环境的参考日期

第20章 动态内存管理

  • 区分浅拷贝和深拷贝
  • 重载new和delete:
    • 标准C++库定义了12个用于分配和释放内存的函数。当使用new运算符是,它会计算出所需要的字节数然后按顺序调用这些函数中的一个来分配这些字节。这12个函数是:
      • void *operator new(size_t);//标量形式
      • void *operator new(size_t,const nothrow_t);
      • void *operator delete(void*);
      • void *operator delete(void*,const nothrow_t);
      • void *operator new[](size_t);//数组形式
      • void *operator new[](size_t,const nothrow_t);
      • void *operator delete[](void*);
      • void *operator delete[](void*,const nothrow_t);
      • void *operator new(size_t,void *);//配置new
      • void *operator new[](size_t,void *);
      • void *operator delete(void*, void*);
      • void *operator delete[](void*,void*);
  • 配置new
  • 当预先不知道需要多少内存来存储一个对象,或者不知道需要多少个对象是,可以使用动态内存
  • new运算符调用operator new 来为一个动态对象分配内存,然后再调用适当的构造函数。delete运算符通过调用对象的析构函数来销毁这个对象,然后调用operator delete
  • 对于数组一定要使用delete[]
  • 无论什么时候,当一个类是指针成员时,都要检查是否需要深拷贝
  • 记住对内存操作的失败将引发bad_alloc异常
  • 在给定的地址上使用配置new来构建一个对象
  • 为了使用内存碎片的数量尽可能少,可以考虑将动态对象的同类集合分配到一个专用的内存池中,如容器。
  • 无论什么时候,只要有可能就让标准库来为你管理内存

 

上述知识点都是摘抄自《C和C++代码精粹》中文版。

      posted @ 2012-10-18 09:50  涵曦  阅读(1854)  评论(1编辑  收藏  举报