对象的创建,初始化,销毁与拷贝

一些小细节

在c++中,struct的功能已经进行扩展,基本和class的功能一致
唯一的区别就在于struct默认为public,class默认为private

对象的创建

默认创建

如果不需要任何附加条件,仅仅是创建一个对象的话,直接写是OK的
对象创建时,如果是带括号的无参创建,实际意义是函数声明(很奇怪,在函数内部还能声明函数)

#include <iostream>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	void setbrand(const string& brand)
	{
		_brand = brand;
	}
	void setprice(const int& price)
	{
		_price = price;
	}
	
private:
	string _brand;
	int _price;
};

int main()
{
	Computer p1;	
	return 0;
}

函数成员声明再定义

函数成员可以像外部函数一样声明后再在类外部进行定义
但此时会有一个缺点,我们想要通过函数成员对数据成员进行修改时,数据成员不允许访问,所以一般的,如果想要在类外部定义函数成员,就需要像namespace的引用方法一样去定义

#include <iostream>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	void setbrand(const string& brand);
	void setprice(const int& price);
private:
	string _brand;
	int _price;
};

//类外部定义函数成员,通过作用域来引用,此时可以访问数据成员
void Computer::setbrand(const string& brand)
{
	_brand = brand;
}
void Computer::setprice(const int& price)
{
	_price = price;
}

int main()
{
	Computer p1;
	return 0;
}

对象初始化

构造函数

构造函数就是用来初始化数据成员的
形式:没有返回值,非void,函数名与类名必须相同

默认构造函数

由系统提供的一个构造函数,因此即使对象的创建过程没有构造函数也是允许的

构造函数定义

#include <iostream>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:

	//构造函数一般写法
	Computer(const string& brand, const int& price)//建议取地址进行引用,可以节省内存占用
	{
		_brand = brand;
		_price = price;

		cout << "Have done structure!" << endl;
	}
private:
	string _brand;
	int _price;
};

void test0()
{
	Computer p1("HUAWEI", 4999);//因为有了显示构造函数,对象的创建也会发生改变
}
int main()
{
	test0();

	return 0;
}

上述方法只是一般构造函数写法,赋值并非是真正对数据成员初始化

构造函数初始化对象

对于类中数据成员的初始化,需要初始化列表进行完成
形式:在构造函数的头与构造函数的体之间,用 ’ : ’ 进行分隔

Computer(const string& brand, const int& price)
		:_brand(brand)//如果brand为字符数组,可以在函数体内部通过strcpy或者memset对brand初始化
		, _price(price)//如果数据成员有多个,为了代码规范,就另起一行并以逗号分隔
	{
		cout << "Have done structure!" << endl;
	}

初始化数据成员的顺序与初始化列表无关,如果有两个数据成员ix和iy,当已经把ix初始化了后,用ix去初始化iy,iy的值仍为随机值

对象的销毁

析构函数

用于对象的销毁,系统会默认提供
形式:同构造函数一样,没有返回值
没有参数,函数名与类名相同,但前面带个 ‘~’,且析构函数只能有一个

一般形式

#include <iostream>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	Computer(const string& brand, const int& price)
		:_brand(brand)
		, _price(price)
	{
		cout << "Have done structure!" << endl;
	}
	~Computer()
	{
		cout << "Have done Delete" << endl;
	}
private:
	string _brand;
	int _price;
};

void test0()
{
	Computer p1("HUAWEI", 4999);
	p1.~Computer();

}
int main()
{
	test0();

	return 0;
}

输出结果

Have done structure!
Have done Delete
Have done Delete

可以看到输出了两次销毁,因此可以断定对象的销毁一定调用了析构函数,但调用析构函数并不一定代表对象的销毁

不同类型对象销毁的次序

一般来说,对象的销毁时间与其生命周期有关
全局对象生命周期最长,在程序结束时才能销毁
静态对象在main函数结束时进行销毁
局部对象以及语句块中的变量在函数或代码块结束时进行销毁
堆对象在delete或者free时进行销毁,如果不写就会造成内存泄漏

#include <iostream>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	Computer(const string& brand, const int& price)
		:_brand(brand)
		, _price(price)
	{
		//cout << "Have done structure!" << endl;
	}
	~Computer()
	{
		cout<<_brand<<' '<< "Have done Delete" << endl;
	}
private:
	string _brand;
	int _price;
};

Computer p4("All", 4);

void test0()
{
	Computer p1("temp", 1);//局部变量
	static Computer p3("static", 3);
}
int main()
{
	test0();

	Computer p2("main", 2);
	Computer* p5 = new Computer("new", 5);

	delete p5;

	return 0;
}

输出结果

temp Have done Delete
new Have done Delete
main Have done Delete
static Have done Delete
All Have done Delete

析构函数的注意事项

在Computer类中, 我们用string定义了_brand数据成员
如果非要采用字符数组的形式进行定义,会出现不确定数组大小是否合适的问题
改用指针的形式,通过new或者malloc分配空间可以解决该类问题

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	Computer(const char* brand, const int& price)
		:_brand(new char[strlen(brand)+1]())//通过new分配空间,后面加的小括号可以初始化_brand
		, _price(price)
	{
		strcpy(_brand, brand);//初始化brand
		//cout << "Have done structure!" << endl;
	}
	~Computer()
	{
		cout<<_brand<<' '<< "Have done Delete" << endl;
	}
private:
	char* _brand;
	int _price;
};

此时又会出现新的问题,我们无法确定对象在销毁时,new分配的空间是否会被delete掉,通过内存检测工具发现是没有销毁掉的,因此我们需要对析构函数进行改进,而这一情况的讲解我会放在拷贝函数之后

对象的拷贝

如果未显示定义,系统会默认提供一个拷贝函数
类中定义的固定形式

//Point类名

class Point
{
public:
	Point(const Point& p)
	: _ix(p._ix)
	,_iy(p._iy)
	{
		;
	}
private:
	int _ix;
	int _iy;
}

对象拷贝时的两种情况

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	Computer(const char* brand, const int& price)
		:_brand(new char[strlen(brand)+1]())//通过new分配空间,后面加的小括号可以初始化_brand
		, _price(price)
	{
		strcpy(_brand, brand);//初始化brand
		//cout << "Have done structure!" << endl;
	}
	~Computer()
	{
		cout<<_brand<<' '<< "Have done Delete" << endl;
	}

	void print()
	{
		cout << _brand << ' ';
		cout << _price << endl;
	}
private:
	char* _brand;
	int _price;
};

void point(Computer p)
{
	p.print();
}

Computer retu()
{
	Computer pt("xx", 11);
	return pt;
}
void test1()
{
	Computer p1("1", 1);
	
	//对象未被创建时
	Computer p2 = p1;
	Computer p3(p1);
	//对象已被创建时
	//一般形式
	Computer p4("2", 2);
	p4 = p1;
	//实参传给形参
	point(p1);
	//返回值为对象
}
int main()
{
	test1();

	return 0;
}

通过直接赋值的方式进行拷贝是浅拷贝,可以通过对赋值运算符重构使之成为深拷贝,同时注意函数传参如果采用实参传形参而不是引用的形式,那么传参的拷贝是浅拷贝

Computer& operator = (const Computer& p)
	{
		Computer tmp("", 1);
		tmp._brand = new char[strlen(p._brand) + 1]();
		tmp._price = p._price;
		strcpy(tmp._brand, p._brand);

		cout<<tmp._brand<<' '<<"Have done copy"<<endl;

		return  tmp;
	}

构造拷贝函数时的两个注意点

1.为什么要使用&

如果不去使用&符号,函数形式会变成Computer(const Computer p)
可以发现如果这么做,参数部分就是创建对象的形式,他会像一个栈递归一样无限创建下去,直到内存爆满
又因为进行了指针访问,可以认为形参对象就在类的内部,可以访问数据成员
使用指针也可以代替&

2.为什么需要const修饰

首先需要了解左值和右值的概念
右值:没有写入内存的数据,不能进行取地址访问
左值:以及写入内存的数据,可以进行取地址访问
值得注意的是,函数如果有返回值,那么他的返回值就是临时变量,这里的临时变量就是右值,无法取地址访问
而通过const修饰,可以绑定右值

	int a1 = 1;
	int& a2 = a1;
	int& a3 = 1;
	const int& a4 = 1;

可以发现带有a3无法编译通过,去掉a3后,a1a2a4是可以编译通过的,因为1是右值,无法进行取地址访问,而通过const修饰绑定右值后就可以强行访问了
同理,函数返回值也是如此

class Computer
{
public:
	Computer(const char* brand, const int& price)
		:_brand(new char[strlen(brand)+1]())//通过new分配空间,后面加的小括号可以初始化_brand
		, _price(price)
	{
		strcpy(_brand, brand);//初始化brand
		//cout << "Have done structure!" << endl;
	}
	~Computer()
	{
		cout<<_brand<<' '<< "Have done Delete" << endl;
	}
	Computer(const Computer& p)
		:_brand(p._brand)//此时采用地址赋值的形式,会在后文解释改进方法
		,_price(p._price)
	{
		cout<<_brand<<' '<<"Have done copy"<<endl;
	}
	void print()
	{
		cout << _brand << ' ';
		cout << _price << endl;
	}
private:
	char* _brand;
	int _price;
};

Computer retu()
{
	Computer pt("xx", 11);
	return pt;
}
void test2()
{
	Computer a1("1",1);
	Computer a2 = retu();
}

如果在拷贝函数定义过程中去掉了const修饰,a2是无法通过的,通过const绑定右值后即可进行

关于拷贝函数和析构函数的创建时机

前文在析构函数中提到,如果采用字符数组的形式以new分配空间, 在对象销毁时会发生内存泄漏
此时我们选择对析构函数进行改进

	void release()
	{
		delete[] _brand;
		_brand = nullptr;//c++11中更安全的置空方法
	}
	~Computer()
	{
		release();
		cout<< "Have done Delete" << endl;
	}

此时我们可以避免内存发生泄漏
我们通过sizeof查看类的大小时往往不是我们想要的结果,比如_brand为11个字节时,再加上int时,该类的大小实际却为16,这是因为一种对齐数的存在,在64位系统下类和结构体的大小必须为8的倍数,32位系统下必须为4的倍数

在拷贝函数中我们注意到,当时我们只是采用地址直接赋值的方式对新的对象复制
往往会发生以下情况

void test3()
{
	Computer p1("HUAWEI", 4999);
	Computer p2 = p1;
	p1.~Computer();
	p2.print();
}

此时我们发现在程序运行过程中发生崩溃,这是因为字符数组我们仅仅复制了地址过去,p1在销毁后,p2中brand的地址仍是p1的brand,brand的值为NULL,进行了非法访问
改进方法,在拷贝函数中,不再是复制地址,而是另开辟一个新的空间存放brand,通过strcpy将brand复制过去

Computer(const Computer& p)
		:_brand(new char[strlen(p._brand) + 1]())
		,_price(p._price)
	{
		strcpy(_brand, p._brand);

		cout<<_brand<<' '<<"Have done copy"<<endl;
	}

全过程代码

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	Computer(const char* brand, const int& price)
		:_brand(new char[strlen(brand)+1]())//通过new分配空间,后面加的小括号可以初始化_brand
		, _price(price)
	{
		strcpy(_brand, brand);//初始化brand
		//cout << "Have done structure!" << endl;
	}
	void release()
	{
		delete[] _brand;
		_brand = nullptr;//c++11中更安全的置空方法
	}
	~Computer()
	{
		if(_brand) release();
		cout<< "Have done Delete" << endl;
	}
	Computer(const Computer& p)
		:_brand(new char[strlen(p._brand) + 1]())
		, _price(p._price)
	{
		cout << _brand << ' ' << "Have done copy" << endl;
	}
	//对赋值运算符重构
	Computer& operator = (const Computer& p)
	{
		Computer tmp("", 1);
		tmp._brand = new char[strlen(p._brand) + 1]();
		tmp._price = p._price;
		strcpy(tmp._brand, p._brand);

		cout<<tmp._brand<<' '<<"Have done copy"<<endl;

		return  tmp;
	}
	void print()
	{
		cout << _brand << ' ';
		cout << _price << endl;
	}
private:
	char* _brand;
	int _price;
};

Computer p44("All", 4);

void test0()
{
	Computer p1("temp", 1);//局部变量
	static Computer p3("static", 3);
}

void point(Computer p)
{
	p.print();
}

Computer retu()
{
	Computer pt("xx", 11);
	return pt;
}
void test1()
{
	Computer p1("1", 1);
	
	//对象未被创建时
	Computer p2 = p1;
	Computer p3(p1);
	//对象已被创建时(注意对象已存在时直接赋值为浅拷贝)
	//一般形式
	Computer p4(p1);
	p4 = p1;
	//实参传给形参
	point(p1);
	//返回值为对象
	p4 = retu();
}

void test2()
{
	Computer a1("1",1);
	Computer a2 = retu();
}
void test3()
{
	Computer p1("HUAWEI", 4999);
	Computer p2(p1);
	p1.~Computer();
	p2.print();
}
int main()
{
	test0();

	Computer p2("main", 2);
	Computer* p5 = new Computer("new", 5);
	delete p5;

	test1();

	test2();

	test3();

	return 0;
}

对象的现代写法
深拷贝采用swap的优化方式,而浅拷贝不允许,解释:在函数实参传形参过程中,采用了浅拷贝,当前对象未被创建过,swap后临时变量地址未知,delete时会出现问题

#include <iostream>
#include <cstring>
#include <algorithm>

using std::cout;
using std::endl;
using std::string;

class Computer
{
public:
	Computer(const char* brand, const int& price)
		:_brand(new char[strlen(brand) + 1]())//通过new分配空间,后面加的小括号可以初始化_brand
		, _price(price)
	{
		strcpy(_brand, brand);//初始化brand
		cout << "Have done structure!" << endl;
	}
	void release()
	{
		//cout << &_brand << endl;
		delete[] _brand;
		_brand = nullptr;//c++11中更安全的置空方法
	}
	~Computer()
	{
		if (_brand) release();
		cout << "Have do ne Delete" << endl;
	}

	void swap(Computer& p)
	{
		std::swap(this->_brand, p._brand);
		std::swap(this->_price, p._price);
	}

	Computer(const Computer& p)
		:_brand(new char[strlen(p._brand) + 1]())
		,_price(p._price)
	{
		strcpy(_brand, p._brand);
	}
	Computer& operator = (const Computer& p)
	{
		if (this == &p) return *this;

		Computer tmp(p._brand, p._price);

		swap(tmp);

		return *this;//返回类自己
	}

	void print()
	{
		cout << _brand << ' ';
		cout << _price << endl;
	}
private:
	char* _brand;
	int _price;
};

Computer p44("All", 4);

void test0()
{
	Computer p1("temp", 1);//局部变量
	static Computer p3("static", 3);
}

void print(Computer a)
{
	a.print();
}

Computer retu()
{
	Computer pt("xx", 11);
	return pt;
}
void test1()
{
	Computer p1("1", 1);

	//对象未被创建时
	Computer p2 = p1;
	Computer p3(p1);
	//对象已被创建时(注意对象已存在时直接赋值为浅拷贝,使用重构改为深拷贝)
	//一般形式
	Computer p4(p1);
	p4 = p1;
	//实参传给形参,浅拷贝
	print(p1);
	//返回值为对象
	p4 = retu();
}

void test2()
{
	Computer a1("1", 1);
	Computer a2 = retu();
}
void test3()
{
	Computer p1("HUAWEI", 4999);
	Computer p2(p1);
	p1.~Computer();
	p2.print();
}
int main()
{
	//test0();

	Computer p2("main", 2);
	Computer* p5 = new Computer("new", 5);
	delete p5;

	test1();

	//test2();

	//test3();

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涅槃豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值