在完成了C++面向对象高级编程(上)第二周的学习之后,有一些总结和心得在这里通过学习笔记的方式分享出来,供也在学习C++的小伙伴用作学习交流,如有理解不到位的地方,欢迎批评指正。
本周学习了Class中的两个经典分类中的class with pointer member(s),主要内容是Big Three,三个特殊函数。
我们如何创建String类型的对象:
1.直接创建;
2.不直接创建,传入一个已经创建好了的对象作为参数,这种方式就叫做拷贝构造。在创建这个对象的时候,我们会调用与之前所说的(第1点)不同的构造函数,所以,我们要设计一个构造函数的重载;
3.已经创建好的对象可以进行自由赋值操作,这需要我们创建操作符重载函数来实现这个功能。
只要是类中有指针成员,就一定要设计拷贝构造函数和拷贝赋值的函数。下面就来谈谈Big
Three。在C++程序设计里,它是一个以设计的基本原则而制定的定律,三法则的要求在于,假如类有明显地定义下列其中一个成员函数,那么其他二个成员函数也必须一同编写至该类内,亦即三个成员函数缺一不可。
一.Copy ctor(拷贝构造函数)
我们一般都不分配固定大小的数组,这样效率太低,我们一般都采取动态分配内存的方式创建字符串,我们不创建数组,而是创建一个指针。如果我们不自己创建拷贝构造函数,对于“b=a;”这样的操作,编译器会默认拷贝,但值得注意的是,系统默认的拷贝是一种“浅拷贝”,会造成内存泄漏,如下图,系统拷贝过来的只是指向目标字符串的指针,并没有完全拷贝内容,只有自己创建拷贝构造函数,才能达到“深拷贝”的效果。
所谓“深拷贝”,就是先分配跟目标字符串一样大的空间(注意有结束符,所以长度+1),再将来源端拷贝到目的端:
二.Copy
assignment operator(拷贝赋值函数)
跟拷贝构造函数类似,拷贝赋值函数也是先分配空间,再赋值:
拷贝赋值分为三步:
1.清空自己;
2.分配空间;
3.把来源端拷贝到目的端。
这里需要注意的是,检测自我赋值非常重要!这一点不仅仅是提高效率,更是为了以后回收正确的内存空间,否则当来源端和目的端本身都指向同一个空间时,delete[]的操作会将这一区域清除掉,造成后续步骤无法操作,如下图:
三.Destructor(析构函数)
在创建对象时,会调用构造函数;然而在对象生命结束时,还需要调用析构函数来释放刚才动态生成的空间,否则会内存泄露。
四.Stack(栈)和Heap(堆)
栈(stack)——由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。一般,它的空间分配的方式是由系统自动分配。是某作用域scope的一块内存空间(memory space);
堆(heap)——一般由程序员分配释放,操作系统提供的一块global内存空间,程序可动态分配(dynamic allocate)以获得若干区块(blocks),程序员必须手动释放这些空间!
五.New和Delete
New操作:先分配memory,再调用ctor。分为三步:
1.分配内存;
2.转型;
3.构造函数。
Delete操作:先调用dtor,再释放memory。分为两步:
1.析构函数;
2.释放内存。
六.动态分配所得内存块(memory block)
1.分配一个复数占用的内存空间
调试模式下:8+(32+4)+(4*2)=52字节,填补成16的倍数,即为645字节;
非调试模式下:8+(4*2)=16字节;
2.分配一个字符串占用的内存空间
调试模式下:4+(32+4)+(4*2)=48字节;
非调试模式下:4+(4*2)=12字节,填补后为16字节;
3.动态分配所得的数组占用空间
Complex* p=new Complex[3];
调试模式下:(8*3)+(32+4)+(4*2)+4=72字节,填补后为80字节;
非调试模式下:(8*3)+(4*2)+4=36字节,填补后为48字节;
String* p=new String[3];
调试模式下:(4*3)+(32+4)+(4*2)+4=60字节,填补后为64字节;
非调试模式下:(4*3)+(4*2)+4=24字节,填补后为32字节.
可见,我们创建一个复数或者字符串时,系统其实分配了相当大的空间!
注意:array new一定要搭配array delete,并且在delete数组时一定要加[],否则编译器不知道是数组,只会调用一次析构函数,造成内存泄漏。
七.一些补充
1. Static
在数据或函数之前加上关键字static,成为静态数据或静态函数。静态数据在class之外必须设初值;静态函数只能处理静态数据。调用static函数的方式:
a.通过object调用;
b.通过class name调用。
2. class template(类模板)和function template(函数模板)
类模板:template
函数模板:template 编译器会对函数模板进行实参推导(argument deduction)。
3. namespace
namespace std.将标准库中的函数包装在一个单元里,编程时就不用写全称了。
4.C++中还有非常多的细节值得去深入