五、拷贝构造函数
1、默认拷贝构造函数
拷贝构造函数是一种特殊的构造函数,这个构造函数有一个参数,该参数类型是本类型的一个引用。
一般而言,我们不需要显示的提供拷贝构造函数,编译器会自动替我们合成一个默认的拷贝构造函数。默认的拷贝构造函数只进行浅拷贝,也就是说只是简单的把成员变量的值进行copy。
以下两种情况,如果使用默认的拷贝构造函数就会出问题。
(1)类成员变量,涉及到动态内存分配的问题。
class Point2D
{
public:
Point2D()
{
_x = new int;
_y = new int;
}
private:
int *_x;
int *_y;
};
int _tmain(int argc, _TCHAR* argv[])
{
Point2D pa;
Point2D pb = pa;
return 0;
}
pb对象和pb对象中的_x指向同一片内存,_y也指向同一片内存,任意一个对象改变其成员变量的值都会影响到另一个对象。更严重的是,当一个对象将其delete _x, _y后,另外一个对象中的_x, _y变成了野指针。
(2)构造函数中需要做一些特殊任务。
class Point2D
{
public:
Point2D()
{
_x = new int;
_y = new int;
count ++;
}
private:
int *_x;
int *_y;
static int count;
};
int Point2D::count = 0;
int _tmain(int argc, _TCHAR* argv[])
{
Point2D pa;
Point2D pb = pa;
return 0;
}
pb = pa后,count的值仍然为1,并没有将计数器自增。
2、自定义拷贝构造函数
如果默认的拷贝构造函数不能正常工作,那我们就需要主动提供拷贝构造函数,避免编译器替我们添加默认拷贝构造函数。
3、禁止默认拷贝构造函数
如果要禁止拷贝构造,有两种方法。
(1)必须显示的声明拷贝构造函数,且不对这个声明进行定义,以避免编译器为我们合成默认的拷贝构造函数。另外,还需要将拷贝构造函数声明为private。如果该类的成员函数或其friend函数调用拷贝构造函数或赋值操作符函数,会出现链接报错。
(2)让该类继承自UnCopyable
class UnCopyable
{
public:
UnCopyable();
~UnCopyable();
private:
UnCopyable(const UnCopyable&);
UnCopyable& operator=(const UnCopyable&);
};
六、拷贝赋值操作符
类似于拷贝构造函数。
拷贝赋值操作符是用一个对象去覆盖另一个已经存在对象的值。而拷贝构造函数是用一个对象去初始化另一个不存在的对象。
七、类型转换
(1)隐式转换
隐式转换,不需要任何操作符,直接转换,前提是这两种类型必须是兼容类型,不然会编译报错;
int iTemp = 100;
short sTemp = 1;
iTemp = sTemp;
(2)显式转换
C++是强类型语言,很多时候都必须要进行显式转换;类型转换过程中,如果存在数据的丢失,就很有可能导致程序的奔溃,所以要谨慎对待。
C++提供了四个标准转换运算符:dynamic_cast, reinterpret_cast, static_cast, const_cast。
static_cast< type-id > ( expression )
适用场景:
a)用于相关类型之间的转换,包括子类到父类的转换,父类到子类的转换;子类指针转换成父类指针是安全的;但父类指针转换成子类指针是不安全的。(父类和子类之间的动态类型转换建议用dynamic_cast)。
b) 枚举类型与整数类型之间的转换;浮点类型与指数类型之间的转换。
c)把空指针转换成目标类型的空指针。
d)把任何类型的表达式转换成void类型。
注意:
a)static_cast没有在运行时进行类型安全检查,因此,使用前,需要我们确保这种转换是安全的。相比dynamic_cast, static_cast没有类型安全检查的开销。
b) static_cast不能去掉类型的const、volitale属性(用const_cast)。
dynamic_cast<type-id>(expression)
该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
适用场景:
a) dynamic_cast主要用于类层次间的上行转换和下行转换,类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的, 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
b) 类之间的交叉转换,结果为NULL。
reinterpret_cast<type-id> (expression)
适用场景:
a)转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。
b)在比特位级别上进行转换。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。但不能将非32bit的实例转成指针。
c)最普通的用途就是在函数指针类型之间进行转换。
d)很难保证移植性。
const_cast<type_id> (expression)
修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
适用场景:
a)常量指针被转化成非常量的指针,并且仍然指向原来的对象;
b)常量引用被转换成非常量的引用,并且仍然指向原来的对象。
class Point2D
{
public:
Point2D(){printf("constructor\n");}
~Point2D(){printf("destructor\n");}
public:
int _x;
int _y;
};
int _tmain(int argc, _TCHAR* argv[])
{
const Point2D const_point2d;
volatile Point2D volatile_point2d;
Point2D *p2d = const_cast<Point2D *> (&const_point2d);
p2d->_x = 1; // OK
p2d = &const_point2d;//error C2440: '=' : cannot convert from 'const Point2D *__w64 ' to 'Point2D *'
p2d->_x = 2;
const Point2D *cp2d = const_cast<Point2D *> (&volatile_point2d);
cp2d->_x = 1; // error C2166: l-value specifies const object
}
八、static数据成员/成员函数
(1)static成员函数
static成员函数类似于普通的函数,只是这个函数需要通过class来访问,并且受public,private等的访问限制。与普通的类成员函数不同的是,static成员函数不能访问成员变量,因为没有this指针。
(2)static成员变量
static成员变量也受public, private等的访问限制,属于全局静态数据,注意:请不要在.h文件中,对其定义,如果头文件被包含多次,则会造成重定义。
九、const成员方法
表示不能改变成员变量的值,不管是直接的还是间接的。
十、纯虚函数和抽象类
如果一个函数被声明为纯虚函数,也就意味着它的实现需要子类去实现,父类不会实现此函数。含有纯虚函数的类叫做抽象类,抽象类不能实例化。