笔记的方法-读书简记 刷新:重新发现商业与未来-读书简记 蛤蟆先生去看心理医生-读书简记 十分钟冥想-读书简记 乔布斯、禅与投资-读书简记 掌控习惯-读书简记 金钱心理学-读书简记 被讨厌的勇气-读书简记 身心合一的奇迹力量-读书简记 零极限-读书简记 投资最重要的事-读书简记 语言学的邀请-读书简记 更富有、更睿智、更快乐-读书简记 管理的常识-读书简记 卡片笔记写作法-读书简记 纳瓦尔宝典-读书简记 卓有成效的管理者-读书简记 贪婪的多巴胺-读书简记 清醒的活-读书简记 像哲学家一样生活:斯多葛哲学的生活艺术-读书简记 你是你吃出来的-读书简记 你可以跑的更快-读书简记 丹尼尔斯经典跑步训练法-读书简记 非暴力沟通-读书简记 异类-读书简记 稀缺-读书简记 为什么要睡觉-读书简记 事实-读书简记 世界上最快乐的人-读书简记 病毒学概览-读书简记 免疫学概览-读书简记 内观-读书简记 沟通的艺术-读书简记 你的生命有什么可能-读书简记 演化的故事-读书简记 经济学原理:宏观经济学分册-读书简记 经济学原理:微观经济学分册-读书简记 社会心理学-读书简记 追寻记忆的痕迹-读书简记 情绪-读书简记 远见:如何规划职业生涯3阶段-读书简记 存在主义心理治疗-读书简记 P·E·T父母效能训练-读书简记 彼得·林奇的成功投资-读书简记 2015-2020美国居民膳食指南-读书简记 中国居民膳食指南(2016)-读书简记 批判性思维-读书简记 代码大全-读书简记 游戏力-读书简记 成功,动机与目标-读书简记 基因组:人种自传23章-读书简记 YOU身体使用手册-读书简记 登天之梯-读书简记 为什么学生不喜欢上学-读书简记 请停止无效努力-读书简记 麦肯基疗法-读书简记 跟简七学理财-课程简记 指数基金投资指南(2017中信版)-读书简记 指数基金投资指南(2015雪球版)-读书简记 让大脑自由:释放天赋的12条定律-读书简记 养育的选择-读书简记 GPU高性能编程CUDA实战-读书简记 百万富翁快车道-读书简记 原则-读书简记 穷查理宝典-读书简记 C++并发编程实战-读书简记 哲学家们都干了些什么-读书简记 Effective C++-读书简记 通往财富自由之路-读书简记 Linux命令行与Shell脚本编程大全-读书简记 刻意练习-读书简记 写给大家看的设计书-读书简记 习惯的力量-读书简记 好好学习-读书简记 硅谷最受欢迎的情商课-读书简记 富爸爸,穷爸爸-读书简记 如何说孩子才会听,怎么听孩子才会说-读书简记 阻力最小之路-读书简记 ProGit-读书简记 思考:快与慢-读书简记 C语言深度剖析-读书简记 编程珠玑-读书简记 Head First 设计模式-读书简记 反脆弱-读书简记 我的阅读书单 小强升职记-读书简记 观呼吸-读书简记 黑客与画家-读书简记 晨间日记的奇迹-读书简记 如何高效学习-读书简记 即兴的智慧-读书简记 精力管理-读书简记 C++编程思想-读书简记 拖延心理学-读书简记 自控力-读书简记 伟大是熬出来的-读书简记 生命不能承受之轻-读书简记 高效能人士的七个习惯-读书简记 没有任何借口-读书简记 一分钟的你自己-读书简记 人生不设限-读书简记 暗时间-读书简记
笔记的方法-读书简记 刷新:重新发现商业与未来-读书简记 蛤蟆先生去看心理医生-读书简记 十分钟冥想-读书简记 乔布斯、禅与投资-读书简记 掌控习惯-读书简记 金钱心理学-读书简记 被讨厌的勇气-读书简记 身心合一的奇迹力量-读书简记 零极限-读书简记 投资最重要的事-读书简记 语言学的邀请-读书简记 更富有、更睿智、更快乐-读书简记 管理的常识-读书简记 卡片笔记写作法-读书简记 纳瓦尔宝典-读书简记 卓有成效的管理者-读书简记 贪婪的多巴胺-读书简记 清醒的活-读书简记 像哲学家一样生活:斯多葛哲学的生活艺术-读书简记 你是你吃出来的-读书简记 你可以跑的更快-读书简记 丹尼尔斯经典跑步训练法-读书简记 非暴力沟通-读书简记 异类-读书简记 稀缺-读书简记 为什么要睡觉-读书简记 事实-读书简记 世界上最快乐的人-读书简记 病毒学概览-读书简记 免疫学概览-读书简记 内观-读书简记 沟通的艺术-读书简记 你的生命有什么可能-读书简记 演化的故事-读书简记 经济学原理:宏观经济学分册-读书简记 经济学原理:微观经济学分册-读书简记 社会心理学-读书简记 追寻记忆的痕迹-读书简记 情绪-读书简记 远见:如何规划职业生涯3阶段-读书简记 存在主义心理治疗-读书简记 P·E·T父母效能训练-读书简记 彼得·林奇的成功投资-读书简记 2015-2020美国居民膳食指南-读书简记 中国居民膳食指南(2016)-读书简记 批判性思维-读书简记 代码大全-读书简记 游戏力-读书简记 成功,动机与目标-读书简记 基因组:人种自传23章-读书简记 YOU身体使用手册-读书简记 登天之梯-读书简记 为什么学生不喜欢上学-读书简记 请停止无效努力-读书简记 麦肯基疗法-读书简记 跟简七学理财-课程简记 指数基金投资指南(2017中信版)-读书简记 指数基金投资指南(2015雪球版)-读书简记 让大脑自由:释放天赋的12条定律-读书简记 养育的选择-读书简记 GPU高性能编程CUDA实战-读书简记 百万富翁快车道-读书简记 原则-读书简记 穷查理宝典-读书简记 C++并发编程实战-读书简记 哲学家们都干了些什么-读书简记 Effective C++-读书简记 通往财富自由之路-读书简记 Linux命令行与Shell脚本编程大全-读书简记 刻意练习-读书简记 写给大家看的设计书-读书简记 习惯的力量-读书简记 好好学习-读书简记 硅谷最受欢迎的情商课-读书简记 富爸爸,穷爸爸-读书简记 如何说孩子才会听,怎么听孩子才会说-读书简记 阻力最小之路-读书简记 ProGit-读书简记 思考:快与慢-读书简记 C语言深度剖析-读书简记 编程珠玑-读书简记 Head First 设计模式-读书简记 反脆弱-读书简记 小强升职记-读书简记 观呼吸-读书简记 黑客与画家-读书简记 晨间日记的奇迹-读书简记 如何高效学习-读书简记 即兴的智慧-读书简记 精力管理-读书简记 C++编程思想-读书简记 拖延心理学-读书简记 自控力-读书简记 伟大是熬出来的-读书简记 生命不能承受之轻-读书简记 高效能人士的七个习惯-读书简记 没有任何借口-读书简记 一分钟的你自己-读书简记 人生不设限-读书简记 暗时间-读书简记

C++编程思想-读书简记

2014年08月15日

写在前面


C++编程思想反反复复读过三遍,每次都有不一样的理解和收获,下面是一些备忘记录。

第一章 对象导言


第二章 对象的创建与使用


1 如果两个加引号的字符数组相邻,并且它们之间没有标点,编译器会将其组成单个字符数组。

2 调用其他程序,只需包含头文件,调用system()函数即可。

3 文件读入除了cin,还可以ifstream in("file"); getline(in,s); 注意getline会丢弃换行符。

第三章 C++中的C


1 系统头文件中定义了不同数据类型可能存储的最大最小值,c++中用代替。

2 全局变量:在所有函数体的外部定义的变量。如果在一个文件中使用extern关键字来声明另一个文件中存在的全局变量,这个文件即可使用此变量。

3 局部变量:出现在一个作用域内,需要注意的是register变量,其作用是告诉编译器,“尽可能快的访问这个变量”;register只能在一个块中声明(不可能有全局的或静态的register变量);不可得到或计算register变量的地址。

4 静态变量:变量的值在程序整个声明期都存在,初始化只在函数第一次调用时执行;static的第二层意思是,在某个作用域外不可访问。

5 常量const,必须有初始值。

6 volatile变量,和const,volatile告诉编译器“不知道何时会改变”;编译器不进行优化时,volatile可能不起作用,但当开始优化时(当编译器开始寻找冗余的读入时),可以防止出现重大错误。

7 c++的显式转换

操作 说明
static_cast 用于“良性”和“适度良性”转换,包括不用强制转换(例如自动类型转换)
const_cast 对“const” 和/或 “volatile”进行转换
reinterpret_cast 转换为完全不同的意思。为了安全使用它,关键必须转换回原来的类型。转换成的类型一般只能用于未操作,否则就是为了其他隐秘的目的。这是所有转换类型中最危险的
dynamic_cast 用于类型安全的向下转换

static_cast 包括编译器允许我们所做的不用强制类型转换的“安全”变换和不太安全但清楚定义的变换。包含典型的强制类型转换、窄化变换、使用void*的强制变换(C++中不用转换是不允许从void*赋值的,不像C)、隐式类型转换盒类层次的静态定位。

const_cast 不用转换是不能将const指针赋给非const指针的。

//: C03:const_cast.cpp
int main() {
  const int i = 0;
  int* j = (int*)&i; // Deprecated form
  j  = const_cast<int*>(&i); // Preferred
  // Can't do simultaneous additional casting:
//! long* l = const_cast<long*>(&i); // Error
  volatile int k = 0;
  int* u = const_cast<int*>(&k);
} ///:~

reinterpret_cast 思想是当需要使用的时候,所得到的东西已经不同,以至于他不能用于类型的原来目的,除非再次把他转换回来。

//: C03:reinterpret_cast.cpp
#include <iostream>
using namespace std;
const int sz = 100;

struct X { int a[sz]; };

void print(X* x) {
  for(int i = 0; i < sz; i++)
    cout << x->a[i] << ' ';
  cout << endl << "--------------------" << endl;
}

int main() {
  X x;
  print(&x);
  int* xp = reinterpret_cast<int*>(&x);
  for(int* i = xp; i < xp + sz; i++)
    *i = 0;
  // Can't use xp as an X* at this point
  // unless you cast it back:
  print(reinterpret_cast<X*>(xp));
  // In this example, you can also just use
  // the original identifier:
  print(&x);
} ///:~

8 创建复合类型

typedef 原类型名 别名;

c语言定义结构体变量需要加上 struct 关键字(struct Structurel s1,s2;),或者声明结构体时加上typedef。struct的名字不必和typedef的名字相同,但一般使用相同的名字。如下:

//: C03:SelfReferential.cpp
// Allowing a struct to refer to itself

typedef struct SelfReferential {
  int i;
  SelfReferential* sr; // Head spinning yet?
} SelfReferential;

int main() {
  SelfReferential sr1, sr2;
  sr1.sr = &sr2;
  sr2.sr = &sr1;
  sr1.i = 47;
  sr2.i = 1024;
} ///:~

9 使用枚举提高程序清晰度

//: C03:Enum.cpp
// Keeping track of shapes

enum ShapeType {
  circle,
  square,
  rectangle
};  // Must end with a semicolon like a struct

int main() {
  ShapeType shape = circle;
  // Activities here....
  // Now do something based on what the shape is:
  switch(shape) {
    case circle:  /* circle stuff */ break;
    case square:  /* square stuff */ break;
    case rectangle:  /* rectangle stuff */ break;
  }
} ///:~

注意c++中不能写为shape++,但c可以。

10 提供的atoi atol atof 可以将assii字符转换为int long double浮点数。

11 #define P(EX) cout<<#EX<<": "<<EX<<endl; #为字符串化操作符,讲锁连接的部分转化为一个字符串,##为字符串连接符,将两个字符串连接成一个字符串,均在预处理时完成字符串的替换。

12 中assert宏,当断言不为真的时候会出错并给出信息。 assert(i==100); 完成调试后,加上#define NDEBUG 可以消除宏产生的代码。

13 指向函数的指针数组,下面是一个表格式驱动码的例子,可以根据状态变量去选择执行函数。

//: C03:FunctionTable.cpp
// Using an array of pointers to functions
#include <iostream>
using namespace std;

// A macro to define dummy functions:
#define DF(N) void N() { \
   cout << "function " #N " called..." << endl; }

DF(a); DF(b); DF(c); DF(d); DF(e); DF(f); DF(g);

void (*func_table[])() = { a, b, c, d, e, f, g };

int main() {
  while(1) {
    cout << "press a key from 'a' to 'g' "
      "or q to quit" << endl;
    char c, cr;
    cin.get(c); cin.get(cr); // second one for CR
    if ( c == 'q' ) 
      break; // ... out of while(1)
    if ( c < 'a' || c > 'g' ) 
      continue;
    (*func_table[c - 'a'])();
  }
} ///:~

第四章 数据抽象


1 c++允许将任何类型的指针赋值给void*,但不允许将void指针赋给任何其他类型的指针。

2 ::a,全局域解析符。

第五章 隐藏实现


1 private关键字意味着,除了该类型的创建者和类的内部成员函数之外,任何人不能访问;protected和private基本相似,只有一点不同:继承的结构可以访问protected成员,但不能访问private成员。

第六章 初始化与清除


1 就像其他成员函数被调用一样。传递到构造函数的第一个(秘密)参数是this指针,也就是调用这一函数的对象的地址,不过构造函数来说,this指针指向一个没有初始化的内存块,其作用就是正确的初始化该内存块。

2 默认构造函数就是不带任何参数的构造函数;当一个结构中没有构造函数的时候,编译器会自动为他创建一个,一旦有了一个构造函数,编译器救回确保不管什么情况下他总会被调用,一旦有构造函数但没有默认构函数,V v这样的定义总会出现编译错误。

第七章 函数重载与默认参数


1 只能通过范围和参数来重载,不能通过返回值来重载。

2 union与class不同之处在于存储数据的方式,union不能在继承中作为基类。

3 默认参数的使用有两条规则,一、只有参数列表后部参数才是可默认的;二、一旦一个函数调用中开始使用默认函数,这个参数后面的所有参数必须是默认的。

4 函数声明时可以没有标识符,如void f(int x, int =0, float =1.1),c++中函数定义时也并不一定需要标识符,如void f(int x, int , float flt),中间的参数为占位符,这样的目的是以后可以修改函数定义而不必修改所有的函数调用。

第八章 常量


1 c++中的const默认为内部连接,通常c++编译器不为其创建存储空间,而是将定义保存在符号表里;但如果加上extern后,就强制进行了内存分配(另外还有一种情况,如取一个const地址)。

2 const可以用于集合,这时同样会分配内存,const意味着不能改变的一块存储空间;然后不能再编译期间使用它的值,因为编译器在编译期间不需要知道存储的内容。

3 当处理const指针时,编译器仍将努力避免存储分配并进行常量折叠

表达式 解释
const int* u 等价于 int const* u 表示u是一个指针,指向一个const int可以改变指针,但不能改变指向的内容
int* const w w 是一个指针,这个指针式指向int的const指针可以改变指向的内容,指针本身不能变
const int* const x const指针指向const对象都不可以改变

4 对于内部类型,按值返回常量是无关紧要的;但对于用户定义的类型,按值常量返回是很重要的,使得返回的对象不能使一个左值。

5 临时量会自动成为常量,f1(f2())如果f1是按值传递没有问题,如果是按引用传值但不是const引用,这种情况会导致编译错误。

6 类的const

为了保证一个类对象为常量,引进了const成员函数,const成员函数只能对于const对象调用在类里简历的普通(非staticd )const时,不能给它初值,这个初始化工作必须放在构造函数里,并且只能放在初始化列表里如果想让一个类在编译期间拥有常量成员,使用static const,在不支持static const的版本中,可以使用不带实例的无标记enum(枚举在编译器间必须有值)const成员函数,修饰符const必须放在函数参数表的后面,此类函数不能以任何方式改变非const成员及调用非const成员函数,如果想在const成员函数里改变某些数据,一种是可以使用强制转换常量性,((Y*)this)->i++或者(const_cast(this))->i++;常用的是加入关键字mutable : mutable int i

第九章 内联函数


1 宏的实现是用预处理器而不是编译器,没有了参数压栈、生成汇编的call、返回参数、执行汇编的return等开销,但在c++中使用预处理宏存在两个问题:第一个在C中也存在,看起来宏像个函数调用,但并不总是这样,隐藏了难以发现的错误;第二个问题是预处理器不允许访问类的成员数据,意味着预处理器宏不能用作类的成员函数。

2 #define 的空格很脆弱 #define F (x) (x+1) F(1)--> (x) (x+1) 但如果#define F(x) (x+1) F (1)调用是没问题的;用大写字母标记宏。

3 在c++中,宏的概念是作为内联联函数来实现的,内联函数能够像普通函数一样具有我们期望的任何行为,唯一不同的是内联函数在适当的地方像宏一样展开,所以不需要函数调用的开销;声明为内联的方法是在函数前加inlien关键字,一般放在头文件。

4 任何类内部定义的函数,自动地成为内联函数。

5 当编译器遇到一个内联函数:对于任何函数,编译器在它的符号表里放入函数类型(名字,参数类型,返回类型),当编译器看到内联函数和对内联函数体进行分析没发现错误时,就将对应于函数体的代码也放入符号表,调用时带入即可。

6 太复杂的代码和需要显示或隐性取函数地址的代码都不能执行内联。

7 c++规定,只有在类声明结束后其中的内联才会被计算。

第十章 名字控制


1 静态对象存储在程序的静态数据区,如果没有为内部类型的静态变量提供一个初始值的话,编译器会确保在开始时为它初始化为零;所有全局对象都是隐含静态存储的。

2 静态对象的析构函数是在程序从main中退出时,或者标准的函数exit被调用时才被调用,多数情况下main函数的结尾也是调用exit来结束程序的;这意味着析构函数内部使用exit是很危险的,会导致无穷的递归;可以用标准的c库函数atexit来制定当程序跳出main或调用exit之前应执行的操作,atexit注册的函数可以在所有对象的析构函数之前被调用。

3 namespace只能在全局范围内使用,但他们可以互相嵌套;namespace定义的结尾,右花括号的后面不必跟一个分号;namespace可以再多个头文件中用一个标识符来定义,就好像重复定义一个类一样;namespace的名字可以用另外一个名字做他的别名(namespace a=std);可以使用未命名的名字空间,但一个翻译单元只能有一个。

4 可以使用声明(using declaration)一次性引入名字到当前范围内;这种方法不像使用指令(using directive)那样吧名字档次当前范围内的全局名来看待,using声明时再当前范围之内进行的一个声明,这意味着在这个范围内可以不顾来着using指令的名字。

//: C10:UsingDeclaration.h
#ifndef USINGDECLARATION_H
#define USINGDECLARATION_H
namespace U {
  inline void f() {}
  inline void g() {}
}
namespace V {
  inline void f() {}
  inline void g() {}
} 
#endif // USINGDECLARATION_H ///:~
//: C10:UsingDeclaration1.cpp
#include "UsingDeclaration.h"
void h() {
  using namespace U; // Using directive
  using V::f; // Using declaration
  f(); // Calls V::f();
  U::f(); // Must fully qualify to call
}
int main() {} ///:~

using声明给出了标识符完整的名字,但没有类型方面的信息,也就是说,using声明可以一次性声明相同名字重载的函数;另外,using声明可以引起一个函数用相同的类型来重载(但在使用时会有不确定性)

5 静态数据成员的定义必须在类的外部而且只能定义一次 int A::i =1;。

6 静态成员函数为类的全体对象服务而不是为类的特殊对象服务,可以用普通的方法调用静态成员函数,更典型的方法是自我调用: X::f(); ;静态成员函数不能访问一般的数据成员,只能访问静态数据成员,也只能调用其他的静态成员函数,静态成员函数没有this指针。

7 将类的一个静态数据成员放到类的内部,并把构造函数变为私有,这样就是设计模式中的单件模式,如下例中Egg类只有一个唯一的对象存在。

//: C10:Singleton.cpp
// Static member of same type, ensures that
// only one object of this type exists.
// Also referred to as the "singleton" pattern.
#include <iostream>
using namespace std;

class Egg {
  static Egg e;
  int i;
  Egg(int ii) : i(ii) {}
  Egg(const Egg&); // Prevent copy-construction
public:
  static Egg* instance() { return &e; }
  int val() const { return i; }
};

Egg Egg::e(47);

int main() {
//!  Egg x(1); // Error -- can't create an Egg
  // You can access the single instance:
  cout << Egg::instance()->val() << endl;
} ///:~

为了完全繁殖创建其他对象,还需要增加一个拷贝构造函数的私有函数,否则还可以进行Egg e=*Egg::instance() 或者 Egg e(*Egg::instance())进行创建

8 静态初始化的相依性:如果静态对象位于不同的文件中不能确定这些静态对象的初始化顺序,如果具有相依性,则会引起问题,解决的方法有:

一、不用它或者如果实在要用就把关键的静态对象的定义放在一个头文件中,保证以正确的顺序初始化

二、如果确信把静态对象放在几个不同的翻译单元中是不可避免的——如在编写一个库时,则可以用以下两个技术

A 在库的头文件中加上一个额外的类,这个类负责静态对象的动态初始化,如下面的简单例子:

//: C10:Initializer.h
// Static initialization technique
#ifndef INITIALIZER_H
#define INITIALIZER_H
#include <iostream>
extern int x; // Declarations, not definitions
extern int y;

class Initializer {
  static int initCount;
public:
  Initializer() {
    std::cout << "Initializer()" << std::endl;
    // Initialize first time only
    if(initCount++ == 0) {
      std::cout << "performing initialization"
                << std::endl;
      x = 100;
      y = 200;
    }
  }
  ~Initializer() {
    std::cout << "~Initializer()" << std::endl;
    // Clean up last time only
    if(--initCount == 0) {
      std::cout << "performing cleanup" 
                << std::endl;
      // Any necessary cleanup here
    }
  }
};
// The following creates one object in each
// file where Initializer.h is included, but that
// object is only visible within that file:
static Initializer init;
#endif // INITIALIZER_H ///:~

当第一次包含Initializer.h的翻译单元被初始化时,initCount为零,这时初始化已完成,对其余的翻译单元,initCount不会为零,并忽略其初始化。

B 第二个技术基于这样的事实:函数内部的静态对象,在函数第一次被调用时初始化,且只初始化一次,由此我们可以将静态对象用函数包装:

//: C10:Technique2.cpp
#include "Dependency2.h"
using namespace std;

// Simulate the dependency problem:
extern Dependency1 dep1;
Dependency2 dep2(dep1);
Dependency1 dep1;

// But if it happens in this order it works OK:
Dependency1 dep1b;
Dependency2 dep2b(dep1b);

// Wrapping static objects in functions succeeds
Dependency1& d1() {
  static Dependency1 dep1;
  return dep1;
}

Dependency2& d2() {
  static Dependency2 dep2(d1());
  return dep2;
}

int main() {
  Dependency2& dep2 = d2();
} ///:~

这样dep1,dep2 就不会出现初始化顺序的问题

9 如果在c++中编写一个程序需要c的库,可以用替代连接说明来实现: extern “C” { #include "xxx.h"} 、extern "C" float f(int a)、extern “C” {float f(); float g(int a ,int b);}

第十一章 引用和拷贝构造函数


1 当引用被创建时,必须初始化,且一旦一个引用被初始化为指向一个对象,它就不能改变为另一个对象的引用,不可以有NULL引用。

2 拷贝构造函数,它常被称为X(X&)(X引用的X),在函数调用时,这个构造函数式控制通过传值方式传递和返回用户定义类型的根本所在。

3 如果设计了拷贝构造函数,当从现有的对象创建新对象时,编译器将不使用位拷贝,编译器总是调用我们的拷贝构造函数。

4 如果我们加入了拷贝构造函数,我们就告诉了编译器我们将自己处理构造函数的创建,编译器将不再创建默认的构造函数,并且除非我们显式的创建一个默认的构造函数,如同下例中为WithCC所做的那样,否则将会出错。

//: C11:DefaultCopyConstructor.cpp
// Automatic creation of the copy-constructor
#include <iostream>
#include <string>
using namespace std;

class WithCC { // With copy-constructor
public:
  // Explicit default constructor required:
  WithCC() {}
  WithCC(const WithCC&) {
    cout << "WithCC(WithCC&)" << endl;
  }
};

class WoCC { // Without copy-constructor
  string id;
public:
  WoCC(const string& ident = "") : id(ident) {}
  void print(const string& msg = "") const {
    if(msg.size() != 0) cout << msg << ": ";
    cout << id << endl;
  }
};

class Composite {
  WithCC withcc; // Embedded objects
  WoCC wocc;
public:
  Composite() : wocc("Composite()") {}
  void print(const string& msg = "") const {
    wocc.print(msg);
  }
};

int main() {
  Composite c;
  c.print("Contents of c");
  cout << "Calling Composite copy-constructor"
       << endl;
  Composite c2 = c;  // Calls copy-constructor
  c2.print("Contents of c2");
} ///:~

为了对使用组合(和继承的方法)的类创建拷贝构造函数,编译器递归地为所有的成员对象和基类调用拷贝构造函数,如果成员对象还有别的对象,那么后者的拷贝构造函数也将被调用。

5 默认的是拷贝构造函数是按位拷贝;仅当准备用按值传递的方式传递类对象时,才需要拷贝构造函数。

6 声明一个私有的拷贝构造函数可以防止按值传递方式传递。

7 下面的例子说明了如何使用指向数据成员的指针,成员指针的语法要求选择一个对象的同时间接引用成员指针:选择一个类的成员意味着类中偏移,需要把这个偏移和具体对象的开始地址结合。

//: C11:PointerToMemberData.cpp
#include <iostream>
using namespace std;

class Data {
public:  
  int a, b, c; 
  void print() const {
    cout << "a = " << a << ", b = " << b
         << ", c = " << c << endl;
  }
};

int main() {
  Data d, *dp = &d;
  int Data::*pmInt = &Data::a;
  dp->*pmInt = 47;
  pmInt = &Data::b;
  d.*pmInt = 48;
  pmInt = &Data::c;
  dp->*pmInt = 49;
  dp->print();
} ///:~

8 通过给普通函数插入类名和作用域运算符就可以定义一个指向成员函数的指针,其可以在创建时初始化,也可以在任何其他时候;不像非成员函数,当获取成员函数地址时,&符号是必须的。

//: C11:PmemFunDefinition.cpp
class Simple2 { 
public: 
  int f(float) const { return 1; }
};
int (Simple2::*fp)(float) const;
int (Simple2::*fp2)(float) const = &Simple2::f;
int main() {
  fp = &Simple2::f;
} ///:~

9 程序运行时,通过改变指向所指向的内容可以选择可改变我们的行为,成员指针也如此,允许运行时选择一个成员,注意下面例子所用语法。

//: C11:PointerToMemberFunction2.cpp
#include <iostream>
using namespace std;

class Widget {
  void f(int) const { cout << "Widget::f()\n"; }
  void g(int) const { cout << "Widget::g()\n"; }
  void h(int) const { cout << "Widget::h()\n"; }
  void i(int) const { cout << "Widget::i()\n"; }
  enum { cnt = 4 };
  void (Widget::*fptr[cnt])(int) const;
public:
  Widget() {
    fptr[0] = &Widget::f; // Full spec required
    fptr[1] = &Widget::g; 
    fptr[2] = &Widget::h;
    fptr[3] = &Widget::i;
  }
  void select(int i, int j) {
    if(i < 0 || i >= cnt) return;
    (this->*fptr[i])(j); // 当被间接引用时,语法也需要 成员指针总是和一个对象绑定在一起
  }
  int count() { return cnt; }
};

int main() {
  Widget w;
  for(int i = 0; i < w.count(); i++)
    w.select(i, 47);
} ///:~

第十二章 运算符重载


1 运算符的定义类似一个普通函数的定义,只是函数的名字由关键字operator及其后紧跟的运算符组成。 2 函数参数表中参数的个数取决于: 一、运算符是一元的还是二元的;二、运算符被定义为全局的还是成员的两个因素。

3 一元运算符重载的实例如下,注意Integer类使用的是全局运算符重载,byte使用的是成员运算符重载。

//: C12:OverloadingUnaryOperators.cpp
#include <iostream>
using namespace std;

// Non-member functions:
class Integer {
  long i;
  Integer* This() { return this; }
public:
  Integer(long ll = 0) : i(ll) {}
  // No side effects takes const& argument:
  friend const Integer&
    operator+(const Integer& a);
  friend const Integer
    operator-(const Integer& a);
  friend const Integer
    operator~(const Integer& a);
  friend Integer*
    operator&(Integer& a);
  friend int
    operator!(const Integer& a);
  // Side effects have non-const& argument:
  // Prefix:
  friend const Integer&
    operator++(Integer& a);
  // Postfix:
  friend const Integer
    operator++(Integer& a, int);
  // Prefix:
  friend const Integer&
    operator--(Integer& a);
  // Postfix:
  friend const Integer
    operator--(Integer& a, int);
};

// Global operators:
const Integer& operator+(const Integer& a) {
  cout << "+Integer\n";
  return a; // Unary + has no effect
}
const Integer operator-(const Integer& a) {
  cout << "-Integer\n";
  return Integer(-a.i);
}
const Integer operator~(const Integer& a) {
  cout << "~Integer\n";
  return Integer(~a.i);
}
Integer* operator&(Integer& a) {
  cout << "&Integer\n";
  return a.This(); // &a is recursive!
}
int operator!(const Integer& a) {
  cout << "!Integer\n";
  return !a.i;
}
// Prefix; return incremented value
const Integer& operator++(Integer& a) {
  cout << "++Integer\n";
  a.i++;
  return a;
}
// Postfix; return the value before increment:
const Integer operator++(Integer& a, int) {
  cout << "Integer++\n";
  Integer before(a.i);
  a.i++;
  return before;
}
// Prefix; return decremented value
const Integer& operator--(Integer& a) {
  cout << "--Integer\n";
  a.i--;
  return a;
}
// Postfix; return the value before decrement:
const Integer operator--(Integer& a, int) {
  cout << "Integer--\n";
  Integer before(a.i);
  a.i--;
  return before;
}

// Show that the overloaded operators work:
void f(Integer a) {
  +a;
  -a;
  ~a;
  Integer* ip = &a;
  !a;
  ++a;
  a++;
  --a;
  a--;
}

// Member functions (implicit "this"):
class Byte {
  unsigned char b;
public:
  Byte(unsigned char bb = 0) : b(bb) {}
  // No side effects: const member function:
  const Byte& operator+() const {
    cout << "+Byte\n";
    return *this;
  }
  const Byte operator-() const {
    cout << "-Byte\n";
    return Byte(-b);
  }
  const Byte operator~() const {
    cout << "~Byte\n";
    return Byte(~b);
  }
  Byte operator!() const {
    cout << "!Byte\n";
    return Byte(!b);
  }
  Byte* operator&() {
    cout << "&Byte\n";
    return this;
  }
  // Side effects: non-const member function:
  const Byte& operator++() { // Prefix
    cout << "++Byte\n";
    b++;
    return *this;
  }
  const Byte operator++(int) { // Postfix
    cout << "Byte++\n";
    Byte before(b);
    b++;
    return before;
  }
  const Byte& operator--() { // Prefix
    cout << "--Byte\n";
    --b;
    return *this;
  }
  const Byte operator--(int) { // Postfix
    cout << "Byte--\n";
    Byte before(b);
    --b;
    return before;
  }
};

void g(Byte b) {
  +b;
  -b;
  ~b;
  Byte* bp = &b;
  !b;
  ++b;
  b++;
  --b;
  b--;
}

int main() {
  Integer a;
  f(a);
  Byte b;
  g(b);
} ///:~

4 二元运算符的重载。

//: C12:Integer.h
// Non-member overloaded operators
#ifndef INTEGER_H
#define INTEGER_H
#include <iostream>

// Non-member functions:
class Integer { 
  long i;
public:
  Integer(long ll = 0) : i(ll) {}
  // Operators that create new, modified value:
  friend const Integer
    operator+(const Integer& left,
              const Integer& right);
  friend const Integer
    operator-(const Integer& left,
              const Integer& right);
  friend const Integer
    operator*(const Integer& left,
              const Integer& right);
  friend const Integer
    operator/(const Integer& left,
              const Integer& right);
  friend const Integer
    operator%(const Integer& left,
              const Integer& right);
  friend const Integer
    operator^(const Integer& left,
              const Integer& right);
  friend const Integer
    operator&(const Integer& left,
              const Integer& right);
  friend const Integer
    operator|(const Integer& left,
              const Integer& right);
  friend const Integer
    operator<<(const Integer& left,
               const Integer& right);
  friend const Integer
    operator>>(const Integer& left,
               const Integer& right);
  // Assignments modify & return lvalue:
  friend Integer&
    operator+=(Integer& left,
               const Integer& right);
  friend Integer&
    operator-=(Integer& left,
               const Integer& right);
  friend Integer&
    operator*=(Integer& left,
               const Integer& right);
  friend Integer&
    operator/=(Integer& left,
               const Integer& right);
  friend Integer&
    operator%=(Integer& left,
               const Integer& right);
  friend Integer&
    operator^=(Integer& left,
               const Integer& right);
  friend Integer&
    operator&=(Integer& left,
               const Integer& right);
  friend Integer&
    operator|=(Integer& left,
               const Integer& right);
  friend Integer&
    operator>>=(Integer& left,
                const Integer& right);
  friend Integer&
    operator<<=(Integer& left,
                const Integer& right);
  // Conditional operators return true/false:
  friend int
    operator==(const Integer& left,
               const Integer& right);
  friend int
    operator!=(const Integer& left,
               const Integer& right);
  friend int
    operator<(const Integer& left,
              const Integer& right);
  friend int
    operator>(const Integer& left,
              const Integer& right);
  friend int
    operator<=(const Integer& left,
               const Integer& right);
  friend int
    operator>=(const Integer& left,
               const Integer& right);
  friend int
    operator&&(const Integer& left,
               const Integer& right);
  friend int
    operator||(const Integer& left,
               const Integer& right);
  // Write the contents to an ostream:
  void print(std::ostream& os) const { os << i; }
}; 
#endif // INTEGER_H ///:~
//: C12:Integer.cpp {O}
// Implementation of overloaded operators
#include "Integer.h"
#include "../require.h"

const Integer
  operator+(const Integer& left,
            const Integer& right) {
  return Integer(left.i + right.i);
}
const Integer
  operator-(const Integer& left,
            const Integer& right) {
  return Integer(left.i - right.i);
}
const Integer
  operator*(const Integer& left,
            const Integer& right) {
  return Integer(left.i * right.i);
}
const Integer
  operator/(const Integer& left,
            const Integer& right) {
  require(right.i != 0, "divide by zero");
  return Integer(left.i / right.i);
}
const Integer
  operator%(const Integer& left,
            const Integer& right) {
  require(right.i != 0, "modulo by zero");
  return Integer(left.i % right.i);
}
const Integer
  operator^(const Integer& left,
            const Integer& right) {
  return Integer(left.i ^ right.i);
}
const Integer
  operator&(const Integer& left,
            const Integer& right) {
  return Integer(left.i & right.i);
}
const Integer
  operator|(const Integer& left,
            const Integer& right) {
  return Integer(left.i | right.i);
}
const Integer
  operator<<(const Integer& left,
             const Integer& right) {
  return Integer(left.i << right.i);
}
const Integer
  operator>>(const Integer& left,
             const Integer& right) {
  return Integer(left.i >> right.i);
}
// Assignments modify & return lvalue:
Integer& operator+=(Integer& left,
                    const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i += right.i;
   return left;
}
Integer& operator-=(Integer& left,
                    const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i -= right.i;
   return left;
}
Integer& operator*=(Integer& left,
                    const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i *= right.i;
   return left;
}
Integer& operator/=(Integer& left,
                    const Integer& right) {
   require(right.i != 0, "divide by zero");
   if(&left == &right) {/* self-assignment */}
   left.i /= right.i;
   return left;
}
Integer& operator%=(Integer& left,
                    const Integer& right) {
   require(right.i != 0, "modulo by zero");
   if(&left == &right) {/* self-assignment */}
   left.i %= right.i;
   return left;
}
Integer& operator^=(Integer& left,
                    const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i ^= right.i;
   return left;
}
Integer& operator&=(Integer& left,
                    const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i &= right.i;
   return left;
}
Integer& operator|=(Integer& left,
                    const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i |= right.i;
   return left;
}
Integer& operator>>=(Integer& left,
                     const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i >>= right.i;
   return left;
}
Integer& operator<<=(Integer& left,
                     const Integer& right) {
   if(&left == &right) {/* self-assignment */}
   left.i <<= right.i;
   return left;
}
// Conditional operators return true/false:
int operator==(const Integer& left,
               const Integer& right) {
    return left.i == right.i;
}
int operator!=(const Integer& left,
               const Integer& right) {
    return left.i != right.i;
}
int operator<(const Integer& left,
              const Integer& right) {
    return left.i < right.i;
}
int operator>(const Integer& left,
              const Integer& right) {
    return left.i > right.i;
}
int operator<=(const Integer& left,
               const Integer& right) {
    return left.i <= right.i;
}
int operator>=(const Integer& left,
               const Integer& right) {
    return left.i >= right.i;
}
int operator&&(const Integer& left,
               const Integer& right) {
    return left.i && right.i;
}
int operator||(const Integer& left,
               const Integer& right) {
    return left.i || right.i;
} ///:~
//: C12:IntegerTest.cpp
//{L} Integer
#include "Integer.h"
#include <fstream>
using namespace std;
ofstream out("IntegerTest.out");

void h(Integer& c1, Integer& c2) {
  // A complex expression:
  c1 += c1 * c2 + c2 % c1;
  #define TRY(OP) \
    out << "c1 = "; c1.print(out); \
    out << ", c2 = "; c2.print(out); \
    out << ";  c1 " #OP " c2 produces "; \
    (c1 OP c2).print(out); \
    out << endl;
  TRY(+) TRY(-) TRY(*) TRY(/)
  TRY(%) TRY(^) TRY(&) TRY(|)
  TRY(<<) TRY(>>) TRY(+=) TRY(-=)
  TRY(*=) TRY(/=) TRY(%=) TRY(^=)
  TRY(&=) TRY(|=) TRY(>>=) TRY(<<=)
  // Conditionals:
  #define TRYC(OP) \
    out << "c1 = "; c1.print(out); \
    out << ", c2 = "; c2.print(out); \
    out << ";  c1 " #OP " c2 produces "; \
    out << (c1 OP c2); \
    out << endl;
  TRYC(<) TRYC(>) TRYC(==) TRYC(!=) TRYC(<=)
  TRYC(>=) TRYC(&&) TRYC(||)
} 

int main() {
  cout << "friend functions" << endl;
  Integer c1(47), c2(9);
  h(c1, c2);
} ///:~

//: C12:Byte.h
// Member overloaded operators
#ifndef BYTE_H
#define BYTE_H
#include "../require.h"
#include <iostream>
// Member functions (implicit "this"):
class Byte { 
  unsigned char b;
public:
  Byte(unsigned char bb = 0) : b(bb) {}
  // No side effects: const member function:
  const Byte
    operator+(const Byte& right) const {
    return Byte(b + right.b);
  }
  const Byte
    operator-(const Byte& right) const {
    return Byte(b - right.b);
  }
  const Byte
    operator*(const Byte& right) const {
    return Byte(b * right.b);
  }
  const Byte
    operator/(const Byte& right) const {
    require(right.b != 0, "divide by zero");
    return Byte(b / right.b);
  }
  const Byte
    operator%(const Byte& right) const {
    require(right.b != 0, "modulo by zero");
    return Byte(b % right.b);
  }
  const Byte
    operator^(const Byte& right) const {
    return Byte(b ^ right.b);
  }
  const Byte
    operator&(const Byte& right) const {
    return Byte(b & right.b);
  }
  const Byte
    operator|(const Byte& right) const {
    return Byte(b | right.b);
  }
  const Byte
    operator<<(const Byte& right) const {
    return Byte(b << right.b);
  }
  const Byte
    operator>>(const Byte& right) const {
    return Byte(b >> right.b);
  }
  // Assignments modify & return lvalue.
  // operator= can only be a member function:
  Byte& operator=(const Byte& right) {
    // Handle self-assignment:
    if(this == &right) return *this;
    b = right.b;
    return *this;
  }
  Byte& operator+=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b += right.b;
    return *this;
  }
  Byte& operator-=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b -= right.b;
    return *this;
  }
  Byte& operator*=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b *= right.b;
    return *this;
  }
  Byte& operator/=(const Byte& right) {
    require(right.b != 0, "divide by zero");
    if(this == &right) {/* self-assignment */}
    b /= right.b;
    return *this;
  }
  Byte& operator%=(const Byte& right) {
    require(right.b != 0, "modulo by zero");
    if(this == &right) {/* self-assignment */}
    b %= right.b;
    return *this;
  }
  Byte& operator^=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b ^= right.b;
    return *this;
  }
  Byte& operator&=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b &= right.b;
    return *this;
  }
  Byte& operator|=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b |= right.b;
    return *this;
  }
  Byte& operator>>=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b >>= right.b;
    return *this;
  }
  Byte& operator<<=(const Byte& right) {
    if(this == &right) {/* self-assignment */}
    b <<= right.b;
    return *this;
  }
  // Conditional operators return true/false:
  int operator==(const Byte& right) const {
      return b == right.b;
  }
  int operator!=(const Byte& right) const {
      return b != right.b;
  }
  int operator<(const Byte& right) const {
      return b < right.b;
  }
  int operator>(const Byte& right) const {
      return b > right.b;
  }
  int operator<=(const Byte& right) const {
      return b <= right.b;
  }
  int operator>=(const Byte& right) const {
      return b >= right.b;
  }
  int operator&&(const Byte& right) const {
      return b && right.b;
  }
  int operator||(const Byte& right) const {
      return b || right.b;
  }
  // Write the contents to an ostream:
  void print(std::ostream& os) const {
    os << "0x" << std::hex << int(b) << std::dec;
  }
}; 
#endif // BYTE_H ///:~
//: C12:ByteTest.cpp
#include "Byte.h"
#include <fstream>
using namespace std;
ofstream out("ByteTest.out");

void k(Byte& b1, Byte& b2) {
  b1 = b1 * b2 + b2 % b1;

  #define TRY2(OP) \
    out << "b1 = "; b1.print(out); \
    out << ", b2 = "; b2.print(out); \
    out << ";  b1 " #OP " b2 produces "; \
    (b1 OP b2).print(out); \
    out << endl;

  b1 = 9; b2 = 47;
  TRY2(+) TRY2(-) TRY2(*) TRY2(/)
  TRY2(%) TRY2(^) TRY2(&) TRY2(|)
  TRY2(<<) TRY2(>>) TRY2(+=) TRY2(-=)
  TRY2(*=) TRY2(/=) TRY2(%=) TRY2(^=)
  TRY2(&=) TRY2(|=) TRY2(>>=) TRY2(<<=)
  TRY2(=) // Assignment operator

  // Conditionals:
  #define TRYC2(OP) \
    out << "b1 = "; b1.print(out); \
    out << ", b2 = "; b2.print(out); \
    out << ";  b1 " #OP " b2 produces "; \
    out << (b1 OP b2); \
    out << endl;

  b1 = 9; b2 = 47;
  TRYC2(<) TRYC2(>) TRYC2(==) TRYC2(!=) TRYC2(<=)
  TRYC2(>=) TRYC2(&&) TRYC2(||)

  // Chained assignment:
  Byte b3 = 92;
  b1 = b2 = b3;
}

int main() {
  out << "member functions:" << endl;
  Byte b1(47), b2(9);
  k(b1, b2);
} ///:~

5 参数和返回值

一、对于任何函数参数,如果仅需要从参数中读而不改变它,默认地应当作为const引用来传递,当函数是一个类成员函数时就转换为const成员函数;只有会改变左侧参数的运算符赋值如+=,和operator=,左侧参数不是常量,按引用传之。

二、返回值的类型取决于运算符的具体含义,如果结果是产生一个新值,就需要产生一个作为返回值的新对象,例如Integer::operator+ 这个对象作为一个常量通过传值方式返回,作为一个左值结果不会改变。

三、所有赋值运算符的返回值对于左值应该是非常量引用。

四、通过传值方式返回要创建的新对象时,要使用临时对象语法,例如operator+,return Integer(left.i, right.i); 这样编译器会直接把这个对象创建在外部返回值的内存单元,而不是创建一个局部对象。

6 不常用的运算符

下标运算符operator[],必须是成员函数并且值接受一个参数;运算符new和delete。

7 当希望一个对象表现的像一个指针,通常要用到operator->,如果想用类包装一个指针以使指针安全,或是在迭代器普通的用法中,这样做会特别有用(迭代器是一个对象,这个对象可以作用于其他对象的容器或集合上,每次选择他们中的一个,而不用提供对容器的直接访问)。

8 指针间接引用运算符(operator->)一定是一个成员函数它有着额外的、非典型的限制:他必须返回一个对象(或对象的引用),该对象也有一个指针间接引用运算符;或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指向的内容,下面是个简单的例子。

//: C12:SmartPointer.cpp
#include <iostream>
#include <vector>
#include "../require.h"
using namespace std;

class Obj {
  static int i, j;
public:
  void f() const { cout << i++ << endl; }
  void g() const { cout << j++ << endl; }
};

// Static member definitions:
int Obj::i = 47;
int Obj::j = 11;

// Container:
class ObjContainer {
  vector<Obj*> a;
public:
  void add(Obj* obj) { a.push_back(obj); }
  friend class SmartPointer;
};

class SmartPointer {
  ObjContainer& oc;
  int index;
public:
  SmartPointer(ObjContainer& objc) : oc(objc) {
    index = 0;
  }
  // Return value indicates end of list:
  bool operator++() { // Prefix
    if(index >= oc.a.size()) return false;
    if(oc.a[++index] == 0) return false;
    return true;
  }
  bool operator++(int) { // Postfix
    return operator++(); // Use prefix version
  }
  Obj* operator->() const {
    require(oc.a[index] != 0, "Zero value "
      "returned by SmartPointer::operator->()");
    return oc.a[index];
  }
};

int main() {
  const int sz = 10;
  Obj o[sz];
  ObjContainer oc;
  for(int i = 0; i < sz; i++)
    oc.add(&o[i]); // Fill it up
  SmartPointer sp(oc); // Create an iterator
  do {
    sp->f(); // Pointer dereference operator call
    sp->g();
  } while(sp++);
} ///:~

例子中尽管sp实际上没有成员函数f()和g(),但指针间接引用运算符自动地位用SmartPointer::operator-> 返回的Obj*调用那些函数。

更常见的是,“灵巧指针”和“迭代器”类嵌入他所服务的类中,前面的例子可以按以下方式重写,在OjbContainer中嵌入SmartPointer。

//: C12:NestedSmartPointer.cpp
#include <iostream>
#include <vector>
#include "../require.h"
using namespace std;

class Obj {
  static int i, j;
public:
  void f() { cout << i++ << endl; }
  void g() { cout << j++ << endl; }
};

// Static member definitions:
int Obj::i = 47;
int Obj::j = 11;

// Container:
class ObjContainer {
  vector<Obj*> a;
public:
  void add(Obj* obj) { a.push_back(obj); }
  class SmartPointer;
  friend SmartPointer;
  class SmartPointer {
    ObjContainer& oc;
    unsigned int index;
  public:
    SmartPointer(ObjContainer& objc) : oc(objc) {
      index = 0;
    }
    // Return value indicates end of list:
    bool operator++() { // Prefix
      if(index >= oc.a.size()) return false;
      if(oc.a[++index] == 0) return false;
      return true;
    }
    bool operator++(int) { // Postfix
      return operator++(); // Use prefix version
    }
    Obj* operator->() const {
      require(oc.a[index] != 0, "Zero value "
        "returned by SmartPointer::operator->()");
      return oc.a[index];
    }
  };
  // Function to produce a smart pointer that 
  // points to the beginning of the ObjContainer:
  SmartPointer begin() { 
    return SmartPointer(*this);
  }
};

int main() {
  const int sz = 10;
  Obj o[sz];
  ObjContainer oc;
  for(int i = 0; i < sz; i++)
    oc.add(&o[i]); // Fill it up
  ObjContainer::SmartPointer sp = oc.begin();
  do {
    sp->f(); // Pointer dereference operator call
    sp->g();
  } while(++sp);
} ///:~

9 operator->* 是一个二元运算符,它是专门为模仿前一章介绍的内部数据类型的成员指针行为而提供的(对于一个普通函数指针,调用的时候 (fp)(1),这个运算符想要使成员函数指针的调用为(w->fp)(1) ,这就要求operator->*必须返回一个对象,对于这个对象,可以用正在调用的成员函数为参数调用operator(),请注意operator()的调用必须是成员函数,他是唯一的允许在它里面有任意个参数的函数 ),请看下面的例子。

//: C12:PointerToMemberOperator.cpp
#include <iostream>
using namespace std;

class Dog {
public:
  int run(int i) const { 
    cout << "run\n";  
    return i; 
  }
  int eat(int i) const { 
     cout << "eat\n";  
     return i; 
  }
  int sleep(int i) const { 
    cout << "ZZZ\n"; 
    return i; 
  }
  typedef int (Dog::*PMF)(int) const;
  // operator->* must return an object 
  // that has an operator():
  class FunctionObject {
    Dog* ptr;
    PMF pmem;
  public:
    // Save the object pointer and member pointer
    FunctionObject(Dog* wp, PMF pmf) 
      : ptr(wp), pmem(pmf) { 
      cout << "FunctionObject constructor\n";
    }
    // Make the call using the object pointer
    // and member pointer
    int operator()(int i) const {
      cout << "FunctionObject::operator()\n";
      return (ptr->*pmem)(i); // Make the call
    }
  };
  FunctionObject operator->*(PMF pmf) { 
    cout << "operator->*" << endl;
    return FunctionObject(this, pmf);
  }
};

int main() {
  Dog w;
  Dog::PMF pmf = &Dog::run;
  cout << (w->*pmf)(1) << endl;
  pmf = &Dog::sleep;
  cout << (w->*pmf)(2) << endl;
  pmf = &Dog::eat;
  cout << (w->*pmf)(3) << endl;
} ///:~

其中23行的PMF是一个typedef,用于简化定义一个指向Dog成员函数的指向成员的指针,operator->*创建并返回一个FunctionObject对象,注意operator->*既知道指向成员的指针所调用的对象(this),又知道这个指向成员的指针,并把他们传递给存储这些值的FunctionObject构造函数。

10 不能重载的运算符:operator.、operator.* 、没有的运算符(例如求幂operator**)、用户自定义的;不能改变优先级规则。

11 运算符可能是成员运算符或非成员运算符,似乎没多大差异,应该选择哪种?总的来说,如果没有什么差异,他们应该是成员运算符,这样强调了运算符和类的联合,当左侧操作数是当前类的对象时,运算符会工作的很好;但有时左侧运算符是别的对象,这种情况通常出现在输入输出流重载 operator<< 和>>,看下面的例子。

//: C12:IostreamOperatorOverloading.cpp
// Example of non-member overloaded operators
#include "../require.h"
#include <iostream>
#include <sstream> // "String streams"
#include <cstring>
using namespace std;

class IntArray {
  enum { sz = 5 };
  int i[sz];
public:
  IntArray() { memset(i, 0, sz* sizeof(*i)); }
  int& operator[](int x) {
    require(x >= 0 && x < sz,
      "IntArray::operator[] out of range");
    return i[x];
  }
  friend ostream&
    operator<<(ostream& os, const IntArray& ia);
  friend istream&
    operator>>(istream& is, IntArray& ia);
};

ostream& 
operator<<(ostream& os, const IntArray& ia) {
  for(int j = 0; j < ia.sz; j++) {
    os << ia.i[j];
    if(j != ia.sz -1)
      os << ", ";
  }
  os << endl;
  return os;
}

istream& operator>>(istream& is, IntArray& ia){
  for(int j = 0; j < ia.sz; j++)
    is >> ia.i[j];
  return is;
}

int main() {
  stringstream input("47 34 56 92 103");
  IntArray I;
  input >> I;
  I[4] = -1; // Use overloaded operator[]
  cout << I;
} ///:~

47行iostream的一种新类型被使用:stringstream(在中声明),该类包含一个string,并且把它转化为一个iostream。

12 所有的一元运算符和+= -= /= *= ^= &= |= %= >= <<=建议使用成员运算符;= () [] -> ->*必须使用成员;所有其他二元运算符建议使用非成员运算符重载。

13 重载赋值符operator=,注意检查一下自赋值,如下:

DogHouse(Dog* dog, const string& house)
  : p(dog), houseName(house) {}
DogHouse(const DogHouse& dh)
  : p(new Dog(dh.p, " copy-constructed")),
    houseName(dh.houseName 
      + " copy-constructed") {}
DogHouse& operator=(const DogHouse& dh) {
  // Check for self-assignment:
  if(&dh != this) {
    p = new Dog(dh.p, " assigned");
    houseName = dh.houseName + " assigned";
  }
  return *this;
}

例子中拷贝构造函数和operator=对指针所指向的内容做了一个新的拷贝,并有细狗函数删除,但是如果对象需要大量内存或过高的初始化,我们可以用引用计数来解决这个问题,见下例:

//: C12:ReferenceCounting.cpp
// Reference count, copy-on-write
#include "../require.h"
#include <string>
#include <iostream>
using namespace std;

class Dog {
  string nm;
  int refcount;
  Dog(const string& name) 
    : nm(name), refcount(1) {
    cout << "Creating Dog: " << *this << endl;
  }
  // Prevent assignment:
  Dog& operator=(const Dog& rv);
public:
  // Dogs can only be created on the heap:
  static Dog* make(const string& name) {
    return new Dog(name);
  }
  Dog(const Dog& d) 
    : nm(d.nm + " copy"), refcount(1) {
    cout << "Dog copy-constructor: " 
         << *this << endl;
  }
  ~Dog() { 
    cout << "Deleting Dog: " << *this << endl;
  }
  void attach() { 
    ++refcount;
    cout << "Attached Dog: " << *this << endl;
  }
  void detach() {
    require(refcount != 0);
    cout << "Detaching Dog: " << *this << endl;
    // Destroy object if no one is using it:
    if(--refcount == 0) delete this;
  }
  // Conditionally copy this Dog.
  // Call before modifying the Dog, assign
  // resulting pointer to your Dog*.
  Dog* unalias() {
    cout << "Unaliasing Dog: " << *this << endl;
    // Don't duplicate if not aliased:
    if(refcount == 1) return this;
    --refcount;
    // Use copy-constructor to duplicate:
    return new Dog(*this);
  }
  void rename(const string& newName) {
    nm = newName;
    cout << "Dog renamed to: " << *this << endl;
  }
  friend ostream&
  operator<<(ostream& os, const Dog& d) {
    return os << "[" << d.nm << "], rc = " 
      << d.refcount;
  }
};

class DogHouse {
  Dog* p;
  string houseName;
public:
  DogHouse(Dog* dog, const string& house)
   : p(dog), houseName(house) {
    cout << "Created DogHouse: "<< *this << endl;
  }
  DogHouse(const DogHouse& dh)
    : p(dh.p),
      houseName("copy-constructed " + 
        dh.houseName) {
    p->attach();
    cout << "DogHouse copy-constructor: "
         << *this << endl;
  }
  DogHouse& operator=(const DogHouse& dh) {
    // Check for self-assignment:
    if(&dh != this) {
      houseName = dh.houseName + " assigned";
      // Clean up what you're using first:
      p->detach();
      p = dh.p; // Like copy-constructor
      p->attach();
    }
    cout << "DogHouse operator= : "
         << *this << endl;
    return *this;
  }
  // Decrement refcount, conditionally destroy
  ~DogHouse() {
    cout << "DogHouse destructor: " 
         << *this << endl;
    p->detach(); 
  }
  void renameHouse(const string& newName) {
    houseName = newName;
  }
  void unalias() { p = p->unalias(); }
  // Copy-on-write. Anytime you modify the 
  // contents of the pointer you must 
  // first unalias it:
  void renameDog(const string& newName) {
    unalias();
    p->rename(newName);
  }
  // ... or when you allow someone else access:
  Dog* getDog() {
    unalias();
    return p; 
  }
  friend ostream&
  operator<<(ostream& os, const DogHouse& dh) {
    return os << "[" << dh.houseName 
      << "] contains " << *dh.p;
  }
}; 

int main() {
  DogHouse 
    fidos(Dog::make("Fido"), "FidoHouse"),
    spots(Dog::make("Spot"), "SpotHouse");
  cout << "Entering copy-construction" << endl;
  DogHouse bobs(fidos);
  cout << "After copy-constructing bobs" << endl;
  cout << "fidos:" << fidos << endl;
  cout << "spots:" << spots << endl;
  cout << "bobs:" << bobs << endl;
  cout << "Entering spots = fidos" << endl;
  spots = fidos;
  cout << "After spots = fidos" << endl;
  cout << "spots:" << spots << endl;
  cout << "Entering self-assignment" << endl;
  bobs = bobs;
  cout << "After self-assignment" << endl;
  cout << "bobs:" << bobs << endl;
  // Comment out the following lines:
  cout << "Entering rename(\"Bob\")" << endl;
  bobs.getDog()->rename("Bob");
  cout << "After rename(\"Bob\")" << endl;
} ///:~

函数attach增加一个Dog引用记录,detach减少一个,如果没有对象引用(0),则delete this;在进行修改前,必须保证所修改的Dog没有被别的使用,可以调用DogHouse::unalias(),它又进而调用Dog::unalias()来做到这14点(如果引用为1,则返回Dog指针,如果大于1,则就要赋值这个Dog) operator=处理等号左侧已创建的对象,所以它首先必须通过Dog调用detach来整理这个存储单元;如果没有其他对象使用它,这个老的Dog将被销毁,然后operator=重复拷贝构造函数的行为。

14 自动类型转换

一、第一种是使用构造函数转换(目的类执行转换),可以使用explicit关键字来组织构造函数转换,如下

//: C12:ExplicitKeyword.cpp
// Using the "explicit" keyword
class One {
public:
  One() {}
};

class Two {
public:
  explicit Two(const One&) {}
};

void f(Two) {}

int main() {
  One one;
//!  f(one); // No auto conversion allowed
  f(Two(one)); // OK -- user performs conversion
} ///:~

去掉explicit会进行自动转换

二、通过运算符重载(源类执行转换),可以创建一个成员函数,这个函数通过在关键字operator后跟随想要转换到的类型的方法,将当前类型转换为希望的。

//: C12:OperatorOverloadingConversion.cpp
class Three {
  int i;
public:
  Three(int ii = 0, int = 0) : i(ii) {}
};

class Four {
  int x;
public:
  Four(int xx) : x(xx) {}
  operator Three() const { return Three(x); }
};

void g(Three) {}

int main() {
  Four four(1);
  g(four);
  g(1);  // Calls Three(1,0)
} ///:~

使用构造函数技术没办法实现从用户定义类型向内置类型的转换,这只有运算符重载可能做到。

15 当提供不止一种类型的自动转换时,会出现扇出问题(fan-out),如下:

//: C12:TypeConversionFanout.cpp
class Orange {};
class Pear {};

class Apple {
public:
  operator Orange() const;
  operator Pear() const;
};

// Overloaded eat():
void eat(Orange);
void eat(Pear);

int main() {
  Apple c;
//! eat(c);
  // Error: Apple -> Orange or Apple -> Pear ???
} ///:~

第十三章 动态对象创建


1 重载new和delete,使用new和delete的内存分配系统是为通用目的而设计的,在特殊情况下它并不能满足要求,最常见的改变分配系统的原因是出于效率考虑:也许要创建和销毁一个特点的类的非常多的对象以至于这个运算变成了速度的瓶颈,c++允许重载new和delete来实现自己的存储分配方案,重载时可以改变的只是内存分配部分。

第一个例子是重载全局new和delete:

//: C13:GlobalOperatorNew.cpp
// Overload global new/delete
#include <cstdio>
#include <cstdlib>
using namespace std;

void* operator new(size_t sz) {
  printf("operator new: %d Bytes\n", sz);
  void* m = malloc(sz);
  if(!m) puts("out of memory");
  return m;
}

void operator delete(void* m) {
  puts("operator delete");
  free(m);
}

class S {
  int i[100];
public:
  S() { puts("S::S()"); }
  ~S() { puts("S::~S()"); }
};

int main() {
  puts("creating & destroying an int");
  int* p = new int(47);
  delete p;
  puts("creating & destroying an s");
  S* s = new S;
  delete s;
  puts("creating & destroying S[3]");
  S* sa = new S[3];
  delete []sa;
} ///:~

new返回的是使void*,所做的是分配内存,完成一个对象建立是在后面调用了构造函数之后;delete的参数是一个指向有new分配的内存的void*,它是在调用析构函数之后得到的指针

第二个例子是对一个类重载new和delete。

//: C13:Framis.cpp
// Local overloaded new & delete
#include <cstddef> // Size_t
#include <fstream>
#include <iostream>
#include <new>
using namespace std;
ofstream out("Framis.out");

class Framis {
  enum { sz = 10 };
  char c[sz]; // To take up space, not used
  static unsigned char pool[];
  static bool alloc_map[];
public:
  enum { psize = 100 };  // frami allowed
  Framis() { out << "Framis()\n"; }
  ~Framis() { out << "~Framis() ... "; }
  void* operator new(size_t) throw(bad_alloc);
  void operator delete(void*);
};
unsigned char Framis::pool[psize * sizeof(Framis)];
bool Framis::alloc_map[psize] = {false};

// Size is ignored -- assume a Framis object
void* 
Framis::operator new(size_t) throw(bad_alloc) {
  for(int i = 0; i < psize; i++)
    if(!alloc_map[i]) {
      out << "using block " << i << " ... ";
      alloc_map[i] = true; // Mark it used
      return pool + (i * sizeof(Framis));
    }
  out << "out of memory" << endl;
  throw bad_alloc();
}

void Framis::operator delete(void* m) {
  if(!m) return; // Check for null pointer
  // Assume it was created in the pool
  // Calculate which block number it is:
  unsigned long block = (unsigned long)m
    - (unsigned long)pool;
  block /= sizeof(Framis);
  out << "freeing block " << block << endl;
  // Mark it free:
  alloc_map[block] = false;
}

int main() {
  Framis* f[Framis::psize];
  try {
    for(int i = 0; i < Framis::psize; i++)
      f[i] = new Framis;
    new Framis; // Out of memory
  } catch(bad_alloc) {
    cerr << "Out of memory!" << endl;
  }
  delete f[10];
  f[10] = 0;
  // Use released memory:
  Framis* x = new Framis;
  delete x;
  for(int j = 0; j < Framis::psize; j++)
    delete f[j]; // Delete f[10] OK
} ///:~

第三个是为数组重载new和delete,数组版的和单个对象版本的是一样的。

//: C13:ArrayOperatorNew.cpp
// Operator new for arrays
#include <new> // Size_t definition
#include <fstream>
using namespace std;
ofstream trace("ArrayOperatorNew.out");

class Widget {
  enum { sz = 10 };
  int i[sz];
public:
  Widget() { trace << "*"; }
  ~Widget() { trace << "~"; }
  void* operator new(size_t sz) {
    trace << "Widget::new: "
         << sz << " bytes" << endl;
    return ::new char[sz];
  }
  void operator delete(void* p) {
    trace << "Widget::delete" << endl;
    ::delete []p;
  }
  void* operator new[](size_t sz) {
    trace << "Widget::new[]: "
         << sz << " bytes" << endl;
    return ::new char[sz];
  }
  void operator delete[](void* p) {
    trace << "Widget::delete[]" << endl;
    ::delete []p;
  }
};

int main() {
  trace << "new Widget" << endl;
  Widget* w = new Widget;
  trace << "\ndelete Widget" << endl;
  delete w;
  trace << "\nnew Widget[25]" << endl;
  Widget* wa = new Widget[25];
  trace << "\ndelete []Widget" << endl;
  delete []wa;
} ///:~

2 定位new和delete

operator new()还有其他两个不常见的用途:一、在内存的特定位置放置一个对象;二、想在调用new时能够选择不同的内存分配方案;这两个特性可以用相同的机制实现:重载的operator new()可以带一个或多个参数。

下面的例子显示了如何在衣蛾特定的存储单元里放置一个对象。

//: C13:PlacementOperatorNew.cpp
// Placement with operator new
#include <cstddef> // Size_t
#include <iostream>
using namespace std;

class X {
  int i;
public:
  X(int ii = 0) : i(ii) {
    cout << "this = " << this << endl;
  }
  ~X() {
    cout << "X::~X(): " << this << endl;
  }
  void* operator new(size_t, void* loc) {
    return loc;
  }
};

int main() {
  int l[10];
  cout << "l = " << l << endl;
  X* xp = new(l) X(47); // X at location l
  xp->X::~X(); // Explicit destructor call
  // ONLY use with placement!
} ///:~

注意第28行,new后是参数表(没有size_t参数,它由编译器处理),参数表后面是正在创建的对象的类名字;注意29行显示的调用析构函数,这种情况只有在支持operator new()的定位语法(如果用到栈上创建的对象,析构函数会调用两次出现严重错误,如果为展商创建的对象用这种方法,析构函数将被执行但内存不释放)

第十四章 继承和组合


1 构造函数的调用顺序是首先会调用基类构造函数,然后调用成员对象构造函数。

2 任何时候重新定义基类中的一个重载函数(重定义),在新类中所有其他版本则被自动的隐藏;如果基类的成员函数是虚函数,成为重写。

3 构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建,另外operator=也不能被继承;在继承过程中,如果不亲自创建这些函数,编译器会生成他们。

4 一旦决定写自己的拷贝构造函数和赋值运算符,编译器就会假定我们已知道所做的一切,并且不在像在生成的函数那样自动地调用基类版本;而如果想调用基类版本,那我们就必须亲自显式的调用他们。

5 组合(has a , 继承 is a)通常是在希望新类内部具有已存在类的功能时使用,而不是希望已存在类作为它的接口。

6 通过在基类表中去掉public或者通过显式的声明private,可以私有的继承基类;当私有继承是,我们是“照此实现”,也就是说创建的新类具有基类的所有数据和功能,但这些功能时隐藏的,所有他只是部分的内部实现;当私有继承时,基类的所有public成员都变成了private,如果希望他们当中任何一个是可视飞,只需用派生类的public部分声明他们的名字即可:

//: C14:PrivateInheritance.cpp
class Pet {
public:
  char eat() const { return 'a'; }
  int speak() const { return 2; }
  float sleep() const { return 3.0; }
  float sleep(int) const { return 4.0; }
};

class Goldfish : Pet { // Private inheritance
public:
  Pet::eat; // Name publicizes member
  Pet::sleep; // Both overloaded members exposed
};

int main() {
  Goldfish bob;
  bob.eat();
  bob.sleep();
  bob.sleep(1);
//! bob.speak();// Error: private member function
} ///:~

private继承使用的情况比较少(可能想产生像基类接口一样的接口部分,而不允许该对象的处理像一个基类对象),通常希望使用组合而不是private继承。

7 有事希望某些东西隐藏起来,但仍允许其派送类的成员访问,关键字protected就用上了,其意思是:就这个类的用户而言,它是private的,但它可被从这个类继承来的任何类使用;保护继承的派生类对其他类来说是“照此实现”,但它对于派送类和友元是is a,不常用。

8 多重继承:在继承时,只需在基类列表中增加多个类,用逗号隔开。

第十五章 多态性和虚函数


1 面向对象程序设计语言中的三个基本特征是:数据抽象、继承、多态,多态性提供了接口和具体实现之间的另一层隔离,从而将what和how分离开来。

2 虚函数仅仅在声明时需要使用关键字virtual,定义时并不需要;如果一个函数在基类中被声明为virtual,那么所有派生类它都是virtual的,在派生类中virtual函数的重定义常称为重写。

3 典型的编译器对每个包含虚函数的类创建一个表(称为VTABLE),在VTABLE中,编译器放置特定类的虚函数的地址;在每个带有虚函数的类中,编译器秘密的放置一个指针(vpointe人,VPTR),指向这个对象的VTABLE。当通过基类指针做虚函数调用时,编译器静态的插入能取得这个VPTR并在VTABLE表中查找函数地址的代码,这样就可以调用正确的函数并引起晚捆绑的发生。

4 在设计时,如果希望基类仅仅作为其派生类的一个接口,即仅想对基类进行向上的类型转换,使用它的接口但不希望用户实际的创建一个基类的对象,我们可以在类中加入至少一个纯虚函数,来使基类成为抽象类。纯虚函数使用关键字virtual,并且在其后面加上=0(virtual void f()=0;)。

5 当继承一个抽象类时,必须实现所有的纯虚函数,否则继承出的类也是一个抽象类。

6 如果对一个对象进行向上类型转换,而不使用地址或引用,就会出现对象切片。

7 我们不能在重新定义过程中修改虚函数的返回类型,但也有特例。

//: C15:VariantReturn.cpp
// Returning a pointer or reference to a derived
// type during ovverriding
#include <iostream>
#include <string>
using namespace std;

class PetFood {
public:
  virtual string foodType() const = 0;
};

class Pet {
public:
  virtual string type() const = 0;
  virtual PetFood* eats() = 0;
};

class Bird : public Pet {
public:
  string type() const { return "Bird"; }
  class BirdFood : public PetFood {
  public:
    string foodType() const { 
      return "Bird food"; 
    }
  };
  // Upcast to base type:
  PetFood* eats() { return &bf; }
private:
  BirdFood bf;
};

class Cat : public Pet {
public:
  string type() const { return "Cat"; }
  class CatFood : public PetFood {
  public:
    string foodType() const { return "Birds"; }
  };
  // Return exact type instead:
  CatFood* eats() { return &cf; }
private:
  CatFood cf;
};

int main() {
  Bird b; 
  Cat c;
  Pet* p[] = { &b, &c, };
  for(int i = 0; i < sizeof p / sizeof *p; i++)
    cout << p[i]->type() << " eats "
         << p[i]->eats()->foodType() << endl;
  // Can return the exact type:
  Cat::CatFood* cf = c.eats();
  Bird::BirdFood* bf;
  // Cannot return the exact type:
//!  bf = b.eats();
  // Must downcast:
  bf = dynamic_cast<Bird::BirdFood*>(b.eats());
} ///:~

在Cat中,eats的返回类型是指向CatFood的指针,而CatFood是派生于PetFood,返回类型是从基类函数的返回类型继承而来的,合约仍被遵守。

8 构造函数不能为虚函数,但析构函数能够且常常必须是虚函数(如下例,delete bp只调用基类的析构函数,而delete b2p基类和派生类的都会执行)。

//: C15:VirtualDestructors.cpp
// Behavior of virtual vs. non-virtual destructor
#include <iostream>
using namespace std;

class Base1 {
public:
  ~Base1() { cout << "~Base1()\n"; }
};

class Derived1 : public Base1 {
public:
  ~Derived1() { cout << "~Derived1()\n"; }
};

class Base2 {
public:
  virtual ~Base2() { cout << "~Base2()\n"; }
};

class Derived2 : public Base2 {
public:
  ~Derived2() { cout << "~Derived2()\n"; }
};

int main() {
  Base1* bp = new Derived1; // Upcast
  delete bp;
  Base2* b2p = new Derived2; // Upcast
  delete b2p;
} ///:~

9 纯虚析构函数在c++中是合法的,但在使用时有一个额外的限制:必须为纯虚函数提供一个函数体。

//: C15:PureVirtualDestructors.cpp
// Pure virtual destructors
// require a function body
#include <iostream>
using namespace std;

class Pet {
public:
  virtual ~Pet() = 0;
};

Pet::~Pet() {
  cout << "~Pet()" << endl;
}

class Dog : public Pet {
public:
  ~Dog() {
    cout << "~Dog()" << endl;
  }
};

int main() {
  Pet* p = new Dog; // Upcast
  delete p; // Virtual destructor call
} ///:~

10 在析构函数中,只有成员函数的“本地版本”被调用。

11 typeid关键字是用来检测指针类型的。

第十六章 模板介绍


1 模板参数不局限于类定义的类型,可以使用内置类型;这些参数值在编译期间变成模板的特定示例的常量,我们甚至可以对这些参数使用默认值。

//: C16:Array3.cpp
// Built-in types as template arguments
#include "../require.h"
#include <iostream>
using namespace std;

template<class T, int size = 100>
class Array {
  T array[size];
public:
  T& operator[](int index) {
    require(index >= 0 && index < size,
      "Index out of range");
    return array[index];
  }
  int length() const { return size; }
};

class Number {
  float f;
public:
  Number(float ff = 0.0f) : f(ff) {}
  Number& operator=(const Number& n) {
    f = n.f;
    return *this;
  }
  operator float() const { return f; }
  friend ostream&
    operator<<(ostream& os, const Number& x) {
      return os << x.f;
  }
};

template<class T, int size = 20>
class Holder {
  Array<T, size>* np;
public:
  Holder() : np(0) {}
  T& operator[](int i) {
    require(0 <= i && i < size);
    if(!np) np = new Array<T, size>;
    return np->operator[](i);
  }
  int length() const { return size; }
  ~Holder() { delete np; }
};

int main() {
  Holder<Number> h;
  for(int i = 0; i < 20; i++)
    h[i] = i;
  for(int j = 0; j < 20; j++)
    cout << h[j] << endl;
} ///:~

此例使用了懒惰初始化:Array是被检查的对象数组,并且防止下标越界,类Holder很想Array,只是它有一个指向Array的指针,而不是指向类型Array的嵌入对象,该指针在构造函数中不进行初始化,而是推迟到了第一次访问。

2 两个例子

一、带有迭代器的Stack和Ptash

//: C16:TStack2.h
// Templatized Stack with nested iterator
#ifndef TSTACK2_H
#define TSTACK2_H

template<class T> class Stack {
  struct Link {
    T* data;
    Link* next;
    Link(T* dat, Link* nxt)
      : data(dat), next(nxt) {}
  }* head;
public:
  Stack() : head(0) {}
  ~Stack();
  void push(T* dat) {
    head = new Link(dat, head);
  }
  T* peek() const { 
    return head ? head->data : 0;
  }
  T* pop();
  // Nested iterator class:
  class iterator; // Declaration required
  friend class iterator; // Make it a friend
  class iterator { // Now define it
    Stack::Link* p;
  public:
    iterator(const Stack<T>& tl) : p(tl.head) {}
    // Copy-constructor:
    iterator(const iterator& tl) : p(tl.p) {}
    // The end sentinel iterator:
    iterator() : p(0) {}
    // operator++ returns boolean indicating end:
    bool operator++() {
      if(p->next)
        p = p->next;
      else p = 0; // Indicates end of list
      return bool(p);
    }
    bool operator++(int) { return operator++(); }
    T* current() const {
      if(!p) return 0;
      return p->data;
    }
    // Pointer dereference operator:
    T* operator->() const { 
      require(p != 0, 
        "PStack::iterator::operator->returns 0");
      return current(); 
    }
    T* operator*() const { return current(); }
    // bool conversion for conditional test:
    operator bool() const { return bool(p); }
    // Comparison to test for end:
    bool operator==(const iterator&) const {
      return p == 0;
    }
    bool operator!=(const iterator&) const {
      return p != 0;
    }
  };
  iterator begin() const { 
    return iterator(*this); 
  }
  iterator end() const { return iterator(); }
};

template<class T> Stack<T>::~Stack() {
  while(head)
    delete pop();
}

template<class T> T* Stack<T>::pop() {
  if(head == 0) return 0;
  T* result = head->data;
  Link* oldHead = head;
  head = head->next;
  delete oldHead;
  return result;
}
#endif // TSTACK2_H ///:~
//: C16:TStack2Test.cpp
#include "TStack2.h"
#include "../require.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
  ifstream file("TStack2Test.cpp");
  assure(file, "TStack2Test.cpp");
  Stack<string> textlines;
  // Read file and store lines in the Stack:
  string line;
  while(getline(file, line))
    textlines.push(new string(line));
  int i = 0;
  // Use iterator to print lines from the list:
  Stack<string>::iterator it = textlines.begin();
  Stack<string>::iterator* it2 = 0;
  while(it != textlines.end()) {
    cout << it->c_str() << endl;
    it++;
    if(++i == 10) // Remember 10th line
      it2 = new Stack<string>::iterator(it);
  }
  cout << (*it2)->c_str() << endl;
  delete it2;
} ///:~
//: C16:TPStash2.h
// Templatized PStash with nested iterator
#ifndef TPSTASH2_H
#define TPSTASH2_H
#include "../require.h"
#include <cstdlib>

template<class T, int incr = 20>
class PStash {
  int quantity;
  int next;
  T** storage;
  void inflate(int increase = incr);
public:
  PStash() : quantity(0), storage(0), next(0) {}
  ~PStash();
  int add(T* element);
  T* operator[](int index) const;
  T* remove(int index);
  int count() const { return next; }
  // Nested iterator class:
  class iterator; // Declaration required
  friend class iterator; // Make it a friend
  class iterator { // Now define it
    PStash& ps;
    int index;
  public:
    iterator(PStash& pStash)
      : ps(pStash), index(0) {}
    // To create the end sentinel:
    iterator(PStash& pStash, bool)
      : ps(pStash), index(ps.next) {}
    // Copy-constructor:
    iterator(const iterator& rv)
      : ps(rv.ps), index(rv.index) {}
    iterator& operator=(const iterator& rv) {
      ps = rv.ps;
      index = rv.index;
      return *this;
    }
    iterator& operator++() {
      require(++index <= ps.next,
        "PStash::iterator::operator++ "
        "moves index out of bounds");
      return *this;
    }
    iterator& operator++(int) {
      return operator++();
    }
    iterator& operator--() {
      require(--index >= 0,
        "PStash::iterator::operator-- "
        "moves index out of bounds");
      return *this;
    }
    iterator& operator--(int) { 
      return operator--();
    }
    // Jump interator forward or backward:
    iterator& operator+=(int amount) {
      require(index + amount < ps.next && 
        index + amount >= 0, 
        "PStash::iterator::operator+= "
        "attempt to index out of bounds");
      index += amount;
      return *this;
    }
    iterator& operator-=(int amount) {
      require(index - amount < ps.next && 
        index - amount >= 0, 
        "PStash::iterator::operator-= "
        "attempt to index out of bounds");
      index -= amount;
      return *this;
    }
    // Create a new iterator that's moved forward
    iterator operator+(int amount) const {
      iterator ret(*this);
      ret += amount; // op+= does bounds check
      return ret;
    }
    T* current() const {
      return ps.storage[index];
    }
    T* operator*() const { return current(); }
    T* operator->() const { 
      require(ps.storage[index] != 0, 
        "PStash::iterator::operator->returns 0");
      return current(); 
    }
    // Remove the current element:
    T* remove(){
      return ps.remove(index);
    }
    // Comparison tests for end:
    bool operator==(const iterator& rv) const {
      return index == rv.index;
    }
    bool operator!=(const iterator& rv) const {
      return index != rv.index;
    }
  };
  iterator begin() { return iterator(*this); }
  iterator end() { return iterator(*this, true);}
};

// Destruction of contained objects:
template<class T, int incr>
PStash<T, incr>::~PStash() {
  for(int i = 0; i < next; i++) {
    delete storage[i]; // Null pointers OK
    storage[i] = 0; // Just to be safe
  }
  delete []storage;
}

template<class T, int incr>
int PStash<T, incr>::add(T* element) {
  if(next >= quantity)
    inflate();
  storage[next++] = element;
  return(next - 1); // Index number
}

template<class T, int incr> inline
T* PStash<T, incr>::operator[](int index) const {
  require(index >= 0,
    "PStash::operator[] index negative");
  if(index >= next)
    return 0; // To indicate the end
  require(storage[index] != 0, 
    "PStash::operator[] returned null pointer");
  return storage[index];
}

template<class T, int incr>
T* PStash<T, incr>::remove(int index) {
  // operator[] performs validity checks:
  T* v = operator[](index);
  // "Remove" the pointer:
  storage[index] = 0;
  return v;
}

template<class T, int incr>
void PStash<T, incr>::inflate(int increase) {
  const int tsz = sizeof(T*);
  T** st = new T*[quantity + increase];
  memset(st, 0, (quantity + increase) * tsz);
  memcpy(st, storage, quantity * tsz);
  quantity += increase;
  delete []storage; // Old storage
  storage = st; // Point to new memory
}
#endif // TPSTASH2_H ///:~
//: C16:TPStash2Test.cpp
#include "TPStash2.h"
#include "../require.h"
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Int {
  int i;
public:
  Int(int ii = 0) : i(ii) {
    cout << ">" << i << ' ';
  }
  ~Int() { cout << "~" << i << ' '; }
  operator int() const { return i; }
  friend ostream&
    operator<<(ostream& os, const Int& x) {
      return os << "Int: " << x.i;
  }
  friend ostream&
    operator<<(ostream& os, const Int* x) {
      return os << "Int: " << x->i;
  }
};

int main() {
  { // To force destructor call
    PStash<Int> ints;
    for(int i = 0; i < 30; i++)
      ints.add(new Int(i));
    cout << endl;
    PStash<Int>::iterator it = ints.begin();
    it += 5;
    PStash<Int>::iterator it2 = it + 10;
    for(; it != it2; it++)
      delete it.remove(); // Default removal
    cout << endl;
    for(it = ints.begin();it != ints.end();it++)
      if(*it) // Remove() causes "holes"
        cout << *it << endl;
  } // "ints" destructor called here
  cout << "\n-------------------\n";  
  ifstream in("TPStash2Test.cpp");
  assure(in, "TPStash2Test.cpp");
  // Instantiate for String:
  PStash<string> strings;
  string line;
  while(getline(in, line))
    strings.add(new string(line));
  PStash<string>::iterator sit = strings.begin();
  for(; sit != strings.end(); sit++)
    cout << **sit << endl;
  sit = strings.begin();
  int n = 26;
  sit += n;
  for(; sit != strings.end(); sit++)
    cout << n++ << ": " << **sit << endl;
} ///:~

二、Shape

//: C16:Shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include <iostream>
#include <string>

class Shape {
public:
  virtual void draw() = 0;
  virtual void erase() = 0;
  virtual ~Shape() {}
};

class Circle : public Shape {
public:
  Circle() {}
  ~Circle() { std::cout << "Circle::~Circle\n"; }
  void draw() { std::cout << "Circle::draw\n";}
  void erase() { std::cout << "Circle::erase\n";}
};

class Square : public Shape {
public:
  Square() {}
  ~Square() { std::cout << "Square::~Square\n"; }
  void draw() { std::cout << "Square::draw\n";}
  void erase() { std::cout << "Square::erase\n";}
};

class Line : public Shape {
public:
  Line() {}
  ~Line() { std::cout << "Line::~Line\n"; }
  void draw() { std::cout << "Line::draw\n";}
  void erase() { std::cout << "Line::erase\n";}
};
#endif // SHAPE_H ///:~
//: C16:Drawing.cpp
#include <vector> // Uses Standard vector too!
#include "TPStash2.h"
#include "TStack2.h"
#include "Shape.h"
using namespace std;

// A Drawing is primarily a container of Shapes:
class Drawing : public PStash<Shape> {
public:
  ~Drawing() { cout << "~Drawing" << endl; }
};

// A Plan is a different container of Shapes:
class Plan : public Stack<Shape> {
public:
  ~Plan() { cout << "~Plan" << endl; }
};

// A Schematic is a different container of Shapes:
class Schematic : public vector<Shape*> {
public:
  ~Schematic() { cout << "~Schematic" << endl; }
};

// A function template:
template<class Iter>
void drawAll(Iter start, Iter end) {
  while(start != end) {
    (*start)->draw();
    start++;
  }
}

int main() {
  // Each type of container has 
  // a different interface:
  Drawing d;
  d.add(new Circle);
  d.add(new Square);
  d.add(new Line);
  Plan p;
  p.push(new Line);
  p.push(new Square);
  p.push(new Circle);
  Schematic s;
  s.push_back(new Square);
  s.push_back(new Circle);
  s.push_back(new Line);
  Shape* sarray[] = { 
    new Circle, new Square, new Line 
  };
  // The iterators and the template function
  // allow them to be treated generically:
  cout << "Drawing d:" << endl;
  drawAll(d.begin(), d.end());
  cout << "Plan p:" << endl;
  drawAll(p.begin(), p.end());
  cout << "Schematic s:" << endl;
  drawAll(s.begin(), s.end());
  cout << "Array sarray:" << endl;
  // Even works with array pointers:
  drawAll(sarray, 
    sarray + sizeof(sarray)/sizeof(*sarray));
  cout << "End of main" << endl;
} ///:~

版权声明:本文为博主原创文章,转载请注明出处 本文总阅读量    次