构造函数和析构函数
2个函数分别用于对象的初始化和清理,如果我们自己不提供函数方法,编译器会提供默认的方法,但是里面的内容为空实现
构造函数为创建对象时,给对象初始化属性赋值。方法名和类名一样,参数可以为空或者有参数列表,可以重载,构造方法只会调用一次,没有返回值即void类型(但这个void不用我们写)
析构函数在对象被销毁前调用。方法名在构造前加个~波浪号,析构函数不可以有参数,只会调用一次,同样不可写返回类型(即不用写return)
如上,我们分别定义了构造和析构,构造是我们Person p那里调用,而析构是我们按任意键退出时调用
构造函数的分类和调用
按照有无参数可以分为有参和无参构造,编译器默认提供的就是无参构造
如上,p1为无参构造,p2为有参构造,有参和无参对应的就是构造方法的重载
按照构造方法传参可以分为普通构造和拷贝构造,普通就不用说了,这里看下拷贝构造
拷贝构造本质就是想把已经创建的该类对象进行属性拷贝,但是由于不想改变原单位,所以使用const常量修饰,同时使用引用不开辟额外空间
构造方法的调用分为隐式法,显示法,括号法 3种
如上为括号法,当无参不能写括号,有参传入指定参数,p3使用了拷贝构造,是在对象名后加括号
如果括号法无参加了括号,会发现没有实例,这是因为会被认为是个函数的声明
显示法
如上,我们分别使用了显示法,显示法本质就是构造方法函数的调用。
这里Person()等号右侧的部分单写相当于是个匿名对象,如果不给左侧赋值,编译器执行完这行就会销毁,如下
匿名对象注意事项,使用匿名对象不要使用拷贝,会报错,因为会把括号看成是省略,认为重新定义了对象
隐式构造,自动进行构造识别
如上,我们定义Person p1 = 10;看着好像左右2侧类型不一致该报错,但是因为符合有参构造输入,所以是可以执行的,相当于进行了有参构造
如上分析,还是显示法比较好理解且不容易出现错误。
拷贝构造函数调用时机
拷贝构造我们知道可以直接复制已有对象属性创建新对象。其还有在函数值传递时和函数返回类对象时被调用
如上,是值传递对象给方法,我们给方法传入参数p,本质是将p传给拷贝函数进行调用
将类函数内局部类对象返回时,本质也调用了拷贝构造,我们可以查看地址,显示出来的地址不同,图中的析构是局部变量的析构执行
构造函数的调用规则
默认情况下,编译器会给类添加3个无参,有参(有参和无参函数体为空),拷贝(默认自动将全部属性拷贝)的构造方法
如上,我们可以看到拷贝会自动拷贝值,虽然我们没指定哪些,默认的会将属性全部拷贝
如果我们只写有参构造,则默认无参构造方法失效,但拷贝构造依然有效
如上,我们定义了有参构造,默认无参构造就没有了
如果只写拷贝构造函数,则默认的无参和有参都无效(对象都不能好好创建了)
深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:堆区重新申请空间,拷贝操作
我们如上,使用默认的拷贝构造,默认是使用的浅拷贝,即p1里面的赋值都拷贝到p2里,然而这样就会出现问题
如上,我们之前的属性是在栈区开辟,如果我们给属性在堆区开辟,使用new方法,如上,有参构造使用指针接收,再在析构方法里delete释放,这里判断下height指针是否为空,如果不为空就删掉,让其置为NULL,看起来逻辑好像没错,但是运行上面的程序就会报错
这里是为什么呢,浅拷贝将指针的内容也拷贝了过去,所以p1,p2的height都是指向一个堆地址,当p1释放,此堆区被置不可用,但是p2析构还是会调用释放,p2的height指针还指向这个地方,然而已经不能诶调用,所以报错,即浅拷贝带来堆区内容重复释放
为了不出现这个异常,我们就要自定义拷贝构造,使用深拷贝,让指针指向新开辟地址
如上,我们在拷贝函数里age还是赋值,但是对于指向堆区的指针要重新赋值,*p.height是因为height为指针,要解引用获得值
所以有在堆区开辟的属性,一定要在拷贝构造函数里使用深拷贝
初始化列表
除了构造方法,对于类还有初始化列表的操作实现初始化属性
如上,格式是在构造方法参数小括号后使用:参数名(初始值)中间逗号分隔的形式,如上,就实现了无参初始化给属性赋值10,20,30
同理有参构造可以使用如上,即将参数给属性的括号里
类对象作为类属性成员
如上,我们定义个Person类,有name和Phone属性,Phone又是额外的类,这里我们就用到前面的参数列表初始化,有人会问了,我们的phone(b)不是相当于Phone phone = b吗,左侧是手机,右侧是字符串,这里用了隐式转换,因为Phone有有参的构造符合。同时我们可以分析下构造和析构的顺序
我们构造就不用说了,手机的构造在人构造前,因为参数传参过程就调用了手机构造,而析构的顺序是和构造相反的
静态成员
静态成员就是类成员加上static修饰,分为静态属性和静态方法
静态属性,所有对象共享一个区域,编译阶段已分配内存,类内声明,类外初始化
静态方法,所有对象享用一个方法,只能访问静态变量,不能访问非静态变量
如上,我们定义静态变量age,加了static声明,但是初始化不能写在类内部,因为多对象共享,所以我们可以看到他们的age地址是一个,类外初始化时,使用数据类型 类名::属性名 = 属性值;
静态方法可以访问静态变量,调用课可以用过对象.访问,也可以通过类名::访问,静态方法不能访问非静态变量,因为静态方法是编译时已内存中生成,而非静态的是创建对象时才定义,并不关联