C++之拷贝构造、拷贝赋值

本文详细介绍了C++中的拷贝构造函数和拷贝赋值操作,包括它们的本质、使用时机、调用语法及何时需要自定义。此外,还讨论了深拷贝和浅拷贝的概念,并探讨了实现简单string类和单例模式时需要注意的拷贝构造和赋值行为。最后,提到了C++11引入的移动构造和移动赋值,以及它们在效率上的优势。

拷贝构造

class Stu{
public:
    int no;
    string name;
    int age;
public:
    Stu(int no=10086, string name="jin", int age = 18):no(no), name(name), age(age){}
	//拷贝构造函数:由同类型的对象构造一个新的副本对象
    Stu(const Stu &s){
		no = s.no;
        name = s.name;
        agr = s.age
    }
};
int main()
{
	Stu s1 = (10010, "jink", 100);
    
    //拷贝构造,但是调用的并不是Stu这个构造函数,而是拷贝构造函数
    Stu s2(s1);
}
定义

由已经存在的同类型对象构造一个新的副本对象,调用的是拷贝构造函数、

  • 拷贝构造函数的形式是固定不变的:

    class 类名{
        类名(const 类名& 对象){
    		
        }
    };
    
本质

拷贝构造函数也是构造函数,是一个特殊

  • 特殊在参数是一个同类型的对象引用

如果在实现一个类时 没有为这个类添加拷贝构造函数,则编译器会提供一个默认的

默认的拷贝构造函数是:全员复制(逐字节拷贝)副本和元对象一摸一样

程序员可以俺自己的需求构造拷贝构造函数,构造后编译器不再提供默认拷贝构造函数

使用时机
  • 非引用传递对象
  • 放入容器的对象都是通过拷贝构造函数
  • 用存在的对象构造新的同类型的对象
调用语法
  • 类名 对象1 = 对象2;
  • 类名 新对象(老对象);
非默认拷贝构造函数的使用时机
  • 如果需要实现深拷贝时则需要自己实现拷贝构造函数 默认的拷贝构造函数是浅拷贝
  • 有指针指向动态内存时一般需要实现拷贝构造函数
  • 浅拷贝——按照字节拷贝 按字节复制 拷贝对象和原对象一模一样
    • 对于指针,只拷贝内存地址
  • 深拷贝——对于普通数据而言没有深拷贝一说,拷贝指针所指的内容
    • 对于指针,重新申请内存,把原指针指向的内容拷贝的新的内存空间中

所以当有指针指向动态内存时,一般需要使用独立构造的深拷贝

class Ptr{
private:
    int *ptr;
public:
    Ptr(const Ptr &pp){}
}

拷贝赋值函数

拷贝赋值的时点

当两个已经存在的同类型对象之间的相互赋值 ,调用的就是拷贝赋值

拷贝赋值函数形如:

class 类名{
	类名& operator(const 类名 &对象名){}
}

如果一个类没有实现拷贝赋值函数,编译器提供默认的拷贝赋值函数,且为浅拷贝

若需要实现深拷贝,则需要程序员手动提供

一般来说:遵循三/五原则

  • C++11之前为三:析构,拷贝构造,拷贝赋值,要么全部自己提供 ,要么全用默认
  • C++11之后为五:析构,拷贝构造, 拷贝赋值,移动构造, 移动赋值

实现简单的string类

class String{
public:
    //用C风格的字符串构造 String对象
    String(const char *s = NULL):
    str(strcpy(new char[s?strlen(s)+1:1, s?s:"")){}
    ~String(void){
        if(str != nullptr){
            delete[] s;
            str = nullptr;
        }
    }
	//拷贝构造函数   用同类型的对象拷贝构造一个同类型的副本对象
    String (const String &s):
        str(strcpy(new char[strlen(s.str)+1, s.str){}
    String &operator=(const String& ss){
        if(this != s){
            String _tmp(s);
            swap(str,_tmp.str);
	//看不到释放内存  但它有释放  delete [] _tmp.str
	//_tmp对象生命周期在语句块结束之后 到期  自动调用析构 
        }
        return *this;
    } 
    size_t length(void)const{
        return strlen(str);
    }
    const String *c_str(void)const{
        return str;
    }
prevate:
    char *str;
};

封装一个栈以及简单测试:

#include <bits/stdc++.h>
using namespace std;

class Stack{
public:
    Stack(int cap, int sizes = 0):
        cap(cap){
            elems = new int[cap];
        }
        //elems(new int[cap])
    ~Stack(void){
        if(sizes != 0){
            delete[] elems;
            elems = nullptr;
        }
    }
    Stack(Stack &stack){
        cap = stack.capacity();
        sizes = stack.size();
        int *Elem =  new int[stack.cap];
        for(int i = 0; i < stack.size(); i++){
            Elem[i] = stack.elems[i];
        }
    }
    Stack &operator = (Stack &stack){
        if(this != &stack){
            Stack _tmp(stack);
            swap(elems, _tmp.elems);
            cap = stack.capacity();
            sizes = stack.size();
        }
        return *this;
    }
    void push(int elem){
        if(full()){
            throw out_of_range("full");//抛出异常<stdexcept>
        }
        elems[sizes++] = elem;
    }
    int pop(void){
        if(empty()){
            throw out_of_range("empty");
        }
        return elems[--sizes];
    }
    bool full(){
        return cap == sizes;
    }
    bool empty(){
        return sizes == 0;
    } 
    int top(void){
        if(empty()){
            throw out_of_range("empty");
        }
        return elems[sizes-1];
    }
    int size(void){
        return sizes;
    }
    int capacity(void){
        return cap;
    }
private:
    int cap;
    int sizes;
    int *elems;

};

int main()
{
    Stack stack(10);
    for(int i = 0; i < 10; i++){
        stack.push(i);
    }
    for(int i = 0; i < 10; i++){
        cout<<stack.pop()<<endl;
    }
}
默认的无参构造
  • 默认无参构造:在一个类没有实现构造函数到时候编译器自动提供的,
    • 如果需要显式调用有参构造函数,需要提供成员名和实参
  • 默认拷贝构造函数:会在初始化列表中调用类类型成员的拷贝构造函数
    • 需要实现对成员的拷贝
  • 默认拷贝赋值函数:会在函数体中调用类类型的成员的拷贝赋值函数
    • 需要实现对成员的拷贝
  • 默认析构函数:会自动调用类类型 的成员的析构函数

对一个空类至少有以下几个成员函数

  1. 无参构造
  2. 拷贝构造
  3. 拷贝赋值
  4. 析构函数
  5. & 取值函数 T* operator&(void)
  6. & 常取址函数 const T* operator&(void)const
  • 如果在一个类中只提供拷贝构造函数,则其只有拷贝构造函数一个成员函数

移动构造

class 类名{
public:
    类名(类名&& obj){
        
    }
};
  • 用一个存在的对象来构造一个同类型新的对象,但是旧的对象是马上就会消亡的,为了效率着想,就没有必要调用拷贝构造(造成资源浪费,复制一份,析构一份),这个时候就可以选择移动构造,把旧对象的资源移动(赠予)新的对象,需要保证旧的对象能够正常析构

  • 旧对象不能再使用了

移动赋值

  • 用一个存在的对象给另外一个存在的对象的进行赋值,正常情况下,被赋值的资源应该析构,然后把另外一个对象的资源拷贝一份给被赋值的对象,但是如果=右边的对象马上就会消亡时,为了效率着想,就没有必要拷贝一份给被赋值的对象,直接把资源交给(转移给)被赋值的对象
  • =右边的对象不能再使用了
class 类名{
public:
    类名& operator=(类名&& obj){
        
    }
};

单例模式

模式:总结出的开发套路,能减少代码重复度,提高代码可靠性和效率,且实现特定的功能

单例模式:

定义:一个类只能创建一个对象

  • 懒汉模式
    • 不到万不得已不会动,只有饿了才会找吃的
    • 单例模式的对象只有有需求时才会创建
class SingleTon{
public:
    static SingleTon* getInstance(void){
   		if(ps == nullptr){
			ps = new SingleTon();
        }
        return ps;
    }
    void realse(void){
		--cnt;
        if(cnt == 0){
            delete ps;
            ps = nullptr;
        }
    }
    ~SingleTon(){
        
    }
private:
    int no;
    string name;
private:
    SingleTon(){}
    SingleTon(const SingleTon& s){}
    
    static SingleTon *ps;//指向唯一实例的指针
    static int cnt;
};
SingleTon SingleTon::ps = nullptr;
int SingleTon::cnt = 0;
int main()
{
	SingleTon *p1 = SingleTon::getIntance();
}

优缺点

  • 只有有需求时才会创建,节省内存空间,用完了可以及时释放
  • 线程不安全 两个线程同时去创建可能会创建多份实例,所以需要考虑线程同步,线程同步效率变低
  • 饿汉模式
    • 很饿,需要时刻准备吃的
    • 该模式下单例模式对象一直存在,无论是否有需求
class SingleTon{
public:
    static SingleTon* getInstance(void){
        return s;
    }
    ~SingleTon(){}
private:
    int no;
    string name;
    static SingleTon s;
private:
    SingleTon(){}
    SingleTon(const SingleTon& s){}
    
    static SingleTon s;//唯一实例
};
SingleTon SingleTon::s;

优缺点

  • 不管是否有需求,一直占用内存,浪费内存空间
  • 饿汉模式是在程序加载阶段就去实例,能够保证只有一个线程,线程安全的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值