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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
网上看到的一个比较好的代码
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- dynamic_cast(expression)
-
- 是将一个基类对象指针(或引用)转换到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理。(基类转子类)
-
- 前提条件:当我们将dynamic_cast用于某种类型的指针或引用时,只有该类型含有虚函数时,才能进行这种转换。否则,编译器会报错。
dynamic_cast运算符的调用形式如下所示:
- 前提条件:当我们将dynamic_cast用于某种类型的指针或引用时,只有该类型含有虚函数时,才能进行这种转换。否则,编译器会报错。
dynamic_cast<type*>(e) //e是指针
dynamic_cast<type&>(e) //e是左值
dynamic_cast<type&&>(e)//e是右值
- 1
- 2
- 3
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); //两个不同类型的函数指针之间可以互相转换
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 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
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
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
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 声明 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信息/改善性能
文章来源: yujiang.blog.csdn.net,作者:鱼酱2333,版权归原作者所有,如需转载,请联系作者。
原文链接:yujiang.blog.csdn.net/article/details/103469020
- 点赞
- 收藏
- 关注作者
评论(0)