版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/YL970302/article/details/83958496
C++中的权限public和private
绝大多数数据成员的声明出现在成员访问说明符private之后,在成员访问说明符private之后声明的变量或者函数,只可以被声明它们的类的成员函数(或者“友元”)所访问。
C++中有四种特殊的函数,分别是(1)构造函数(2)拷贝构造函数(3)赋值函数(以“=”为例)(4)析构函数
在讲这四种函数之前我们先讨论一下关于this指针
- 在成员方法声明或者定义的时候加上this指针
- 成员方法调用的时候会自动传参this指针
- 在成员方法内使用成员变量的地方,加上this指针的解引用
下面我们将用一段代码分别进行讲解
一、构造函数:
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
class CGoods
{
private:
char *_name;
int _num;
double _price;
public :
CGoods(const char *name, int num, double price) //构造函数
{
//assert(NULL != name);
if(NULL != _name)
{
_name = new char[strlen(name)+1];//_name是char *型的,所以先要申请空间
strcpy_s(_name,strlen(name)+1,name);
}
else
{
_name = NULL;
}
_num = num;
_price = price;
}
(1)构造函数的写法:函数名与类名相同,没有返回值,参数列表根据需求定义
(2)构造函数的使用
- 初始化对象的时候自动调用
- 如果不自己实现构造函数,编译器会默认生成一个默认构造函数。如果自己实现了,则编译器不会生成了
- 构造函数可以重载
二、拷贝构造
- 用一个已经存在的队形,构造同类型的新的对象
- 防止浅拷贝
- 如果不自己实现,编译器会默认生成一个浅拷贝的拷贝构造
- 拷贝构造函数必须传引用(为什么呢?因为如果传对象的话,对象又要去调用构造函数,这样会出现四递归)
public:
CGoods(const CGoods &src) //拷贝构造,注意这里传的是引用
{
//_name = src._name//errno,这是浅拷贝,析构的时候会崩溃
//goods1的this->_name = goods2的this->_name
//this->_name = this->src._name这样写也是可以的
//因为_name是char *型的,所以我们先给它申请空间
_name = new char[strlen(src._name) + 1];
strcpy_s(_name,strlen(src._name) + 1,src._name);
_num = src._num;
_price = src._price;
}
int main()
{
CGoods goods1("cup",30,15);
goods1.Show();
CGoods goods2(goods1);
//拷贝构造 //先构造的后释放,所以在拷贝构造那里如果进行浅拷贝的话(指向了同一块空间),等析构的时候就是把那段空间给释放了,再析构上goods1的时候就是在释放一段没有东西的空间,就会出错
goods2.Show();
- 由上面的代码我们知道,goods1是一个已存在的对象,而新对象goods2又是和它同类型的,所以我们用goods1拷贝构造goods2。
- 上面的浅拷贝用图片表示,goods1有一个空间,它为_name申请的空间,拷贝构造goods2之后goods2也有了一段属于自己的空间,但此时如果进行了浅拷贝,那么它俩就指向了同一块空间,而析构函数的特点是,先构造的后释放,后构造的先释放,所以我们发现,它会先释放goods2的空间,那么刚刚被申请的空间就被释放了,等到再析构goods1的时候我们发现那段空间已经不存在了,如果再去继续释放的话程序就会崩溃,所以再拷贝构造的时候我们要防止浅拷贝
三、等号运算符重载函数(等号赋值)
- 用一个已经存在的对象给另一个已经存在的对象赋值
- 如果不自己实现,编译器会默认生成一个浅拷贝的=
- 防止自赋值
- 防止内存泄漏
- 防止浅拷贝
-
public: CGoods &operator = (const CGoods &src) //"="重载 { cout << "CGoods &operator=(const CGoods& src)" << endl; //防止自赋值如果不防止自赋值,就会把src和要赋值的都被清楚掉了,把原来_name也给释放掉了 if(this == &src) { return *this; } //防止内存泄漏 if( NULL != _name) delete []_name; //防止浅拷贝 (_name = src.name)是浅拷贝 _name = new char[strlen(src._name) + 1]; strcpy_s(_name,strlen(src._name) + 1,src._name); _num = src._num;//goods2的this._name = 参数的(goods)的_num _price = src._price; } int main() { CGoods goods1("cup",30,15); goods1.Show(); CGoods goods2(goods1); //拷贝构造 //先构造的后释放,所以在拷贝构造那里如果进行浅拷贝的话(指向了同一块空间),等析构的时候就是把那段空间给释放了,再析构上goods1的时候就是在释放一段没有东西的空间,就会出错 goods2.Show(); CGoods goods3 = goods2;//拷贝构造 goods3 = goods1;//"="重载 }
四、析构函数
-
一个对象的生存周期满,自动调用的成员方法
-
析构函数没有参数,不可以重载
-
如果不自己实现,编译器会默生成
-
防止内存泄漏
public:
~CGoods()
{
cout << "~CGoods()" << endl;
if (NULL != _name)
{
delete[]_name;
_name = NULL;
}
}
五、临时对象
(1)内置类型的临时对象都是常量
-
int &a = (int)10;//errno int &b = 20;//errno
这是内置类型的临时变量常量,这两句句为什么出错是因为不允许泄露常量的引用或者地址给非常量的引用或者指针
-
const int &a = 10; // ok //先产生一个临时变量,然后讲临时变量的值给引用a,又因为临时变量的值不可以改变,所以我们加上const
因为产生的临时变量是不可改变的,所以我们加上const就可以
(2)自定义类型产生的临时对象分为
- 显式:出现类型名,非常量
-
CGoods &ga = (CGoods)"fangbianmian"; //ok //对象一旦被引用,它的生存周期就和引用相同
- 隐式:未出现类型名,常量
CGoods &gb = "kuangquanshui"; //errno
const CGoods &gb = "kuangquanshui"; //ok
六、下面我们用一段代码来讲解临时变量在这几个函数中的使用
-
CGoods goods4("xiangchang");
上述代码它的过程是:
- 先利用char *构造一个临时对象
- 用临时对象拷贝构造goods5
- 析构临时对象
- 上面的三个过程可以直接优化成 直接构造
CGoods goods5 = "latiao";
- 步骤:先利用char *构造临时对象
-
再用临时对象给goods5赋值(因为之前的goods5对象已存在)
-
析构临时对象
-
上述的三个步骤不能优化成 直接构造
调用函数 的时候参数代入和返回值中的临时对象
CGoods fun1(CGoods& goods) //fun1(goods5);
{
//CGoods goods1 = goods;
return "jidan";
/*
构造临时对象
拷贝构造goods6
析构临时对象
优化为直接构造
*/
}
int main()
{
//...goods5已存在...
CGoods goods6(fun1(goods5));}
第三步,临时对象返回,然后拷贝构造goods6,析构临时对象