椋鸟C++笔记#3:类的默认成员函数

文章目录

      • 类的默认成员函数
      • 构造函数(Constructor)
        • 构造函数的特点
      • 析构函数 (Destructor)
        • 析构函数的特点
      • 拷贝构造函数(Copy Constructor)
        • 拷贝构造函数的特点
      • 运算符重载(Operator Overloading)
        • 赋值运算符重载
        • 前置/后置的自增和自减运算符重载
      • const成员
      • 实现完整的日期类
      • 关于取地址和const取地址运算符(&操作符)

萌新的学习笔记,写错了恳请斧正。

类的默认成员函数

我们写一个类,在类体中什么都不写,这样的类就叫空类

空类真的什么都没有吗?并不是的。

类有六个默认成员函数,如果我们自己没有写这六个函数,类也会自己去补齐它们。

  1. 默认构造函数(Default Constructor) - 用于无参的初始化一个对象。
  2. 析构函数(Destructor) - 用于在对象生命周期结束时进行资源的回收等收尾工作。
  3. 拷贝构造函数(Copy Constructor) - 用于通过同类型的另一个对象来初始化新对象。
  4. 拷贝赋值运算符(Copy Assignment Operator) - 用于将一个对象的内容赋值给另一个同类型的对象。
  5. 移动构造函数(Move Constructor) - C++11引入,用于通过"移动"而非拷贝另一个同类型的对象来初始化新对象。
  6. 移动赋值运算符(Move Assignment Operator) - C++11引入,用于通过"移动"而非拷贝来将一个对象的内容赋值给另一个同类型的对象。

其中后两个先不管,下面我们介绍一下前面几个函数。

构造函数(Constructor)

我们先看这样一个类:

class Date
{
public:void Init(int y, int m, int d){m_year = y;m_month = m;m_day = d;}void Print(){cout << m_year << "-" << m_month << "-" << m_day << endl;}private:int m_year;int m_month;int m_day;
}

对于这个日期类,我们每次要实例化一个对象时都需要这样先创建对象再使用Init函数完成初始化,非常的不方便。

所以在C++中,我们就有了构造函数。构造函数是一个特殊的成员函数,名字与类名相同。当我们创建对象时,其构造函数就会被自动调用,并且在对象的整个生命周期中只调用一次。

比方说我们可以把上面的类改写成这样:

class Date
{
public:Date(int y, int m, int d)	// 构造函数前面就不要加void了{m_year = y;m_month = m;m_day = d;}void Print(){cout << m_year << "-" << m_month << "-" << m_day << endl;}private:int m_year;int m_month;int m_day;
}
构造函数的特点
  1. 函数名与类名相同。

  2. 没有返回值。

  3. 对象实例化时编译器自动调用对应的构造函数。

  4. 同样可以重载:

    class Date
    {
    public:Date()	// 无参{m_year = 1900;m_month = 1;m_day = 1;}Date(int y, int m, int d)	// 带参{m_year = y;m_month = m;m_day = d;}void Print(){cout << m_year << "-" << m_month << "-" << m_day << endl;}private:int m_year;int m_month;int m_day;
    }int main()
    {Date d1;	// 调用无参的构造函数Date d2(2024, 5, 22);	//调用带参的构造函数return 0;
    }
    

    注意:这里实例化时不能这样写:

    int main()
    {Date d();	// 调用无参的构造函数不要带括号!return 0;
    }
    

    这里的语句可能会被认为是在声明一个叫d的函数,其返回值是Date类型的对象。

  5. 如果类中没有显式定义构造函数,编译器会自动生成一个无参的构造函数,如果显式定义了就不会这么做。

    这里默认生成的构造函数是遵循什么规则给对象初始化的呢?且看:

    C++中的类型分为内置类型(基本类型,比如int、double这些自带的)和自定义类型(我们用class、struct、union等自己定义出来的)。而默认生成的构造函数不一定会对内置类型进行初始化(不排除某些编译器有自己的想法?),对于自定义类型编译器会去寻找其构造函数以实现初始化

    但是在C++11中,打了一个补丁:内置成员变量可以在类中声明时给默认值(要给就全给)。

    class Date
    {
    public:Date(int y, int m, int d){m_year = y;m_month = m;m_day = d;}void Print(){cout << m_year << "-" << m_month << "-" << m_day << endl;}private:int m_year = 1900;int m_month = 1;int m_day = 1;
    }int main()
    {Date d;	//1900-1-1d.Print();return 0;
    }
    

析构函数 (Destructor)

析构函数与构造函数相反,在对象销毁时自动调用析构函数,完成对象中资源的清理工作

析构函数的特点
  1. 析构函数名是在类名前加上字符~
  2. 析构函数无参数无返回值类型,不能重载
  3. 一个类只能有一个析构函数。若显式未定义,则自动生成默认的析构函数。
  4. 对象生命周期结束时,自动调用析构函数。
  5. 和构造函数同样的,自动生成的析构函数不会清理内置类型的变量(本身也不需要清理),对于自定义类型的变量去寻找其析构函数并调用。

这里我们还是以之前的Stack类为例:

#pragma once#include <iostream>
#include <string>using namespace std;typedef int StackDataType;class Stack
{
public:Stack(int size);~Stack();void Push(StackDataType data);StackDataType Pop();StackDataType Top();bool IsEmpty();bool IsFull();int GetSize();int GetTop();private:StackDataType* m_data;int m_size;int m_capacity;
};
#include "Stack.h"Stack::Stack(int size = 4)
{m_data = new StackDataType[size];m_size = 0;m_capacity = size;
}Stack::~Stack()
{delete[] m_data;
}void Stack::Push(StackDataType data)
{if (IsFull()){cout << "Stack is full!" << endl;return;}m_data[m_size++] = data;
}StackDataType Stack::Pop()
{if (IsEmpty()){cout << "Stack is empty!" << endl;return -1;}return m_data[--m_size];
}StackDataType Stack::Top()
{if (IsEmpty()){cout << "Stack is empty!" << endl;return -1;}return m_data[m_size - 1];
}bool Stack::IsEmpty()
{return m_size == 0;
}bool Stack::IsFull()
{return m_size == m_capacity;
}int Stack::GetSize()
{return m_size;
}int Stack::GetTop()
{return m_size - 1;
}

拷贝构造函数(Copy Constructor)

如果我们想通过一个已有对象去创建一个新对象,就要用到拷贝构造函数。简单来说,就是我们有一个类X的对象a,现在想再创建一个类X的对象b而且要和a完全一致。那么就直接使用拷贝构造函数,使用a来创建b。

拷贝构造函数的特点
  1. 拷贝构造函数和默认构造函数一样都是构造函数的一个重载形式

  2. 拷贝构造函数的参数只有一个且为类类型对象的引用。使用传值的方式会造成无穷递归调用,会被编译器报错。

    class Date
    {
    public:Date(int y, int m, int d){m_year = y;m_month = m;m_day = d;}Date(const Date& d){m_y = d.m_y;m_m = d.m_m;m_d = d.m_d;}void Print(){cout << m_year << "-" << m_month << "-" << m_day << endl;}private:int m_year = 1900;int m_month = 1;int m_day = 1;
    }
    

    与之类似的,我们一般传参时,能使用引用就尽量使用引用类型,可以提高程序效率。

  3. 如果没有显式定义,编译器会生成默认的拷贝构造函数。但是默认的拷贝构造函数是直接将内存按字节序复制过来,属于浅拷贝(值拷贝)。但是我们很多时候是需要深拷贝的,尤其是存在资源申请时,因为这种情况下依旧直接复制内存并不能申请新的资源,不能完成我们所需的拷贝。

运算符重载(Operator Overloading)

运算符重载是面对对象编程的一种方法。在C++中,运算符重载允许我们为类对象自定义运算符的计算方法。

简单来说,我们自定义一个类,编译器是不知道这个类的加减乘除等计算是如何进行的。像加减乘除等计算,一般只能用于系统的内置类型,但我们可以通过运算符重载来规定自定义类型遇到这些运算符应该怎么处理。

运算符重载函数的名字为:operator后面加上需要重载的运算符符号

比方说下面就是重载了日期类的日期加天数等于新日期的计算(+=的重载):

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){m_year = year;m_month = month;m_day = day;}Date(const Date& date){m_year = date.m_year;m_month = date.m_month;m_day = date.m_day;}~Date() {};int GetMonthDays(int year, int month){if (month == 2){if (IsLeapYear(year)){return 29;}else{return 28;}}else if (month == 4 || month == 6 || month == 9 || month == 11){return 30;}else{return 31;}}bool IsLeapYear(int year){if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0){return true;}else{return false;}}Date& operator+=(int days){while (days > 0){if (days + m_day > GetMonthDays(m_year, m_month)){days -= GetMonthDays(m_year, m_month) - m_day + 1;m_day = 1;if (m_month == 12){m_month = 1;m_year++;}else{m_month++;}}else{m_day += days;days = 0;}}return *this;}void Print(){std::cout << m_year << "-" << m_month << "-" << m_day << std::endl;}private:int m_year;int m_month;int m_day;
};

我们也可以将运算符重载为全局函数。但是需要涉及的成员变量是公有的(这会破坏类的封装性,但是也可以通过友元来解决,后面会讲):

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){m_year = year;m_month = month;m_day = day;}Date(const Date& date){m_year = date.m_year;m_month = date.m_month;m_day = date.m_day;}~Date() {};int m_year;int m_month;int m_day;
};bool operator==(const Date& d1, const Date& d2)
{return d1.m_year == d2.m_year&& d1.m_month == d2.m_month&& d1.m_day == d2.m_day;
}void test()
{Date d1(2024, 1, 1);Date d2(2024, 1, 1);cout << (d1 == d2) << endl;	// 由于优先级问题需要加括号
}

注意:

  1. 只能重载已有的运算符,不能自创新的符号
  2. 不能对内置类型去重载。
  3. 5个运算符不能重载.*(通过成员函数指针访问类的成员函数的运算符)::(作用域限定符)sizeof?:(三目运算符).
  4. 重载操作符至少要有一个类类型参数作为类成员函数重载时,其有一个隐藏的参数,为其第一个参数this。
赋值运算符重载

赋值运算符重载与拷贝构造函数类似。其参数应当选择传引用返引用以提高效率,并且这样能够支持连续赋值。

注意:

  1. 应当注意是否出现自己给自己赋值的情况
  2. 赋值运算符不能重载为全局函数,只能是成员函数(全局函数没有this指针,而且赋值运算符如果不显式实现,编译器会生成一个默认的,此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突)。
  3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式在内存逐字节拷贝(浅拷贝)。

我们还是以日期类为例:

Date& Date::operator=(const Date& date)
{if (this != &date){m_year = date.m_year;m_month = date.m_month;m_day = date.m_day;}return *this;
}
前置/后置的自增和自减运算符重载

我们以自增运算符为例,前置自增应当这样重载:

Date& Date::operator++()
{*this += 1;return *this;
}

而后置自增运算符应当如何重载呢?C++规定:为了区分,后置运算符重载时增加一个int类型参数,但是调用该函数时忽略该参数,就像这样:

Date Date::operator++(int)
{Date date = *this;*this += 1;return date;
}void test()
{Date d1;Date d2(2024, 1, 1);d1 = d2++;d1 = ++d2;
}

注意后置自增是使用后再自增,因此要返回自增前的旧值。

const成员

在成员函数的括号后面加一个const,就将这个成员函数称为const成员函数。这里实际修饰的是该成员函数的this指针,表示这个成员函数不能对类的任何成员进行修改。

具体的例子可以看下面实现完整的日期类中。

实现完整的日期类

下面我们通过上面学的内容实现一个日期类吧!

#pragma onceclass Date
{
public:Date(int year = 1900, int month = 1, int day = 1);Date(const Date& date);~Date();int GetMonthDays(int year, int month) const;int GetYearDays(int year) const;bool IsLeapYear(int year) const;Date& operator+(int days) const;Date& operator-(int days) const;Date& operator-=(int days);Date& operator+=(int days);Date& operator++();Date operator++(int);Date& operator--();Date operator--(int);Date& operator=(const Date& date);bool operator==(const Date& date) const;bool operator!=(const Date& date) const;bool operator<(const Date& date) const;bool operator>(const Date& date) const;bool operator<=(const Date& date) const;bool operator>=(const Date& date) const;int operator-(const Date& date) const;void Print() const;private:int m_year;int m_month;int m_day;
};
#include <iostream>
#include "Date.h"using namespace std;Date::Date(int year, int month, int day)
{m_year = year;m_month = month;m_day = day;
}Date::Date(const Date& date)
{m_year = date.m_year;m_month = date.m_month;m_day = date.m_day;
}Date::~Date()
{
}int Date::GetMonthDays(int year, int month) const
{if (month == 2){if (IsLeapYear(year)){return 29;}else{return 28;}}else if (month == 4 || month == 6 || month == 9 || month == 11){return 30;}else{return 31;}
}int Date::GetYearDays(int year) const
{if (IsLeapYear(year)){return 366;}else{return 365;}
}bool Date::IsLeapYear(int year) const
{if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0){return true;}else{return false;}
}Date& Date::operator+(int days) const
{Date date = *this;date += days;return date;
}Date& Date::operator-(int days) const
{Date date = *this;date -= days;return date;
}Date& Date::operator-=(int days)
{while (days > 0){if (days >= m_day){days -= m_day;if (m_month == 1){m_month = 12;m_year--;}else{m_month--;}m_day = GetMonthDays(m_year, m_month);}else{m_day -= days;days = 0;}}return *this;
}Date& Date::operator+=(int days)
{while (days > 0){if (days + m_day > GetMonthDays(m_year, m_month)){days -= GetMonthDays(m_year, m_month) - m_day + 1;m_day = 1;if (m_month == 12){m_month = 1;m_year++;}else{m_month++;}}else{m_day += days;days = 0;}}return *this;
}Date& Date::operator++()
{*this += 1;return *this;
}Date Date::operator++(int)
{Date date = *this;*this += 1;return date;
}Date& Date::operator--()
{*this -= 1;return *this;
}Date Date::operator--(int)
{Date date = *this;*this -= 1;return date;
}Date& Date::operator=(const Date& date)
{if (this != &date){m_year = date.m_year;m_month = date.m_month;m_day = date.m_day;}return *this;
}bool Date::operator==(const Date& date) const
{if (m_year == date.m_year && m_month == date.m_month && m_day == date.m_day){return true;}else{return false;}
}bool Date::operator!=(const Date& date) const
{return !(*this == date);
}bool Date::operator<(const Date& date) const
{if (m_year < date.m_year){return true;}else if (m_year == date.m_year){if (m_month < date.m_month){return true;}else if (m_month == date.m_month){if (m_day < date.m_day){return true;}}}return false;
}bool Date::operator>(const Date& date) const
{return !(*this < date) && *this != date;
}bool Date::operator<=(const Date& date) const
{return *this < date || *this == date;
}bool Date::operator>=(const Date& date) const
{return *this > date || *this == date;
}int Date::operator-(const Date& date) const
{int days = 0;Date temp = *this;if (temp < date){while (temp != date){temp++;days++;}}else{while (temp != date){temp--;days++;}}return days;
}void Date::Print() const
{std::cout << m_year << "-" << m_month << "-" << m_day << std::endl;
}

关于取地址和const取地址运算符(&操作符)

有人说我们自定义的类型能够直接取地址或者const取地址,因此这两种运算符也是被编译器默认重载了的默认成员函数。

事实上这不能认为是类的默认成员函数,虽然我们自己可以去重载这两个运算符(不推荐,一般也用不上),但是空类生成的时候编译器并没有生成这样一个重载成员函数,这个操作是C++内置的基础功能。只有当显式地为类重载了取地址操作符,这个操作的行为才会改变。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/839829.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中关于infinity的解释

在Java中&#xff0c;Infinity 不是一个直接的关键字或常量&#xff0c;但它是浮点数&#xff08;float 和 double&#xff09;可以表示的一个特殊值。具体来说&#xff0c;Infinity 通常与两个特殊的浮点值相关联&#xff1a;正无穷大 (POSITIVE_INFINITY) 和负无穷大 (NEGATI…

Java编程注释教程

在Java SE&#xff08;Standard Edition&#xff09;中&#xff0c;注释是用于解释说明程序的文字&#xff0c;它们不会影响程序的执行&#xff0c;但可以提高程序的可读性和可维护性。Java SE支持三种类型的注释&#xff1a; 单行注释&#xff1a; 使用两个斜杠&#xff08;/…

缩进在编程中的重要性及正确使用方法

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 前言 缩进不当引发的问题 缩进的正确使用方法 缩进错误的调试与修复 总结 前言 在编程世…

Unity 资源 之 限时免费的Lowpoly农场动物,等你来领!

Unity资源 之 Lowpoly farm animals 农村动物 前言资源包内容领取兑换码 前言 Unity 资源商店为大家带来了一份特别的惊喜——限时免费的农场动物资源&#xff01;这是一个充满趣味和实用性的资源包。 资源包内容 在这个资源包中&#xff0c;你可以找到丰富多样的低地养殖动物…

Vue3路由配置

路由其实就是一组对应关系&#xff0c;将一个路径与一个组件对应起来&#xff0c;当路径发生变化&#xff0c;路由器就可以通过路由规则&#xff0c;找到当前路径对应的组件&#xff0c;并将该组件呈现到页面上 使用路由步骤&#xff1a; 1.终端输入 npm i vue-router 2.在App…

UDP网络聊天室

前言 基于UDP的网络聊天室 一、项目需求 如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信息如果有人下线&#xff0c;其他用户可以收到这个人的下线信息服务器可以发送系统信息 二、步骤 1.创建UD…

Softing工业将亮相2024年阿赫玛展会——提供过程自动化的连接解决方案

您可于2024年6月10日至14日前往美因河畔法兰克福11.0号馆&#xff0c;Softing将在C25展位展出&#xff0c;欢迎莅临&#xff01; 作为工业应用中数据交换领域公认的专家&#xff0c;Softing工业致力于帮助各行各业的客户部署网络自动化和优化生产流程。 使用Softing产品&…

mybatis plus 配置多数据源(数据源进行切换)

多数据源(数据源进行切换) AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源&#xff0c;这样我们可以在执行查询之前&#xff0c;设置使用的数据源。实现可动态路由的数据源&#xff0c;在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey()…

如何在OpenHarmony上使用SeetaFace2人脸识别库?

简介 相信大部分同学们都已了解或接触过OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;了&#xff0c;但你一定没在OpenHarmony上实现过人脸识别功能&#xff0c;跟着本文带你快速在OpenHarmony标准设备上基于SeetaFace2和OpenCV实现人脸识别。 项目效…

【Vue】Vue2路由

目录 路由作用Vue Router路由Vue Router路由的组成VueRouter常用的函数Vue Router的使用安装Vue Router创建router引入router使用 备注 Vue多级路由&#xff08;嵌套路由&#xff09;编写组件配置嵌套路由 Vue中的动态路由代码示例父组件Home.vue子组件路由配置 路由的 query 参…

黑龙江等保测评深入理解

“没有网络安全&#xff0c;就没有国家安全”&#xff0c;等级保护测评是指按照网络安全系统制定的一系列的防护过程&#xff0c;对已经有的和即将上线的商业服务的基础设施&#xff08;系统&#xff0c;数据库&#xff0c;中间件等&#xff09;所做的一系列的检查&#xff0c;…

HeyGen AI是什么?怎样使用HeyGen AI?

在数字时代&#xff0c;视频内容为王。无论是在社交媒体还是网站上&#xff0c;视频都以其独特的方式吸引着人们的眼球。然而&#xff0c;制作出专业水准的视频往往需要大量的时间和技术知识。HeyGen AI正是为了解决这一难题而诞生的。 HeyGen AI简介 HeyGen AI是一个创新的视…

618值得买的好物清单,这些数码好物你千万不能错过!

​随着618购物节的距离越来越近&#xff0c;你是不是已经开始疯狂浏览购物app&#xff0c;准备大肆采购一番了&#xff1f;但是在购物之前&#xff0c;还是得先做一做功课&#xff0c;避免陷入购物陷阱&#xff0c;而作为一名经验丰富的数码爱好者&#xff0c;想通过这次机会给…

Thinkphp内核开发盲盒商城源码v2.0 对接易支付/阿里云短信/七牛云存储

源码简介 这套系统是我从以前客户手里拿到的,100完整可用,今天测试防红链接失效了,需要修改防红API即可!前端页面展示我就不放了,懂的都懂 优点是Thinkphp开发的&#xff0c;二开容易。 源码图片 资源获取&#xff1a;Thinkphp内核开发盲盒商城源码v2.0 对接易支付/阿里云短…

kafka监控配置和告警配置——筑梦之路

kafka_exporter项目地址&#xff1a;https://github.com/danielqsj/kafka_exporter docker-compose部署kafka_exporter # docker-compose部署多个kafka_exporter&#xff0c;每个exporter对接一个kafka# cat docker-compose.ymlversion: 3.1 services:kafka-exporter-opslogs…

KubeEdge学习

KubeEdge学习主要包括对KubeEdge的理解、安装、配置、部署应用以及了解其在实践中的应用案例等方面。以下是关于KubeEdge学习的详细步骤和要点&#xff1a; 理解KubeEdge&#xff1a; KubeEdge是一个开源的系统&#xff0c;它基于Kubernetes构建&#xff0c;旨在将本机容器化应…

vue3+Ts 关于生成环境与开发环境请求路径问题

这里我是创建了axios 实例&#xff0c;通过axios 实例去请求后端&#xff0c; // 创建axios实例 export const service:AxiosInstance axios.create({//请求地址baseURL: (window as any).Config.BACKEND_URL, //import.meta.env.VITE_APP_BASE_API, // 超时timeout: 10000 *…

3DMax文件打开跳出请求操作需要提升

解决方法如下 打开autoremove&#xff0c;点击扩展功能&#xff0c;点击管理员已经阻止运行此应用 提示修复成功后&#xff0c;重启电脑再尝试打开max文件。

保研笔试复习——nju

文章目录 一、单选计算机网络计算机组成原理数字逻辑电路数据结构操作系统微机系统 多选题计算机网络计算机系统结构操作系统 免责声明&#xff1a;题目源自于网络&#xff0c;侵删。 就在今天2024-5-18&#xff0c;考的题下面的只有一道AVL的原题&#xff0c;其他都不是原题&a…