Effective C++读书笔记
BYR_jiandong
这个作者很懒,什么都没留下…
展开
-
条款 11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
// 一个很简单的 String 类class String {public:String(const char *value);~String(); // 没有拷贝构造函数和 operator=private:char *data;};String::String(const char *value){if (value) {data = ne原创 2014-12-17 10:52:58 · 737 阅读 · 0 评论 -
条款 30: 避免这样的成员函数:其返回值是指向成员的非 const 指针或引用, 但成员的访问级比这个函数要低
使一个成员为 private 或 protected 的原因是想限制对它的访问,所以写个函数来让用户随意地访问受限的成员没多大意义。所以,如果有个公有函数,返回指向成员的非const指针或引用,成员一般是私有的,那这样就相当于把私有成员暴露给外部接口。 举例如下: class Address { ... }; // 某人居住在此class Person {原创 2014-12-30 20:33:26 · 1013 阅读 · 0 评论 -
条款 35: 使公有继承体现"是一个" 的含义
C++面向对象编程中一条重要的规则是:公有继承意味着"是一个" 。一定要牢牢记住这条规则。 当写下类 D("Derived" )从类 B("Base")公有继承时,你实际上是在告诉编译器(以及读这段代码的人) :类型 D 的每一个对象也是类型 B 的一个对象,但反之不成立;你是在说:B 表示一个比 D 更广泛的概念,D 表示一个比B 更特定概念;你是在声明:任何可以使用类原创 2014-12-31 16:37:04 · 477 阅读 · 0 评论 -
条款 37: 决不要重新定义继承而来的非虚函数
假设类 D 公有继承于类 B,并且类 B 中定义了一个公有成员函数 mf。mf的参数和返回类型不重要,所以假设都为 void。换句话说,我这么写: class B {public:void mf();...};class D: public B { ... };D x; // x 是类型 D 的一个对象那么,如果发现这么做:B *pB = &x; // 得到 x原创 2014-12-31 16:53:43 · 634 阅读 · 0 评论 -
条款 39: 避免"向下转换" 继承层次
class Person { ... };class BankAccount {public:BankAccount(const Person *primaryOwner,const Person *jointOwner);virtual ~BankAccount();virtual void makeDeposit(double amount) = 0;virtual void m原创 2015-01-02 20:46:30 · 574 阅读 · 0 评论 -
条款 40: 通过分层来体现"有一个" 或" 用...来实现"
使某个类的对象成为另一个类的数据成员,从而实现将一个类构筑在另一个类之上,这一过程称为"分层"(Layering) 。 class Address { ... }; // 某人居住之处class PhoneNumber { ... };class Person {public:...private:string name; // 下层对象Address a原创 2015-01-02 21:21:45 · 451 阅读 · 0 评论 -
条款 41: 区分继承和模板
结论:当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类。当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类。下面的代码通过定义一个链表来实现 Stack 类,假设堆栈的对象类型为 T:class Stack {public:Stack();~Stack();void push(const T& object);T pop();bool e原创 2015-01-02 22:08:25 · 492 阅读 · 0 评论 -
揭开私有继承的面纱 && 条款 42: 明智地使用私有继承
如果说保护继承大多是为了语言完整性的话,私有继承还是有一些用途的。私有继承 vs 公有继承公有继承继承的是接口与实现,它表示了类与类之间is-a的关系。而私有继承继承的仅仅是实现,它表示了has-a (或者 is-implemented-in-terms-of)的关系在公有继承中,基类的公有成员在派生类中仍然公有,基类拥有的接口完整无缺地传给了派生类,也就是说基类对转载 2015-01-02 22:39:20 · 522 阅读 · 0 评论 -
友元函数与友元类
被指定为某类的友元的函数称为该类的友元函数。被指定为某类的友元的称为授予友元关系的那个类的友元类。在需要允许某些特定的非成员函数访问一个类的私有成员(及受保护成员),而同时仍阻止一般的访问的情况下,友元是有用的。使用友元的优点:可以灵活地实现需要访问若干类的私有或受保护成员才能完成的任务,通过使用友元函数重载,可以更自然地使用C++语言的I/O库。输入输出操作符的重载需要用友元原创 2015-01-03 19:38:51 · 560 阅读 · 0 评论 -
条款 43: 明智地使用多继承
C++中,关于 MI (多继承)一条不容争辩的事实是,MI 的出现就象打开了潘朵拉的盒子,带来了单继承中绝对不会存在的复杂性。其中,最基本的一条是二义性(参见条款 26) 。如果一个派生类从多个基类继承了一个成员名,所有对这个名字的访问都是二义的;你必须明确地说出你所指的是哪个成员。class Lottery {public:virtual int draw();...};class原创 2015-01-03 14:44:18 · 534 阅读 · 0 评论 -
条款 46: 宁可编译和链接时出错,也不要运行时出错
运行时错误的概念和 C++没什么关系,就象在 C 中一样。没有下溢,上溢,除零检查;没有数组越界检查(运行时错误),等等。一旦程序通过了编译和链接,你就得靠自己了---- 一切后果自负。 现在的情况下,它的含义就是要避免运行时错误。只要有可能,就要让出错检查从运行时退回到链接时,或者,最理想的是,编译时。 这种方法带来的好处不仅仅在于程序的大小和速度,还有可靠原创 2015-01-03 15:05:06 · 650 阅读 · 0 评论 -
条款 17: 在 operator=中检查给自己赋值的情况
自己给自己赋值的情况: class X { ... }; X a; a = a; // a 赋值给自己 另一种给自己赋值的情况: a = b; 如果 b 是 a 的另一个名字(例如,已被初始化为 a 的引用),那这也是对自己赋值。 在赋值运算符中要特别注意可能出现别名原创 2014-12-30 19:30:54 · 662 阅读 · 0 评论 -
条款 6:析构函数里对指针成员调用 delete
大多数情况下,执行动态内存分配的的类都在构造函数里用 new 分配内存,然后在析构函数里用 delete 释放内存。最初写这个类的时候当然不难做,你会记得最后对在所有构造函数里分配了内存的所有成员使用 delete。 然而,这个类经过维护、升级后,情况就会变得困难了,因为对类的代码进行修改的程序员不一定就是最早写这个类的人。而增加一个指针成员意味着几乎都要进行下面的工作:原创 2014-12-28 20:18:26 · 3311 阅读 · 0 评论 -
条款 16: 在 operator=中对所有数据成员赋值
实际编程中,这意味着写赋值运算符时,必须对对象的每一数据成员赋值:template // 名字和指针相关联的类的模板class NamedPtr { // (源自条款 12)public:NamedPtr(const string& initName, T *initPtr);NamedPtr& operator=(const NamedPtr& rhs);private:st原创 2014-12-28 20:48:52 · 549 阅读 · 0 评论 -
条款 45: 弄清 C++在幕后为你所写、所调用的函数
如果你没有声明下列函数,体贴的编译器会声明它自己的版本。这些函数是:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符。另外,如果你没有声明任何构造函数,它也将为你声明一个缺省构造函数。所有这些函数都是公有的。class Empty {public:Empty(); // 缺省构造函数Empty(const Empty& rhs); // 拷贝构造函数~Empty();原创 2014-12-17 09:23:01 · 559 阅读 · 0 评论 -
条款 36: 区分接口继承和实现继承
(公有)继承的概念看起来很简单,进一步分析,会发现它由两个可分的部分组成:函数接口的继承和函数实现的继承.class Shape {public:virtual void draw() const = 0;virtual void error(const string& msg);int objectID() const;};class Rectangle: public Sha原创 2014-12-17 13:56:55 · 484 阅读 · 0 评论 -
条款 14: 确定基类有虚析构函数
class EnemyTarget {public:EnemyTarget() { ++numTargets; }EnemyTarget(const EnemyTarget&) { ++numTargets; }~EnemyTarget() { --numTargets; }static size_t numberOfTargets(){ return numTargets原创 2014-12-17 16:24:51 · 494 阅读 · 0 评论 -
条款 27: 如果不想使用隐式生成的函数就要显式地禁止它
假设想写一个类模板 Array,它所生成的类除了可以进行上下限检查外,其它行为和 C++标准数组一样。设计中面临的一个问题是怎么禁止掉 Array 对象之间的赋值操作,因为对标准 C++数组来说赋值是不合法的:double values1[10];double values2[10];values1 = values2; // 错误! 对很多函数来说,这不是原创 2014-12-17 10:34:00 · 557 阅读 · 0 评论 -
条款 31: 千万不要返回局部对象的引用,也不要返回函数内部用 new 初始化的 指针的引用
返回一个局部对象的引用。它的问题在于,局部对象----- 顾名思义---- 仅仅是局部的。也就是说,局部对象是在被定义时创建,在离开生命空间时被销毁的。所谓生命空间,是指它们所在的函数体。当函数返回时,程序的控制离开了这个空间,所以函数内部所在的局部对象被自动销毁。因此,如果返回局部对象的引用,那个局部对象其实已经在函数调用者使用它之前被销毁了。 当想提高程序的效率而使函数的原创 2014-12-27 18:56:27 · 1017 阅读 · 0 评论 -
条款 23: 必须返回一个对象时不要试图返回一个引用
一旦程序员抓住了“传值”在效率上的把柄(参见条款 22) ,他们会变得十分极端,恨不得挖出每一个隐藏在程序中的传值操作。岂不知,在他们不懈地追求纯粹的“传引用”的过程中,他们会不可避免地犯另一个严重的错误:传递一个并不存在的对象的引用。这就不是好事了。 class Rational {public:Rational(int numerator = 0, int denomin原创 2014-12-27 19:18:03 · 522 阅读 · 0 评论 -
尽可能使用 const
使用 const 的好处在于它允许指定一种语意上的约束——某种对象不能被修改——编译器具体来实施这种约束。通过 const,你可以通知编译器和其他程序员某个值要保持不变。只要是这种情况,你就要明确地使用 const ,因为这样做就可以借助编译器的帮助确保这种约束不被破坏。 对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为const,或二者同时指定为 con原创 2014-12-27 20:26:21 · 501 阅读 · 0 评论 -
条款 13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同
templateclass Array {public:Array(int lowBound, int highBound);...private:vector data; // 数组数据存储在 vector 对象中// 关于 vector 模板参见条款 49size_t size; // 数组中元素的数量int lBound, hBound; // 下限,上限};原创 2014-12-28 22:38:14 · 1095 阅读 · 0 评论 -
条款 22: 尽量用“传引用”而不用“传值”
除非明确指定,函数的形参总是通过“实参的拷贝”来初始化的,函数的调用者得到的也是函数返回值的拷贝。用传值来传递对象,会调用大量的构造函数和析构函数,效率低下。所以,为避免这种潜在的昂贵的开销,就不要通过值来传递对象,而要通过引用。 const Student& returnStudent(const Student& s) { return s;} 这会非常高效:没有构原创 2014-12-27 20:00:14 · 758 阅读 · 0 评论 -
条款 12: 尽量使用初始化而不要在构造函数里赋值
看这样一个模板,它生成的类使得一个名字和一个 T 类型的对象的指针关联起来。templateclass NamedPtr {public:NamedPtr(const string& initName, T *initPtr);...private:string name;T *ptr;};因为有指针成员的对象在进行拷贝和赋值操作原创 2014-12-27 22:55:19 · 2165 阅读 · 0 评论 -
条款 7:预先准备好内存不够的情况 set_new_handler
operator new 在无法完成内存分配请求时会抛出异常(以前的做法一般是返回 0,一些旧一点的编译器还这么做)。 用一个很简单的出错处理方法,可以这么做:当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。这个方法基于一个常规,即当 operator new 不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数——一般称为 new-handler 函原创 2015-01-03 15:40:10 · 647 阅读 · 0 评论