7.三大构造函数:拷贝构造,拷贝赋值,析构函数
Class With Pointer member(s) :要写析构函数,而析构函数主要的作用就是删除新建的内存空间,删除不再使用的指针指向的不再使用的Object
String类讲解代码的Part:
String(const char* cstr=0); //构造函数,默认将cstr的初始值赋为0
String(const String& str); //拷贝构造,将参数str的m_data拷贝。深拷贝,自己创建一块内 存空间将str的m_data重新拷贝一份。因为它是接受自己(String)这种东西,所以是拷贝构造
String& operator=(const String& str); // 拷贝赋值。
~String();
char* get_c_str() const { return m_data; }
Q:为什么我需要重新写拷贝构造和拷贝赋值??
A:因为编译器使用的默认的拷贝构造和拷贝赋值是使用值的,而不知指针的。所以当我们使用带指针的时候一定要自己写拷贝构造和拷贝赋值。
Q:拷贝构造函数 VS 拷贝赋值函数 两者有什么区别??
A:拷贝构造函数是新建一块内存空间来存放新的内容。也就是值拷贝。拷贝的是内容。
拷贝赋值函数是拷贝的地址。也就是位拷贝。当其中一个对象修改的时候另外的一个也会随之变化。
第二个例子就是使用编译器默认的拷贝赋值,可以看到发生了内存泄露。
这里有个相关内容的BLOG地址:http://www.cnblogs.com/kaituorensheng/p/3245522.html
来看看拷贝赋值函数的编写:
8.堆,栈与内存管理
8.1 堆 栈
堆必须使用new来动态分配内存,使用delete删除。
栈是比较小的,如果什么东西都使用栈来存储可能会发生爆栈的情况。例如:
在第一种情况中内存发生了溢出而中止了程序,第二种情况则正常编译成功。
说明使用堆相比于使用栈不那么容易发生溢出,它取决于你内存的大小。
8.2 static object global object heap object
static object静态对象在函数作用域执行完之后仍然存在于程序中没有被销毁,直到程序执行完之后才消失。
global object是全局对象,到程序执行完之后才消失。
Q:两者有什么区别??
A:区别在于global object更早地被创建(在程序的一开始),而静态对象则是在执行到指定的语句的时候才被创建。
heap object是堆对象,它和上面两个没有那么像。其生命周期是在new创建时开始,在delete删除时结束。记住new后必须delete删除,否则会发生内存泄露。
8.3编译器如何创建删除对象的
可以知道,构造函数是从外而内构造的,而析构函数是从内而外析构的。
Q:为什么array new 要对于 array delete
A:
这张图可以知道如果使用delete只能删除一个dtor,而使用array delete才能删除三个dtor。这样才不会发生内存泄露。
10. 补充拓展:类模板,函数模板以及其他
从图中可以看到,通过this指针来指向不同的对象来告诉函数要处理什么对象数据。
静态函数:静态函数没有this pointer,所以静态函数只能处理静态数据
Q:静态函数什么时候用?好处有什么?
A:不用实例化; 被预编译; 访问速度比较快。
http://www.cnblogs.com/devinzhang/archive/2012/04/27/2473570.html
举个栗子:
可以看到,在类外一定要加上double Account::m_rate = 8.0;
Singleton设计模式:
使用静态实现的设计模式。非常有意思。来看看代码:
Singleton设计模式将类的实例化使用static完成并如果调用只调用一次。
下面摘抄百度知道的一个回答:
static关键字至少有下列作用:
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问。
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
上面(较差)和下面(较好)的区别在于一个把static A a放在private区,另一个放在了getInstance()区。为什么放在getInstance区叫好呢? 因为如果放在private区不论如何都被创建了,而放在getInstance()区只有在调用getInstance()的时候在被创建。这样使得程序的执行有更高的效率。
模板:
模板涉及比较浅不细说。
命名空间:
比较浅不细说。
11.组合与继承
举个课件里的栗子:
可以看到queue中包含了deque,而deque中又包含了Itr,这就是一次类的组合(Composition)。
下面再来介绍一个特性:委托(Delegation)
依旧是课件:
Q:什么使用用组合什么时候用继承呢?
A:优先使用对象组合,而不是类继承
Q:委托和组合有什么区别呢?
A:从课件中可以看到,委托其实就是Composition by Reference(学术界只说by reference不说by pointer。。。。so。。。。)。使用引用的组合。委托是对一个类的功能进行扩展和复用的方法。它的做法是:写一个附加的类提供附加的功能,并使用原来的类的实例提供原有的功能。扩展和复用一个类的功能常用的一种方法是继承,而另一种更普遍的方法则是委托。在很多情况下委托很适用,而继承则并不适用。
pimpl(Handle/Body)实现方法:图左边(String.hpp)提供一个借口,而具体实现方式在(String.cpp)的实现方法就叫做pimpl。又叫做编译防火墙(左边不用编译只要编译右边)。
继承(Inheritance):
继承下的构造和析构还是特别容易理解,不细说。
虚函数:
Q:侯老师留的一个小问题,Derived是Base的子类,又和Component组合,而Base和Component之间没有任何关系,探究这样子的一个结构中构造函数和析构函数的调用顺序
A:下面是我的答案:
可以看到编译器先构造了Base,然后构造了Component,最后才构造了Derived。紧接着最先析构了Derived,接着析构了Component,最后析构了Base。我们可以知道编译器首先构造父类的对象,接着构造了组合类的对象,最后才构造了自己。而这也是合乎逻辑的。
而另一个问题:
这个问题很明白,从内而外构造是Component,Base,Derived。而析构时相反顺序的。
委托+继承的方法(最强大的!):
举个栗子:
观察者模式资料:
http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html
http://lavasoft.blog.51cto.com/62575/201617/
Prototype设计模式:
创建一个未来的对象。
说明: #(protected) -(private) 下划线(静态) 没有标记=(+)也就是公有的
代码:
简单说就是通过复制一份已经存在的实例来建立一个新的实例(未来的实例)。原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。
http://blog.csdn.net/hguisu/article/details/7518947
总结一下:
了解到的方法:
1.Adapter
2.Handle/Body
3.Template Method
4.Observer
5.Prototype
几种面向对象的方法:
复合 继承 委托 继承+委托 继承+虚函数
以上。