接下来一小节我们就看
怎么使用类类型来定义我们的对象
这一节涉及到四个主题
一个是对象的定义和使用
这是最基本的同学们要会用的地方
然后是我们要深刻地了解
对象的构造和析构的过程
然后是我们怎么去定义一个对象的数组
首先来看对象的定义与使用
正常情况下边
我们用class定义的类和用struct定义的类
和用struct定义的结构体
在数据的存储表示上面
实际上是非常非常类似的
那么其实就可以像结构体一样
定义和使用对象
当然在外界只能使用对象的公开成员
在对象的内部
可以使用这个对象的所有的成员
我们后面还会讲到
在这个类的派生类的内部
你可以使用它的公开成员和保护成员
但是你不可以使用基类的私有成员
总之有一条
私有成员不可在类的外部直接访问
我们看像这样的一个例子
我写一个主函数main
我定义一个Circle类型的对象
小写的circle
然后我再circle.SetOrigin设置原点
传0.0、0.0
就按照这个方式去完成一个类的
成员函数的一个调用
然后我们circle.SetRadius(1.0)
设置它的半径
完成这两个数据的设置之后
我们就能够算它的周长和面积了啊
所以我们cout 调用GetPerimeter输出周长
调用GetArea输出它的面积
我们程序就结束了
你看circle这个类的基本使用
定义变量的方式
使用这些成员函数的方式
其实和结构体那个模式是非常非常类似的
就是一个“.”解析它的成员就可以了
稍微多说一句 在早期的C++实现里面
其实是没有C++编译器的
这样的代码都要转换成C的结构
然后调用C的编译器来编译我们的程序
像这样的函数调用：circle.SetOrigin(0.0, 0.0)
它实际上将会转换成SetOrigin(this, 0.0, 0.0)
它会按照这样的模式来调用C的编译器
来编译我们的程序
当然了 它要能够区分Circle类的SetOrigin
和另外一个类的同样同名的函数
它能够区分这一点就可以了
所以你注意看
我们前面讲this指针为什么重要
就体现在这个地方
接下来就是对象的构造
对象的构造是非常重要的一件事情
从某种程度来讲 构造就是初始化
要创建一个对象
我们就是要构造这个对象
构造这个对象
那么你就要给它设定一个初始的值
因为在内存里边分配一段存储空间
要存储这个数据对象
这个内存一分配出来
里边一定是有数据的
要么是有意义的数据
要么是无意义的位序列
但是一定是有数据的
所以如果你没有对它进行初始化
这就意味着这样一个对象
一开始的值是不可信的
你不知道它是什么 从某种程度上来讲
构造的目的就是为了让这个对象
在构造的一瞬间
就应该具有一个有意义的数据
就是从这一点上来说 构造就是初始化
我们在定义这个对象的时候
初始化它的数据成员
对象构造的最主要的一个技术手段
就是使用我们的构造函数
构造函数将是与我们的类类型同名的
没有返回值类型的一个函数
那个函数特殊在哪里呢
同学们回忆一下我们刚才的那个实现
你就会发现构造函数
它前面没有返回值类型
别说没int了 连void都没有
第二个 C++为我们提供了一个
特殊的技术手段 叫函数重载
对于类来讲 可以以重载构造函数
C++允许你写很多个不同参数的构造函数
为构造这个对象提供一个非常灵活的手段
这是非常重要的一个特征
构造函数本身是可以带缺省参数的
我们不建议 什么意思呢
就是你在声明这个构造函数的时候
可以为这个构造函数的某些参数
定义一个缺省的值
就是当你没有传递这个参数的时候
我们的编译器就自动地
传递一个缺省的参数进去
但实际编程的时候
我们不建议同学们这么写
因为它是一个不好的方式
它会影响你判断这个构造函数
真实的参数到底有几个
从源代码级别你是看不出来的
你比如讲 如果一个构造函数有俩参数
它的第二个参数是缺省的
当你只传一个参数构造这个对象的时候
仅仅看它的源代码
你实际上不知道这个构造函数
到底是一个单参数的版本
还是双参数的版本
如果你这个类同时还提供一个
单参数的构造函数 两者就有可能混淆
不仅你会混淆 编译器也会混淆
所以这个地方是需要特别去注意的
我们不建议同学们写
带缺省参数的构造函数
第四个 至少公开一个构造函数
因为构造这个对象是在类的外部去做的
在程序运行的时候这个对象需要构造出来
它可能是在全局的堆里构造的
也可能是在函数的栈上构造的
也可能是动态构造的
不管哪种构造的模式
都需要在类的外部调用它的构造函数
对这个类的对象进行初始化
所以说 你要想在外界构造这个对象
那你必须公开一个构造函数 外界可用
实际上 构造函数本身
你写在public里面是可以的
你写在protected里面也是可以的
写在private里面也是可以的
不管你写在哪个访问控制下面 都是可以的
但是你要想让外界能够构造这个对象
必须至少公开一个函数
我们后面还有一个很特殊的例子
就告诉大家如果我有一个对象
我就不想从外界构造它呢
那么你就可以将它的全部的构造函数
都写在私有字段 写在private后面
让外界不能够构造这个类的对象
这也是可以的
所以一般来讲至少公开一个构造函数
但并不绝对 同学们记住这句话
最后一条
只能由系统在创建的时候自动调用
就是这个构造函数你实现出来以后
它是由系统自动调用的
程序代码里面不能够主动地去调用它
从实现这个角度来讲
我们不能够在这个类的外部
来调用它的构造函数吗
当然不是 有的面向对象语言
就允许你主动地调用构造函数
但是对于我们C++代码来讲
它是不可以的
我们看圆类库的接口
怎么实现构造函数
还是原来classCircle这个类的定义
添加它的构造函数
一个是提供了三个参数的构造函数
还提供了一个不带参数的构造函数
注释掉的这一个
就是带缺省参数的构造函数
可能只传一个半径
我缺省认为它的原点就在0.0和0.0
那么第二个参数和第三个参数
都传了缺省值0.0
书写方式就是这样子
有一点是需要说明的就是
如果这个函数带缺省参数
那么所有的缺省参数
必须是这些参数列表中的最后几个
你可以带一个 那就最后一个是缺省的
如果是两个 那最后两个是缺省的
不能前面有缺省参数
后面那个参数反而不缺省了
那是不可以的
我们前面不讲过了嘛
这个模式其实不合适
所以我们其实就不应该这么写
如果你真的需要一个单参数的
构造函数版本
那么实际上你就应该写一个Circle
带一个单参数
小括号对里面写上double r就可以了
构造函数的定义
Circle::Circle里面
按照这个模式写三个量 都传递0
然后是Circle的带三参数的版本
我们把r、x和y传进去
主函数的里面 看我们怎么使用它
构造这个对象——Circle类的类型名字
后面跟着变量的名字circle
后面是(r, x, y) 传三个参数
看我们这段代码实现
其实我们就是对那个构造函数的一次调用
circle(r, x, y)就会创建这样一个对象
实际上 C++编译器在看到这段代码的时候
它就会为我们分配一个Circle类的存储空间
也就是为我们定义好这个对象
创建Circle类的对象 为它分配空间
然后会调用我们的构造函数
将r、x、y三个值写进去
这个就是构造函数的调用
如果在类里面没有明确的构造函数
其实是可以的
如果你真地没有定义这个对象的构造函数
那么编译器就会自动地
为我们创建一个缺省的构造函数
并且自动地调用 这是一个
缺省的构造函数本身是没有参数的
函数体里面也没有任何代码 空的
它什么都不做
就只是自动地完成构造函数的逻辑
它其实就是分配好空间
如果你定义了一个构造函数
不管这个构造函数是什么样子
有参数没参数
它都不再替你生成一个缺省的构造函数
所以如果你生成的构造函数
是带有参数的版本 并且这些参数
没有全部设置为缺省参数的话
那就意味着缺少了一个不带参数的
构造函数版本
某些时候可能会导致问题
我们看这个缺省构造函数的例子
Circlecircle定义了这样一个对象
这个就会自动替我们调用缺省的构造函数
它后面是没有小括号对的
如果加上小括号对它就不对了
你不能使用这个带小括号对的模式
去调用它的缺省构造函数
这在C++的编译器里面是没有办法通过的
还有一类很特殊的构造函数
我们称之为拷贝构造函数
拷贝构造函数仅限于构造
一个已有对象的一个副本
它将完成从一个对象
拷贝到另外一个对象上的任务
拷贝构造必须要求已有对象
才能创建它的副本
那么在这种情况下面
这样的拷贝构造函数
它一定是一个单参数的
它接受某一个对象进来 然后才能拷贝它
所以它一定是单参数的
当然在C++代码实现中
它接受过来的那个对象
它的格式是固定的
必须是本类的某一个常对象的一个引用
你比如说如果是个Circle类
你传过来的应该是constCircle&
对这个const Circle类的对象的一个引用
如果你没有定义拷贝构造函数
那么系统会自动地替你生成一个
缺省的拷贝构造函数
缺省的这个拷贝构造函数
它的实现逻辑是一个简单的位拷贝的动作
什么叫位拷贝
就是把那个对象的内存区域
一个字节、一个字节完全地
搬到另外一个对象里
不管那个字节里面的信息是什么意思
全部地拷贝过去就行了
这个拷贝的动作
你觉得好像这个对象里面的所有数据成员
全都被你拷贝过去了 一点问题都没有
但是这个拷贝的层次是不够深的
我们称它为浅拷贝
为啥呢 你想啊
如果我这一个类的声明里面带有一个指针
这个指针指向一个动态分配的
目标数据对象
当我拷贝这个对象的时候
你是把那个指针所指向的目标数据对象
拷贝到这个类的新的副本里面去了呢
还是仅仅是把那个指针的值拷贝过去了呢
浅拷贝仅仅能够完成这个指针值的拷贝
它不能够完成指针所指向的
目标数据对象的拷贝
如果你要想完成那个指针所指向的
目标数据对象的拷贝
那么你必须编写自己的拷贝构造函数
那个动作我们就叫深拷贝
浅拷贝那个时候可能达不到你的要求
所以你就需要深拷贝
一定要自己定义拷贝构造函数
这个我们后边会详细讨论它
我们的代码 你可以看
拷贝构造函数就这样一个书写方式
具体的实现就是这个样子
这个就是一个标准的浅拷贝动作