• C++新特性笔记(1)


    1.左值引用和右值引用

    左值是某种存储支持的变量,右值是临时变量或可读但没有固定寻址方法的变量。

    左值有具体的地址可以访问,右值有地址但没有固定寻址方法,右值是一种优化技巧。

    左值引用只能接受左值,若加上const修饰就可以接收右值,但实际上用const修饰以后左值引用的变量以后,引用得来的变量就变成了一个不可修改的右值。

    右值分纯右值,和将亡值。纯右值是只字面值、算数表达式、返回非引用类型的函数调用等等的数据值;将亡值是c++11新引入的类型与右值引用(移动语义)相关的值类型。

    用在函数传参和函数返回值时,左值引用避免了对象的拷贝

    void get(int& a)//只接收左值,get(10)会报错,因为10是一个右值
    void get(const int& a)//可以接收左值和右值,但是a无法再被修改
    
    • 1
    • 2

    右值引用只能接受右值,语法上是再加上一个&,右值引用的好处是,当我们输入值为右值时,我们知道它只是一个临时创建的变量,我们可以放心对其进行修改。

    右值引用实现了移动语义和完美转发

    void get(int&& x)//右值引用,get(10)也不会报错
    
    void get_string1(string&& s)
    {
    	s = "修改";
    }
    void get_string2(string&& s)
    {
       string* change;
    	change = &s;
    	*change = "硬改";
    }
    int main()
    {
       string s1 = "不";
       string s2 = "修改";
       //get_string(s1) 报错,因为s1是一个左值
       get_string1(s1+s2);
       cout << s1 + s2 <<endl;
       get_string2(s1+s2);
       cout << s1 + s2 <<endl;//输出还是不修改,右值引用不会对外面产生任何影响
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述 虽然右值无法直接访问地址,但是确实有地址的,可以通过间接的方式获取其内存地址

    void get(int&& x,int*& i)
    {
    	i = &x;
    	cout << i << endl;
    }
    
    int main()
    {
    	int* i = nullptr;
    	get(10,i);
    	cout << i << endl;
    	cout << *i << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述右值引用可以用在move语义的状态,实现简单的move效果

    
    class String {
    public:
    	String() = default;
    	String(const char* string)
    	{
    		std::cout << "create!" << std::endl;
    		m_Size = strlen(string);
    		m_Data = new char[m_Size];
    		memcpy(m_Data, string, m_Size);
    	}
    	
    	String(const String& other)
    	{
    		std::cout << "copy!" << std::endl;
    		m_Size = other.m_Size;
    		m_Data = new char[m_Size];
    		memcpy(m_Data, other.m_Data, m_Size);
    	}
    
    	String(String&& other) noexcept
    	{
    		std::cout << "move!" << std::endl;
    		m_Size = other.m_Size;
    		m_Data = other.m_Data;
    
    		other.m_Size = 0;
    		other.m_Data = nullptr;
    	}
    	~String()
    	{
    		std::cout << "delete!" << std::endl;
    		delete m_Data;
    	}
    	void print() const
    	{
    		for (int i = 0; i < m_Size; i++) printf("%c", m_Data[i]);
    		printf("\n");
    	}
    private:
    	char* m_Data;
    	int m_Size;
    };
    
    
    class A {
    public:
    	A() = default;
    	A(const String& s):s(s){}
    	A(String&& s) :s((String&&)s) {}
    	void print()
    	{
    		s.print();
    	}
    	
    	
    private:
    	String s;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    如果不使用右值引用的拷贝构造函数,在A a(“拷贝”)这样的对象实例化的时候,实际上先调用了有参构造函数,再调用了拷贝构造函数。
    在这里插入图片描述开辟了两个堆再依次销毁,浪费性能,那么我们可以使用右值引用实现一个简单的move效果(实际上做了一个浅拷贝的工作)。

    A(String&& s) :s(std::move(s)) {}//(String&&)很少用,一般直接用move
    
    • 1

    move()函数实际上就是将左值转为右值。

    在不用右值引用时,函数模板不能完美地转发给内部调用函数,要么只能给内部的其他函数,被转发的参数只能是右值或左值其中之一,不能达到泛型的目的,使用右值引用后就能解决这个问题,达到完美转发。

    完美转发实现函数时std::forward,std::forward可以保持原来的值属性不变。

    #include 
    
    
    void reference(int &v){
        printf("左值\n");
    }
    
    void reference(int &&v){
        printf("右值\n");
    }
    
    template <typename T>
    void pass(T && v){
        reference(v);
        reference(std::forward<T>(v));
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    std::forward内部实现:

    template<typename _Tp>
    constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t) noexcept{
    	return static_cast<_Tp&&>(__t);
    }
    
    template<typename _Tp>
    constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type&& __t)noexcept{
    	static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument substituting _Tp is an lvalue reference type");
    	return static_cast<_Tp&&>(__t);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.auto和decltype操作符

    C++中auto是根据用户初始化内容自动推导类型

    1. 使用auto时必须初始化
    2. auto变量不能作为自定义类型的成员变量
    3. auto不能声明数组
    4. 模板实例化类型不能是auto类型
    5. auto作为函数返回值类型时,需要额外再次指定返回类型,如:auto get(int a,double b)->int

    decltype可以用来获取变量、函数、表达式的类型

    追踪返回值类型:auto和decltype相结合

    auto func(int a,double b)->decltype(a+b)
    {
       return a+b;
    }
    
    • 1
    • 2
    • 3
    • 4

    在模板中使用更简洁

    template<class T1,class T2>
    auto mul(T1 &t1,T2 &t2)->->decltype(t1*t2)
    {
        return t1*t2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    Java Set集合如何排序呢?
    ansible服务搭建与windows引用
    GPU不够用:语言模型的分布式挑战
    无法在CentOS7安装docker
    作业比赛编号 : 1280 - 2022年春季学期《算法分析与设计》练习15
    PdfSharp 对中文字体显示乱码的问题
    spring:实现初始化动态bean|获取对象型数组配置文件
    基于simulink的模糊自整定PID控制器设计与仿真
    『现学现忘』Git基础 — 20、Git中忽略文件补充
    解决过的Java相关问题
  • 原文地址:https://blog.csdn.net/weixin_44971209/article/details/126174093