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;
}