欢迎来到
银狐的个人博客

C++类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载、Const成员)详细解读

C++类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载、Const成员)详细解读 构造函数什么是构造函数?构造函数的特点 析构函数析构函数概念析构函数特性 拷贝构造函数拷贝构造函数的概念拷贝构造函数的特征 赋值运算符重载运算符重载 Const成员补充

构造函数 什么是构造函数?

当建立一个对象时,通常最需要立即做的工作是初始化对象,如对数据成员赋初值等 构造函数就是用来在创造对象时初始化对象,为对象数据成员赋初始值 ——初始化对象+申请资源

下面以创建Date对象举例说明: 创建Data对象的同时并给对象设置一个值 C++提供了构造函数(constructor)来处理对象的初始化问题。构造函数是类的一种特殊成员函数,不需要人为调用,而是在建立对象时自动被执行。换言之,在建立对象时构造函数就被自动执行了,而且在上述代码中:

1.名字与类名相同,创建类类型对象时由编译器自动调用 2.在对象的声明周期只调用一次

构造函数的特点

构造函数的特性其主要特征如下: 1.名字与类名相同 2.无返回值 3.对象实例化时编译器会自动调用构造函数 4.构造函数可以重载 在上述代码中,带参构造函数和无参构造函数形成重载

如果我们在类中没有写一个构造函数,那么我们调用对象时,编译器自动生成一个默认构造函数。一旦用户在类中定义了一个构造函数,则这个默认构造C++类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载、Const成员)详细解读 第1张图片-银狐博客函数将不会生成,也就是说首先会调用用户定义的构造函数。

在Data类中没有一个构造函数,当我们创建一个对象时,编译器会自动调用默认构造函数将a进行了初始化,只是a中的成员变量为随机值,使用无参的构造函数不需要()。

类中无构造函数,调用系统默认构造函数,初始化后a的值为随机值,具体代码如下: 综上:我们建议定义出一个全缺省构造函数,因为它可以灵活应用,它既可以传参,也可以不需要传参也能够初始化对象。

析构函数 析构函数概念

用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特殊的成员函数——析构函数,用该函数去完成一些资源清理工作——释放资源。

例如构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。 那么如何去声明和定于析构函数?

析构函数特性

析构函数是特殊的成员函数

其特征如下: 1.析构函数是在在类名加 ~ 2.一个类只能有一个析构函数,如果类中没有定义一个析构函数,则系统会自动生成一个默认的析构函数 3.无参数无返回值。 4.对象声明周期结束时会编译器会自动调用析构函数。

将SeqList中的变量s进行初始化: 下面程序,我们看到编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

我们自己没有写析构函数,那么编译器会去调用系统默认的析构函数,而这个析构函数对内置类型不做处理,对自定义类型会去调用它自己的析构函数

注意:析构函数是无参数返回的函数且没有参数,一个类只有一个析构函数,不写的话则会生成默认析构函数

拷贝构造函数 拷贝构造函数的概念

拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)。

在下述代码中,d1调用拷贝构造函数将d的内容拷贝给自己,虽然没有定义拷贝构造函数,然而系统调用默认的拷贝构造函数。

拷贝构造函数的特征

拷贝构造函数也是特殊的成员函数,特征如下:

1.拷贝构造函数是构造函数的一个重载形式 2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用 3.若未显示定义,系统生成默认的拷贝构造函数 (默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝)

如果拷贝构造函数的参数为为传值,那么在传值的过程会去调用拷贝构造函数去拷贝一个临时变量,每一次调用都会去在调用拷贝构造函数,以此类推,则会引起无穷无尽的调用拷贝构造函数。所以拷贝构造函数必须传引用 data(data &a)为语法要求,不会为a开辟空间,仅当a的别名使用,不会触发其他函数调动 如果不加&,对象初始化对象就会调动拷贝构造函数,就得引发下一个a,这样下去就没完没了了,不允许产生无限递归的现象。

4.如果说编译器生成默认拷贝构造函数就可以实现拷贝的话,是不是自己就不用写了?对于下面这种类,程序运行是崩溃的,那么我们就需要用深拷贝来解决。

赋值运算符重载 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号 函数原型:返回值类型operator操作符 示例: 说白了运算符重载就是要给 注意: 不能通过连接其他符号来创建新的操作符:比如operator@ 重载操作符必须有一个类类型或者枚举类型的操作数 用于内置类型的操作符,其含义不能改变,例如:内置类型+,不能改变其含义 *,::,sizeof,?,、以上5个运算符不能重载 运算符重载实际上就是给这个所谓的运算符赋一个其它的涵义 t4给t3赋值其实可以直接调动成员函数的方法,类似于t4.Assign(t3),t4.Assign(t3)与t4.operator=(t3)的结果是一致的。但是在C++中更想要简单一点(不希望调函数,比如t1+t2,我们不希望写成add(t1,t2),而是直接t1+t2),其实在这里,t4=t3 中的=已经不是一个“=”了,而是 operator= 但是这里的operator可以省略,所以说 这里的 = 就不是等号了,而是一个“函数”。

问题1:

上述代码中——Test * operator = (const Test &t) 这个const可不可以不要?,还有& 当然可以取消,取消不影响代码结果,但是为了程序的运行效率,取消&的话,会多调用一次拷贝构造函数,注意析构函数是从后往前析构。也就是说加了&会提升程序的运行效率(少一次调用拷贝构造,当然也少一次析C++类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载、Const成员)详细解读 第2张图片-银狐博客构,不开辟空间,不花费时间)具体示例如下: 因为引用的对象我们一般是不希望被改变的,因此加上const,if(this !=&t)是防止自己给自己赋值 对于上述代码,return *this返回后 this指针的值是存在的,所以上述Test后面可以加&进行引用,所以说能不能引用返回,就看函数结束后,被引用的对象还活着没有而且建议用引用,因为可以少调动一次拷贝构造函数。

综上:赋值运算符主要有四点: 1.参数类型 2.返回值 3.检测是否给自己赋值 4.返回*this 5.一个类如果没显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝

对于如下这么多对象,他们构造,拷贝构造,析构的顺序及对应的流程如下: 下面这种办法少产生一次拷贝构造的调用,就是说拷贝构造产生的无名临时变量,直接就是t4的值。而不是像上述代码一样多拷贝了一次,然后再将拷贝后的值给t4,这样增加了程序的运行效率

运算符重载就是给+重新定义一个新的功能 重载+

下面为运算符重载的相关代码;
// An highlighted blockclass MyInt;ostream& operator<<(ostream &out, const MyInt &t);class MyInt{friend ostream& operator<<(ostream &out, const MyInt &t);//声明友元函数public:MyInt(int i = 0){m_i = i;}public:MyInt operator+(const MyInt &x){     return MyInt(m_i + x.m_i);}MyInt operator-(const MyInt &x){return MyInt(m_i - x.m_i);}MyInt operator*(const MyInt &x){return MyInt(m_i * x.m_i);}MyInt operator/(const MyInt &x){return MyInt(m_i / x.m_i);}MyInt operator%(const MyInt &x){return MyInt(m_i % x.m_i);}public:MyInt& operator+=(const MyInt &x){m_i += x.m_i;return *this;}MyInt& operator-=(const MyInt &x);MyInt& operator*=(const MyInt &x);MyInt& operator/=(const MyInt &x);MyInt& operator%=(const MyInt &x);public://a > b  x.operator>(b)bool operator>(const MyInt &x){return m_i > x.m_i;}bool operator<=(const MyInt &x){return !(*this > x);//当前对象比x大然后取反就是小于等于就是真}bool operator<(const MyInt &x){return m_i < x.m_i;}bool operator>=(const MyInt &x){return !(*this < x);}bool operator==(const MyInt &x){return m_i == x.m_i;}bool operator!=(const MyInt &x){return m_i != x.m_i;}public:MyInt& operator++() //ǰ++{m_i++;return *this;}MyInt operator++(int) //++{MyInt tmp = *this;m_i++;return tmp;}private:int m_i;};ostream& operator<<(ostream &out, const MyInt &t)//友元函数实现{out << t.m_i;return out;}void main(){MyInt a = 10;MyInt b = 20;MyInt c;c = a + b;c = a - b;c = a * b;c = a / b;c = a % b;cout << "c = " << c << endl;a += b;if (a > b)cout << "a > b" << endl;else    cout << "a <= b" << endl;MyInt x = a++;MyInt y = a--;}

定义复数类的运算符重载实现: 对于Complex y = c1+10来说可以,但是Complex y = 10+c1就不可以。c1是个对象,+是函数,函数要被对象才能驱动,c1+10 实际上是c1.operator。10不能构造成临时对象,10+的话就不匹配重载的+号了,而是想象成普通的运算符了。 当然如果非要Complex y = 10+c1成立的话,这里需要用到友元函数来操作,如下: 重载为友元函数,且友元函数必须给出成员的参数,成员函数和友元不一样,不需要加类的作用域限定符::

下面运算符输出流的重载,它通常作为某个类的友元函数出现 下面为C++输出的时候,cout不管任何情况就可以直接输出各种类型的结果,会自动匹配类型。 cout其实是ostream的一个对象(endl同样也是一个对象的概念)而且是一个全局对象,所以引入头文件后都可以使用。 ostream其实是这个东西的一个命名方式,是一个基本输出流对象 basic_ostream 表面上看起来它什么都不管,实际上背后可以生成各种类型的类型,相当于调用了不同的运算符重载,这些底层的代码我们平时是看不到的。

下面举相关个别运算符重载: ostream是输出的一种类型,cout是out的对象,为什么out可以引用cout,因为他们两个是同一种类型,那为什么要重载为友元,是因为希望第一个参数就是cout,第二个参数才是对象,如果不重载友元,那么第一个参数只能用对象去驱动它。最后把cout返回,然后继续往后输出。

Const成员

将const修饰的类成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在函数成员函数中不能对类的任何成员进行修改。

const修饰的方法我们称之为常方法 ,如下代码我们在函数后面加了const实际上就是对内部的this指针变量加了限制不可改变。 普通的对象调动常方法是没有问题的,常对象调用普通方法就不行 那么什么时候写成常方法? 要修改数据成员的话则不能写成常方法,如果只为读而不改,则强烈建议写成常方法。c++采用最佳匹配原则,常方法和普通方法可以共存。

补充

六种类的默认成员函数: 构造函数,析构函数,拷贝构造函数,运算符重载之外的另外两种为:

普通对象取地址符,常对象取地址符 下面为六种类的默认函数的基本代码表示:

赞(0) 打赏
版权声明:本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
文章名称:《C++类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载、Const成员)详细解读》
文章链接:https://www.yinhu3.com/1965.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。
如果文章侵犯到你的权益,请查看本站免责声明:《免责声明》

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

愿意请我喝杯矿泉水吗

支付宝扫一扫打赏