目录
一.默认成员函数
1.1 什么是默认成员函数
如果一个一个类中什么都没有,这样的类被称为空类。空类在代码中看似什么都没有,但其实编译器会在其中生成6个默认的成员函数。默认函数就是我们什么都不写,编译器也会自动生成的函数。默认函数的功能如下图所示,它们能帮助我们完成清理和初始化、拷贝和复制、取地址重载,三个最主要的功能。
二.构造函数
2.1 构造函数的作用
构造函数是一个特殊的成员函数,其函数名与类名相同,创建类类型的对象时自动调用该函数,对对象中的成员进行初始化。
2.2 构造函数的特点
(1)函数名与类名相同
(2)没有返回值(不需要写void)
(3)对象是实例化时编译器自动调用对应的构造函数
(4)构造函数可以重载
(5)实例化时会自动调用
(6)默认生成的构造函数不会处理内置类型的参数,但是对于自定义类型会调用其默认构造函数。如下代码所示,我们在构造一个类以后我们如果没有写构造函数的话,系统就会默认生成一个,并且分析其中的形参类型,因为这里的形参都是int类(内置类型)所以编译器并不会处理这里的参数。
#include<iostream>
using namespace std;
class Date
{
public:
/* Date(int _year , int _month ,int _day) //默认生成的默认构造函数
{
}*/
void print()
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //调用自动生成的默认构造函数进行初始化
d1.print();
return 0;
}
程序运行的结果是一堆随机值,因为Date构造函数中的参数是内置类型,而且成员变量并没有设置缺省参数,所以编译器并没有对内置类型的参数做初始化的操作。
所以在这里我们想完成初始化的操作有两种方式,其一是自己编写一个构造函数,其二是给成员变量设置缺省参数具体,操作如下所示:
其一:自己编写一个构造函数,并且在形参中设置缺省参数。
#include<iostream>
using namespace std;
class Date
{
public:
Date (int year=2024, int month = 4, int day = 16)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
}
private:
int _year ;
int _month;
int _day;
};
int main()
{
Date d1(2024,4,15); //实例化对象d1调用内部的成员函数进行初始化
d1.print();
return 0;
}
其二:在成员变量中设置缺省值,再利用编译器自动生成的构造函数进行初始化。
这种写法和上一种是一样的。
#include<iostream>
using namespace std;
class Date
{
public:
/* Date()
{
}*/
void print()
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
}
private:
int _year =2024;
int _month = 4;
int _day 16; //这里只是声明,可以理解为缺省参数,并不是不是构造变量,并不开辟空间
};
int main()
{
Date d1; //利用自动生成的构造函数进行初始化
d1.print();
return 0;
}
(3)对于自定义类型会调用其默认构造函数的样例:在这里我先写了一个栈的类,然后写了一个myQueue的类,其中包含两个stack栈的小类,在myQueue中我不需要写构造函数来初始化我的类,因为myQueue会调用stack类来初始化类中的成员。
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(size_t n = 4 )
{
a = (int*)malloc(sizeof(int) * n);
if (a == nullptr)
{
perror("malloc失败!");
exit(-1);
}
capacity = n;
top = 0;
cout << "success!" << endl;
}
void insert()
{
for (int i = 0; i < this->capacity; i++)
this->a[i] = i + 1;
}
void print()
{
for (int i = 0; i < this->capacity; i++)
cout << this->a[i] << " ";
}
private:
int* a;
int top;
int capacity;
};
class myQueue
{
Stack _pushst;
Stack _popst; //调用stack的构造函数
};
int main()
{
Stack s1(10);
s1.insert();
s1.print();
return 0;
}
(7)无参的构造函数和全缺省的构造函数都成为默认构造函数,一旦用户显示定义编译器将不再生成
三.析构函数
3.1析构函数的概念
与构造函数的功能相反,析构函数不是完成对象本身的销毁,局部对象的销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中的资源清理工作。
3.2 析构函数的特性
析构函数是特殊的成员函数,他有如下几个特征:
(1)析构函数名是在类名前加上字符 ~
(2)无参数无返回值类型
(3)一个类只能有一个析构函数,若没有显示定义。系统会自动生成。析构函数不能重载
(4)对象生命周期结束时,C++编译器系统自动调用析构函数
(5)后定义的对象会先被析构
3.3 析构函数的使用场景
在绝大部分的场景中我们都需要自己写析构函数,但是如果类中只有简单的自定义类型的话,我们可以不写,因为这样的变量都是存在栈中的,但是如果有在堆上开辟的空间的话(比如使用了malloc函数),我们就需要自己写析构函数了。以下是栈和堆的区别:
栈:自动开辟空间,函数调用结束后自动释放内存空间
堆:动态申请的内存空间,需要收开辟和手动释放,相比于栈来说是很自由的。
四.拷贝构造
对于普通的内置类型的变量来说,我们可以简单的将两个变量的值相互拷贝,但是拷贝对象不能像拷贝内置类型变量那样去拷贝对象,这个时候我们就需要用到拷贝构造了。
4.1 拷贝构造函数的定义
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型
对象创建新对象时由编译器自动调用。
4.2 拷贝构造函数的特性
(1)拷贝构造是构造函数的一个重载形式。
(2)拷贝构造函数只有一个且必须是类类型对象的引用,使用传值方式编译器会报错,因为会引发无限递归调用。
以下是发生无线递归调用的情况,下面的拷贝构造函数的形参是一个类类型,主函数需要把d1拷贝到d中,从而有涉及到把一个对象的值拷贝到另一个对象里的问题了,从此往复就是无限递归调用的情况了。
class Date
{
public:
Date(int year = 2024, int month = 4,int day = 16)
{
_year = year;
_month = month;
_day = day;
}
Date(Date d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
} //错误!!!!这么写会造成无线递归调用
void print()
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
Date d2(d1);
Date d3 = d1;
}
正确的写法应该应该如下所示,这里拷贝构造函数中的形参类型是类类型对象的引用函这里的是d就是主函数中d1的一个别名了,这样就不会出现无线递归的调用的问题了。
class Date
{
public:
Date(int year = 2024, int month = 4,int day = 16)
{
_year = year;
_month = month;
_day = day;
}
Date(Date & d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
} //错误!!!!这么写会造成无线递归调用
void print()
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
Date d2(d1);
Date d3 = d1;
}
4.3 浅拷贝和深拷贝
(1)浅拷贝:在对象中的成员中,对于内置类型成员的拷贝,我们是可以直接将被拷贝对象中的成员变量的值,赋给另一个对象的成员变量,这种称为浅拷贝。上面一段代码就是 一段很典型的浅拷贝案例。
(2)深拷贝:在对象中的成员中,对于自定义类型成员,如果有成员在堆上申请了一段空间,例如类中使用malloc开辟了一段空间,我们不能直接把这段空间的地址赋给别人,我们需要自己也开辟一段相同大小的空间,并且将被拷贝对象中malloc开辟空间中的值赋给拷贝对象新开辟的空间里,此为深拷贝。具体案例如下所示。
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(Stack& st)
{ //深拷贝
a = (int*)malloc(sizeof(int) * st.capacity);
if (a == nullptr)
{
perror("malloc失败!");
exit(-1);
}
memccpy(a, st.a, sizeof(int) * st.capacity);
capacity = st.capacity;
top = st.capacity;
cout << "success!" << endl;
}
Stack(size_t n = 4 )
{
a = (int*)malloc(sizeof(int) * n);
if (a == nullptr)
{
perror("malloc失败!");
exit(-1);
}
capacity = n;
top = 0;
cout << "success!" << endl;
}
private:
int* a;
int top;
int capacity;
};
int main()
{
Stack s1(10);
Stack s2(s1);
return 0;
}
(3)使用深拷贝的原因
如果以上的情况下我们没有使用深拷贝方法的话,就会出现将一块空间析构两次的问题。因为d1 和 d2 的a指针都指向了一块空间,d2的a指针在这里存放的是d1指向空间的地址,在d2析构后,析构d1时因为a指向的那块空间已经释放了,但是这里的析构函数会再释放一遍这里的空间,这在编译器中是不允许出现的情况。
class Stack
{
public:
Stack(Stack& st)
{
a = st.a; //d1 和 d2 的a指针都指向了一块空间,没有使用深拷贝
//这么写是错误的
memccpy(a, st.a, sizeof(int) * st.capacity);
capacity = st.capacity;
top = st.capacity;
cout << "success!" << endl;
}
Stack(size_t n = 4 )
{
a = (int*)malloc(sizeof(int) * n);
if (a == nullptr)
{
perror("malloc失败!");
exit(-1);
}
capacity = n;
top = 0;
cout << "success!" << endl;
}
private:
int* a;
int top;
int capacity;
};
int main()
{
Stack d1;
Stack d2 = d1;
}
总结:拷贝构造对于内置类型的拷贝我们使用浅拷贝,对于自定义类型使用深拷贝。
五.运算符的重载
5.1 运算符重载的意义
我们在进行内置类型变量的运算时是十分简单的,但是对于自定义类型对象的运算时,我们不能使用运算符默认的规则在进行运算,例如我有日期类型的对象d1和d2,我想要判断d1和d2的日期是否相等我不能直接写 d1==d2
5.2 运算符重载及形式
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,说人话就是重新定义运算符的运算规则,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字 operator 后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
5.3 运算符重载的注意事项
(1)不能通过连接其他符号来创建新的操作符:比如operator@
(2) 重载操作符必须有一个类类型参数
(3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
(4)作为类成员函数重载时,形参看起来比操作数数目少1,因为成员函数的第一个参数是this。(5).* :: sizeof ?: . 这五个运算符不能重载
(6)不能改变操作数的个数
5.4 运算符重载的案例
(1)比较两个日期对象是否相等,重载了等于 == 的运算规则
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2024, int month = 4,int day = 16)
{
_year = year;
_month = month;
_day = day;
}
bool operator ==(const Date & d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
Date d2 = d1;
cout << (d1 == d2) << endl;
return 0;
}
(2)比较两个日期对象是否形成小于的关系,重载了小于 < 的运算规则
这里写法上 d1<d2 和 d1.operator<(d2) 是一样的
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2024, int month = 4,int day = 16)
{
_year = year;
_month = month;
_day = day;
}
bool operator ==(const Date & d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator <(const Date & d)
{
if (_year < d._year)
return ture;
else if (_year == d._year_ && _month < d._month)
return ture;
else if (_year == d._year_ && _month == d._month && _day <d._day)
return ture;
else
return false;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
Date d2 = d1;
cout << (d1 == d2) << endl;
return 0;
}
(3)比较两个日期对象是否形成小于等于的关系,重载了小于等于 <= 的运算规则
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2024, int month = 4,int day = 16)
{
_year = year;
_month = month;
_day = day;
}
bool operator ==(const Date & d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator <(const Date & d)
{
if (_year < d._year)
return ture;
else if (_year == d._year_ && _month < d._month)
return ture;
else if (_year == d._year_ && _month == d._month && _day <d._day)
return ture;
else
return false;
}
bool operator ==(const Date & d)
{
return *this < d || &this == d;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
Date d2 = d1;
cout << (d1 == d2) << endl;
return 0;
}
(4)将一个日期加上N天,求出加上N天的日期
这里先定义了一个Getmonthday的函数其功能是返回某一年的某一月有多少天,再让原日期的_day加上N天,如果加出来的天数大于Getmonthday函数的值的话,月份就加1,如果月份大于13的话年份就加一。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2024, int month = 4,int day = 16)
{
_year = year;
_month = month;
_day = day;
}
bool operator ==(const Date & d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
int Getmonthday(int year, int month)
{
int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 10 != 0) || (year % 400 == 0)))
return 29;
return monthArray[month];
}
Date& operator +=(int day) //这有两个参数this和day
{
_day += day;
while (_day>Getmonthday(_year,_month))
{
_day -= Getmonthday(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
void print()
{
cout << this->_year << "/" << this->_month << "/" << this->_day;
}
private:
int _year;
int _month;
int _day ;
};
int main()
{
Date d1;
d1 += 100;
d1.print();
return 0;
}