class CGoods
{
public:
CGoods(char *const name, int count, float price)
{
if (name == NULL)
{
_name = new char;
*_name = '\0'; //如果是空也给分配一个字节内存,避免操作空指针挂掉
}
else
{
_name = new char[strlen(name) + 1];
strcpy(_name, name);
}
_count = count;
_price = price;
}
//拷贝构造函数,生成新对象,
//该方法属于this和src两个对象共有的,所以可以直接访问对象的成员变量和方法
CGoods(CGoods const &src)//src指向的是右值,this指向的是左值
{
//开辟一个和src一样大的内存,不能和src指向同一块内存
_name = new char[strlen(src._name) + 1];
//将this指针的内存中数据初始化为src对象的内存中的数据
strcpy(_name,src._name);
_count = src._count;
_price = src._price;
}
void show();
void setname(char *name){ strcpy(_name, name); }
void getname(char *name){ strcpy(name, _name); }
//char *getname(){ return _name ;}
//该函数会将_name的地址返回回去,如果类外拿到地址,可以通过地址解引用来修改内存中的值,不满足面向对象的封装
float getprice(){ return _price; }
//赋值运算符的重载函数
void operator=(const CGoods &src)
{
//1.防止自赋值,this保存的地址和src引用的对象的地址一样时
if (this == &src)
return;
//2.在接收其他对象的数据之前,先要删掉自己原来已有的外部资源
delete _name;
//防止浅拷贝,重新申请新的空间,并用src的数据给赋过去
_name = new char[strlen(src._name) + 1];
strcpy(_name, src._name);
_count = src._count;
_price = src._price;
}
private:
char *_name;
int _count;
float _price;
};
//类外定义,函数名前加上类的作用域,告诉编译器产生的这个函数符号是类的作用域下的
void CGoods::show()
{
cout << "name: " << _name << "price: " << _price << "count:" << _count << endl;
}
第一部分:认识编译器对对象生成过程的优化
对象的基本分类:
1.栈上的
2.堆上的
3.数据段上的
临时对象分类:
显式生成临时对象:
CGoods c3 = CGoods(“可乐”,9,6.6);
//这里指定了临时对象类型是CGoods了
隐式生成临时对象:
c3 = 99.8;
//没有指定类型,先隐式生成临时对象,临时对象赋值给c3,析构临时对象
参数中const的含义: 重点
内置类型产生的临时量:都是常量,不能被改变
自定义类型产生的临时量:都是变量,可以被修改
所以函数形参是:const CGoods &src
含义1:防止实参是个常量,因为常量需要用常引用来引用;
含义2:防止修改实参的值。
int main()
{
CGoods c1("苹果", 10, 8.9);
CGoods c2("香蕉", 11, 9.8);
CGoods c3 = CGoods("可乐",9,6.6);
//显式的生成临时对象,用临时对象拷贝构造c3,然后临时对象析构
return 0;
}
C++编译器的优化: 重点
用临时对象构造同类型的新对象时,临时对象不生成,而是用生成临时对象的方式直接构造目标对象
CGoods c3 = CGoods("可乐",9,6.6);
这里临时对象是:CGoods("可乐",9,6.6)
目标对象是:CGoods c3
理论上:
先产生临时对象,然后用临时对象拷贝构造c3对象,然后析构临时对象,期间三次函数调用
实际上编译器优化后:
这个临时对象不产生,而是用产生临时对象的方式直接构造新对象c3,所以只调用一次构造函数
综上所述:
我们不要先定义,后赋值来产生一个新对象,直接定义的方式来产生新对象;
即用: CGoods c3 = CGoods(“可乐”,9,6.6);
而不是:
CGoods c3;
c3 = CGoods(“可乐”,9,6.6);
(赋值时,这个临时对象一定产生,不然拿什么赋!!!),所以不要这样写。
对比一下这两个过程:
CGoods c3 = CGoods("可乐",9,6.6);
过程:
使用生成临时对象的方式构造c3对象,调用一次构造函数,临时对象不生成
CGoods c3;
c3 = CGoods("可乐",9,6.6);
过程:
调用默认的构造函数生成c3
生成一个临时对象CGoods("可乐",9,6.6)
临时对象赋值给c3 ,这里调用赋值运算符重载函数
临时对象析构
指针和引用呢?
1.临时对象被引用时,生命周期被提升,语句结束,临时对象生命周期被提升,所以不析构,引用变量什么时候结束,这个对象什么时候结束;
2.不要用指针指向一个临时对象,临时对象语句结束,将被析构。
CGoods * p = &CGoods("可乐",9,6.6);
过程:
构造临时对象
析构临时对象
CGoods &q = CGoods("可乐",9,6.6);
过程:
临时对象生成,但是并不析构
(临时对象被引用时,生命周期被提升)
举个例子:需要注意一下,语句作用域问题
void test (CGoods *p)
{
cout<<"call test"<<endl;
}
int main()
{
test(&CGoods("雪碧",8,6.4))
}
执行结果:
call test
临时量出了作用域才被析构,但是这条语句包含函数调用,所以函数调用结束 语句才结束
关键字explict:禁止隐式对象生成,只能显式的生成对象,加载构造函数前。
二、一道例题说明各种对象的生命周期:
class Test
{
public:
Test(int a = 10, int b = 10)
{
}
~Test()
{
}
Test(const Test &src)
{
ma = src.ma;
mb = src.mb;
}
void operator=(const Test &src)
{
if (this == &src)
return;
ma = src.ma;
mb = src.mb;
}
private:
int ma;
int mb;
};
Test t1(20,20); //构造函数
int main()
{
Test t2(20,30); //构造函数
Test t3 = t2; //拷贝构造函数
static Test t4 = Test (40,40);// 构造函数
t2 = Test (50,50); //构造函数 赋值运算符重载 析构函数
t2 = (Test)(60,60);/ /构造函数 赋值运算符重载 析构函数
t2 = 70; / /一个参数的构造函数 赋值运算符重载 析构函数
Test *p1 = &Test(80,80);//生成临时对象,临时对象析构
Test &q1 = Test(90,90); //生成临时对象,不析构,引用生命周期提升
Test *p2 = new Test ; //构造函数
delete p2; //析构函数
}
Test t5 (100,100); //构造函数
构造顺序:
t1 t5 t2 t3 q1 p2
1.数据段的最后析构,生命周期和程序一样
2.栈上的对象先构造的后析构,出作用域析构
3.堆上的手动开辟,需要手动释放delete析构