目录
1.类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成6个默认成员函数。默认成员函数;用户没有显示显现,编译器会生成的成员函数称为默认成员函数。
2.构造函数
2.1.概念
对于一下Date类:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 9, 30);
d1.Print();
Date d2;
d2.Init(2022, 10, 1);
d2.Print();
return 0;
}
对于Date类,可以通过Init公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个每个数据成员都有一个合适的初始值,并且在对象的整个生命周期只调用一次。
2.2.特性
构造函数时特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数主要任务并不是开空间创建对象,而是初始化对象。
特征如下:
1.函数名和类名相同。
2.无返回值。
3.对象实例化时编译器自动调用对应的构造函数。
4.构造函数可以重载
class Date
{
public:
//1.无参的构造函数
Date()
{}
//2.带参数的构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1;//调用无参的构造函数,
//如果调用无参的构造函数,后面不要跟括号,否则就成了函数声明
Date d2(2020, 9, 30);//调用带参的构造函数-
}
5.如果类中没有显示定义构造函数,则C++编译器会生成一个无参的构造函数,一但用户显示定义,编译器将不在生成。
6.关于编译器默认生成的默认构造函数,如果我们不实现默认构造函数,编译器会自动生成,但是编译器生成的默认构造函数对内置类型不处理,对自定义类型调用它的默认构造函数。
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
注意:C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//内置类型
int _year = 2020;
int _month = 9;
int _day = 30;
//自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参的构造函数,全缺省的构造函数,和编译器默认生成构造函数都可以被认为默认构造函数。
3.析构函数
3.1.概念
析构函数与构造函数相反,析构函数并不是完成对对象的销毁,局部对象的销毁工作是由编译器完成的。而对象在销毁时自动调用析构函数,完成对象中的资源的清理工作。
3.2.特性
析构函数是特殊的成员函数,特征如下
1.析构函数名是在类名前加上字符~。
2.无参数,无返回值类型。
3.一个类只能有一个析构函数,若未显示定义,系统会自动生成默认的构造函数。注意:析构函数不能重载。
4.对象生命周期结束时,C++编译系统系统会自动调用析构函数。
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc is fail\n");
exit(-1);
}
_top = 0;
_capacity = 4;
}
void Push(DataType data)
{
_a[_top] = data;
++_top;
}
~Stack()
{
if (_a)
{
free(_a);
_a = nullptr;
}
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
return 0;
}
5.关于编译器自动生成的析构函数,是否会完成一些事情呢?从下面的程序中可以看到,编译器生成的默认析构函数,对自定义类型成员调用它的析构函数。
class Time
{
public:
~Time()
{
cout << "Time()" << endl;
}
private:
int _hour;
int _minute;
int _seconds;
};
class Date
{
private:
//对于自定义类型,系统会调用它的析构函数,对于内置类型,系统会自动回收空间
int _year = 2022;
int _month = 9;
int _day = 30;
Time _t;
};
int main()
{
Date d1;
return 0;
}
6.如果类中没有申请资源时,析构函数可以不写,直接使用编译器默认的析构函数。比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
4.拷贝构造函数
4.1.概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已经存在的类类型对象创建新对象时由编译器自动调用。
4.2.特征
1.拷贝构造函数时构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//Date(const Date d)错误写法
Date(const Date& d)//正确写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
3.若未显示定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数对象按内存存储按照字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝,而自定义类型是调用其拷贝构造函数完成拷贝的。
4.编译器生成的默认拷贝构造函数已经完成字节序的值拷贝了,还需要自己显式实现吗?当然日期类这样的类是没有必要的。那么下面的类呢?
typedef int DataType;
class Stack
{
public:
Stack(size_t _capacity = 4)
{
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
perror("malloc is fail\n");
exit(-1);
}
_top = 0;
_capacity = 4;
}
void Push(const DataType& date)
{
_a[_top] = date;
_top++;
}
~Stack()
{
if (_a != nullptr)
{
free(_a);
_a = nullptr;
}
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
st1.Push(5);
Stack st2(st1);
return 0;
}
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一但涉及到资源申请时,则拷贝构造函数时一定要写的,否则就是浅拷贝。
5.拷贝构造函数典型的调用场景
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022, 1, 13);
Test(d1);
return 0;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用
5.赋值运算符重载
5.1.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
函数名字为:关键字operator后面需要接重载的运算符符号。
函数原型:返回值类型operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符,比如operator@
- 重载操作符必须有一个类类型参数
- 对于内置类型的运算符,其含义不能改变。
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上五个运算符不能重载。这个经常在考试或者面试笔试题中出现。
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
//这里如果我们把运算符重载成全局的话,就需要成员变量是公有的。这样的话,封装性如何保证?
//其实这个问题可以用友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year && d1._month == d2._month
&& d1._day == d2._day;
}
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
//左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year && _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
5.2.赋值运算符重载
1.赋值运算符重载格式
- 参数类型:const Date& 传递引用可以提高传参效率。
- 返回值类型:Date& 返回引用可以提高返回的效率。有返回值的目的是为了支持连续赋值。
- 检测是否自己给自己赋值。
- 返回*this,要复合连续赋值的含义。
class Date
{
public:
Date(int year = 2020, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
2.赋值运算符只能重载类的成员函数,不能重载成全局函数
class Date
{
public:
Date(int year = 2020, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
//赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要两个参数
Date& operator=(const Date& d1,const Date& d2)
{
if (&d1 != &d2)
{
d1._year = d2._year;
d1._month = d2._month;
d1._day = d2._day;
}
return d1;
}
// error c2801: "operator = "必须是非静态成员
原因:赋值运算符如果不能显式实现,编译器会生成一个默认的,此时用户再在类外自己实现一个全局的运算符重载,就和编译器在类中生成的默认赋值运算符冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认的赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接,而自定义类型成员变量需要调用对应的类的赋值运算符重载完成重载。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& d)
{
_hour = d._hour;
_minute = d._minute;
_second = d._second;
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2;
d1 = d2;
return 0;
}
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然日期类这样的类是没有必要的。那么下面的类呢?验证一下试试?
#include <iostream>
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_a = (DataType*)malloc(sizeof(DataType)*capacity);
if (_a == nullptr)
{
perror("malloc is fail\n");
exit(-1);
}
_top = 0;
_capacity = 0;
}
void Push(const DataType& data)
{
_a[_top] = data;
_top++;
}
~Stack()
{
if (_a)
{
free(_a);
_a = nullptr;
}
_top = 0;
_capacity = 0;
}
private:
DataType* _a;
size_t _top;
size_t _capacity;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
st1.Push(5);
Stack st2;
st2 = st1;
return 0;
}
注意:如果类中没有涉及到资源管理,赋值运算符是否实现都可以,一但涉及到资源管理则必须要实现。
4.日期类运算符的重载
Date.h
#pragma once
#include <iostream>
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
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);
// >=运算符重载
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"
int Date::GetMonthDay(int year, int month)
{
static int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if ((year == 2) && ((year % 4 == 0 && year % 100 != 0)
|| (year % 400 == 0)))
{
return a[month] + 1;
}
return a[month];
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date::~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
}
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
//return *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;
if (_year == d._year && _month > d._month)
return true;
if (_year == d._year && _month == d._month && _day > d._day)
return true;
return false;
}
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
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);
}
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 (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int day = 0;
while (min != max)
{
++day;
min += 1;
}
return day*flag;
}
6.const成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数的隐含得this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void Print() const
{
cout << "Print() const" << endl;
cout << "year " << _year << endl;
cout << "month " << _month << endl;
cout << "day " << _day << endl;
}
void Print()
{
cout << "Print()" << endl;
cout << "year " << _year << endl;
cout << "month " << _month << endl;
cout << "day " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022,10,11);
d1.Print();
const Date d2(2022, 10, 12);
d2.Print();
return 0;
}
7.取地址及const取地址操作符重载
这两个默认成员函数一般自己不用定义,编译器会自己生成,一般编译器默生成的就够我们用。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};