1.构造函数
1.构造函数特性
- 构造函数名字和类名相同
- 构造函数没有返回值(void有返回值,返回值为空)
- 不写构造函数,每一个类中都存在默认的构造函数,默认的构造函数是没有参数的
- default显示使用默认的构造函数
- delete删掉默认函数
- 当我们自己写了构造函数,默认的构造函数就不存在
- 构造函数是不需要自己调用,在构造函数对象的时候自己调用
- 构造函数决定了对象的长相
- 无参构造函数可以构造无参对象
- 有参构造函数,对象必须要带有参数
- 构造函数允许被重载和缺省
- 构造函数一般情况是公有属性
- 构造函数一般是用来给数据初始化
- 构造函数允许调用另一个构造函数,但是必须采用初始化参数列表的写法:
- 构造函数的初始化参数列表: 构造函数名(参数1,参数2,…):成员1(参数1),成员2(参数2),…{}
2.综合代码
#include<iostream>
#include<string>
using namespace std;
class MM
{
public://构造函数//MM()=default; //使用的是默认的构造函数//MM()=delete;MM(){cout<<"无参构造函数"<<endl;}MM(int a){cout<<"具有一个参数的构造函数"<<endl;}
protected:
};class Girl
{
public:Girl()=delete;
protected:
};class Student
{
public:Student(){m_age=15,m_name="k";}Student(string name,int age){//做初始化操作m_name=name;m_age=age;}void printStudent(){cout<<m_name<<"\t"<<m_age<<endl;}
protected:string m_name;int m_age;
};class Test{
public://构造函数特殊写法Test(int a,int b):a(a),b(b){}Test():Test(9,8){} //无参构造函数调用有参构造函数,构造委托//Test()=default;void print(){cout<<a<<"\t"<<b<<endl;}
protected:int a=0;int b=0;
};struct Data
{int a;int b;int c;Data(int a):a(a){}Data(int a,int b,int c):a(a),b(b),c(c){cout<<"调用三个参数的构造函数"<<endl;}void print(){cout<<a<<"\t"<<b<<"\t"<<c<<endl;}
};
int main()
{MM boy;MM girl(1);//Girl girl; //默认构造函数已经删除,且自己没写构造函数,所以错误//普通对象Student mm("zhangkai",15);mm.printStudent();//new一个对象,new的过程是先在自由存储区创建一个无名对象,再把地址返回Student* pstu = new Student("zhi",29); pstu->printStudent();//对象数组Student stuarry[3]; //无名对象,需要无参构造函数,否则错误stuarry[1].printStudent();stuarry[2].printStudent();stuarry[0].printStudent();//初始化参数列表Test test(99,88);test.print();Test bb;bb.print();Test xx={88,99}; //这个过程也是调用构造函数过程,{}中数据个数要和构造函数参数一致xx.print();Data oo(3);oo.print();Data data(1,2,3);data.print();
}
2.析构函数
1.析构函数特性
- 函数名等于~加上类名
- 析构函数没有参数,所以析构函数不能被重载也不能被缺省
- 对象死亡(生命周期结束)的最后一个事情是调用析构函数
- 析构函数都是公有属性
- 什么时候写析构函数?
- 当类的成员new了内存就需要自己手动写析构函数
- 不写析构函数,也会存在一个析构函数,但是不具有释放new的内存的功能
2.综合代码
#include<iostream>
using namespace std;
class MM
{
public:MM(){p=new int;}void freeMM(){delete p;p=nullptr;}~MM(){cout<<"我是析构函数"<<endl;delete p;p=nullptr;}
protected:int* p;
};
int main()
{{MM mm;//mm.freeMM(); //当然也可以自己写函数释放,不过要手动释放MM* p=new MM;delete p; //立刻马上调用析构函数 }cout<<"..............."<<endl;return 0;
}
3.拷贝构造函数
1.拷贝构造函数特性
- 不写拷贝构造函数,存在一个默认拷贝构造函数
- 拷贝构造函数名和构造函数一样,算是构造函数特殊形态
- 拷贝构造函数唯一的一个参数就是对对象的引用
- 普通引用
- const引用
- 右值引用——>移动拷贝
- 当我们通过一个对象产生另一个对象时候就会调用拷贝构造函数
2.综合代码
#include<iostream>
#include<string>
using namespace std;
class MM{
public:MM()=default;MM(MM& object){cout<<"调用拷贝构造函数"<<endl;}
protected:
};class Girl{
public:Girl(string name,int age):name(name),age(age){}Girl():Girl("",0){}Girl(const Girl& object){ //拷贝构造函数就是通过一个对象赋值另一个对象name=object.name;age=object.age;cout<<"调用拷贝构造函数"<<endl;}void print(){cout<<name<<"\t"<<age<<endl;}
protected:string name;int age;
};void printBoy(Girl girl) //调用拷贝构造函数,Gilr girl=实参
{girl.print();
}void printGirl(Girl& girl)
{girl.print();
}void testGirl()
{Girl mm("小妹",19);Girl girl(mm); //调用girl.print();Girl beauty=mm; //调用beauty.print();cout<<"传入普通变量"<<endl;printBoy(girl); //调用cout<<"传入引用"<<endl; //不调用拷贝构造函数printGirl(girl);//匿名对象的拷贝构造函数,匿名对象是右值,可以用const或者右值引用//const里不可修改//右值引用里提供了可修改的接口Girl xx=Girl("zhangkai",19);xx.print();
}class Boy{
public:Boy(string name,int age):name(name),age(age){}Boy(Boy&& object){name=object.name;age=object.age;cout<<"右值引用的拷贝构造"<<endl;}Boy(Boy& object){name=object.name;age=object.age;cout<<"普通的拷贝构造"<<endl;}
protected:string name;int age;
};void testBoy(){Boy boy("boy",10); Boy bb=boy; //调用普通对象Boy coolman=Boy("dasd",29); //右值引用的拷贝构造函数//没有打印结果,IDE做了优化,看不到
}
int main()
{MM mm;MM girl=mm; //会调用拷贝构造函数MM boy(girl); //会调用拷贝构造函数//string str="dasd";//string str2(str);//string str3=str2;//string类型赋值实际上是调用拷贝构造函数//调用拷贝构造函数语句一定有类名//不调用拷贝构造函数,这是先创建对象,然后赋值,属于运算符重载MM npc; npc=girl; cout<<"............"<<endl; testGirl();cout<<"............"<<endl;testBoy();return 0;
}
3.深浅拷贝问题
深浅拷贝只在类中存在指针,并且做了内存申请的,才会存在引发析构问题(内存释放问题)
- 默认的拷贝构造都是浅拷贝
- 拷贝构造函数中做普通的赋值操作也是浅拷贝
错误代码
#include<iostream>
#include<cstring>
using namespace std;
class MM{
public:MM(const char* str,int num){int length=strlen(str)+1;name=new char[length];strcpy_s(name,length,str);age=num;}~MM(){if(name!=nullptr){delete[] name;name=nullptr;}}
protected:char* name;int age;
};
void testQuestion(){MM mm("zhangzhang",19);MM xx=mm;
}
int main()
{testQuestion();return 0;
}
原因如下图:
正确代码
#include<iostream>
#include<cstring>
using namespace std;
class MM {
public:MM(const char* str, int num){int length = strlen(str) + 1;name = new char[length];strcpy_s(name, length, str);age = num;}MM(const MM& object){//深拷贝int length = strlen(object.name) + 1;name = new char[length];strcpy_s(name, length, object.name);age = object.age;}~MM(){if (name != nullptr){delete[] name;name = nullptr;}}
protected:char* name;int age;
};
void testQuestion() {MM mm("zhangzhang", 19);MM xx = mm;
}
int main()
{testQuestion();return 0;
}
解释:
4.构造和析构的顺序问题
- 一般情况构造顺序和析构顺序是相反的(先构造后释放,后构造先释放)
- new对象,调用delete直接被释放
- static对象,最后释放(生命周期最长)
#include<iostream>
#include<string>
using namespace std;
class MM
{
public:MM(string info="A"):info(info){cout<<info;}~MM(){cout<<info;}
protected:string info;
};
void testOrder()
{MM mm1("B");static MM mm2("C");MM* p=new MM("D");delete p;MM arr[3];
}int main()
{testOrder();return 0;
}
5.c++类的组合
介绍:
- 一个类包含另一个类的对象为数据成员叫做类的组合。当多种事物是一个事物的一部分,采用组合类来完成描述,c++中组合的使用优先于继承
- 注意:类不能包含自身对象,否则会形成死循环
- 组合类的构造函数,必须要采用初始化参数列表的方式调用分支类的构造函数
- 组合类的构造顺序:先构造分支类,分支类的顺序只和声明顺序有关,和初始化参数列表一点毛线关系
#include<iostream>
#include<string>
using namespace std;class MM
{
public:MM(){cout<<"构造mm"<<endl;}MM(string name):name(name){}void print(){cout<<"MM:"<<name<<endl;}
protected:string name;
};class GG
{
public:GG(){cout<<"构造gg"<<endl;}GG(string name,int age):name(name),age(age){}void print(){cout<<name<<"\t"<<age<<endl;}
protected:string name;int age;
};class Family
{
public:Family(string mmName,string ggName,int age):mm(mmName),gg(ggName,age){}//但是分支类中必须存在无参的构造函数Family(){cout<<"构造组合类"<<endl;}void print(){gg.print();mm.print();}
protected:MM mm;GG gg;
};
int main()
{Family dd("mm","gg",19);dd.print();Family object;object.print(); //先分支再组合,且分支的顺序与声明的顺序一致return 0;
}
6.c++类中类
- 类中类的访问问题以及类中类先申明后定义的写法
- 类中类依旧受权限限定
- 访问必须要类名::剥洋葱的方式访问
#include<iostream>
using namespace std;
class xx {
public:xx(){cout<<"外面的构造函数"<<endl;}
protected:
public://类中类依旧受权限限定,就相当于把一个类丢到另外一个类中,他们两个没有关系class dd{public:dd(){cout<<"类中类构造函数"<<endl;}void print(){cout<<"类中类构造函数"<<endl;}protected:};
};
void testlzl()
{xx::dd bb;bb.print();
}
int main()
{testlzl();return 0;
}