Effective C++ 章节精简版

一.C++联邦语言

1.带有类的C/C/template/STL
2.对于单纯常量,最好以const对象替换#defines。对于形式函数的宏,
最好用inline代替宏。
enum取地址会违法.
控制其域
宁愿编译器替换预处理
宏的括号,及算法优先级的烦恼
3.const星号左边:被指物体常量/const星号右边:指针自身常量
对单纯遍历,最好以const对象或enums替换#deines
对于形式函数的宏,最好用inline替换为#define
4.确定对象被使用前已被初始化
5.为内置型对象进行手工初始化,因为C++不保证初始化他们
使用初始化列表初始化,不要在构造函数本体内使用赋值操作
为免除“跨单元之初始化次序”,以local static 对象替换non-local static对象

二.构造析构函数赋值

1.默认有构造函数,析构函数,拷贝构造函数。private/baseclass,可以阻止其自动提供。
2.有多态性质的类,析构函数也要有析构函数
3.没有多态性质的就不声明多态
4.析构函数中有异常
6.自赋值可能导致指针被提前释放的问题

三.资源管理

1.auto_ptr /share_ptr管理资源/使用RAII对象
2.new []delete[]
3.auto_ptr,复制动作会使它(被指物)指向null

四.设计与声明

-让接口容易被正确使用,不易被勿用
1.好的接口很容易被正确使用,不容易被误用。应该在实现的所有接口中努力达成这些性质。
2.“促进正常使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
3.“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除用户的资源管理责任。
4.tr1::shared_ptr支持定制型删除器(custom deleter)
1.切记将成员变量声明为private。赋予客户访问数据的/一致性/细微划分/弹性。
2.protected并不比public根据封装性。
-尽量用non-member non-friend函数替换member函数。可以增加封装性/包裹弹性/扩充性。

五.实现

1.尽可能延后变量定义式。这样做可增加程序清晰度和改善效率。
2.用到变量在定义就行,防止没有必要的生命成本
四个关键字
C风格的转型动作:(T)expression
函数风格的转型动作:T(expression)
C++ 提供四种新类型转换

  • const_cast (expression)
    • 通常是将对象的长良性消除,是唯一有此能力的C++ style转型操作符
  //1. 指针指向类 
  const A *pca1 = new A; 
  A *pa2 = const_cast<A*>(pca1); //常量对象转换为非常量对象 
  pa2->m_iNum = 200; //fine 
	   //2. 指针指向基本类型 
  const int ica = 100; 
  int * ia = const_cast<int *>(&ica); 
  *ia = 200; 
     
     3.常量引用转为非常量引用 
    A a0; 
  const A &a1 = a0; 
  A a2 = const_cast<A&>(a1); //常量引用转为非常量引用 

	
    //常量对象被转换成非常量对象时出错 
  const A ca; 
  A a = const_cast<A>(ca); //不允许 
  
  const int i = 100; 
  int j = const_cast<int>(i); //不允许 

     const int a = 1;//允许
	int * b = const_cast<int*>(&a);
	*b = 2;
	
	const int a = 1;//允许
	int & b = const_cast<int&>(a);
	b = 2;

网上看到的一个比较好的代码

const string& shorter(const string& s1, const string& s2) {
	return s1.size() <= s2.size() ? s1 : s2;
}

string& shorter(string& s1, string& s2) {
	//重载调用到上一个函数,它已经写好了比较的逻辑
	auto &r = shorter(const_cast<const string&>(s1), const_cast<const string&>(s2));
	//auto等号右边为引用,类型会忽略掉引用
	return const_cast<string&>(r);
}

  • dynamic_cast(expression)
    • 是将一个基类对象指针(或引用)转换到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。(基类转子类)
    • 前提条件:当我们将dynamic_cast用于某种类型的指针或引用时,只有该类型含有虚函数时,才能进行这种转换。否则,编译器会报错。
      dynamic_cast运算符的调用形式如下所示:
dynamic_cast<type*>(e)  //e是指针
dynamic_cast<type&>(e)  //e是左值
dynamic_cast<type&&>(e)//e是右值

e能成功转换为type*类型的情况有三种:

  • 1)e的类型是目标type的公有派生类:派生类向基类转换一定会成功。

  • 2)e的类型是目标type的基类,当e是指针指向派生类对象,或者基类引用引用派生类对象时,类型转换才会成功,当e指向基类对象,试图转换为派生类对象时,转换失败。

  • 3)e的类型就是type的类型时,一定会转换成功。

如果一条dynamic_cast语句的转换目标是指针类型并且转换失败了,会返回一个空指针,则判断条件为0,即为false;如果转换成功,指针为非空,则判断条件为非零,即true。

补充:dynamic_cast 有个普通版本来说,他会调用strcmp,用以比较class名称,所以效率较低

  • reinterpret_cast(expression)
    T必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。
#include <iostream>
using namespace std;
class A
{
public:
    int i;
    int j;
    A(int n):i(n),j(n) { }
};
int main()
{
    A a(100);
    int &r = reinterpret_cast<int&>(a); //强行让 r 引用 a
    r = 200;  //把 a.i 变成了 200
    cout << a.i << "," << a.j << endl;  // 输出 200,100
    int n = 300;
    A *pa = reinterpret_cast<A*> ( & n); //强行让 pa 指向 n
    pa->i = 400;  // n 变成 400
    pa->j = 500;  //此条语句不安全,很可能导致程序崩溃
    cout << n << endl;  // 输出 400
    long long la = 0x12345678abcdLL;
    pa = reinterpret_cast<A*>(la); //la太长,只取低32位0x5678abcd拷贝给pa
    unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐个比特拷贝到u
    cout << hex << u << endl;  //输出 5678abcd
    typedef void (* PF1) (int);
    typedef int (* PF2) (int,char *);
    PF1 pf1;  PF2 pf2;
    pf2 = reinterpret_cast<PF2>(pf1); //两个不同类型的函数指针之间可以互相转换
}
  • static_cast(expression)
    ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
    进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
    进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
    ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
    ③把空指针转换成目标类型的空指针。
    ④把任何类型的表达式转换成void类型。

可以将non_const转为const,相反则不能(相反使用const_cast)
小结:

  • 如果可以,尽量避免转型,特别是注重效率的代码避免使用dynamiy_cast
  • 如果转型是必要的,试着将它隐藏域某个函数背后,客户随后可以调用该函数,而不需将转型放进他们代码内
  • 宁可使用C++ style(新式)转型,不要使用旧式转型。

避免返回 handle(包括 reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助 const 成员函数的行为像个 const,
并将发生“虚吊号码牌”的可能性降至最低。

  • 将大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制
    升级更容易,也可以使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
    • 不要只因为function template出现在文件,就将它inline
  • 将文件的编译依赖关系降到最低:相依声明式,不要依与定义式
    template具现化与inline具现化无关

六.继承与面向对象设计

public意味着,基类的事情也一定适用于子类

class Base { 
private: 
    int x; 
public: 
    virtual void mf1() = 0; 
    virtual void mf1(int); 
    virtual void mf2(); 
    void mf3(); 
    void mf3(double); 
}; 
class Derived : public Base { 
public: 
    virtual void mf1(); 
    void mf3(); 
    void mf4();
Derived d;
int x;
d.mf1();// ok 调用 Derived::mf1
d.mf1(x);//error  Derived::mf1掩盖了Base::mf1
d.mf2();// ok 调用Base::mf2()
d.mf3();//ok 调用Derived::mf3()
d.mf3(x);//error Derived::mf3遮掩了 Base::mf3

1.派生类内的名称会遮掩基类内的名称。
2.可以使用using 声明式或者转交函数。
-纯虚函数只继承接口
-虚函数既继承接口,也提供了一份默认实现;调用基类虚函数使用Base::fun()
-普通函数既继承接口,也强制继承实现。这里假定讨论的成员函数都是public的。
NVI:该设计是令客户通过public non-virtual成员函数间接调用private virtual函数,称为non-virtual interface(NVI)手法。
它是模板方法设计模式的一个独特表示;相当对virtual函数进行一层的包装,可以称为是virtual函数的外覆器它是模板方法设计模式的一个独特表示;相当对virtual函数进行一层的包装,可以称为是virtual函数的外覆器(warpper).
non-virtual函数,采用就近调用原则。virtual函数系动态绑定,而缺省参数值却是静态绑定。
Base* ps = new Derived;ps->func(defaultParam);ps的静态类型就是Base*,而动态类型则是Derived*。

template<typename T>  
class Set  
{  
public:  
    bool member(const T& item)const  
    {  
        return find(rep.begin(),rep.end(),item) != rep.end();  
    }  
    void insert(const T& item)  
    {  
        if(!member(item))  
            rep.push_back(item);  
    }  
    void remove(const T& item)  
    {  
        typename list<T>::iterator it = find(rep.begin(),rep.end(),item);  
        if(it != rep.end())  
            rep.erase(it);  
    }  
    size_t size()const  
    {  
        return rep.size();  
    }  
    void print()  
    {  
        list<T>::iterator it = rep.begin();  
        for(;it != rep.end();++it)  
            cout<<*it<<"\t"<<endl;  
    }  
private:  
    list<T> rep;  //通过list来实现set
};  

private继承根据某物实现
多重继承可能导致歧义性,带有virtual会增加成本。可以做虚接口继承类或是private继承

七.模板与泛型编程

对template而言,接口是隐式的,基于有效表达式/多态则通过template具现化和函数重载发生于编译期
typename内出现的名称依赖于tenplate参数的时候,称之为属名称
如果出现嵌套就叫嵌套从属名称。
任何情况你想要在template中指涉一个嵌套从属名称,就用typename

template <typename C> //这个合法的 C++ 代码 
void print2nd(const C& container) 
{ 
    if (container.size() >= 2) 
    { 
        typename C::const_iterator iter(container.begin()); 
        ++iter; 
        int value = *iter; 
        std::cout << value; 
    } 
}
  • 声明 template 参数时,前缀关键字 class 和 typename 可互换。
  • 请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class list(基类列表)或 member initialization list(成员初值列表)内以它作为 base class 修饰符。

八.定制new和delete
new-handler作用:让更多内存可被使用/安装另一个new-handler/卸除new-handler/抛出bad_alloc(或派生自bad_alloc)的异常/不返回(abort或exit)
operator new/delete:用来检查运用上的错误/统计数据/增加分配和回归内存的速度/降低缺省内存管理器带来的额外开销/弥补非最佳齐位/收集heap信息/改善性能

发布了243 篇原创文章 · 获赞 80 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/hiwoshixiaoyu/article/details/103469020