7.Big Three:拷贝构造,拷贝赋值,析构
(1)什么时候需要自己写拷贝构造和拷贝赋值函数
当编译器提供的默认拷贝构造和拷贝赋值函数不再满足要求的时候,比方说类里面带指针,必须自己写拷贝构造和拷贝赋值函数;
String(constString& str);
String& operator=(constString& str);
如果不这么做,会怎么样?如图1所示,使用默认的拷贝构造和拷贝赋值函数,是一种浅拷贝,只会把指针拷贝过来,会造成内存泄漏,同时两个指针指向同一个地方,以后改动任何一个变量,会造成另外一个的改变。
图 1
(2)怎么写拷贝构造和拷贝赋值函数
拷贝构造:①创造自己;②拷贝
拷贝构造函数,顾名思义就是拷贝和构造。拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的参数(对象的引用)是不可变的(const类型)。
如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数,这个默认的参数可能为X::X(const X&)或X::X(X&),
由编译器根据上下文决定选择哪一个,默认拷贝构造函数的行为如下:默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同
,执行先父类后子类的构造.拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise Copy)的动作.
a)如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
b)如果数据成员是一个数组,对数组的每一个执行按位拷贝.
c)如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值
举例:
inline
String::String(constString& str)
{
m_data =newchar[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
拷贝赋值:①delete自己;②重新创造自己;③拷贝
拷贝赋值函数参数跟拷贝构造函数相同,两者的区别在于:构造函数是对象创建并用另一个已经存在的对象来初始化它。
赋值函数只能把一个对象赋值给另一个已经存在的对象。
注意:考虑自我拷贝的情况
举例:
inline
String& String::operator =(constString& str)
{
if(this== &str)
return*this;
delete[] m_data;
m_data =newchar[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return*this;
}
(3)如果class里面有指针,多半要做动态分配
做了动态分配,则在创建的对象死亡之前析构函数会被调用起来;
Stack(堆),是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身会形成一个Stack用来放置它所接受的参数,以及返回地址。
在函数本体(function body)内声明的任何变量(local object),其所使用的内存块都取自上述Stack。
Heap(堆),或者说system heap,是指由操作系统提供的一块global内存空间,程序可动态分配从某种获得若干区块。
(1)stack objects的生命期
classComplex{...}
...
{
Complex c1(1,2);
}
c1便是所谓的Stack object,其生命在作用于(scope)结束之际结束。这种作用域内的object,又称为auto object,因为它会被自动清理;
(2)static local objects的生命期
classComplex {...}
...
{
staticComplex c2(1,2);
}
c2便是static object,其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。
(3)global objects的生命期
classComplex {...}
...
Complex c3(1,2);
intmain()
{
...
}
c3便是所谓global object,其生命在整个程序结束之后才结束。也可以把它视为一种static object,其作用域是整个程序。
(4)heap objects的生命期
classComplex {...}
...
{
Complex* p=newComplex;
...
deletep;
}
p指的便是heap object,其生命在它被delete掉之后结束。如果没有delete p;会出现内存泄漏(memory leak),因为当作业域结束,p所致的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没有机会delete p)。
(5)new:先分配memory,再调用ctor
绝大部分编译器对调用new,转化为三步,详见图2
图 2
(6)delete:先调用dtor,再释放memory
图 3
(7)动态分配所得的array
图 4
(8)array new一定要搭配array delete
图 5
(1)static
图 6
类complex成员函数只有一份(不可能创建了几个对象就有几个函数),但是要处理很多对象,那就得靠this pointer来处理不同的对象。
而static的部分就和对象脱离了,它存在于内存的一部分,只有一份。
什么时候会使用static对象呢?就是和对象无关的部分。
staic成员函数和一般成员函数的特征就是static成员函数没有this pointer,既然没有this pointer,那static 成员函数不能和一般的函数一样去访问处理non-static data members,那只能处理static members
例如:
classAccount
{
public:
staticdoublem_rate;
staticvoidset_rate(constdouble& x) {m_data = x };
};
doubleAccount::m_rate = 8.0;//静态数据必须要这样定义,因为脱离对象,
intmain()
{
Account::set_rate(5.0);
Account a;
a.set_rate(7.0);
}
调用static函数有两种方法:
①通过object调用;
②通过class name 调用;
(2)Singleton模式(把ctors放在private区)
classA
{
public:
staticA& getInstance();
setup() {...}
private:
A();
A(constA& rhs);
staticA a;
...
};
A& A::getInstance()
{
returna;
}
a本来就存在,和对象无关,然后不想其他人创建,那就把构造函数放在private里,那怎么取得a呢,就用个static A& getInstance()取得a,这是与外界的接口。但这不是最好的写法,因为不管你用不用,a都存在。所以更好的写法如下:
classA
{
public:
staticA& getInstance();
setup() {...}
private:
A();
A(constA& rhs);
...
};
A& A::getInstance()
{
staticA a;//只有当有人掉用这个函数,a才会存在
returna;
}
(3)cout
图 7
可以看出cout就是一种ostream,实际上是重载了<<运算符的函数,用于打印不同类型的数。
(4)class template,类模板
template
classcomplex
{
public:
complex (T r = 0, T i = 0)
: re (r), im(i)
{}
complex& operator += (constcomplex&);
T real()const{returnre; }
T imag()const{returnim; }
private:
T re, im;
friendcomplex& _doapl (complex*,constcomplex&);
};
//调用如下
{
complex c1(2.5,1.5);
complex c2(2,6);
}
(5)function template函数模板
classstone
{
public:
stone(intw,inth,intwe)
: _w(w), _h(h), _weight(we)
{}
booloperator < (conststone& rhs)const
{return_weight < rhs._weight; }
private:
int_w, _h, _weight;
};
template
inlineconstT& min(constT& a,constT& b)
{
returnb < a ? b : a;
}
//使用
stone r1(2,3), r2(3,3), r3;
r3 = min(r1,r2);//则会调用min函数,函数里面会接着调用stone::operator<函数
(6)namespace
以防和别人写的东西重名。
使用方法有两种:
①using directive
usingnamespacestd;//把namspace空间的东西全打开
cin >> i;
cout <<"hello"<< endl;
②using declaration
usingstd::cout;
std::cin >> i;
cout <<"hello"<< endl;
不要在头文件中使用using namespace std;,容易造成命名空间污染;