对象的创建,初始化,销毁与拷贝
对象
- 一些小细节
- 对象的创建
- 默认创建
- 函数成员声明再定义
- 对象初始化
- 构造函数
- 默认构造函数
- 构造函数定义
- 构造函数初始化对象
- 对象的销毁
- 析构函数
- 不同类型对象销毁的次序
- 析构函数的注意事项
- 对象的拷贝
- 对象拷贝时的两种情况
- 构造拷贝函数时的两个注意点
- 关于拷贝函数和析构函数的创建时机
- 全过程代码
一些小细节
在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;
}