effectiveC++.docx

上传人:b****3 文档编号:12892801 上传时间:2023-04-22 格式:DOCX 页数:29 大小:26.75KB
下载 相关 举报
effectiveC++.docx_第1页
第1页 / 共29页
effectiveC++.docx_第2页
第2页 / 共29页
effectiveC++.docx_第3页
第3页 / 共29页
effectiveC++.docx_第4页
第4页 / 共29页
effectiveC++.docx_第5页
第5页 / 共29页
点击查看更多>>
下载资源
资源描述

effectiveC++.docx

《effectiveC++.docx》由会员分享,可在线阅读,更多相关《effectiveC++.docx(29页珍藏版)》请在冰豆网上搜索。

effectiveC++.docx

effectiveC++

NO4正确初始化

1、classPoint{

public:

intx,y;

};

Pointp;

cout<

未初始化

2、c++对象成员变量的初始化发生在进入构造体函数之前。

所以Student类的构造函数,时间上是先初始化一个临时对象addr,再copy给this的addr:

Student(Addressaddr,intage){

Addressthis->addr=addr;

this->age=age;

}

NO5默认编写的函数

1、默认生成:

复制构造函数赋值操作符重载析构函数

默认构造函数(如果自己未定义构造函数)

2、当class里面有const成员、引用类型时不自动添加赋值操作符重载(因为这些对象初始化后不能再改变了)

classA{

public:

constintage;

string&name;

A():

age

(1),name(string("afd")){}

};

Aa;

Ab=a;

b=a;//error,不会自动重载=

NO6明确禁止不需要自动生成的函数

1、将成员函数声明为private并且不去实现他们

classA{

public:

A(){}

private:

A&operator=(constA&);

A(constA&);

};

Aa;Ab(a);//error

Ac;c=a;//error

2、集成一个专门用来防止copy构造和赋值操作

classUncopyable{

protected:

Uncopyable(){}

~Uncopyable(){}

private:

Uncopyable&operator=(constUncopyable&);

Uncopyable(constUncopyable&);

};

classA:

publicUncopyable{

};

NO7为多态基类声明virtual析构函数

1、

classperson{

public:

person(){}

~person(){}

};

classstudent:

publicperson{

};

person*p=newstudent;

deletep;

此时只能删除person部分,而student剩余部分无法删除。

解决办法:

persion的析构函数设为virtual。

2、没有virtual函数的类不需要virtual析构函数,那样只会增加对象的大小。

3、当继承一个没有virtual的stl类时会产生类似于1中的情况

classMyString:

publicstring{}

MyString*pms=newMyString();

string*ps=pms;

deleteps;//只能部分delete

NO8析构函数不要抛出异常

1、如果某个操作可能引起异常,且发生异常时必须要处理,则需将这个操作放在非析构函数中,以使得用户有时间处理他们。

NO9构造、析构函数不要调用virtual函数

构造函数和析构函数中不能调用virtual函数,因为此时构造尚未完成,所以不可能调用派生类类的方法而只能调用基类的方法。

NO10operator=返回*this

赋值可以写成连锁形式:

x=y=z=15;因此operator=一般应该返回一个引用指向当前对象:

classA{

public:

A():

val(99){}

intval;

A&operator=(constA&rhs){

this->val=rhs.val;

return*this;

}

};

Aa,b,c;

c.val=(100);

a=b=c;

NO11在operator=中处理自我赋值

方法1:

if(this->p==rhs.p)

classPeople{

};

classHouse{

public:

House(People*_p):

p(_p){}

House&operator=(constHouse&rhs){

if(this->p==rhs.p){return*this;}

deletep;

this->p=rhs.p;

return*this;

}

private:

People*p;

};

方法2:

House&operator=(constHouse&rhs){

People*_p=this->p;

this->p=newPeople(*rhs.p);

delete_p;

return*this;

}

方法3:

自己写一个swap函数

NO12复制对象时需要复制每一个成分

classPeople{

public:

People&operator=(constPeople&rhs){

this->age=rhs.age;

//this->name=rhs.name;如果遗忘了,编译器不会提醒

}

private:

intage;

stringname;

};

classStudent:

publicPeople{

public:

Student&operator=(constStudent&rhs){

People:

:

operator=(rhs);

this->id=rhs.id;

}

private:

intid;

};

NO13以对象管理资源

1、

classDBConnection{

};

DBConnection*conn_pool(){

returnnewDBConnection;

}

voidfunc(){

DBConnection*conn=conn_pool();

//……如果发生异常,或者提前return则conn指向的对象无法删除

deleteconn;

}

2、解决办法:

voidtest(){

std:

:

auto_ptrpdb(conn_pool());

pdb.get()->do_sth();

}

3、注意事项

auto_ptr复制时,参数指针变成null(因为需要防止两个指针指向同一个对象)

不能作为容器元素

4、shared_ptr

可以追踪对象的引用计数,可以用来作为容器元素

5、auto_ptr和sharted_ptr析构函数内均做delete而不是delete[],因此它们不适合指向动态分配的array对象。

NO14shared_ptr删除器

classA{};

voiddel_func(A*a){

cout<<"删¦?

除y对?

象¨®"<

}

voidtest(){

A*a=newA;

shared_ptrpa(a,del_func);

}

NO15在资源类中提供对原始资源的访问

1、显示转换,.get()->*等等:

classA{

public:

A():

a(7){}

voiddisplay(){cout<<"值¦Ì:

êo"<

inta;

};

voidtest(){

A*a=newA;

shared_ptrpa(a);

cout<

pa->display();

cout<<(*pa).a<

}

2、隐式转换

classDB{

public:

DB():

name("oracle"){}

stringname;

};

classDBManager{

public:

DBManager(DB&d):

data(d){}

operatorDB()const{

returndata;

}

DBget(){returndata;}

private:

DBdata;

};

voidprint_db(DBd){

cout<

}

voidtest(){

DBd;

DBManagerdbm(d);

print_db(dbm.get());//显示转换

print_db(dbm);//通过重载运算符()的隐式转换

print_db(dbm.operatorDB());//和上面一句是等价的

}

NO16delete与delete[]

1、删除数组指针要用delete[]

2、注意typedef导致的混淆

typedefstringAddressLines[4];

string*pal=newAddressLines;

//等价于string*pal=newstring[4];

delete[]pal

//但是如果没有看到typedf语句的话很容易把pal当成一个string的指针,从而写出了:

deletepal;

NO17以独立语句将new对象置入智能指针

classWidget{};

intget_priority(){

staticinti=0;

returni++;

}

voidprocessWidget(std:

:

tr1:

:

shared_ptrpw,intpriority){

//dosomething;

}

voidtest(){

//错误做法:

因为编译器并没有确定processWidget前后两个参数产生顺序,如果//get_priority()调用在newwidget和构造shared_ptr直接发送异常会导致内存泄漏。

//processWidget(std:

:

tr1:

:

shared_ptr(newWidget),get_priority());

//正确做法:

把构造shared_ptr的语句放到函数调用外面。

shared_ptrpw(newWidget);

processWidget(pw,get_priority());

}

NO18让接口容易被正确使用

1、导入新类型以预防客户端错误

2、避免与内置类型不一致

eg:

重载operator*()需要加上修饰符const,以使得a*b=c不合法

3、使用shared_ptr,直接返回包装好的指针(且同时制定删除器而不是由用户对shared_ptr进行删除)

NO19略

NO20用const的引用传递代替值传递

1、值传递的函数使用的是传递过来对象的副本,所以函数不能能改变他,因此换做引用传递时必须保证是constXXX&x.

2、引用传递还可以防止对象切割导致的问题:

classPeople{

public:

virtualvoidspeak()const{cout<<"iamapeople"<

};

classChinese:

publicPeople{

public:

voidspeak()const{cout<<"我是中国人"<

};

//Chinesepfunc(p)只能输出"iamapeople",因为根据实参构造形参时把baseclass

//以外的部分切割了。

voidfunc(Peoplep){

p.speak();

}

//可以正常运行

voidfunc(constPeople&p){

p.speak();

}

当然了,通过这种方法是调用的必须是const成员函数(因为不管事值传递还是const引用传递都不可能改变原来的对象)。

3、pass-by-value成本不高的集中情况:

内置类型、STL的迭代器、函数对象

NO21必须返回对象时别妄想返回其引用

几种错误情况:

1、Studen&get(){

Students(“name”,13);

returns;

}

这样返回的引用指向的student在函数返回时就已经被析构了

即使函数内设置staticStudent也有问题(多线程安全性、两次get()所得对象无法比较)

2、Student&get(){

Student*s=newStudent(“name”,13);

returns;

}

这样删除Student对象变得很困难。

尤其是x*y*z这种隐藏的指针根本无法删除。

NO22将成员变量声明为private

1、提高封装性,改变实现后用户代码可以保存不变

2、其实protected和public一样会带来封装性的严重下降

NO23用非成员函数、非友元替换成员函数

1、no-member相比于member函数可以使更少的代码访问类的private成员

2、采用no-member函数,并将不同作用的non-member函数分布在不同的头文件中会有效提高扩展性:

NO24若参数均需类型转换,改用non-member函数

1、

classRational{

public:

Rational(intnumerator=0,intdenominator=1):

nu(numerator),de(denominator){}

intnumerator()const{returnnu;}

intdenominator()const{returnde;}

private:

intnu;

intde;

};

若Rational中增加一个成员函数operator*:

constRationalopeartor(constRational&rhs){

returnRational(nu*rhs.nu,de*rhs.de);

}

则:

Rationalr4=r*2;//正确

Rationalr5=2*r;//错误

这是因为:

只有当参数被列于参数列时,他才是隐式转换的合格参与者,因此r*2中的2被当成了operator*(constRational&)中的参数。

而2*r中的2不在参数列表中。

2、正确做法:

改成non-member函数:

constRationaloperator*(constRational&lhs,constRational&rhs){

Rationalr(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());

returnr;

}

此时2*r也可以正确运行

tips:

这个non-member函数不需要作为友元,因为他可以通过函数访问nu,de.

member函数的反面是non-member函数而不是friend函数。

NO25swap函数

有时swap函数效率很低(eg:

类A有一个指向向量的指针,而A的复制函数是需要将向量中的元素也复制。

此时swap函数会多次复制向量元素,而实际上只需要交互指针就行了)。

解决办法:

添加swap()成员函数,并提供class所在命名空间的swap非成员函数。

#include

usingstd:

:

vector;

namespacemy{

template

classA{

public:

A(vector*_p):

p(_p){}

voidswap(A&rhs){

vector*tmp=rhs.p;

rhs.p=p;

this->p=tmp;

}

private:

vector*p;

int*ptr;

};

template

voidswap(A&lhs,A&rhs){

lhs.swap(rhs);

}

}

NO26尽可能延后变量定义式的出现时间

1、延迟变量定义到使用前一刻,并且尽量直接构造而不是复制。

2、

//方法1成本:

1构造+1析构+n赋值

MyClassA;

for(inti=0;i

A=…

}

//方法2成本:

n构造+n析构

for(inti=0;i

MyClassA=…

}

 

NO27尽量少做转型动作

1、static_cast的一个错误例子:

public:

B():

i(0){}

virtualvoidpp(){++i;}

inti;

};

classD:

publicB{

public:

voidpp(){

B:

:

pp();

//static_cast(*this).pp();这样改变的是D的base部分的一个副本,所以最终i=1而不是2.

++i;

}

};

NO28避免返回指向对象内部成分的句柄

1、返回这样的句柄可能导致封装性下降

2、当句柄的寿命比所指对象长时会导致:

danglinghandles(因为对象被销毁时,句柄所指内容也不复存在,但是这个句柄被传到外界了,别人使用这个句柄时便指向了无效的内容。

NO29为异常安全而努力

1、异常安全的函数:

√不泄露资源(例如用Lock类代替lock()函数)

√不允许数据败坏

2、三个保证:

√基本承诺:

有效状态,但不知是哪种状态

√要么成功、要么完全失败。

√不抛异常

NO30inline函数

1、节省调用时间、增加代码大小(可能导致额外的换页)

2、只是一个申请,不是强制命令

3、virtual函数不可能是inline,因为执行前无法判断具体调用哪个函数,从而进行替换

4、函数指针进行的调用通常不会inline

inlinevoidfunc(){cout<<"dosomething"<

void(*fptr)()=func;

func();//通常是inline的

fptr();//通常不会是inline

5、看似简单的构造函数、析构函数有时也不适合inline,因为构造、析构函数中会调用成员变量、基类的构造、析构函数,这些加起来可能相当庞大。

NO31将文件的编译依存关系降至最低

1、引用和指针只需要类型声明,而对象需要定义式

classNode;

classTree{

Node&root;//正确

Node*root;//正确

Noderoot;//需要:

#include"Node.h"

}

2、函数声明中用到对象,可以只提供声明

classPeople;

Peoplekiss(Peoplegf);//如果加上{…}变成函数定义就不合法了

好处:

namespacemyLib{

classData;

classVector;

classString;

//...

Datafunc1();

voidfunc2(Vectorv);

Stringappend(Stringstr);

}

这样使用func1()的用户只需要includeData.h,使用func2()的用户只需要includeVector.h……这样就是的用户不需要使用一个函数而引入所有其他函数需要的类。

3、使用句柄类和接口类能解除接口和实现之间的耦合关系、减少头文件的编译依赖关系(同时他们也能避免用户知道类的实现细节,增加安全性)。

NO32public继承是is-a关系

1、baseclass中重载基类方法会将基类中该方法,和该方法的所有重载方法不可见。

classB{

public:

virtualvoidf1()=0;

virtualvoidf1(double){cout<<"B:

:

f1(double)"<

voidf2(){cout<<"B:

:

f2()"<

voidf2(int){cout<<"B:

:

f2(int)"<

};

classD:

publicB{

public:

usingB:

:

f1;

usingB:

:

f2;

virtualvoidf1(){cout<<"D:

:

f1()"<

voidf2(){cout<<"D:

:

f2()"<

};

Dd;

d.f1();

d.f1(1.1);//需要加入语句usingB:

:

f1;

d.f2();

d.f2

(1);//需要加入语句usingB:

:

f2;

2、对于这种遮蔽的情况加入using声明可以解决,但是用using会把改名字的所有方法引进来。

而private继承中有时只需要用到其中某些函数:

例如,Derive类可能只需要Base类中无参数的函数f2、有参数的f1,此时需要用到转交函数:

classD2:

privateB{

public:

voidf1(doubled){

B:

:

f1(d);

}

voidf2(){

B:

:

f2();

}

}

此时D2的f1(double)、f2()可用而f1()、f2(int)不可用。

NO33区分接口继承和实现继承

1、纯虚函数purevirtual只提供接口

2、普通虚函数提供接口和默认实现

3、非虚函数提供接口和强制实现

PS:

普通虚函数的默认实现可能带来的问题:

由于疏忽,子类使用了默认实现,但是这个实现不符合子类的业务逻辑。

解决方法:

1、提供一个接口和一个protected实现函数。

但是会带来命名污染。

2、方法声明提供接口、方法定义提供默认实现:

classAirplane{

public:

virtualvoidfly()=0;

};

voidAirplane:

:

fly(){cout<<"默认实现"<

//A型飞机适用于默认实现

classModelA:

publicAirplane{

voidfly(){

Airplane:

:

fly();

}

};

//B型飞机不适用与默认实现

classModelB:

publicAirplane{

//此时B必须要提供fly的实现,因此也就不会由于疏忽而错误

voidfly(){

cout<<"B的实现"<

}

};

NO34用非virtual函数代替virtual函数的手法

1、NVI非虚函数接口调用虚函数实现

classPeople{

public:

voidspeak(){

doSpeak();

}

private:

virtualvoiddoSpeak(){cou

展开阅读全文
相关搜索

当前位置:首页 > 幼儿教育 > 唐诗宋词

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1