三大函数——拷贝构造、拷贝赋值、析构函数
拷贝构造——接受的是自己这种东西
ctor和dtor构造函数和析构函数
字符串有两种:
一种是前面有一个常数,用于记录字符串的长度,此字符串的末尾没有结束符号。
另一种是字符串的末尾有结束符号,字符串的开头没有用于记录字符串长度的常数。
new就是分配内存,分配了一个字符的内存。
分配了一个字符的内存,然后把结束符传进来,这样就形成了一个空字符串
strlen是一个函数,获取字符串的长度(strlen是计算机C语言函数,计算字符串s的(unsigned int型)长度,不包括'\0'在内)你的class里面有指针,你多半是要做动态分配。所以你要在他生命结束前,调用析构函数,把内存释放掉)
拷贝赋值函数
如图中的红框①②③,是要把右手里面的东西拷贝赋值给左边的步骤:
a)清空左边的东西
b)申请和右边一样大的内存空间
c)拷贝
如果没有上面那句检测自我赋值( ),会出现如下情况:
检测是否为自我赋值,不仅仅是为了效率,还是为了安全性。
堆栈与内存管理
stack object 的生命周期
static local object
global object 的生命周期
heap object 的生命期
new——先分配内存,后调用构造函数
new的动作分解:
a)调用 operator new 函数来分配内存(operator new 底层调用的是malloc)。对应的,上图分配出
b)第二个动作把我们创建的变量做一个类型转换
c)通过指针调用构造函数Complex(注意:构造函数在类里面,所以是成员函数,会有this指针。谁调用成员函数,this指针就指向谁。因此,上图中的第三步完整的写法应该是如图所示的形式:。
这里的this指针指向了pc)
delete:先调用析构函数,再释放内存
array new要搭配array delete
否则会造成内存泄漏。让我们看看是哪一种内存泄漏
内存泄漏的是动态分配的内存。
设计一个class,我总是要去思考我需要什么样的数据。由于不知道字符串的长度,所以大部分人设计字符串这种类中的数据都是在里面放一根指针,将来要分配多大的字符串内容,就动态地去分配字符串的大小,用new的方式去动态分配一块内存(在32位的平台里面,一根指针占内存是4字节,所以不管你里面字符有多长,字符串本身就4个字节的内存)
Class里面带指针,所以我要关注三个重要的函数:
拷贝构造:他是一个构造函数,所以没有返回值。他要有一个拷贝蓝本,蓝本就是他自己(传入reference是可以的,又因为我们不会改变蓝本,所以前面可以加一个const)
拷贝赋值:赋值是要把来源段的拷贝到目的端,所以涞源段的内容和拷贝构造是相同的(所以他传入的参数和拷贝构造的参数是相同的)
因为传入的值我们不打算去改变他,所以前面加一个const。
拷贝赋值的返回值(要不要return by reference,要看函数执行所返回的结果是不是放在里local object中,只要不是local object,就可以传reference)
析构函数:
辅助函数:我们希望把最后的结果丢给cout来输出到屏幕上(加了const是因为不会改变数据)
拷贝赋值函数:
涞源段拷贝到目的端,目的端是已经存在的东西,所以
第一个动作应该是把目的端的内存清空
第二个动作是重新分配一块够大的空间:
第三个动作是把来源端拷贝到目的端:
接下来要思考赋值之后的返回值(如果不写返回值的话,连串的赋值行为就会受限)
&str得到的是一根指针
String&是引用
扩展补充:类模板、函数模板以及其他
进一步补充:static
谁调用我,谁就是那个this pointer,所以c的地址就是this pointer
成员函数有一个隐藏的参数this pointer,但是我们不能写进去,这个是编译器帮我们写进去
静态数据:加入了static的数据,就跟对象脱离了,他不属于对象,他在内存的某一个区域单独有一份,我们不必知道他在那里,反正后面的代码能够找得到就好了
静态函数:他的身份跟一般的成员函数字内存中是相同的,我们所指的相同指的是他也在内存中只有一份。函数在内存中当然只有一份,不可能因为你创建了好几个对象,就有好几个函数
静态函数跟一般函数的差别就在于,静态函数没有this pointer。因此静态函数如果去处理数据,他只能去处理静态的数据。
例子:
进一步补充:把ctors(构造函数)放在private区
当我们希望写的class只产生一个对象的时候,可以这么用。
把构造函数写在private里面,这样外界就无法再创建对象。
这么写有个缺陷,就是如果外界不需要这个数据,这个数据依然存在,这样会造成内存的浪费。更好的写法如下:
进一步补充:cout
为什么cout可以接受任何类型的数据,因为里面重载了很多
进一步补充:类模板
模板会造成代码的膨胀,但是这并不是缺点,因为你确实是需要这种类型的函数,即使不用模板,你也要写出来
进一步补充:function template,函数模板
类模板在用的时候要明确指出类型( ),函数模板则不需要,因为编译器会做实参的推导(argument deduction)
进一步补充:namespace
namespace等同于你把你的东西都封锁在这个命名空间里了,这样就不会打架。
Using directive(使用命令):等同于你把封锁打开,调用的时候就用写全名(e.g std::cin)了,可以直接写cin
Using declaration:一行一行的打开,不是全开,因为里面东西可能会很多
或者是都不打开,就每一步都规规矩矩的写全名