首页 > 编程语言 >C++中&和&&的相关笔记

C++中&和&&的相关笔记

时间:2023-03-22 15:11:07浏览次数:39  
标签:右值 对象 rhs 笔记 C++ && 拷贝 构造函数 赋值

目录

1. 引言

C++中&有三种用途,而&&有两种用途

2. &的作用

2.1 位运算

C++中的位运算十分高效,数据分段时经常用到!

例如,统计一个数字中有多少位是1的个数,代码如下:

int count(int x) {
    int res = 0;
    while(x) {
        if (x & 1)
            res++;
        x = x >> 1;
     }
     
     return res;
}

获取n的第k位数字:

n >> k & 1;

返回n的最后一位1:

lowbit(n) = n & -n;

2.2 取地址

&可以用于获取函数、变量地址等;

int a = 10;
int *pa = &a; // pa指针指向a的地址

void isEqual(int a, int b){
    return a == b;
}

void (*func)(int, int);

func pfun = &isEqual;// 声明和初始化函数指针地址

2.3 引用

常用于函数传参,临时变量引用等

避免引起拷贝

std::vector<std::vector<int> > vec(10);

auto &pv = vec[0];// 引用数组的值,避免复制

3. &&的用途

3.1 逻辑运算符AND

用于条件判断

3.2 右值引用

C++11之后才有的效果,即移动语义

3.2.1 背景知识

函数的返回值是一个对象的话,则可能需要对函数中的局部对象进行拷贝。若对象很大,则会影响程序的效率。那么如何避免对象返回的拷贝呢?

拷贝构造函数的执行时机:

  1. 使用一个对象给另一个对象进行初始化
  2. 对象为函数返回值,以值的方式返回;
  3. 对象作为函数参数,以值传递方式传递给函数;

#include <iostream>
using namespace std;
/*
注意点:
有些编译器出于程序执行效率的考虑,编译的时候进行了优化,
    函数返回值对象就不用复制构造函数初始化了,但这并不符合 C++ 的标准
*/
class A {
public:
    A();
    A(char _c): c(_c) {cout << "构造函数执行" << endl;}
    A(const A& rhs){
        cout << " 拷贝构造函数执行" << endl;
        c = rhs.c;
    }
    virtual ~A() {
        cout << "  析构函数执行" << endl;
    }
    A clone() const{
        A a = *this; // 拷贝构造函数
        return a;//  析构函数,拷贝构造函数
    }
private:
    char c;
};
int main() {
    A a = A('b');// 构造函数,=
    a.clone();
    return 0;
}

程序输出结果:

# g++ 10.0+
构造函数执行
 拷贝构造函数执行
  析构函数执行
  析构函数执行
  
# vc++ 
构造函数执行
 拷贝构造函数执行
 拷贝构造函数执行
  析构函数执行
  析构函数执行

有些编译器出于程序执行效率的考虑,编译的时候进行了优化函数返回值对象就不用复制构造函数初始化了,但这并不符合 C++ 的标准

拷贝构造函数和赋值运算符的联系和区别:

  1. 两者都可以将一个对象的值复制给另外一个对象;
  2. 拷贝构造函数使用传入的对象生成一个新对象;而赋值运算则只是将值复制给另外一个对象,没有新对象的产生;
  3. 判断依据:看是否有新的对象生成

3.2.2 左值和右值

C++如何判断左值还是右值:

  1. 左值一般是可寻址的变量,右值一般不可寻址

    左值即我们之前使用那些变量,而右值则是一些常量或无名临时对象

  2. 左值有持久性,右值有短暂性

  3. 左值符号&,右值符号&&

3.2.3 移动构造函数和移动赋值函数

移动和拷贝是相对的,移动类似于文件移动,从一个位置移到另外一个位置,拷贝则是数据的赋值,类似于文件复制

实现对象复制可以使用拷贝构造函数和赋值运算符;

实现转移语义,则可以定义转移构造函数转移赋值操作符

对于右值的拷贝和赋值会调用转移构造函数和转移操作符,若转移构造函数和转移操作符,则拷贝函数或赋值运算将会被调用;

移动构造函数的构建
对于类A,定义如下:

class A{
public:
    explicit A(size_t len): _length(len), _data(new int[len]) {
        std::cout << "构造函数" << std::endl;
    }
    
    ~A() {
        std::cout << "析构函数" << std::endl;
    }
    
    A(const A& other): _length(other._length), _data(new int[other._length]) {
        std::cout << "拷贝构造函数" << std::endl;
        std::copy(other._data, other._data + _length, _data);
    }
    
    // 赋值运算
    A & operator=(const A& other) {
        std::cout << "赋值运算=" << std::endl;
        
        if (this != &other) {
            delete[ ]_data;
            
            _length = other._length;
            _data = new int[_length];
            std::copy(other._data, other._data + _length, _data);
        }
        
        return *this;
    }
    
    int length() const {
        return _length;
    }

private:
    size_t _length;
    int *_data;
};

为上述的类添加移动构造函数

  1. 定义一个空的构造函数方法,参数为一个对类类型的右值引用
  2. 在移动构造函数中,将源对象的类数据成员添加到构造对象中;
  3. 将源对象的数据成员恢复默认值,防止析构函数多次释放资源
// 1 空的构造函数
A(const A&& rhs) : _length(0), _data(nullptr) {
    // 2 数据成员赋值
    _data = rhs._data;
    _length= rhs._length;
    
    // 3 设置默认值,防止多次析构
    rhs._length = 0;
    rhs._data = nullptr;
}

添加移动赋值运算:

  1. 定义一个空的赋值运算符,该运算符的参数为一个右值引用
  2. 避免将对象赋值给自身;
  3. 条件语句中,要将其赋值的对象中释放所有内存然后执行移动构造函数中的步骤2和3
  4. 返回当前对象的引用;
// 1
A & operator=(const A&& rhs) {
    if (*this != rhs) {
        delete[] _data;// 2
        
        _data = rhs._data, _length = rhs._length; // 3
        
        rhs._data = nullptr, rhs._length = 0; // 3
    }
    
    return *this;//4
}

注意:

如果为你的类同时提供了移动构造函数和移动赋值运算符,则可以编写移动构造函数来调用移动赋值运算符,从而消除冗余代码。

// 移动构造函数
A(A&& other) noexcept
   : _data(nullptr)
   , _length(0)
{
   *this = std::move(other);
}

std::move() 将左值转换为右值

标签:右值,对象,rhs,笔记,C++,&&,拷贝,构造函数,赋值
From: https://www.cnblogs.com/zuixime0515/p/17244022.html

相关文章

  • DevOps实战笔记-DevOps平台设计和开发
    DevOps实战笔记-DevOps平台设计和开发1简介1.1DevOps概述最初是瀑布模型,后来是敏捷开发,现在是DevOps,这是现代开发人员构建出色的产品的技术路线。DevOps早在20......
  • js笔记
    forEachmap对原函数的影响arr1=[{a:1},{a:2},{a:3},{a:4}];arr2=[1,2,3,4];//forEach没有返回值arr1.forEach(i=>{//引用类型整个赋值不变i=i.a......
  • css笔记
    水平布局这段时间编写静态页面,学习了几种水平布局,其中有不小的差异和注意事项,特此记录汇总一下。1.display:inline-block将元素修改为行内块元素,可以在不脱离文档流的......
  • 【数据结构】数组与广义表 - 笔记
    数组与广义表的一章相对更为简单,第1,2节都是很熟悉的数组相关定义、实现等。因此这篇博客的讲述重点放在第3节“特殊矩阵的压缩存储”中的“稀疏矩阵”的存储以及第4节“......
  • C++调试方法总结(VS Code & VS & dbg)
    一、VSCodeC++程序调试1.1配置C++运行环境安装C/C++插件后打开C++设置界面:选择编译器、c和c++标准,以及inteliSenseMode之后会在当前.vscode目录下生成一个c_cpp_......
  • 最小割树学习笔记
    前言最小割树(Gomory-HuTree)通过分治的思想,将图中的最小割关系建成一棵带权了树上问题。它的主要用途是求解全源最小割/最大流。前置知识:一种快速的最大流算法(Dinic/......
  • C/C++手机通信录[2023-03-22]
    C/C++手机通信录[2023-03-22]程序设计题目5:手机通信录【问题描述】用C/C++设计出模拟手机通信系统,能实现对手机中的通信录进行添加、修改、查询等功能。【基本要求】......
  • 数据结构笔记1 绪论 概念
    最近这一段时间在学习数据结构。感觉还是很值得的。有老大的话说就是这次投资成功了。开始决定学习的时候买了一本书《数据结构(C语言版)》相信大家都看过吧。是严蔚敏老师......
  • 数据结构笔记4 栈
    栈的定义和概念栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。(1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。(2)当表中没有元素......
  • 使用Jieba分词学习PaddleNLP(学习笔记)
    最近疫情肆虐,实现了我在家办公的愿望,也有更多的时间学习了,于是我参加百度深度学习集训营,刚刚接触新领域,以下是我整理的学习笔记,与大家分享:首先是此次的作业帖:​​h......