查漏补缺——【C++基础篇】

C++中,值传递和引用传递的区别?

类似于Python中的深拷贝和浅拷贝

  • 值传递:在函数调用时,会触发一次参数的拷贝动作,所以对参数的修改不会影响原始的值。如果是较大的对象,复制整个对象,效率较低。
  • 引用传递:函数调用时,函数接收的就是参数的引用,不会触发参数的拷贝动作,效率较高,但对参数的修改会直接作用于原始的值。
void modify_value(int value){ value = 1; }  # 值传递
void modify_value(int& value){ value = 1; }  # 引用传递

什么场景下使用引用传递?

  • 避免不必要的数据拷贝:对于比较大的对象参数(比如std:vector、std:string.、std:list),因为拷贝,值传递会导致大量的内存和事件开销。而引用传递可以避免这些开销。
  • 允许函数修改实参原始值:有时候,我们就是希望函数能够直接修改传入的变量值,这时使用引用传递很合理。

什么场景下使用值传递?

  • 小型数据结构:对于int、char、double、float这种基础数据类型,可以直接简单的使用值传递;
  • 不希望函数修改实参:有时候需要修改变量数据,但是又不希望修改原始值,可以考虑使用值传递。

C++和C的区别?

1)面向对象还是面向过程:

  • C语言是一门面向过程的语言,侧重于通过过程(函数)来解决问题。
  • C++是一门多范式语言,主要支持面向对象,侧重于使用类和对象来组织代码。

2)继承:

  • C++支持继承,允许一个子类继承一个或多个父类,达到代码复用的目的。
  • C语言中没有继承的概念。

3)函数重载:

  • C++支持函数通过参数名和参数个数的重载。
  • C语言不支持重载,函数名必须唯一才行。

4)模板:

  • C++支持模板,支持静态和动态形式的多态。
  • C语言对此都不支持。

5)内存管理:

  • C++使用newdelete操作符来管理内存,也支持使用智能指针来动态管理内存。
  • C语言需要使用mallocfree来申请和释放内存。

6)标准库:

  • C++的STL标准库能力比C语言丰富的多,比如vector、.string、Iist、map等等,还有很多算法相关的能力,这些C语言都没有。

扩展知识

  • 1)可以理解为C++是C语言的超集,绝大多数的C代码可以直接在C++中使用
  • 2)C语言更加轻量级和跨平台。C语言的编译器和运行环境相对简单,占用的资源较少,可以在各种操作系统和硬件设备上运行。
  • 3)性能层面,C语言的标准库也比较小巧,只包含了一些基本的函数和数据结构。而C++的编译器和运行环境相对复杂,占用的资源较多,需要支持面向对象和模板等特性。C++的标准库也比较庞大,包含了许多高级的容器、算法、字符串、输入输出等功能。
  • 4)C语言更加灵活和自由,可以直接操作内存和指针,可以使用宏和预处理指令,可以调用汇编代码等。这些特性使得C语言可以实现一些底层和高效的操作,也方便了与硬件设备的交互。而C++则对程序员施加了更多的约束,例如不允许隐式类型转换、不允许指针运算、不允许多重继承等。这些约束是为了保证程序的安全性和可维护性,但也牺牲了一些灵活性和自由度。
  • 5)C语言更加稳定和成熟,语法和标准也比较稳定,不会经常变动。而C++每隔几年就会出现新的标准和特性。这些变化虽然增加了C++的功能和表达力,但也增加了学习和使用的难度,也可能导致一些兼容性和稳定性的问题。

什么是C++的左值和右值?有什么区别?

  • 左值:可以出现在赋值运算符的左边,并且可以被取地址,通常是有名字的变量
  • 右值:不能出现在赋值运算符的左边,不可以被取地址,表示一个具体的数据值,通常是常量、临时变量

一般可以从两个方向区分左值和右值。

  • 方向1:是否可以放到=左边?
  • 方向2:是否可以取地址并且有无名字?
    int a = b + c;
    int a = 3;
    

    a是左值,有变量名,可以取地址,也可以放到等号左边,表达式b+c的返回值是右值,没有名字且不能取地址,&(b+c)不能通过编译,而且也不能放到等号左边。

扩展

左值引用
可以理解为是对左值的引用。对于左值引用,等号右边的值必须可以取地址,如果不能取地址,则会编译失败,或者可以使用const引用形式,但这样就只能通过引用来读取输出,不能修改数组,因为是常量引用。

int a = 5;
int &b = a;  // b是左值引用  cout<<b; // 5
b = 4;  // cout<<b; // 4
int &c = 10;  // error,10无法取地址,无法进行引用
const int &d = 1;//  ok,因为是常引用,引用常量数字,这个常量数字会存储在内存中,可以取地址
// cout << &d;  // 内存地址

右值引用

可以理解为是对右值的引用。即对一个临时对象或者即将销毁的对象的引用,开发者可以利用这些临时对象,却不需要复制它们。如果使用右值引用,那表达式等号右边的值需要是右值,可以使用std::move函数强制把左值转换为右值。

int &&a = 4;
int &&b = a; //error,a是左值
int &&c = std::move(a); //ok

左值引用和右值引用的使用场景

  • 左值引用:当需要修改对象的值,或者需要引用一个持久对象时使用。
  • 右值引用:当需要处理一个临时对象,并且想要避免复制,或者实现移动语义时使用。

纯右值
纯右值属于右值。运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值

  • 除字符串字面值外的字面值
  • 返回非引用类型的函数调用
  • 后置自增自减表达式i++、i--
  • 算术表达式(a+b,a*b,a&&b,a==b等)
  • 取地址表达式等(&a)

将亡值
纯右值属于右值。将亡值是指C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&&函数的返回值、std::move函数的返回值、转换为T&&类型转换函数的返回值

将亡值可以理解为即将要销毁的值,通过“盗取“其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务

class A {}
A a;
auto c = std::move(a);//c是将亡值
auto d = static_cast<A&&>(a);//d是将亡值

什么是C++的移动语义和完美转发?

移动语义和完美转发都是 C++11 引入的新特性。

移动语义

一种优化资源管理的机制。常规的资源管理是拷贝别人的资源。而移动语义是转移所有权,转移了资源而不是拷贝资源,性能会更好。

移动语义通常用于那些比较大的对象,搭配移动构造函数或移动赋值运算符来使用

示例代码:

class A{
public:
    int *data_;
    int size_;
    A(int size) : size_(size){
        data_ = new int [size];
    }
    A(){}
    A(const A &a){
        size_ = a.size_;
        data_ = new int[size_];
        cout << "copy" << endl;
    }
    A(A &&a){
        this->data_ = a.data_;
        a.data_ = nullptr;
        cout << "move" << endl;
    }
    ~A(){
        if(data_ != nullptr){
            delete[] data_;
        }
    }
};

int main(){
    A a(10);
    A b = a;
    A c = std::move(a);  // 调用移动构造函数
    return 0;
}

如果不使用std:move,,会有很大的拷贝代价,使用移动语义可以避免很多无用的拷贝,提供程序性能,C++所有的STL都实现了移动语义,方便我们使用。例如:

std:vector<string> vecs;
......
std::vector<string> vecm = std::move(vecs);  //免去很多拷贝

完美转发

完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。

使用std:forward实现完美转发,可参考以下代码:

void PrintV(int &t) {
    cout << "lvalue" << endl;
}

void PrintV(int &&t) {
    cout << "rvalue" << endl;
}

template<typename T>
void Test(T &&t) {
    PrintV(t);
    PrintV(std::forward<T>(t));

    PrintV(std::move(t));
}
int main() {
    Test(1);  // lvalue rvalue rvalue
    int a = 1;
    Test(a);  // lvalue lvalue rvalue
    Test(std::forward<int>(a));  // lvalue rvalue rvalue
    Test(std::forward<int &>(a));  // lvalue lvalue rvalue
    Test(std::forward<int &&>(a));  //lvalue rvalue rvalue
    return 0;
}
  • Test(1):1是右值,模板中T &&t这种为万能引用,右值1传到Test函数中变成了右值引用,但是调用PrintV()时候,t变成了左值(因为它变成了一个拥有名字的变量),所以打印Ivalue,而PrintV(std::forward<T>(t))时候,会进行完美转发,按照原来的类型转发,所以打印rvaluePrintV(std::move(t))毫无疑问会打印rvalue
  • Test(a)a是左值,模板中T&&这种为万能引用,左值a传到Test函数中变成了左值引用,所以有代码中打印。
  • Test(std::forward<T>(a)):转发为左值还是右值,依赖于TT是左值那就转发为左值,T是右值那就转发为右值。
  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值