目录
一、类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
二、 构造函数
1.什么是构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,构造函数的任务是初始化对象。并且在对象整个生命周期内只调用一次。
其有四大特征
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
class Date
{
public:
// 1.无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2024,7,13);
return 0;
}
注意,如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
同时我们还可以运用之前在c++入门中缺省值的知识来进行一些优化。我们构造一个全缺省的构造函数
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
因此我们可以构造
Date d1;
Date d2(2024);
Date d3(2024,7);
Date d4(2024,7,13);
注意,全缺省的函数与无参的函数,在语法上构成函数重载,但是在实际调用时会产生错误,因为编译器不知道调用哪一个
2.关于系统自动生成的构造函数
1.我们不写才会生成,我们写了任意一个构造函数就不会生成了
2.不会处理内置类型的成员,例如int double...,是随机值,所有指针,包括自定义类型的指针也都是内置类型,一般会初始化为nullptr。(但是c++11支持声明的时候给缺省值)
在声明给缺省值是给自动生成的构造函数使用的,但是当我们自己写了构造函数但是没有初始化那个内置类型时,也会使用缺省值
3.会处理自定义类型的成员,会去调用这个成员的默认构造函数
我们用栈来实现队列,不需要自己写队列的构造函数,自动生成的构造函数会帮我们调栈的构造函数
总结:一般情况下都需要我们去写构造函数,除非成员全都是自定义函数 。
注意点:无参的构造函数,全缺省的构造函数,编译器默认生成的构造函数都被称为默认构造函数(不传参就可以直接用)。他们三个只能存在一个,多个并存会出现二义性。
三、析构函数
1.什么是析构函数
是与构造函数相配套的函数,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数也有四大特征
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
5. 和构造函数类似,析构函数不会处理内置类型成员
但是对于自定义类型,会调用这个成员的析构函数
6.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
( 后定义的函数生命周期会先结束,所以会先调用析构函数)
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
小总结:c++构造函数和析构函数最重要的特点是自动调用。
四、拷贝构造
我们先由一个现象引出拷贝构造
在c语言中(Stack已经使用typedef)
我们_array是通过malloc开辟空间的,这时候我们发现,传参的时候s仅仅只是将s1中_array变量中储存的地址拿了过去,只是将main栈帧里面存的东西原模原样复制到func2栈帧中而已,他们的_array中存的都是指向堆区的同一块空间的指针。这是一种浅拷贝
但是如果我们在c++中这样使用,当我们调用结束,系统编译器自动调用析构函数时,同一块空间就会出现被释放两次的情况,就会出现错误。
为了避免这种状况,c++中传值传参要调用拷贝构造。
1.拷贝构造函数的定义
拷贝构造函数:只有单个形参,该形参是对相同类型对象的引用(一般常用const修饰,例如Date(const Date &d) ),在用于已存
在的类型对象创建新对象时由编译器自动调用。
2.拷贝构造的特征
1. 拷贝构造函数是构造函数的一个重载形式
2. 拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。
如果是传值,如下图
我们首先要传值传参,把d1传到d,就要调用我们自己的拷贝构造函数,把d1拷贝到d
然后我们这个拷贝构造函数又要传值传参,把d1传给d,就又要调用我们自己的拷贝构造函数
因此会进行循环
3. 若未显式定义,编译器会生成默认的拷贝构造函数,它对于内置类型进行浅拷贝,对于自定义类型调用它的拷贝构造。 因此当我们没有写任何一个拷贝构造函数时,传值传参时候的现象于c语言中一模一样。这也是c++兼容c语言的体现
小知识
1.深拷贝举例
2. 拷贝构造另一等价写法
Date d3 = d1
与Date d3(d1)是等价的
3.传引用传参是不需要拷贝构造的
4.传值返回创建临时变量时,也会调用拷贝构造
(临时变量sizeof较小的话会用寄存器返回,如果较大,会在main函数中提前开辟空间)
(并且临时变量具有常性)
五、赋值运算符重载
1.运算符重载
运算符重载之后会使我们的代码可读性更强
函数名字为:关键字operator后面接需要重载的运算符符号。例如:operator > ,operator +
函数原型:返回值类型 operator操作符(参数列表) 例如
bool operator==(const Date& d1, const Date& d2)
注意点
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个自定义类型参数,用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
3.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
例如 bool operator==(const Date& d) d1.opetator ==(d2)
当然也可隐式 d1 == d2 二者等价
4. .* :: sizeof ?: (三目操作符) . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
2.赋值运算符重载
仍然用日期类进行举例
void operator= (const Date& d)//这里const防止被误修改 不传引用也可以,但是传引用不用再开空间,效
//率更高
{
_year = d._year;
_month = d._month;
_day = d._day;
}
使用时
d1.operator=(d2);
d1 = d2;
与拷贝构造有点像,但是与拷贝构造不同的是,我们要现有两个对象,然后把一个对象的值赋到另一个对象中
拷贝对象是依靠一个已经存在的对象初始化另一个对象
不过我们还可以继续优化
Date& operator= (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
这样就可以支持连续赋值了
d1 = d2 = d3;
而且一般出了作用域还在,我们就返回引用。
为了防止有人写
d1 = d1;
这样的代码我们可以添加一个if判断一下
Date& operator= (const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
再小小地补充一个知识
Date operator= (const Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
传值传参和传值返回的时候都要调用拷贝构造,一个是把实参传给形参,另一个是创建一个临时变量 。
最后,对于赋值重载,我们不写它也会自动生成,它对于内置类型会进行浅拷贝,对于自定义类型会调用它的赋值重载函数
知识补充
1.函数声明的缺省值
函数如果声明和定义分开,缺省值只能在声明的时候给。声明和定义分开可以让我们更容易地去看这个类的结构。
声明的时候给了缺省值,编译的时候就可以编译通过了,然后在链接的时候通过修饰过后的函数名去找这个函数
在.cpp中定义函数的时候,函数名前面要加类名:
2.前置++和后置++的区分
添加int参数占位形成函数重载
++d1 -> d1.operator++();
Date& operator++()
{
*this += 1;
retuen *this;
}
d1++ -> d1.operator++(int);//后置++加了一个int参数,进行占位,根前置++构成函数重载
Date operator++(0) //本质后置++调用,编译器进行特殊处理
{
Date tmp(*this);
*this += 1;
return tmp;
}
六、日期类函数
1.Date.h
#pragma once
class date
{
public:
int GetMonthDay(int year, int month);// 获取某年某月的天数
date(int year = 2024, int month = 1, int day = 1);// 全缺省的构造函数
date(const date& d);// 拷贝构造函数d2(d1)
date& operator=(const date& d);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)
~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);// 日期-日期 返回天数
void print()const;//打印
private:
int _year;
int _month;
int _day;
};
2.Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
#include <iostream>
using namespace std;
int date::GetMonthDay(int year, int month)// 获取某年某月的天数
{
const static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 40 == 0))
{
return 29;
}
return arr[month];
}
date::date(int year , int month, int day)// 全缺省的构造函数
{
_year = year;
_month = month;
_day = day;
}
date::date(const date& d)// 拷贝构造函数d2(d1)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
date& date::operator=(const date& d)// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
date::~date()// 析构函数
{
_year = 0;
_month = 0;
_day = 0;
}
date& date::operator+=(int day)// 日期+=天数
{
if (day < 0)
{
*this -= day;
}
_day = _day + day;
while (_day > GetMonthDay(_year, _month))
{
_day = _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 < 1)
{
_year--;
_month = 12;
}
_day = _day + GetMonthDay(_year, _month);
}
return *this;
}
date& date::operator++()// 前置++
{
_day = _day + 1;
if (_day > GetMonthDay(_year, _month))
{
_day = _day - GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
date date::operator++(int)// 后置++
{
date tmp = *this;
*this = ++(*this);
return tmp;
}
date&date :: operator--()// 前置--
{
_day = _day - 1;
if (_day <= 0)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day = _day + GetMonthDay(_year, _month);
}
return *this;
}
date date::operator--(int)// 后置--
{
date tmp = *this;
*this = --(*this);
return tmp;
}
bool date::operator>(const date& d)// >运算符重载
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
return false;
}
bool date::operator==(const date& d)// ==运算符重载
{
if (_year == d._year && _month == d._month && _day == d._day)return true;
return false;
}
bool date::operator >= (const date& d)// >=运算符重载
{
if (*this > d || *this == d)return true;
return false;
}
bool date::operator < (const date& d)// <运算符重载
{
if (*this >= d)return false;
return true;
}
bool date::operator <= (const date& d) // <=运算符重载
{
if (*this > d)return false;
return true;
}
bool date::operator != (const date& d)// !=运算符重载
{
if (*this == d)return false;
return true;
}
int date::operator-(const date& d)// 日期-日期 返回天数
{
date datemax = *this;
date datemin = d;
int flag = 1;
if (*this < d)
{
datemax = d;
datemin = *this;
flag = -1;
}
int count = 0;
while (datemin != datemax)
{
datemin++;
count++;
}
return count * flag;
}
void date::print()const//打印
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
3.test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
#include <iostream>
using namespace std;
void test1()
{
date d1;
d1.print();
date d2(d1);
d2.print();
date d3;
d3 = d1;
d3.print();
}
void test2()
{
date d1;
d1 += 100;
d1.print();
date d2 = d1 + 50;
d2.print();
date d3;
d3 -= 100;
d3.print();
date d4 = d3 - 50;
d4.print();
}
void test3()
{
date d1;
date d2 =++d1;
d2.print();
date d3;
date d4 = d3++;
d4.print();
date d5;
date d6 = --d5;
d6.print();
date d7;
date d8 = d7--;
d8.print();
}
void test4()
{
date d1(2024, 7, 15);
date d2(2024, 9, 1);
bool a, b, c, d, e, f;
a = d1 > d2;
b = d1 == d2;
c = d1 >= d2;
d = d1 < d2;
e = d1 <= d2;
f = d1 != d2;
int ret= d1 - d2;
printf("%d%d%d%d%d%d\n", a, b, c, d, e, f);
printf("%d\n", ret);
}
int main()
{
//test1();
//test2();
//test3();
test4();
return 0;
}
七、const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
void Date::Print()const
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//相当于void Date:Print(const Date* this)
//但是形参和实参中不能显式写出this指针
const Date d1(2024,7,15);
d1.Print();
//权限平移
Date d2(2024,7,15);
d2.Print();
//权限缩小
八、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。
小知识
1临时对象具有常性,不能修改
所以在传值返回前面加个const没有什么意义
例如 const date operator + (int)
2.函数 const 与 函数 可以同时存在,例如
date operator = (int)const date operator = (int),
编译器会认为里面隐含的参数分别是const date* this 和date *this,构成函数重载。编译器会调用最匹配的。同时,如果没有非const 版本的,那就只会去调用const版本的,因为权限可以缩小
3.const 对象不能调用非const成员函数
我们先有如下一个类
struct SeqList
{
public:
int size()
{
return _size;
}
private:
// C++11
int* _a = (A*)malloc(sizeof(A)*10);
size_t _size = 0;
size_t _capacity = 0;
};
当我们再去·全局定义一个函数
void func(const SeqList s1)
{
for(int i = 0; i < s1.size();i++)
{
;
}
}
这里s1.size()调不了 因为int size()的参数是 SeqList* this而不是const SeqList* this
int size()
{
return _size;
}
所以我们要把函数改成
int size()const
{
return _size;
}
4.
const 修饰的对象,我们上面的SeqList类
我们不能修改
_a
size_t
size_t
的值。
但是我们可以修改
_a里面储存的指针指向的值
可以_a[0]++
5.
根据4中的问题,如果我们想要让const对象里面成员变量里面储存的值不能被修改,我们可以使用2中的函数重载。
即
const int& operator[](size_t i)const // 读
{
assert(i < _size0);
return _a[i];
}
int& operator[](size_t i) //写
{
assert(i < _size0);
return _a[i];
}
这样就可以把读写分开
6.
const对象不能调用非const函数
同理const函数也不能调用其它非const函数
7.
总结一下,只读函数可以加上const,内部不涉及修改的函数都是只读函数
一组相互附用的函数如果一个函数加了const,必须全部加const
声明和定义分离两边都加const