目录
默认成员函数
构造函数
构造函数的定义
构造函数的性质
默认的构造函数
编译器自动生成的构造函数的作用
析构函数
析构函数的定义
析构函数的性质
编译器自动生成的析构函数的作用
拷贝构造函数
拷贝构造函数的性质
编译器生成的拷贝构造函数的作用
运算符重载
运算符重载的定义
赋值运算符重载
编译器生成的赋值运算符重载
const成员
总结
我们知道,类中我们可以定义类的成员函数,但是当我们一个成员函数都不定义时,难道类中就没有成员函数吗?当然不是,当我们不定义成员函数时,编译器会默认生成六个成员函数,我们本期主要讲解C++中的默认成员函数。
什么是构造函数呢?
在之前学习栈这个数据结构对栈进行相关操作时,我们会对栈进行初始化,这里的初始化操作我们可以理解为构造函数所起的作用。我们依然通过日期类为大家讲解。
构造函数:构造函数是一个特殊的成员函数,在实例化对象时编译器自动调用构造函数,并且整个过程只调用一次,构造函数主要是用于对对象的成员变量进行初始化。
先看如下代码:
class Date
{
private:
int _year;
int _month;
int _day;
public:
//无参的构造函数
Date()
{
_year = 2023;
_month = 11;
_day = 18;
}
//全缺省的构造函数
Date(int year=2023,int month=11,int day=18)
{
_year = year;
_month = month;
_day = day;
}
//编译器生成的默认构造函数
void PrintInfo()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
};
1.构造函数没有函数名称
参考上述代码
2.构造函数没有返回值
参考上述代码
3.创建对象时,编译器会自动调用构造函数对对象进行初始化。
4.构造函数可以重载
上述代码中的有参数的构造函数和无参数的构造函数满足函数重载
什么是默认的构造函数呢?我们直接给出定义,默认的构造函数就是不用参数就可以调用的构造函数,默认的构造函数总共有三种:
1.没有参数的构造函数
Date()
{
_year = 2023;
_month = 11;
_day = 18;
}
2.全缺省的构造函数
Date(int year=2023,int month=11,int day=18)
{
_year = year;
_month = month;
_day = day;
}
3.当我们自己没有写构造函数时,编译器会自动生构造函数,我们一旦写了构造函数,编译器就不会自动生成构造函数。编译器自动生成的构造函数也是默认构造函数。
1.对内置类型的成员变量,不做任何处理;
2.对自定义类型的成员变量,会调用它们的默认构造函数进行初始化操作,如果自定义类型没有默认构造函数,编译器就会报错。
什么是析构函数呢?
同样回到栈的操作,栈在使用完之后,为了避免内存泄漏,我们会对栈的资源进行清理(释放指针,指针置空),比如动态开辟的空间的释放。析构函数也是类似的功能,主要完成对对象相关资源的清理。
析构函数:析构函数是一类特殊的成员函数,在对像销毁时编译器自动调用析构函数,完成对对象资源的清理工作。
我们用C++实现栈的代码为大家一一解释:
class Stack
{
private:
int* _a;
int _top;
int _capacity;
public:
//构造函数,栈的初始化
Stack(int top = 0,int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout <<"malloc fail" << endl;
exit(-1);
}
_top = top;
_capacity = capacity;
}
//析构函数,栈资源的清理
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
};
1.析构函数无参数无返回值。
参考上述代码
2.析构函数函数名为~函数名。
参考上述代码
3.一个类只有一个析构函数,若没有显示定义,编译器会自动生成一个析构函数。
4.与构造函数相反,在对象销毁时,编译器会自动调用析构函数,完成对象相关资源的清理工作。
1.对于内置类型成员变量不做处理
2.对于自定义类型成员变量会去调用自定义类型的析构函数,完成对对象资源的清理工作。
注意:并不是所有的类都需要析构函数,当我们自定义类型的操作关系到堆区资源的开辟时,为了防止内存泄漏,我们一般会用到析构函数,但是之前的日期类,因为没有牵扯堆区资源的开辟,所以用不到析构函数。
什么是拷贝构造函数呢?
引用生活中的例子,可以理解为你在学校时,抄写其它同学的作业。在类和对象中,拷贝构造函数就是用一个已经存在的对象,对一个新创建的对象进行赋值操作。
我们通过日期类和栈类为大家一一讲解,先来看如下代码:
class Stack
{
private:
int* _a;
int _top;
int _capacity;
public:
//构造函数,栈的初始化
Stack(int top = 0,int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout <<"malloc fail" << endl;
exit(-1);
}
_top = top;
_capacity = capacity;
}
//析构函数,栈资源的清理
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
};
class Date
{
private:
int _year;
int _month;
int _day;
public:
//无参的构造函数
Date()
{
_year = 2023;
_month = 11;
_day = 18;
}
//全缺省的构造函数
Date(int year = 2023, int month = 11, int day = 18)
{
_year = year;
_month = month;
_day = day;
}
//编译器生成的默认构造函数
void PrintInfo()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
};
int main()
{
Date d1(2023, 11, 20);
Date d2(d1);
d2.PrintInfo();
Stack s1(0, 8);
Stack s2(s1);
return 0;
}
1.拷贝构造函数的形参只有一个,且必须使用引用传参(且应该为const引用,使用const引用是为了防止实参被修改,不能你抄了我的作业我的作业也被你改了),如果使用值传参,会导致无限递归导致栈溢出。参考上述代码
2.拷贝构造函数也是一个特殊的成员函数,是构造函数的一种。
3.当我们没有显示生成拷贝构造函数时,编译器会自动生成拷贝构造函数。
1.对于内置类型的成员变量进行字节拷贝(浅拷贝)。
2.对于自定义类型的成员变量会去调用自定义类型的拷贝构造函数。
注意:编译器生成的拷贝构造函数也得看使用场景。
比如日期类,我们可以直接使用编译器生成的拷贝构造函数进行字节拷贝,因为日期类没有在堆上动态开辟空间,不用调用析构函数。
对于栈这个类而言,我们则不能直接使用编译器生成的拷贝构造函数。我们知道,栈的空间我们是需要动态开辟的,既然动态开辟了空间,最终对象销毁时,就要调用析构函数进行对象资源的释放,但是如果我们使用了编译器生成的拷贝构造函数进行了字节拷贝,那么就会导致两个指针变量的值相同,都指向了同一块空间,我们知道,在释放空间时,一块空间是不能被释放两次的,如果释放两次就会出错,所以我们不能使用编译器生成的拷贝构造函数,我们得自己写拷贝构造函数(我们称之为深拷贝,后续会为大家讲解)。
何为深拷贝?何为浅拷贝?
就通过栈这个类而言,如果我们采用了浅拷贝,浅拷贝就是字节拷贝,就会导致两个指针指向了同一块空间,释放就会出错。深拷贝就是我们动态开辟了一块与原对象大小相同的空间,这块空间是在堆上开辟的,然后两个指针就指向了不用的两块空间,然后在新空间中,然后对其他的成员变量再进行拷贝赋值,这样就不会在调用析构函数时出错,这些都只是浅浅的阐述了一下,具体的深拷贝我们会在后面为大家仔细讲解。
什么是运算符重载呢?
大家先思考一个问题,我们一般在进行加法的运算时,通常是两个数字进行相加,加法操作符的两个操作数都是对于编译器中的内置类型而言的,怎样对自定义类型之间通过操作符进行操作呢?比如两个日期来比较大小,两个日期来进行相减,编译器是不支持自定义类型之间进行这些操作的,因此我们要用到运算符重载来让编译器支持这些类似的操作。
为了增加代码的可读性,C++引入了一种新的函数,我们称之为运算符重载,这种新的函数与普通的函数类似,有函数返回类型,函数名称,形参列表,不过其函数名比较特殊,通常由operator+操作符组成,这个操作符就是我们重载的操作符。比如:operator>,operator-
运算符重载的函数原型为:函数返回值类型 operator操作符(参数列表)
注意:1.函数返回值类型应该视情况而定,比如两个日期类相减,函数的返回值就应该是天数,应该为整型,两个提日期类比较大小,函数的返回值类型就应该是bool类型。
2.我们不能随意的对运算付进行重载,只能对普遍认知的运算符进行重载,+,=,>等等诸如此类的操作符进行重载,不能对@,¥一些冷门操作符进行重载。
3.运算符重载只能对自定义类型进行重载,不能对内置类型进行重载。
4.运算符重载作为类成员函数时,含有一个默认的形参,为this指针。
5..* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
我们给出日期类中,两个日期类比较大小的运算符重载。
class Date {
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2023,int month=11,int day=30)
{
_year = year;
_month = month;
_day = day;
}
bool 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;
else {
return false;
}
}
};
代码如下:
Date& operator=(const Date& d)
{
//赋值运算符重载不能自己给自己赋值,所以需要判断
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
赋值运算符重载和拷贝构造函数的区别:
拷贝构造函数是用一个已经存在的对象初始化一个刚刚创建的对象;
赋值运算符重载是两个已经存在的对象之间进行赋值操作。
注意:1.赋值运算符重载的返回类型是对象的引用,对象在赋值重载整个栈帧创建和销毁的整个周期中都没有销毁,且引用可以减少对象的拷贝。
2.一定要检测自己是否给自己赋值。
3.我们没有显示定义赋值运算符重载,编译器会默认生成一个赋值运算符重载。
编译器生成的赋值运算符重载和编译器生成的拷贝构造函数类似:
1.对于内置类型成员变量,完成字节序的值拷贝(浅拷贝)。
2.对于自定义类型的成员变量,会去调用自定义类型的赋值运算符重载。
什么是 const成员呢?我们通过一段代码为大家讲解:
class Date
{
private:
int _year;
int _month;
int _day;
void Print()
{
cout << _year << _month << _day << endl;
}
void Print()const //可以理解为const Date*const this,this指针被两个const修饰
{
cout << _year << _month << _day << endl;
}
};
int main()
{
Date d1;
const Date d2;
return 0;
}
我们在C++基础我们讲解了权限的相关概念,我们知道,权限的放大是不允许的,权限不变和权限缩小是允许的。在类和对象上期,我们学习了this指针,我们知道this指针的类型是类名*const this,也就意味着this指针的指向是不可修改的,但是this指针指向的对象是可以修改的,但是上述代码中我们创建了const对象d2,也就意味着d2是不可以被修改的,也就意味着d2对象不能调用一般的的成员函数,得去调用const函数。常规的对象和const对象都可以调用const函数,因为分别是权限的缩小和权限不变,这是允许的。
本期主要学写了C++中的四个重要的默认成员函数,默认成员函数就是我们不写,编译器会默认生成的成员函数。
这个四个默认成员函数我们可以分两类记忆:
1.我们不写编译器生成的构造函数和析构函数,对内置类型不做处理,对自定义类型会去调用其默构造或者析构。
2.我们不写编译器生成的拷贝构造函数和赋值运算符重载,对于内置类型进行字节序的值拷贝(浅拷贝),对于自定义类型调用它们的拷贝构造函数或赋值运算符重载。
好了,本期的内容就是这些,本期内容到此结束^_^