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















![[go学习笔记.第十八章.数据结构] 2.约瑟夫问题,排序,栈,递归,哈希表,二叉树的三种遍历方式](https://img-blog.csdnimg.cn/c059abcbe7cc4f848b6fa547111fab07.png)



