开发者

c/c++中的左值右值详解

目录
  • 左值 (Lvalue)
  • 右值 (Rvalue)
  • 左值引用 (Lvalue Reference)
  • 右值引用 (Rvalue Reference)
  • 浅拷贝(Shallow Copy)
  • 深拷贝(deep copy)
    • 浅拷贝测试
    • 深拷贝测试
  • 再探右值引用
    • 调用测试
  • 总结

    左值 (Lvalue)

    定义:

    • 表达式结束后依然存在的持久对象。
    • 有名字、有持久性的表达式,它是既能够出现在等号左边,也能出现在等号右边的变量。

    右值 (Rvalue)

    定义:

    • 表达式结束后就不再存在的临时对象。
    • 临时的、将亡的值。一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。

    左值和右值主要的区别之一是左值可以被修改,而右值不能。

    int number;
    number = 1

    在这段代码中 number 为左值 ,1 为右值。

    // getValue()返回的临时值是右值
    int value = getValue();

    引用的本质是别名,可以通过引用改变变量的值,传参时传引用可以避免拷贝。

    左值引用 (Lvalue Reference)

    定义: 引用一个对象;

    基本用法:

        int number = 10;
        int& count = number;
        counijQagt++; // number = count = 11;
    • number 是左值,10 是右值
    • count 是左值引用,count 引www.devze.com用 number
    void addCount(int& count)
    {
        count++;
    }
    
    int number = 10;
    addCount(number);

    如果给“左值”引用直接赋“右值”,则会报错,例:

    int& number = 10;
    报错:
    错误 C2440	“初始化”: 无法从“int”转换为“int &”	
    

    const 左值引用不会修改指向值,因此可以指向右值

    例如:

    std::vector 中的方法 push_back
    
    // 如果没有 const 那么使用 v.push_back(10) 这样 10 的类型是右值,就无法通过编译了
    void push_back(const value_type& _Val)
    
    // 可以直接传递字面值
    v.push_back(10) 
    // 可以传递表达式结果
    v.push_back(x+y)

    右值引用 (Rvalue Reference)

    定义:就是必须绑定到右值的引用,它可以指向右值,不能指向左值。C++11中右值引用可以实现“移动语义”,通过 && 获得右值引用。

    右值引用绑定右值

     int&& number = 1;

    错误:右值引用不能直接绑定左值

        int count = 2;
        int&& number = count;
        报错:
        error C2440: “初始化”: 无法从“int”转换为“int &&”
        message : 无法将左值绑定到右值引用

    尝试让右值引用指向左值

        int count = 2;
        // 使用 std::move 把左值强制转换为右值 ,number = 2
        int&& number1 = std::move(count);
        // 等同与 std::move 类型转换 ,number = 2
        int&& number2 = static_cast<int&&>(count);
    	// count = number1 = number2 = 10
    	number1 = 10;

    简单练习:

    void test(int & o) {std::cout << "左值。" << std::endl;}
    void test(int && temp) {std::cout << "右值。" << std::endl;}
    
    int main(){
        int a;
    	int && b = 10; 
    
    	test(a);
    	test(std::move(a));
    	test(b);
    }
    结果:
    a 是一个具名变量,有固定的内存地址是典型的左值。输出:"左值。"
    std::move(a) 将左值 a 转换为右值引用 返回类型是 int&&。输出:"右值"
    虽然 b 的类型是右值引用(int&&)但 b 本身是一个具名变量,可以取地址。 输出:"左值"

    结论:右值引用类型只是用于匹配右值,而并非表示一个右值。因此,尽量不要声明右值引用类型的变量,而只在函数形参使用它以匹配右值。

    浅拷贝(Shallow Copy)

    浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存(分支)。

    • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
    • 如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

    深拷贝(deep copy)

    深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)

    • 拷贝第一层级的对象属性或数组元素
    • 递归拷贝所有层级的对象属性和数组元素
    • 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

    编写代码:

    #include <IOStream>
    
    class Vector {
        int num;
        int* a;
    public:
        // 构造函数
        Vector(int n = 0) : num(n) {
            a = new int[num];
            for (int i = 0; i < num; ++i) {
                a[i] = i + 1;  // 初始化为1,2,3...
            }
        }
    
        // 析构函数
        ~Vector() {
            delete[] a;
        }
    
        void ShallowCopy(Vector& v);
        void DeepCopy(Vector& v);
    
        // 打印数组内容的辅助函数
        void print() const {
            std::cout << "num = " << num << ", array content: ";
            for (int i = 0; i < num; ++i) {
                std::cout << a[i] << " ";
            }
            std::cout << std::endl;
        }
    
        // 修改数组内容的辅助函数
        void modify(int index, int value) {
            if (index >= 0 && index < num) {
                a[index] = value;
            }
        }
    };
    
    // 浅拷贝实现
    void Vector::ShallowCopy(Vector& v) {
        this->num = v.num;
        this->a = v.a;
    }
    
    // 深拷贝实现
    void Vector::DeepCopy(Vector& v) {
        delete[] this->a;  // 先释放原有内存
        this->num = v.num;
        this->a = new int[num];
        for (int i = 0; i < num; ++i) {
            a[i] = v.a[i];
        }
    }

    浅拷贝测试

    int main() {
        // 测试浅拷贝
        std::cout << "=== 测试浅拷贝 ===" << std::endl;
        {
            Vector v1(5);  // 创建一个包含5个元素的向量
            std::cout << "Original v1: ";
            v1.print();
    
            Vector v2(3);  // 创建另一个向量
            std::cout << "Original v2: ";
            v2.print();
    
            v2.ShallowCopy(v1);  // 浅拷贝
            std::cout << "After shallow copy, v2: ";
            v2.print();
    
            // 修改v1,观察v2是否变化
            v1.modify(0, 100);
            std::cout << "After modifying v1, v1: ";
            v1.print();
            std::cout << "After modifying v1, v2: ";
            v2.print();
    
            // 这里会崩溃,因为v1和v2都试图删除同一块内存
            std::cout << "Program will crash here due to double delete\n";
        }
        }
      程序输出:
      === 测试浅拷贝 ===
    Original v1: num = 5, array content: 1 2 3 4 5
    Original v2: num = 3, array content: 1 2 3
    After shallow copy, v2: num = 5, array content: 1 2 3 4 5
    After modifying v1, v1: num = 5, array content: 100 2 3 4 5
    After modifying v1, v2: num = 5, array content: 100 2 3 4 5
    Program will crash here due to double delete
    
    崩溃位置:
        // 析构函数
        ~Vector() {
            delete[] a;
        }
    

    深拷贝测试

    int main() {
    
    
        // 测试深拷贝
        std::cout << "\n=== 测试深拷贝 ===" << std::endl;
        {
            Vector v1(5);
            std::cout << "Original v1: ";
            v1.print();
    
            Vector v2(3);
            std::cout << "Original v2: ";
            v2.print();
    
            v2.DeepCopy(v1);  // 深拷贝
            std::cout << "After deep copy, v2: ";
            v2.print();
    
            // 修改v1,观察v2是否变化
            v1.modify(0, 100);
            std::cout << "After modifying v1, v1: ";
            v1.print();
            std::cout << "After modifying v1, v2:android ";
            v2.print();
    
            // 这里不会崩溃,因为每个对象管理自己的内存
            std::cout << "Program will exit normally\n";
        }
    
        return 0;
    } 
    
    === 测试深拷贝 ===
    Original v1: num = 5, array content: 1 2 3 4 5
    Original v2: num = 3, array content: 1 2 3
    After deep copy, v2: num = 5, array content: 1 2 3 4 5
    After modifying v1, v1: num = 5, array content: 100 2 3 4 5
    After modifying v1, v2: num = 5, array content: 1 2 3 4 5
    Program will exit normally

    再探右值引用

    使用 C++ 11 的右值引用,重载拷贝函数,代码修改为:​​​​​​​

    class Vector {
        int num;
        int* a;
    public:
        // 构造函数
        Vector(int n = 0) : num(n) {
            a = new int[num];
            for (int i = 0; i < num; ++i) {
                a[i] = i + 1;  // 初始化为1,2,3...
            }
        }
    
        // 析构函数
        ~Vector() {
            delete[] a;
        }
        
        //左值引用形参=>匹配左值
        void Copy(Vector& tmp) {
    
            if (nullptr != this->a){
          编程客栈      delete[] this->a;
            }
          
            this->num = tmp.num;
            this->a = tmp.a;
            
            // 防止tmp析构时删除内存
            tmp.a = nullptr;    
        }
     
       //右值引用形参=>匹配右值
        void Copy(Vector&& tmp) {
            this->num = tmp.num;
            this->a = tmp.a;
     python   }
    };

    调用测试

    int main() {
        // 测试左值引用版本
        Vector v1(5);        // v1: {1,2,3,4,5}
        Vector v2;
        v2.Copy(v1);        // 深拷贝:v2获得自己的内存副本
    
        // 测试右值引用版本
        Vector v3;
        v3.Copy(Vector(3)); // 移动:直接窃取临时对象的资源
    
        return 0;
    }

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜