博览网:C++开发工程师之面向对象高级编程(上)第二周笔记

2017-11-01  本文已影响0人  博览网小学员

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里面有指针,多半要做动态分配

做了动态分配,则在创建的对象死亡之前析构函数会被调用起来;

8.堆,栈与内存管理

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

10.扩展补充:类模板,函数模板及其他

(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;,容易造成命名空间污染;

上一篇 下一篇

猜你喜欢

热点阅读