类的6个默认成员函数
class Date{ }; 看起来这只是一个普通的空类! 但是当真里面什么都没有吗?? 其实不然,尽管它是一个空类,编译器依旧会为其生成6个默认的成员函数
具体如下: 默认成员函数的特点? 就是 用户自己没有写,编译器会自动生成。一旦用户显式提供,那么就不再生成 下面我们一 一详细理解每个函数
构造函数
是一个特殊的成员函数
主要特点:
- 函数名与类名相同
- 没有返回值类型
- 创建类类型的对象的时候由编译器自动调用
- 在对象整个生命周期中只调用一次
- 构造函数可以重载
也可以在定义时给一个全缺省参数(无参 && 全参一次搞定)
代码验证: 情景一:使用编译器提供的默认构造函数,查看实例化一个对象到底做了什么 情景二:自定义一个构造函数,查看实例化对象做了什么 实验: 情景三:在上例的基础上,以Date d1 的方式初始化会怎样? 情景四:用户实现一个构造函数,既可以传递参数,也适用于无参构造(全缺省构造) 此时就不会有报错了 注:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
调用场景
创建新对象的时候,由编译器自动调用,并且在整个生命周期内只调用1次
编译器生成的默认构造函数到底有用没?
答案是有用的! 情景模拟: 前提:有两个类,分别是Time和Date类,Time类的对象作为Date类的一个成员变量。Date类没有自定义构造函数,Time类自定义了构造函数,我们实例化一个Date类的对象,观察现象: 分析构造过程如下:
析构函数
概念
是一个特殊的成员函数 与构造函数功能相反,析构函数不是完整对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
特点
- 函数名是
~类名 - 无参数,无返回值
- 一个类有且仅有1个析构函数。若未显式定义,系统会自动生成默认的析构函数
- 对象生命周期结束时,C++编译系统自动调用析构函数
- 编译器自动生成的析构函数,对会自定义类型成员调用它的析构函数
验证: 在Date类中自定义析构函数,观察析构函数的调用场景 冷静分析一下,我们发现,我们实例化的对象d是在main函数的栈帧上开辟的空间,那我们在函数调用完毕后该函数的栈帧也就被释放,那么在该栈帧上的变量也就随之消亡。因此,在这种情况下,析构函数也就可有可无了。 但是析构函数还是有它的用武之地的~~看下面的例子
调用场景
前提:我们使用C++的方式实现一个栈的结构。它的具体操作如下:
#pragma once
#include <iostream>
using namespace std;
typedef int SDataType;
class Stack
{
public:
Stack();
~Stack();
void StackPush(SDataType x);
void StackPop();
SDataType StackTop();
int StackSize();
int StackEmpty();
private:
void StackExpand();
private:
SDataType* _array;
int _size;
int _capacity;
};
void TestStack();
#include "stack.h"
Stack::Stack()
{
_array = (SDataType*)malloc(sizeof(SDataType)* 3);
if (nullptr == _array)
{
perror("malloc");
return;
}
_size = 0;
_capacity = 3;
}
void Stack::StackExpand()
{
int newCapacity = _capacity * 2;
SDataType* tmp = (SDataType*)malloc(sizeof(SDataType)*newCapacity);
if (nullptr == tmp)
{
perror("malloc");
return;
}
for (int i = 0; i < _capacity; i++)
{
tmp[i] = _array[i];
}
free(_array);
_array = tmp;
_capacity = newCapacity;
}
Stack::~Stack()
{
if (_array)
{
free(_array);
_size = 0;
_capacity = 0;
}
}
void Stack::StackPush(SDataType x)
{
if (_size == _capacity)
{
StackExpand();
}
_array[_size++] = x;
}
void Stack::StackPop()
{
if (!StackEmpty())
{
_size--;
}
}
SDataType Stack::StackTop()
{
if (StackEmpty())
{
perror("StackEmpty!");
return -1;
}
return _array[_size - 1];
}
int Stack::StackSize()
{
return _size;
}
int Stack::StackEmpty()
{
return 0 == _size;
}
void TestStack()
{
Stack s;
s.StackPush(1);
s.StackPush(2);
s.StackPush(3);
s.StackPush(4);
s.StackPush(5);
s.StackPush(6);
cout << s.StackSize() << endl;
cout << s.StackTop() << endl;
s.StackPop();
s.StackPop();
cout << s.StackSize() << endl;
cout << s.StackTop() << endl;
}
对于上述实现的代码,我们着重关注一下它的析构函数 我们可以看到,该析构函数对程序从堆上申请的资源进行了释放。如果没有该析构函数,那就会发生内存泄露。 因此,我们可以得到: 只要程序涉及到资源的申请,那么析构函数必须要程序员手动实现,默认的析构函数是无法达到我们的需求的。
拷贝构造函数
概念
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰) ,在用 已存在的类类型对象创建新对象的时候,由编译器自动调用。
特性
- 拷贝构造函数是构造函数的重载形式
- 拷贝构造函数的参数只有一个,并且必须使用引用传参,使用传值传参就会发生无穷递归
- 若未显式定义,系统生成默认的拷贝构造函数。
默认的拷贝构造函数对象按内存存储,按字节序完成拷贝,这种拷贝我们叫做浅拷贝或者值拷贝 浅拷贝在不涉及资源申请的类中可以正常使用,但是在涉及资源申请的类中是无法正常使用的 后者会在释放申请资源的时候,调用两次析构函数,导致程序崩溃 - 默认生成的拷贝构造不能满足申请资源的情况,因此需要我们实现深拷贝
典型调用场景
- 使用已经存在的对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
运算符重载
为什么要有运算符重载
一句话,就是为了提高代码的可读性
基本形式
- 是一个具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
- 函数名: 关键字operator后面加需要重载的运算符符号
- 函数原型: 返回值类型 operator操作符(参数列表)
注意事项
- 不能通过连接其他符号来创建新的操作符:比如 operator @
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变。例如内置类型的+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少一,成员函数的操作符有一个默认的形参this限定为第一个形参
- 5个运算符不能重载
赋值操作符重载
- 形式
返回值类型 operator=(参数){....}
- 参数类型
一般都是类类型对象的引用 相较于传值这样就会少了形参实例化的拷贝构造 - 返回值
一般返回 *this 是类类型对象的引用 - 检测是否在给自己赋值
if(this != &d)
来一个赋值操作符重载的例子 一个类如果没有显示定义赋值运算符重载函数,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝) 和前面一样,对于需要申请资源的类来说,赋值运算符重载依旧需要我们自己编写,否则程序在最后析构的时候就会发生崩溃。
日期类实现
date.h
#pragma once
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2001, int month = 5, int day = 7);
void Print();
int GetMonthDay(int year, int month);
Date(const Date& d);
Date& operator=(const Date& d);
Date& operator+=(int day);
Date operator+(int day);
Date operator-(int day);
Date& operator-=(int day);
Date& operator++();
Date operator++(int);
Date operator--(int);
Date& operator--();
bool operator>(const Date& d);
bool operator==(const Date& d);
inline bool operator >= (const Date& d);
bool operator < (const Date& d);
bool operator <= (const Date& d);
bool operator != (const Date& d);
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
date.cpp
#include "date.h"
Date::Date(int year, int month, int day)
{
if (year >= 0 && month > 0 && month <= 12 && day > 0 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
}
}
void Date::Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
inline int Date::GetMonthDay(int year, int month)
{
static int dayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = dayArray[month];
if (2 == month && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
{
day = 29;
}
return day;
}
Date& Date::operator+=(int day)
{
if (day < 0)
{
*this -= -day;
}
else
{
_day += day;
while (_day > GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month > 12)
{
++_year;
_month = 1;
}
}
}
return *this;
}
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
*this += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
bool Date::operator>(const Date& d)
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day > d._day)
{
return true;
}
}
}
return false;
}
bool Date::operator==(const Date& d)
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
inline bool Date::operator >= (const Date& d)
{
return *this > d || *this == d;
}
bool Date::operator < (const Date& d)
{
return !(*this >= d);
}
bool Date::operator <= (const Date& d)
{
return *this < d || *this == d;
}
bool Date::operator != (const Date& d)
{
return !(*this == d);
}
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (max < min)
{
max = d;
min = *this;
flag = -1;
}
int count = 0;
while (min != max)
{
++min;
count++;
}
return count* flag;
}
具体的测试代码见gitee
const成员函数
-
const修饰类的成员函数 将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。 -
几个经典问题 2.1const对象可以调用非const成员函数吗?(不行) 2.2 非const对象可以调用const成员函数吗?(可以) 2.3 const成员函数内可以调用其它的非const成员函数吗?(不行) 2.4非const成员函数内可以调用其它的const成员函数吗?(可以)
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
以上就是类和对象中6个默认函数的基本认识~~感觉有收获的小伙伴,多多分享给身边的友友们!!
|