目录
前言:
每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其他的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器会自动为A产生四个缺省函数:
- A(void);//缺省的构造函数
- A(const A &a);//缺省的拷贝构造函数
- ~A(void); //缺省的析构函数
- A & operate=(const A &a);//缺省的赋值函数
本文讲述构造函数的相关知识点,其余三个函数会在后面文章陆续发出。
一、构造函数
1.构造函数出现原因
- 举例桌子类Table,属性为长宽高,一个输出函数printf,一个设置函数set,在主函数内定义桌子类的对象t,输出该对象的属性,又定义了桌子属性类的对象t1,设置高为60,输出该对象的属性,如下代码所示:
#include<iostream> using namespace std; class Table { public: void Print(); void Set(); private: int m_length;//长 int m_width;//宽 int m_height;//高 }; void Table::Print() { cout << m_length << " " << m_width << " " << m_height << endl; } void Table::Set() { int m_length = 120;//长 int m_width = 40;//宽 int m_height = 80; } int main() { Table t;//定义对象,自动调用Table的构造函数 t.Print();//期待输出结果为120 40 80 }
- 运行上述代码,输出第一个对象t的属性,期待输出合法的值:120 40 80。但结果出现下面所示的随机值:
- 解决方案:
- 1.设置set函数
void main() { Table t; t.Set(); t.Printf(); }
- 输出结果:
- 此时是合法值,但是不合理,因为用桌子抽象类定义了对象t,t本身应该就拥有桌子类找个属性,本就应该调用就输出结果,而不是需要再输出设置值
- 2、定义构造函数(在👇“使用”处,说明如何使用)
- 通过上面举例发现,必须用Table类的一个成员函数,才能输出Table定义对象的合法值,但成员函数不能在类出现后再调用,而且正常流程应该是在定义完t之后,再给当前属性分配空间赋予它合法值。此时发现,开始自相矛盾了,因此需要一个在定义对象时,系统自动调用类成员函数,给当前对象的属性开辟空间给他合法值的函数——构造函数
- 构造函数满足的就是:不通过对象去调用初始化对象的数据,当这个对象创建出来的时候,他就已经是具有一定的初始值。
- 调用构造函数的目的是给对象的属性分配空间,然后才能初始化输出合法值。
2.定义
- 构造函数是个特殊的成员函数函数,函数名和类名相同,无返回类型,可以带参数(可以重载)
- 在使用该类定义的对象时,发现如果程序员没有定义构造函数,则类会提供一个默认的构造函数,给类中的数据成员分配空间。如果写了,系统就不会再提供构造函数。上例所示运行结果出现了地址,开辟了空间就调用了构造函数,但是程序员没写,就说明了系统提供默认构造函数。
3.使用
- 举例Table桌子类,与上面不同的是,设置构造函数及Table的函数名后带上了参数默认值(如果要带默认值,只能在参数后面初始化的时候写)。示例如下所示:
class Table { public: Table(int l = 120, int w = 40, int h = 80)//带默认值参数的构造函数 { m_length = l;//长 m_width = w;//宽 m_height = h;//高 } void Print() { cout << m_length << " " << m_width << " " << m_height << endl; } private: int m_length;//长 int m_width;//宽 int m_height;//高 }; int main() { Table t;//定义对象,自动调用Table的构造函数 t.Print(); //输出结果为120 40 80 Table t1(60); t1.Print(); //输出结果为60 40 80 }
- 此时运行结果就是合法值
- 说明:
- 上述main函数内定义了对象t,系统执行时自动调用Table的构造函数Table(int l=120,int w=40,int h=80),输出默认值 120 40 80
- 如果构造函数内无参Table(int l,int w,int h),那么在调用时会报错,因为程序员自己定义了构造函数,那么系统不会再提供默认值,而程序员自己写的构造函数又是无参的没有默认值,就没办法输出。
Table tt[5];//定义对象数组
- 定义对象数组,说明当前数组内有5个对象,调用了5次构造函数
Table *p;//指针变量
- 定义指针变量,并未定义对象,不会调用构造函数,而且p为指针,在栈内开辟了四个字节,不需要构造函数再为其开辟内存。
- 使用:
Table *p=&t;//指向t p->print();
- 在定义一个属性的对象时,系统会检测是否有程序员自己写的构造函数,有的话就直接调用程序员写的。如果没有的话,系统就会调用自己的构造函数。
4.构造函数调用顺序
- 遇到对象->自动调用当前类的构造函数,调用步骤:
- 1.传参
- 2.根据数据成员在类中的声明顺序开辟空间
- 3.执行构造函数的函数体
5.构造函数的作用
构造函数三作用:
- 创造对象
- 初始化对象
- 隐式类型转换
注意没有开辟空间。
对象不可以调用构造函数,但是可以调用析构
析构函数没有重载,构造函数有
二、类的组合
1.引出概念
- 对电脑类键盘鼠标屏幕的属性进行举例:
- 首先,键盘、鼠标....这些不是Int什么类型,他们都有自己的属性,因此他们自身就需要一个类来描述。
- 列个框架,对其举例说明:
class CPU { public: CPU()//CPU的构造属性 { cout<<"CPU"<<endl; } }; class Mouse { public: Mouse()//鼠标的构造属性 { cout<<"Mouse"<<endl; } }; class KeyBoard { public: KeyBoard()//屏幕的构造属性 { cout<<"KeyBoard"<<endl; } }; class Computer { public: Computer()//电脑的构造属性 { cout<<"Conputer"<<endl; } private: CPU cpu; Mouse ms; KeyBoard kb; }; int main() { Computer c; }
- 运行结果:
- 一个类的对象(cpu,mouse,keyboard)作为另一个类(computer)的数据成员出现,叫做类的组合
- 另外一个包含的关系是聚合
- 组合是强拥有的关系——整体和部分(cpu坏了conputer也会坏),聚合是弱拥有的关系(就像公司与员工的关系一样,员工离职公司也会照样存在)
三、类成员初始化的困惑——冒号语法
- 以下面代码学生和日期类进行举例:
class Date//日期类 { public: Date(int y,int m,int d) { m_y=y;//年 m_m=m;//月 m_d=d;//日 } void Show() { cout<<m_y<<" "<<m_m<<" "<m_d<<endl; } private: int m_y; int m_m; int m_d; }; class Student { public: Student(int num,char *num,Date d) { m_num=num;//编号 strcpy(m_name,name);//字符串赋值 birthday=d//生日 } void Print() { cout<<m_num<<" "<<m_num<<" "; //输出birthday: //cout<<birthday<<endl;不对,因为cout没有办法直接输出对象,还没有重载输出运算符 //cout<<birthday.y<<endl;也不对,因为年月日是私有的,在外界无法访问 birthday.Show(); ] private: int m_num; char m_name[20]; Date birthday; }; void main() { Date d(2002,12,23);//定义对象,传参 Student s(1001,"lisi",d); s.printf(); }
代码解释:
定义了日期类包含年year月month日day的属性以及一个输出函数
定义了学生类包含编号num生日birthday的属性以及一个输出函数
在主函数内定义Date类的d对象并初始化
主函数内定义Student类的s对象并初始化
输出对象s的属性信息
- 问题:
- 编号1001和姓名lisi都可以按照流程传参输出,但是birthday的d传到Date birthday处后,birthday作为Date的对象,理应调用Date的构造函数来输出初始年月日信息,但是在Date未进行参数初始化,无法进行赋初值输出(上面说了,因为程序员写了构造函数后不论有没有初始化参数,系统都不会再调用自己的构造函数提供默认值)
- 解决方案:
- 1.在参数处进行初始化,并在内部输出结果
- 运行结果:
- 这也可以输出我们想要的结果,但是问题又出现了,为什么输出了两个值,第一行输出年月日初始化结果,第二行输出对象s的属性信息。怎么能在不要第一行的情况下还能有第二行的信息输出?下面就引入了冒泡语法👇
- 2,使用冒泡语法解决
- 冒泡语法的使用:在构造函数的参数后面写一个冒号,然后进行初始化
- 输出结果:
1.使用说明
- birthday(d)的意思是把d的值赋给了birthday
- :后面的内容是初始化,大括号{}里的内容是赋初值,赋初值≠初始化
- 用冒泡语法赋值的,就不必再在构造函数内进行初始化。
2.注意事项
- 是一个冒号:,两个冒号::是作用域。
- :后面只能用(),不能用赋值号=
- 在构造函数体内必须对参数赋初值(赋初值≠初始化)
3.步骤
- 调用构造函数三步骤
- 传参
- 根据数据成员在类里面的声明顺序,用冒号语法后面的值进行初始化
- 执行构造函数函数体
- 一个例题
- 输出结果为 4 8吗?
- 并不是 实际结果为 一个随机值 和8
- 解释:
- 构造函数一步——传参:4->i; 8->j
- 第二步——数据成员在类里面的声明顺序,用冒号语法后面的值进行初始化:先声明的m_i,然后对m_i初始化:m_i(m_j),此时的m_j是随机值,因此m_i的输出结果也为随机值,然后对m_j(j) ->m_j=8
- 第三步执行构造函数体,输出结果。