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

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

拷贝构造函数

在实际代码运用中,我们经常会拷贝一个对象用来做其他事情,在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;
}

我们转到汇编观察

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

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

相关文章
|
9月前
|
存储 编译器 C++
C++类与对象 - 3(拷贝构造函数和运算符重载)(超详细)(上)
C++类与对象 - 3(拷贝构造函数和运算符重载)(超详细)
36 0
|
3月前
|
编译器 C++ 索引
【C++类和对象】拷贝构造与赋值运算符重载(下)
【C++类和对象】拷贝构造与赋值运算符重载
|
3月前
|
存储 编译器 C++
【C++类和对象】拷贝构造与赋值运算符重载(上)
【C++类和对象】拷贝构造与赋值运算符重载
|
存储 编译器 C++
【C++初阶】类与对象:6大默认成员函数------拷贝构造和赋值运算符重载
【C++初阶】类与对象:6大默认成员函数------拷贝构造和赋值运算符重载
45 0
【C++初阶】类与对象:6大默认成员函数------拷贝构造和赋值运算符重载
|
3月前
|
C++
C++——类和对象之拷贝构造
C++——类和对象之拷贝构造
|
编译器 C++
【C++基础(六)】类和对象(中) --拷贝构造,运算符重载(下)
【C++基础(六)】类和对象(中) --拷贝构造,运算符重载(下)
|
3月前
|
存储 安全 编译器
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)
这一篇文章是上一篇的续集(这里有上篇链接)前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数。也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点——类和对象(拷贝构造函数、赋值运算符重载、const成员、取地址及const取地址操作符重载)。
72 0
|
9月前
|
编译器 C++
C++类与对象 - 3(拷贝构造函数和运算符重载)(超详细)(下)
C++类与对象 - 3(拷贝构造函数和运算符重载)(超详细)
46 0
|
算法 C++ 编译器
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)下
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)下
84 0
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)下
|
C语言 C++ 编译器
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)上
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)上
89 0
【C++入门到精通】C++入门 —— 类和对象(拷贝构造函数、赋值运算符重载、const成员函数)上