IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++中类与对象(中) -> 正文阅读

[C++知识库]c++中类与对象(中)

今天继续学习类的相关知识;

之前学习了类的一些基本知识,比如类的定义,类的内存计算,this指针之类的;

今天我们就来学习一些深入的知识——类的默认成员函数

?接下来我们就来深入了解一下这几个函数吧。

目录

构造函数

构造函数的声明规则

默认构造函数定义

析构函数

析构函数的声明规则

析构函数针对的资源

拷贝构造函数

拷贝构造函数的声明规则

拷贝构造函数的定义

拷贝构造函数使用场景

运算符重载

运算符重载定义

运算符重载规则

运算符重载注意点


构造函数

构造函数并非创建一个变量,而是将对象初始化;

每一个默认成员函数都有自己独特的声明方式;

构造函数也有自己独特的规则;

构造函数的声明规则

1. 函数名与类名相同。
2. 无返回值
3. 对象实例化时编译器 自动调用 对应的构造函数。
4. 构造函数可以重载
5. 若是没有显式创建,编译器会自己创建,显示创建了就不会创建了;

为了更好的理解各个函数,我这里创建一个类类型 —— Date;

class Date{
private:
    int _year;
    int _month;
    int _day;
};

按照规则,类类型的构造函数应该怎么写?

按照规则来说,应该这样写:

public:
    Date(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

但是,实际上这样会报一个错误;

就像这样,这是为什么呢?

这是因为,d1被声明的时候就会调用构造函数,而我们这里没有给出初始化值;

而没有初始化值,那么编译器就会调用默认构造函数;

但是我们没有默认构造函数,因此就出错了;

而默认构造函数是什么呢?

默认构造函数定义

无参构造函数;
全缺省构造函数;
编译器默认生成的构造函数;

?那么想要解决这个问题应该怎么办呢?

1:给出初始值;

2:创建一个默认构造函数;

3: 给变量一个默认值;

给了初始值

?

创建默认构造函数

?给了默认值

?就像上面一样,有各种方法能够解决问题;

不过这里最推荐的还是创建一个默认构造函数

而且这个默认构造函数一定要是全缺省的

因为我们编译器的默认构造函数实际上只会给一个随机值

这些都是针对内置类型的构造函数,而若是我们的类的成员变量也是类类型的呢?

构造函数会怎样做?

?我们看到,Birth类里面的Date类型d的初始化已经完成了;

实际上,编译器对于自定义的类型,并不会随便就取初始化;

而是根据自定义类型里面的默认构造函数来初始化对象

析构函数

析构函数虽然听上去像是销毁变量,实际上它只是将对象内部的资源清理干净罢了;

析构函数的声明规则

1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数

?但是实际上,析构函数对于我们的Date类并没有作用;

就像上面说的,析构函数是将对象内部的资源清理掉而已;

而销毁对象这类事情实际上在对象的作用域结束后就自动清理了;

那么析构函数针对的资源是哪些呢?

析构函数针对的资源

1. 动态开辟的空间;

2. 文件流(fopen,fclose)

因此这里用Date类不好演示,最好还是用Stack类来演示;?

class Stack {
private:
    int* _arr;
    int _capacity;
    int _top;
public:
    Stack(int capacity = 4)
    {
        _capacity = 4;
        _arr = (int*)malloc(sizeof(int) * _capacity);
        if (_arr == nullptr)
        {
            perror("malloc fail\n");
            exit(-1);
        }
        _top = 0;
    }
    ~Stack()
    {
        free(_arr);
        _arr = nullptr;
        _capacity = 0;
        _top = 0;
    }
    void Push(int data)
    {
        _arr[_top++] = data;
        if (_top == _capacity)
        {
            _capacity *= 2;
            int* tmp = (int*)realloc(_arr,sizeof(int) * _capacity);
            if (tmp == nullptr)
            {
                perror("realloc fail");
                exit(-1);
            }
            _arr = tmp;
            tmp = nullptr;
        }
    }

};

我们创建好这个类后,在里面插入几个数据,接下来看看析构函数会怎么做吧;

?我们可以看到,析构函数将对应的_arr的空间给释放了,并且将指针置空;

这样就不会有内存泄漏的问题了;

而析构函数还有一个特性,和构造函数一样;

若是类类型内部有一个其它类类型的变量;

那么编译器只会调用其它对应类的析构函数来处理空间;

而析构函数其实并不是全部类都一定要写;

一般只有像栈这种需要动态开辟空间之类的才会需要;

拷贝构造函数

拷贝构造函数实际上是构造函数的重载;

在对象初始化的时候用另一个对象的数据构造;

拷贝构造函数的声明规则

1. 拷贝构造函数 是构造函数的一个重载形式
2. 拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 , 因为会引发无穷递归调用。
3. 若是未显式定义,则编译器会默认生成一个拷贝函数,按字节序拷贝,这种拷贝方式又叫浅拷贝或者值拷贝

拷贝构造函数的定义

用Date类直接实现是这样的:

Date(Date& d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }

?我们可以看到d2成功的复制了d1的值;

但是这只是浅复制,并且实际上我们不写这个拷贝构造函数编译器也能完成;

但是既然编译器能够完成这个工作,那么显式构造的意义何在?

我们先用Stack类来实践一下;

?我们发现,编译器默认构造的虽然完成了工作,但是p2中的指针的地址却是一样的;

因此显式构造一定是有必要的,若是类似这种需要开辟空间的类;

那么显式创建一个拷贝构造函数是必要的;

Stack(Stack& d)
      {
          _capacity = d._capacity;
          _top = d._top;
          _arr = (int*)malloc(sizeof(int) * _capacity);
          int i = 0;
          for (i = 0; i < _top; i++)
          {
              _arr[i] = d._arr[i];
          }
      }

此外,既然拷贝构造函数是构造函数的一种重载,那么拷贝构造函数应该和构造函数有一样的特性

那就是类中有类变量的时候,拷贝构造函数会不会调用对应类的拷贝构造函数呢?

答案是肯定的,但是此处的知识点现在不好说明,只能先卖个关子了;

拷贝构造函数使用场景

使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象

实际上当我们用类类型变量作为实参传给函数;

或者说返回值为类类型的时候,编译器都会调用该函数;

因此这个函数还是比较重要的;

运算符重载

在了解复制重载之前我们需要先了解运算符重载;

我们直到,在初始化对象的时候,我们可以调用拷贝构造函数来实现复制一个对象的操作;

但是考虑到代码可读性,c++又引进了运算符重载;

实际上运算符重载算是一种特殊函数,其函数名是各种符号;

那么运算符重载该怎么做呢?

运算符重载定义

<返回值类型> operator <操作符> (参数列表)

按照上面的定义,我们可以重载不一样的运算符;

但是运算符重载也是有自己独特的规则需要遵守;

运算符重载规则

1.不能通过连接其他符号来创建新的操作符:比如 operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐
藏的this
5.? .*? ::? sizeof? ?:? .?? 注意以上 5个运算符不能重载。

虽然我们知道了以上规则,但是还有几个需要注意的点;

运算符重载注意点

1. 赋值运算符重载格式
参数类型 const T& ,传递引用可以提高传参效率
返回值类型 T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 *this :要复合连续赋值的含义
2.? 赋值运算符只能重载成类的成员函数不能重载成全局函数
3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

而拷贝构造也是有显式和隐式之分,其中编译器自己定义的是逐字节的拷贝;

因此当我们给Stack之类的类对象使用运算符重载时,就需要自己定义了;

此处我们先直接看看Date类的运算符中的拷贝复制;

而若是Stack类则需要自己定义了;

Stack& operator = (Stack& d)
    {
        _capacity = d._capacity;
        _top = d._top;
        int* tmp = (int*)malloc(sizeof(int) * _capacity);
        if (tmp == nullptr)
        {
            perror("malloc failed!");
            exit(-1);
        }
        _arr = tmp;
        for (int i = 0; i < _top; i++)
        {
            _arr[i] = d._arr[i];
        }
    }

我们看到这样d1和d2的_arr指针就不会互相干扰;

而我们运算符重载并不是只有拷贝复制这一操作;

我们可以通过运算符重载,实现类对象的+,-,*,÷等操作;

不过那都是后话了;

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:13:43  更:2022-10-17 12:17:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 6:20:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码