【小梦C嘎嘎——启航篇】类和对象(中篇)😎
- 前言🙌
- 类的6个默认成员函数
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝构造函数的特性有哪些?
- 既然编译器可以自动生成一个拷贝构造函数,为什么我们还要自己设计实现呢???
- 拷贝构造函数典型调用场景有哪些呢???
- 运算符重载
- 运算符重载需要注意的地方
- 赋值运算符重载
- 赋值运算符重载函数有哪些特性???
- 为什么赋值重载函数只能重载成类成员函数不能重载成全局函数呢???
- 既然编译器可以自动生成一个赋值重载函数了,为什么我们还要自己设计实现呢???
- 前置++和后置++重载
- 前置++:返回+1之后的结果
- 后置++:
- const 修饰的成员
- const对象可以调用非const成员函数吗?非const对象可以调用const成员函数吗?const成员函数内可以调用其它的非const成员函数吗?非const成员函数内可以调用其它的const成员函数吗?
- 取地址及const取地址操作符重载
- 总结撒花💞
😎博客昵称:博客小梦
😊最喜欢的座右铭:全神贯注的上吧!!!
😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主!
😘博主小留言:哈喽!😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不多说,文章推上!欢迎大家在评论区唠嗑指正,觉得好的话别忘了一键三连哦!😘
前言🙌
哈喽各位友友们😊,我今天又学到了很多有趣的知识,现在迫不及待的想和大家分享一下!😘我仅已此文,分享有关 C++ 类和对象的相关知识 ~ 都是精华内容,可不要错过哟!!!😍😍😍
类的6个默认成员函数
构造函数
- 构造函数是用来对对象初始化的,并非是为对象开辟空间的。
- 构造函数的函数名与类名相同。
- 无返回值类型,不用写void
- 一个类中可以有多个构造函数,也就是构造函数是可以实现重载的。
- 在定义对象时,会自动调用构造函数,对对象进行初始化操作。
- 构造函数是类的默认成员函数,如果自己没有写,编译器会默认生成一个无参的构造函数。
- 默认构造函数:无参的构造函数、全缺省的构造函数、自己没写编译器自动生成的构造函数。在一个类中,有且只有一个默认构造函数。当有多个时,会出现调用冲突。
- 当要调用无参的构造函数时,定义对象时不要加(),例如Data d(),这样是错误的。这样编译器区分不清它是函数声明还是调用构造函数,产生二义性了。
析构函数
- 析构函数是用来进行对对象资源的清理工作,而不是用来销毁对象,销毁对象是程序结束系统自动销毁回收空间的。
- 析构函数的函数名与类名相同,前面加一个~。
- 无返回值也无参数。
- 一个类中只能有一个析构函数,不能实现重载。
- 在程序结束时,编译器会自动先调用析构函数。
- 构造函数是类的默认成员函数,如果自己没有写,编译器会默认生成一个无参的构造函数。
拷贝构造函数
什么是拷贝构造函数?顾名思义,就是完成对象的之间的拷贝。
拷贝构造函数的特性有哪些?
这里,我对拷贝构造函数总结为以下几点特性:
-
拷贝构造函数是一种特殊的函数。
-
拷贝构造函数其实就是构造函数的一个重载。
-
如果没有显示的实现拷贝构造函数,编译器会自动生成默认拷贝构造函数供程序使用。
-
一个已经存在的对象拷贝给一个还不存在的对象时,编译器会自动调用拷贝构造函数。
-
一般在传值传参和传值返回中,会调用拷贝构造函数。
-
拷贝构造函数函数列表中,有且只有一个参数,一般加上const修饰,且必须是本类类型对象的引用,如果不是引用的话会导致无限递归的问题。
既然编译器可以自动生成一个拷贝构造函数,为什么我们还要自己设计实现呢???
我的理解是:编译器生成的默认构造函数只能完成浅拷贝的工作而不能实现深拷贝,而我们在实际开发过程中有些场景是需要深拷贝才能够解决问题的。由于在C++中,默认拷贝构造函数对内置数据类型只完成值拷贝(浅拷贝),对于自定义类型会调用它们的拷贝构造函数。如果,我们实现的类只有内置类型成员变量,也就是说不涉及申请其他资源的,可以只使用编译器提供的默认拷贝构造函数就可以满足我们的需求,无需自己实现,例如Date类的实现就无需手搓拷贝构造函数。但是,如果我们需要申请其他资源,例如需要在堆区上申请空间资源等,则需要我们根据项目工程的实际场景进行手动实现拷贝构造函数,完成深拷贝。例如,我们进行栈类的实现,如果不进行深拷贝的话,会出现多次释放同一块空间资源的问题,从而报错。
拷贝构造函数典型调用场景有哪些呢???
- 使用已经存在的对象去创建新对象
- 函数参数类型是类类型对象
- 函数返回值类型是类类型对象
如果一个类对象所占空间越大,拷贝所带来的消耗就越大。因此,一般在传参时,能够用引用作为参数就使用引用传参;在返回时,能够使用引用返回就不用船只返回。这样可以减少拷贝次数,提高程序的效率。
运算符重载
赋值运算符重载是运算符重载的一种。其实我们也可以不用运算符重载,写一个函数也能实现我们的需求。但是,使用运算符重载可以大大提高代码的可读性。
运算符重载需要注意的地方
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ? : . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
赋值运算符重载
赋值运算符重载函数有哪些特性???
- 从其函数格式可以看出,其基本格式:返回值类型 operator= (参数列表)。从提高效率来看,一般设计为引用返回即 T&。函数的参数列表中必须有一个本类类型的参数,最好实现为const T & 。赋值重砸函数有返回值的原因其实是为了连续赋值的需求。返回的是*this,以满足连续赋值的需求
- 一般没有自己给自己赋值的场景需求,因此在显示实现赋值重载函数时需要避开这个问题。
- 赋值重载函数只能重载成类的成员函数,而不能重载成全局函数。
- 如果没有显示的实现赋值重载函数,编译器会自动生成一个默认赋值重载函数。其对于内置类型成员变量按照完成值拷贝,对于自定义的类型的成员变量会调用它们的赋值重载函数。
为什么赋值重载函数只能重载成类成员函数不能重载成全局函数呢???
因为,如果在类中没有显示实现赋值重载函数,而是在类外实现赋值重载函数。那么编译器会在类内自动生成一个默认的赋值重载函数。而程序在调用赋值重载函数时,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。 在《c++ Prime 第五版》这本书中也提到,如下图所示:
既然编译器可以自动生成一个赋值重载函数了,为什么我们还要自己设计实现呢???
这里举一个例子来理解:下面是一个栈类的简单实现。
#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 = s1;
return 0;
}
程序运行结果:
我们可以看到,程序执行最终崩掉了。为什么呢?接下来我画图分析一下:
- s1 赋值给s2后,s1和s2的_array指向同一块空间,当s1和s2销毁的时候,都会调用析构函数对 _ array空间进行释放,就出现多次释放同一块空间的问题
- 赋值后,s2申请的_array 指向的空间就没有人标识它的位置了,从而造成了内存泄露。
前置++和后置++重载
前置++:返回+1之后的结果
注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
后置++:
前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载, C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1。而temp是临时对象,因此只能以值的方式返回,不能返回引用
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator++()
{
_day += 1;
return *this;
}
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++;
d = ++d1;
return 0;
}
const 修饰的成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数。隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
const对象可以调用非const成员函数吗?非const对象可以调用const成员函数吗?const成员函数内可以调用其它的非const成员函数吗?非const成员函数内可以调用其它的const成员函数吗?
这几个问题都设计了权限的问题。我们知道,只能让变量的权限平移或者缩小,不能让权限放大。因此,const对象不可以调用非const成员函数(权限放大);非const对象可以调用const成员函数(权限缩小);const成员函数内不可以调用其它的非const成员函数(权限放大);非const成员函数内可以调用其它的const成员函数(权限缩小)。
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载。
总结撒花💞
希望大家通过阅读此文有所收获!
😘如果我写的有什么不好之处,请在文章下方给出你宝贵的意见😊。如果觉得我写的好的话请点个赞赞和关注哦~😘😘😘