在c++中有六个默认函数 他们分别是:
一.构造函数
二.析构函数
三.拷贝构造函数
四.赋值运算符的重载函数
五.取地址操作符的重载函数
六.const修饰的取地址操作符的重载函数
在这里我们只讨论前四个函数
- 构造函数
我们知道面向对象的程序设计语言倾向于对象 而对象一定要经过初始化后使用起来才安全 因此引入了构造函数的概念 对象在创建时都会调用构造函数来进行初始化 构造函数可用于为某些成员变量设置初始值。
构造函数作用:初始化对象的内存空间(对成员变量赋资源)
首先对象的生成分为两步:
- 给对象开辟内存空间
- 给对象的内存调用构造函数来做初始化
需要注意的是:构造函数执行时 对象的内存空间已经分配好了 构造函数的作用只是初始化这片空间 对象在生成时 一定会自动调用某个构造函数来进行初始化
系统默认的函数:1.公有的 2.内联的
构造函数是一种特殊的成员函数 如果用户没有自己写构造函数 编译器会自动生成一个无参的构造函数 也叫默认函数;如果用户编写了构造函数 那么编译器也就不会自动生成默认构造函数了
构造函数名称和类名字相同 并且不写返回值类型(void)也不写;
系统提供的默认构造函数如下:
Student()
{
} //默认构造函数
总结一下构造函数:
(1)构造函数可以重载
(2)不能手动调用
(3)顺序构造
2.析构函数
析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,而析构函数就是对象在销毁的时候自动调用的
析构函数的函数名是在类名前加上~ 不返回任何值,没有返回类型,也没有函数参数。由于没有函数参数,因此它不能被重载 永远只能写一个。换言之,一个类可以有多个构造函数,但是只能有一个析构函数(也可以这样理解:生而不同死了都一样==)
~Student()
{
}
一个类只能有且有一个析构函数,如果没有显式的定义,系统会生成一个缺省的析构函数(合成析构函数)。每有一次构造函数的调用就会有一次析构函数的调用;
对象的销毁:
- 释放对象所占的其他资源
- 释放对象所占的内存空间
当在主函数中用new创建对象时 仅仅是调用构造函数创建了对象,分配了内存空间。但是没有调用析构函数,因为此时指定的对象的内存是由new来创建分配的,此时分配空间是分配在堆上的,只有主动析构或程序结束,才会释放空间 编译器不能够自动调用析构函数将其删除 所以必须要调用delete手动释放才可以。
总结一下析构函数:
(1)析构函数不可以重载
(2)可以手动调用
(3)先构造 后析构
3.拷贝构造函数
原型:类名(const 类名& rhs)
拷贝构造函数是一种特殊的构造函数,它能够用一个已知的对象初始化一个被创建的同类新对象。该函数的形式参数必须是本类对象的常引用,因此与普通构造函数在形式参数上有非常明显的区别。
拷贝构造函数的参数若不是引用而是值传递则会导致拷贝构造函数无限制的递归下去从而导致栈溢出。因此拷贝构造函数的参数必须为引用。
深拷贝与浅拷贝
浅拷贝:在对象复制时 只对对象中的数据成员进行简单的赋值 默认拷贝构造函数执行的也是浅拷贝
来举一个例子:
class Rect
{
public:
Rect( ) //构造函数 ;p指向堆中的空间
{
p=new int(100);
}
~ Rect( ) //析构函数 ;释放动态分配的空间
{
delete p;
}
private:
int width;
int height;
int* p; //指针成员
};
int main()
{
Rect rect1;
Rect rect2(rect1);//复制对象
return 0;
}
分析:在运行定义rect1对象后 由于在构造函数中有一个动态分配 于是为:
在使用rect1复制rect2时 由于执行的是浅拷贝 只是将成员的值进行赋值 这时ret1.p=rect2.p 即这两个指针指向了堆里的同一个空间 如下图:
这并不是我们期待的结果 因为在销毁对象时 两个对象的析构函数将对同一个内存空间释放两次于是就会出错 此时我们就需要深拷贝:
class Rect
{
public:
Rect( ) //构造函数 ;p指向堆中的空间
{
p=new int(100);
}
Rect(const Rect& r)//拷贝构造函数
{
width = r.width;
height = r.height;
p = new int;
*p = *(r.p);
}
~ Rect( ) //析构函数 ;释放动态分配的空间
{
delete p;
}
private:
int width;
int height;
int* p; //指针成员
};
int main()
{
Rect rect1;
Rect rect2(rect1);//复制对象
return 0;
}
此时 在完成对象的复制后为下图:
此时rect1的p和rect2的p各自指向一段内存空间 但它们之指向的空间具有相同的内容 这就是深拷贝 因此当成员有指针成员时 一定要考虑深拷贝!
4.赋值运算符的重载函数
形式: 类名& operator =(const 类名& rhs) 返回一个类对象的引用(如果赋值运算符返回的是类对象本身则会调用拷贝构造函数 如果赋值运算符返回的是对象引用,那么其不会调用类的拷贝构造函数,这是返回对象和返回引用的主要区别,返回引用的效率明显较高)
默认浅拷贝 要注意有指针成员时考虑深拷贝
若赋值构造函数如果参数为值传递 仅仅是多了一次拷贝 并不会无限递归。赋值构造函数参数既可以为引用,也可以为值传递,值传递会多一次拷贝。因此建议赋值构造函数建议也写为引用类型 减少拷贝次数能提高赋值效率
作用:把已存在的对象赋值给相同类型的对象(不生成新对象)
步骤:
- 防止自赋值
- 释放旧资源(防止指向新资源 旧资源内存泄漏)
- 开辟新资源
- 赋值
代码如下:
class CGoods
{
public:
CGoods& operator=(const CGoods& rhs)
{
if (this != &rhs)
{
delete[] mname;
mname = new char[strlen(rhs.mname) + 1]();
strcpy(mname, rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
return *this;
}
private:
char* mname;
float mprice;
int mamount;
};
int main()
{
CGoods good1;
CGoods good3("good3", 10.1, 20);
good1 = good3;//此时this指针在good1里
//good1.operator=(good3);
return 0;
}
C++ 规定,在非静态成员函数内部可以直接使用 this 关键字,this 就代表指向该函数所作用的对象的指针。
因为静态成员函数并不作用于某个对象,所以在其内部不能使用 this 指针;
临时对象:
int main()
{
CGoods good1= (“good1”,”10.1””20”);(类实例化对象)
good1 = 30;
(CGoods类)(int类)
}
首先赋值运算符左右操作数的类型要匹配 然而现在左操作数为CGoods类 右操作数为Int类 此时会先找带有int类型参数的构造函数 来生成一个临时对象 此时临时对象的类型为CGoods 类型就匹配了(临时对象只在构造函数中生成)
临时对象:
- 隐式生成临时对象 : 编译器推演需要的对象类型
- 显式生成临时对象 : 程序指明要生成的对象类型
优化:如果临时对象生成的目的是为了生成新对象 那么就会以生成临时对象的方式生成新对象
生存周期:表达式结束 临时对象销毁 good1 = 30;(遇到;就销毁了)一般表达式结束标志为, ; ?
引用会提升临时对象的生存周期 使临时对象变得和引用对象一样的生存周期
类类型的返回值 都是由临时对象带出来
临时量(包括临时对象):
内置类型==> 常量
自定义类型==> 变量(内存中)
隐式生成的临时量==>常量
形参const:1.防止实参被修改 2.接收隐式生成的临时量(常量 加const为常引用)
explicit作用:禁止隐式生成临时对象(临时对象只在构造函数中生成)