这章我们主要讲解类的6个默认成员函数
一、构造函数
1.1 概念
构造函数的主要任务是初始化对象,名字与类名相同,创建类型对象时编译器自动调用,以保证每个对象在被创建时都有一个合适的初值,并且在整个对象的声明周期只调用一次
1.2 特性
构造函数的主要任务并不是开空间创建对象,而是初始化对象
特点:
1.与类名相同
2.可以构成函数重载
3.无返回值
4.对象实例化时自动调用构造函数
1.3 示例
#include<iostream>
using namespace std;
class Date
{
public:
//可以使用缺省参数
Date(int year=2023,int month=2,int day=23)
{
_year=year;
_month=month;
_day=day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(1998,2,3);
cout << d1._year << endl;
cout << d2._year << endl;
return 0;
}
如果这里不写构造函数,那么输出d1会变成随机值,在内置类型中,我们必须自己写构造函数
#include<iostream>
using namespace std;
class Time
{
public:
//C++11的特性,我们既可以在构造函数里写,也可以在声明的时候写上去
int hour=0;
int time=0;
int mintue=0;
};
class Date
{
public:
//可以使用缺省参数
Date(int year=2023,int month=2,int day=23)
{
_year=year;
_month=month;
_day=day;
}
int _year;
int _month;
int _day;
//自定义类型
Time t;
};
int main()
{
Date d1;
Date d2(1998,2,3);
// cout << d1._year << endl;
cout << d2.t.hour << endl;
return 0;
}
写成如上这样,C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员t调用的它的默认成员函数。
另外,我们常常把编译器提供给我们的构造函数称为默认构造,同时我们写的全缺省构造函数也是默认构造
二、析构函数
2.1 概念
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
2.2 特性
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
2.3 示例
class Stack
{
public:
//默认构造函数
Stack(int sz=4)
{
arr=new int[sz];
top=0;
capacity=sz;
}
//析构函数
~Stack()
{
delete[] arr;
arr= nullptr;
top=capacity=0;
}
//push
//为了演示 这里不写扩容
void push(int val)
{
arr[top++]=val;
}
int top1()
{
return arr[top-1];
}
void pop()
{
top--;
}
private:
int* arr;
int top;
int capacity;
};
int main()
{
Stack st;
st.push(1);
st.push(3);
cout << st.top1() << endl;
st.pop();
Stack st2(10);
return 0;
}
这里的析构顺序是先析构st2然后析构st1,遵从栈的顺序
同理,析构函数也会生成默认析构,但是不对内置类型进行处理,本身内置类型类型也不需要处理,例如int a=10,结束程序后我们什么操作都不用干,编译器帮我们销毁对象
我们在看一个栗子:
class Stack
{
public:
//默认构造函数
Stack(int sz=4)
{
arr=new int[sz];
top=0;
capacity=sz;
}
//析构函数
~Stack()
{
delete[] arr;
arr= nullptr;
top=capacity=0;
}
//push
//为了演示 这里不写扩容
void push(int val)
{
arr[top++]=val;
}
int top1()
{
return arr[top-1];
}
void pop()
{
top--;
}
private:
int* arr;
int top;
int capacity;
};
class Date
{
public:
//可以使用缺省参数
Date(int year=2023,int month=2,int day=23)
{
_year=year;
_month=month;
_day=day;
}
int _year;
int _month;
int _day;
//自定义类型
Stack st;
};
int main()
{
Date d;
return 0;
}
假设日期类声明了一个栈,复用刚刚的栈类,我们发现编译器在Date里面会生成默认构造,而这个默认构造会去调用Stack的析构函数
这里给大家一个原则: 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
三、拷贝构造函数
3.1 概念
创建一个一摸一样的新对象
3.2 特性
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
3.3 示例
class Date
{
public:
//可以使用缺省参数
Date(int year=2023,int month=2,int day=23)
{
_year=year;
_month=month;
_day=day;
}
//拷贝构造函数
Date(const Date& d1)
{
_year=d1._year;
_month=d1._month;
_day=d1._day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d(2021,1,1);
//和d一摸一样
Date d2(d);
return 0;
}
不过这里我们不写拷贝构造函数,也是正确的,对于内置类型我们可以不用写拷贝构造函数,但是自定义类型的拷贝必须要写,因为有深拷贝和潜拷贝的问题,例如stack st1; stack st2(st1);这里必须自己写拷贝构造函数,关于深浅拷贝的问题这里不赘述,后面会有文章专门讲解一下这个问题
四、赋值重载
4.1 概念
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
4.2 示例
class Date
{
public:
//可以使用缺省参数
Date(int year=2023,int month=2,int day=23)
{
_year=year;
_month=month;
_day=day;
}
//返回引用可以减少拷贝
//赋值重载
Date& operator=(const Date& d)
{
if(this!=&d)
{
_year=d._year;
_month=d._month;
_day=d._day;
}
return *this;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2001,11,25);
Date d2;
//这个叫赋值!
//拷贝构造是实例化的时候就拷贝给他,意义不一样
d2=d1;
return 0;
}
不过我们发现,我们不写赋值重载,照样可以完成赋值,赋值重载内置类型可以不写,但是自定义类型必须写,就例如刚刚的stack
五、总结
记住只有这几个成员函数编译器才会默认生成,例如重载大小,编译器不会自动生成,必须我们自己写,所谓的默认成员函数就是编译器可以自动帮我们写的,但是多数情况下不靠谱
自定义类型可以不用写的成员函数:构造函数、析构函数
内置类型可以不用写的成员函数:拷贝构造、赋值重载
取地址重载不太常用,这里不做介绍