Effective C++学习笔记 第三弹 11-18
2011-08-13 19:16
281 查看
条款11:如果class内动态配置有内存,请为此class声明一个copy constructor和一个assignment运算符
1、默认operator=函数
当自定的operator= 不存在时,C++产生一个默认的assignment运算符,该默认函数对对象的复制遵循如下规律
(1)对非指针成员变量 ,生成一个副本,并将内容赋给新副本
(2)对指针变量,将地址赋给了新的副本,即:源和目标指向同一块地址,这便出现了一个问题,当其中一个对象释放这块共同的内存时,另一个对象就无法访问了。而且假如目标对象已经对指针赋值,那么调用默认operator=之后,原来那块内存会永远遗失,这是一个内存泄漏问题。
2、默认的拷贝构造函数
默认的拷贝构造函数的动作和默认operator=函数基本相同,但是只要用值传递的地方都会用到拷贝构造函数
如:void doNothing(A localA){}
String a("abcdefg");
doNothing(a);
当a跳出doNothing作用域后调用析构函数,对象内的指针会被释放,会出现和上面第二点一样的问题。
条款12:在constructor中尽量以initialization动作取代assignment动作(即尽量运用构造函数的成员初始化列表)
撰写构造函数有两种形式
(1)A(const string& initName,T *initPtr) //参数只能是引用传递,假如是值传递会产生一个副本,副本又会调用构造函数,陷入死循环
:name(initName),ptr(initPtr)
{}
(2)A(const string& initName,T *initPtr)
{
name = initName;
ptr = initPtr;
}
对象的构造分两个阶段
(1) data members初始化
(2) 被调用之constructor函数执行起来
根据上面所知,const members和reference members只能被初始化,不能被赋值,假如用第二种方法的构造函数,在第一阶段members已经被初始化,在构造函数中不能执行赋值操作。所以const和reference只能用初始化列表进行初始化。
也可从上得知,用member initialization list效率更高,因为只是调用了一次拷贝构造函数,而第二种构造函数,在第一阶段要调用一次构造函数,第二阶段调用一次assignment运算符。
然而有一种情况下,用assignment取代initialization是合理的内建型别之data members的初始化。
class ManyDataMbrs{
public:
ManyDataMbrs
{
a=b=c=d=e=f=g=1;
}
private:
int a,b,c,d,e,f,g,h;
}
以上提到的const对象必须用对象初始化列表进行初始化,但是static const对象在每一个程序执行过程中只应该初始化一次,不应该在每一个对象的构造函数中assignment或者initialization。
条款13:initialization list中的members初始化次序应该和其在class内的声明次序相同
class members(nonstatic data members)系以它们在class内的声明次序来初始化;和它们在member initialization list中出现的次序完全无关。而对象的data members的destructors总是以“和其cosntructors相反的次序”被调用。
base class data members永远在derived class data members之前初始化,所以应该在你的member initialization lists起始处就列出base class的初值设定。如果你使用多重继承机制,base classes将以“被继承的次序”来初始化。
条款14:总是让base class拥有virtual destructor
class A
{
public:
static int numA;
A()
{}
~A()
{}
};
class B:public A
{
public:
static int numB;
B()
{}
~B()
{}
};
如上程序 用
A* m_a = new B;
delete m_a;测试
执行的顺序为 B() -> A() -> ~A()
当把基类A的析构函数声明为virtual函数时,执行的顺序为 B() -> A() -> ~B() -> ~A()
当class不企图成为一个base class时,令其destructor为virtual通常是个坏主意。因为会增加内存开销,用于维护vptr(virtual table pointer)。
当你无法给你基类虚函数定义时,可以用纯虚函数,有一个或一个以上pure irtual function的类叫抽象类,不能实例化。因为要在vptr中保存虚函数的信息,即使你给不出纯虚函数地址,也必须指定调用的地址。如:virtual ~AWOV() = 0;
条款15:令operator= 传回“*this的reference”
String& String::operator=(const String& other)
{
if (this!=&other)
{
delete[] m_data;
if(!other.m_data) m_data=0;
else
{
m_data = new char[strlen(other.m_data)+1];
strcpy(m_data,other.m_data);
}
}
return *this;
}
通过实验得出以下结论:
1、C++中this是指针,不是对象,所以返回的是*this;
2、首先看传参,声明为const string&,一是为了防止被修改,第二是为了提高效率用引用的方法传递参数,避免值传递生成副本带来的开销;
3、返回类型不为void的原因是用于如下的情况
string a,b,c,d;
a = b = c = d ="Hello";
首先执行d="Hello",返回一个string,作为c=d=("Hello")的右值,依次类推
4、返回类型声明为引用是当出现如下情况时:
b = "Hello";
c = "World";
(a = b) = c;
当不使用引用时,a=b的操作返回的是a的一个副本(使用引用时返回的是a的一个别名,实际就是a);
当不使用引用时,因为返回的是副本于是接下来执行的实际是(a的副本) = c,所以a的值仍是"Hello"
而用引用之后,实际动作是 a = c,这时候 a 才会变为"World"
为了配合a=b=c=d和(a=b)=c的内置型别的机制,我们自己写的类就要用如下缺省版的operator=
C& C::operator=(const C&);或C& C::operator=(const T*)
关于该条款,新手常见的错误是:
(1)返回值类型为void 无法实现 a = b = c这种机制;
(2)返回值声明为const string& 无法实现(a = b)= c,因为返回的是const,不能被修改,所以 a = c无法实现;
(3)参数最好声明为const,如果不是当实参是const时会出现无法转换const到非const的错误,而假如实参是非const,却可以转换为const。
条款16:在operator= 中为所有的data members设定(赋值)内容
看如下程序
class Base{
public:
Base(int initialValue = 0):x(initialValue){}
private:
int x;
};
class Derived:public Base{
public:
Derived(int initialValue)
:Base(initialValue), y(initialValue){}
Derived& operator=(const Derived& rhs);
private:
int y;
};
Derived& Derived::operator=(const Derived& rhs)
{
if(this == &rhs) return *this;
Base::operator=(rhs);
//static_cast<Base&>(*this) = rhs;
y = rhs.y;
return* this;
}
1、Derived(int initialValue)
:Base(initialValue), y(initialValue){}
如果没有Base(initialValue),那只是初始化了y,x只是默认的赋为0
2、Base::operator
a2ba
=(rhs);
如果没有这句,那子类assignment动作时,也没有把x赋给左值;
3、为了将Base成为一起拷过来,也可以写成这样的形式static_cast<Base&>(*this) = rhs;
先将其强制转换成Base基类,然后调用基类的operator=操作。
但是此处一定要将其转换为Base&,如果不加引用,那强制转换后,返回的是源对象的一个副本,rhs赋给副本并为对源对象改变。
条款17:在operator= 中检查是否“自己赋值给自己”
看如下程序
String& String::operator=(const String& rhs)
{
//if(this == &rhs) return *this;
delete[] data;
data = new char[strlen(rhs.data)+1];
strcpy(data,rhs.data);
return* this;
}
当a=a时,这程序会出现错误,data已经被delete,还再对其进行strcpy操作。
注:
相等会有两个概念:1、地址相等 2、值相等
地址相等容易判断,值相等就需要重写操作符==和!=对对象内的每一个成员变量进行比较。
相关文章推荐
- Effective c++学习笔记条款18:让接口容易被正确使用,不易被误用
- Effective c++学习笔记——条款11:在operateor=中自我赋值
- Effective c++学习笔记——条款11:在operateor=中自我赋值
- Effective C++学习笔记 (11)
- JAVA集合框架 黑马程序员学习笔记(11)
- 黑马程序员---学习笔记18:OC基础(5)
- python学习笔记-(18)python中的动态类型(相当重要)
- ASP.NET 3.5核心编程学习笔记(11):SqlConnection、连接池
- 【深度学习】笔记18 Ubuntu16.04下python2.7IDLE的安装
- MySQL学习笔记11:运算符
- 18. JAVA 图形界面 Part 3(表格JTable、本章要点、习题) ----- 学习笔记
- Ext.Net学习笔记18:Ext.Net 可编辑的GridPanel
- Effective C++学习笔记 (12)
- Python学习笔记18-发送邮件
- Linux学习笔记18——信号1
- docker学习笔记18:Dockerfile 指令 VOLUME 介绍
- Effective C++学习笔记之“尽量使用初始化而不要在构造函数里赋值”
- EffectiveC++学习笔记-条款18|19
- C/C++学习笔记18:指针数组和数组指针
- IOS学习笔记18—UIImageView