1、构造函数和析构函数
-
如果程序员没有提供这两函数,系统会自动提供。
-
这俩都得在public下
(1)构造函数相当于初始化
与类名相同,没有返回值,不写void,可以重载(不同的参数)
构造函数由编译器自动调用而不是手动,而且只会调用一次
class Person{
public:
Person(){
cout<<"无参的构造函数"<<endl;
}};
void test(){
Person p1;//这里只是初始化了p1这个对象。但构造函数和析构函数都会执行
}
(2)析构函数
相当于**清理**
与类名相同,类名前面加一个符号“~”,也没有返回值所以不写void,**不可以有参数(不能重载)**
自动调用,只调用一次
~Person(){
cout<<"析构函数"<<endl;
};
2、构造函数的分类及调用
- 按照参数进行分类:无参(默认),有参
- 按照类型进行分类:普通构造函数,拷贝构造函数
(1)拷贝构造函数
Person(const Person &p){
age=p.age;//拷贝别人数据
cout<<"拷贝构造函数调用了!"<<endl;
}
ps:默认构造函数不要加()
(2)调用方法有:
- 括号法调用
- 显示法调用
Person p4=Person(100);
Person(100)叫匿名对象,匿名对象待定。如果编译器发现了对象是匿名的,那么在这行代码结束后就释放这个对象- ![[Pasted image 20220930214937.png]]![[Pasted image 20220930215021.png]]
- 隐式类型转换相当于调用Person p=Person(100)
- ![[Pasted image 20220930215723.png]]
3、拷贝构造函数调用时机
- 用已经创建好的对象来初始化新的对象(很常用)
Person p1;
p1.age=100;
Person p2(p1);// 用已经创建好的对象来初始化新的对象
- 以值传递的方式给函数参数传值
void doWork(Person p1){//相当于Person p1=Person(p)
}
void test02(){
Person p;
p.age=1000;
doWork(p);//相当于Person p1=Person(p)
}
- 以值的方式返回局部对象(不是很常用)
Person doWork2(){
Person p1;
return p1;
}
void test03(){
Person p=doWork2(); //Person p=Person(p1)
}
4、构造函数的调用规则
- 系统默认提供的:无参构造,拷贝构造,析构函数.
- 当我们提供了有参的构造函数(且我们没写默认无参构造函数),那么系统就不会给我们提供默认构造函数了. 但是系统还会提供默认的拷贝构造函数,进行简单的值拷贝.
- 当我们提供了拷贝构造(且我们没写默认无参构造函数),那系统就不会提供其他构造了.
5、深拷贝和浅拷贝
如果属性里有指向堆区空间的数据,那么浅拷贝会导致重复释放内存的异常!
- 浅拷贝:只把地址拷贝过来,系统简单的值拷贝
class Person{
public:
char * name;
int age;
Person(char * iname,int iage){
name= (char *)malloc(strlen(iname)+1);//属性开辟到堆上了
strcpy(name,iname);
age=iage;
}
~Person(){
cout<<"析构函数"<<endl;
if(name!=NULL){ //name属性开辟到堆上了,要释放
free(name);
name=NULL;
} }
};
![[Pasted image 20221005205735.png]]
void test01(){
Person p1("王志",23);
Person p2(p1);//拷贝构造!
}
- 深拷贝:开辟一个新的空间
![[Pasted image 20221005205833.png]]
- 要自己提供拷贝构造,因为浅拷贝会释放堆区空间两次,会奔溃
- 深拷贝,这里为新的name创建了新的空间
Person(const Person &p){
name= (char *)malloc(strlen(p.name)+1);//创建了新的空间
strcpy(name,p.name);
age=p.age;
}
6.多个对象的构造和析构
(1)初始化列表
构造函数:属性(参数),属性(参数),属性(参数){}
①有参构造函数这样写:
Person(int ia,int ib,int ic):a(ia),b(ib),c(ic){}
利用初始化列表来初始化数据.相当于
Person(int ia,int ib,int ic){
a=ia;
b=ib;
c=ic;
}
②无参构造函数这样写:
Person():a(200),b(3000),c(1){}
相当于写死了.
(2)类对象作为成员的案例
- 类对象作为类成员的时候,构造顺序先将类对象一一构造,然后构造自己.
- 但析构的顺序是相反的.
7.explicit关键字
防止构造函数中的隐式类型转换
8.动态对象创建
new与delete配套->堆区
malloc与free配套->栈区
(1)new
C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。
new是运算符!
当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化:
Person* person = new Person;
//堆区开辟
Person p1;
//栈区开辟
- 所有new出来的对象,都会返回该类型的指针,所以要用
ClassName *
去接 - malloc返回的是
void *
,所以必须在malloc之前用(ClassName *)
强转 - malloc不会调用构造函数;new会调用构造函数.
- new是运算符,malloc是函数
(2)delete
delete用来释放堆区空间.
(3)用于数组的new和delete
- 写法:
className * arrayName=new className[size];
delete[] arrayName;
//创建字符数组
char* pStr = new char[100];
//创建整型数组
int* pArr1 = new int[100];
//创建整型数组并初始化
int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//释放数组内存
delete[] pStr;
delete[] pArr1;
delete[] pArr2;
- 注意事项:
- 创建堆上对象数组必须提供默认的构造函数