c++类和对象基础(二)

一、拷贝构造函数调用时机

1.创建新对象时用已有的初始化

2.值传递的方式给函数参数传值

值传递的方法本质上会临时拷贝一个副本出来,即开辟一个新内存空间存储复制出来的内容,会调用拷贝构造函数,且形参更改不会影响实参。这里doWork函数中传入的doWork(p2),相当于是写了Person p1 = p2; 

void doWork(Person p1) {

}
void test02() {
	Person p2;
	doWork(p2);
}

3.值方式返回局部对象(取决于编译器)

注意:vs2022版本不会进行拷贝,两个对象的地址都是一样的

函数调用结束后会释放空间,即原有的内部的p1空间会释放掉,但是由于我return p1,使得编译器会根据p1创建一个新的对象。也就是说,这么一串下来,就等于Person p2(doWork());,会调用拷贝构造函数。

Person doWork() {
	Person p1;
	return p1;
}
void test03() {
	Person p2(doWork());
}

二、构造函数的调用规则

每写一个类,c++会自动编写默认构造/析构函数和拷贝构造函数。

  • 若自行编写有参构造函数,则不再提供默认构造函数,调用会报错
  • 若自行编写拷贝构造函数,则不再提供其他构造函数,调用会报错

注意,只编写拷贝构造函数,则c++不会提供默认构造函数,因此第一个对象会无法构造。

三、深拷贝与浅拷贝

深拷贝:在堆区重新申请空间,进行拷贝。

浅拷贝:简单的赋值操作。

1.浅拷贝

简单的赋值操作,类似于“=”,一旦涉及到堆区、指针等容易出错。

#include<bits/stdc++.h>
using namespace std;

class  Person
{
public:
	int age;
	int* height;

public:
	Person() {
		cout << "默认构造函数" << endl;
	}
	Person(int n,int h) {
		age = n;
		height = new int(h);
		cout << "有参构造函数" << endl;
	}
	Person(const Person& p) {
		age = p.age;
		height = p.height;
		cout << "拷贝构造函数"  << endl;
	}
	~Person() {
		cout << "析构函数" << endl;
		if (height != NULL) {
			delete height;
			height = NULL;
		}
	}

};

int main() {
	Person p1(20,180);
	Person p2(p1);	
	cout << "p1:  " << *p1.height << endl;
	cout << "p2:  " << *p2.height << endl;

	return 0;
}

如果利用编译器提供的拷贝构造函数,会做浅拷贝操作,把数据一个个拷贝过来,因此此时p1与p2时指向同一个堆区地址。堆区是公用的,来回两次释放了同一个空间,会报错。栈区先进后出,p2先释放了指针指向的空间,到p1再释放一次,重复释放就会出问题。

2.深拷贝

深拷贝就相当于再开辟一个堆区地址,使得p1与p2指向两个地址。

#include<bits/stdc++.h>
using namespace std;

class  Person
{
public:
	int age;
	int* height;

public:
	Person() {
		cout << "默认构造函数" << endl;
	}
	Person(int n,int h) {
		age = n;
		height = new int(h);
		cout << "有参构造函数" << endl;
	}
	Person(const Person& p) {
		age = p.age;
		//height = p.height;  编译器默认实现的本质就是这种写法

		//深拷贝操作
		height = new int(*p.height);
		cout << "拷贝构造函数"  << endl;
	}
	~Person() {
		cout << "析构函数" << endl;
		if (height != NULL) {
			delete height;
			height = NULL;
		}
	}

};

int main() {
	Person p1(20,180);
	Person p2(p1);	
	cout << "p1:  " << *p1.height << endl;
	cout << "p2:  " << *p2.height << endl;

	return 0;
}

这里由于在拷贝构造函数中使用了new函数,开辟了新的堆区空间,相当于从头到尾把p1的操作重新进行了一遍,因此不会导致错误。

四、初始化列表

看似更烦了,实则大有好处,可以对const的属性值进行更改了,牛逼克拉斯!还可以控制传入参数的数量,选择性进行初始化操作,多好!

#include<bits/stdc++.h>
using namespace std;

class  Person
{
public:
	int a;
	int b;
	int c;
	const int d;

public:
	//初始化列表
	Person(int m,int n,int x,int y) :a(m), b(n), c(x),d(y)
	{
		cout << "初始化列表" << endl;
	}

	//传统赋值操作
	Person(int m, int n, int x, int y) {
		a = m; 
		b = n;
		c = x;
		d = y;    //注意这里会出错,const不允许修改,但是初始化列表可以进行修改
	}

	~Person() {
		cout << "析构函数" << endl;
	}

};

int main() {
	Person p1(1,2,3,4);
	cout << p1.d << endl;
	return 0;
}

五、类对象作为类成员

#include<bits/stdc++.h>
using namespace std;

class Ji {
public:
	string Ji_Name;
	Ji() {
		cout << "Ji" << endl;
	}
	Ji(string name) {
		Ji_Name = name;
		cout << "Ji" << endl;
	}
	~Ji() {
		cout << "Ji析构函数" << endl;
	}
};

class Dan {
public:
	string Dan_Name;
	Ji a;
	Dan(string name1 , string name2):Dan_Name(name1),a(name2)
	{
		cout << "Dan" << endl;
	}
	~Dan() {
		cout << "Dan析构函数" << endl;
	}
};

int main() {
	Dan p("cc", "pp");
	cout << "Dan的名字:" << p.Dan_Name << "   Ji的名字:" << p.a.Ji_Name << endl;
	return 0;
}

会先把子类成员构造出来,再构造自身。即例子中,Dan类中包含了Ji类,会先构造Ji,再构造Dan。而在析构时相反,先析构Dan,再析构Ji。就是先天下之忧而忧,后天下之乐而乐。哈哈没毛病!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值