类和对象---拷贝构造函数和运算符重载

简介: 类和对象---拷贝构造函数和运算符重载

拷贝构造函数

在实际代码运用中,我们经常会拷贝一个对象用来做其他事情,在C语言中,这个过程十分简单,把值直接全部从一个内容中拷贝到另外一个地方即可,但在C++中却不那么容易

拷贝构造函数的引入

首先要清楚堆创建后,除非通过free否则是不会被还原的,因此如果有这样的C语言代码:

void push(Stack ps)
{
   
   
    ps._a[0] = 10;
    ps._top++;
}

void test2()
{
   
   
    Stack s={
   
   0,0,0};
    StackInit(&s);
    push(s);
    printf("%d", s._a[0]);
}

这里的StackInit函数只是单纯的初始化,给栈开辟空间,而最后运行结果是10,原因就在于在push函数中,虽然是值传递,但是ps结构体中的成员_a依旧拥有改变堆内存的能力,具体可以用下面的图来表示

在这里插入图片描述
那么现在换到C++,引入类的概念后,整个就变得比C语言要复杂一点,原因如下:

首先,定义一个栈的类,并且完成一系列栈的操作

typedef int STDataType;
class Stack
{
   
   
public:
    Stack()
    {
   
   
        capacity = 4;
        a = (STDataType*)malloc(sizeof(STDataType) * capacity);
        if (a == nullptr)
        {
   
   
            perror("malloc fail");
            exit(-1);
        }
        top = 0;
    }
    ~Stack()
    {
   
   
        top = capacity = 0;
        free(a);
        a = nullptr;
    }
    void Push(STDataType x)
    {
   
   
        if (capacity == top)
        {
   
   
            capacity *= 2;
            STDataType* tmp = nullptr;
            tmp = (STDataType*)realloc(a,sizeof(STDataType) * capacity);
            if (tmp == nullptr)
            {
   
   
                perror("realloc fail");
                exit(-1);
            }
            a = tmp;
        }
        a[top] = x;
        top++;
    }
    void Pop()
    {
   
   
        top--;
    }
    STDataType Top()
    {
   
   
        return a[top];
    }
private:
    STDataType* a;
    int top;
    int capacity;
};

和C语言的实现相同,假如我们直接进行传值拷贝,具体做法如下:

void func1(Stack s)
{
   
   
    s.Push(1);
}

int main()
{
   
   
    Stack s1;
    func1(s1);
    return 0;
}

再画出和上面相仿的图

在这里插入图片描述
看似和C语言基本相同,但实际相差很大,C++会执行构造函数和析构函数,那么在进入func1的栈帧后,销毁栈帧的时候就会执行析构函数,_a所指向的空间就被销毁掉了,那么回到main函数的栈帧后,结束程序依旧要进行析构函数,此时_a已经被销毁过一次了,程序就会崩溃,无法正常运行

这其实也就说明,C++中想要直接进行对象拷贝似乎不是一件容易的事,两个对象指向同一片空间就必然会出问题,C++语法就定义了拷贝函数来解决这个问题

拷贝构造函数的特征

拷贝构造函数也是特殊的成员函数,具体表现在:

  1. 拷贝函数是构造函数的一个重载
  2. 拷贝函数的参数只有一个并且必须是类类型对象的引用,使用传值方式编译器会报错,因为涉及到了无穷递归调用
  3. 若未显式定义,编译器会生成默认的拷贝构造函数,默认拷贝构造函数会按对象按内存中的存储字节序完成拷贝,也叫做浅拷贝或值拷贝
  4. 深拷贝就涉及到上面栈在堆上空间的问题
  5. 拷贝构造函数的典型应用场景

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

==下面根据拷贝构造函数的特征进行一一分析==

1. 拷贝构造函数是构造函数的重载

这个很好解释,拷贝构造函数函数名和构造函数相同,只是函数参数不同

2. 拷贝函数的参数只有一个并且必须是类类型对象的引用,使用传值方式编译器会报错,因为涉及到了无穷递归调用

假设我们这里是这样实现拷贝构造函数:

//函数定义
Date(const Date d1)
{
   
   
    _day = d1._day;
    _month = d1._month;
    _year = d1._year;
}
//函数调用
Date d1(d2);

在这里插入图片描述
那么标准写法是如何写的呢

//函数定义
Date(const Date& d1)
{
   
   
    _day = d1._day;
    _month = d1._month;
    _year = d1._year;
}
//函数调用
void func2(Date d2)
{
   
   
    d2.Print();
}

int main()
{
   
   
    Date d1(2002, 10, 12);
    d1.Print();
    func2(d1);
    return 0;
}

**3. 若未显式定义,编译器会生成默认的拷贝构造函数,默认拷贝构造函数会按对象按内存中的存储字节序完成拷贝,也叫做浅拷贝或值拷贝

  1. 深拷贝就涉及到上面栈在堆上空间的问题**

这里需要注意的是:

不写拷贝构造函数时,编译默认生成的拷贝构造,和之前的构造函数特性是不一样的

  1. 内置类型是值拷贝
  2. 自定义的类型是调用它的拷贝

简单来说,像Date类型的就不需要我们进行拷贝构造,但是Stack类型的就需要进行深拷贝

下面是深拷贝的拷贝构造函数

Stack(const Stack& s1)
{
   
   
    top = s1.top;
    capacity = s1.capacity;
    STDataType* tmp = (STDataType*)malloc(sizeof(STDataType) * capacity);
    if (tmp == nullptr)
    {
   
   
        perror("malloc fail");
        exit(-1);
    }
    a = tmp;
}

所谓深拷贝,就是重新在堆上开辟一个空间供构造出的栈使用,这样就避免了函数栈帧中的栈在结束时free掉了堆上的空间使得main函数崩溃的情况出现

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

运算符重载

C++在C的基础上的提升在运算符重载上也可以体现出

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

假设我们现在要比较日期谁大,那么这个场景就可以应用运算符重载

// 类体内定义运算符重载
bool operator <(const Date& d1)
{
   
   
    if (_year > d1._year)
    {
   
   
        return false;
    }
    else if (_year == d1._year && _month > d1._month)
    {
   
   
        return false;
    }
    else if (_year == d1._year && _month == d1._month && _day > d1._day)
    {
   
   
        return false;
    }
    else
    {
   
   
        return true;
    }
}

// 调用main函数
int main()
{
   
   
    int i = 10;
    int j = 20;
    int tmp1 = i < j;
    Date d1(2000, 10, 20);
    Date d2(2001, 8, 10);
    int tmp2 = d1 < d2;
    cout << tmp1 << endl;
    cout << tmp2 << endl;
}

我们转到汇编观察

在这里插入图片描述
从中也不难发现,运算符重载后调用小于实际上是调用了运算符重载函数

这样写代码的可读性大大提高

相关文章
|
8天前
|
人工智能 运维 安全
|
6天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
B站开源IndexTTS2,用极致表现力颠覆听觉体验
在语音合成技术不断演进的背景下,早期版本的IndexTTS虽然在多场景应用中展现出良好的表现,但在情感表达的细腻度与时长控制的精准性方面仍存在提升空间。为了解决这些问题,并进一步推动零样本语音合成在实际场景中的落地能力,B站语音团队对模型架构与训练策略进行了深度优化,推出了全新一代语音合成模型——IndexTTS2 。
638 22
|
7天前
|
人工智能 测试技术 API
智能体(AI Agent)搭建全攻略:从概念到实践的终极指南
在人工智能浪潮中,智能体(AI Agent)正成为变革性技术。它们具备自主决策、环境感知、任务执行等能力,广泛应用于日常任务与商业流程。本文详解智能体概念、架构及七步搭建指南,助你打造专属智能体,迎接智能自动化新时代。
|
13天前
|
人工智能 JavaScript 测试技术
Qwen3-Coder入门教程|10分钟搞定安装配置
Qwen3-Coder 挑战赛简介:无论你是编程小白还是办公达人,都能通过本教程快速上手 Qwen-Code CLI,利用 AI 轻松实现代码编写、文档处理等任务。内容涵盖 API 配置、CLI 安装及多种实用案例,助你提升效率,体验智能编码的乐趣。
1035 110
人工智能 数据可视化 数据挖掘
227 0