汽车之家这样的网站怎么做企业网站要求
news/
2025/10/6 23:32:38/
文章来源:
汽车之家这样的网站怎么做,企业网站要求,网站定制开发前期要有一定的规划,网站制作公司网站源码C左值右值左值和右值的由来什么是左值和右值左值右值的本质引用左值引用右值引用 移动语句与完美转发移动语句实现移动构造函数和转移赋值函数stdmove完美转发Perfect Forwarding C左值右值 自从C11发布之后#xff0c;出现了一个新的概念#xff0c;即左值和右值#xf…C左值右值左值和右值的由来什么是左值和右值左值右值的本质引用左值引用右值引用 移动语句与完美转发移动语句实现移动构造函数和转移赋值函数stdmove完美转发Perfect Forwarding C左值右值 自从C11发布之后出现了一个新的概念即左值和右值英文为lvalue和rvalue,这两个是比较晦涩难懂的基础概念为什么说是基础的概念呢因为只有了解了它才能真正理解move和forward语句。 左值和右值的由来
左值和右值最早是从C语言继承而来的。在C语言中或者是它的继承版本中有如下变现形式
左值是可以位于赋值运算符“”左侧的变量或表达式也可以位于赋值运算符“”右侧右值是不可以位于赋值运算符“”左侧的表达式只能出现在等号右边的变量或者表达式
我们来看看例子
例子1
int a; //声明变量a
int b; //声明变量ba 3; //赋值语句将a的值重新赋值为3此时a为左值
b 4; //此时b为左值a b; //此时a为左值b为右值
b a//此时b为左值a为右值3 a; //编译错误这里应该很好理解3不可能再被赋值了
a b 4; //编译错误这里相当于7 4也是不合理的例子2
//定义了两个函数foo1和foo2
int foo1(int number)
{return number;
}int foo2( int number )
{return number;
}main()
{foo1(1) foo2(2); //编译错误因为foo1和foo2的返回值只能作为右值不能放在等号的左边int temp foo1(1) * foo2(2); //temp为左值foo1(1) * foo2(2)为右值
}
上面这些例子就是左值和右值的例子。
什么是左值和右值
一个变量或者表达式是左值还是右值取决于我们使用的是它的值还是它在内存中的位置作为实例的身份。
int a; //声明变量a
int b; //声明变量ba b; //此时a为左值b为右值这个例子中将b的值赋值给a,将值保存在a的内存中b在这里面是右值a在这里面是左值 因为b作为实例既可以当做左值也可以当做右值。
所以判断一个值是左值还是右值要根据实际在语句汇总的含义来确定。
总结第一点
在一般情况下需要右值的地方可以用左值来代替需要左值的地方必须使用左值左值存放在实例中有持久的状态而右值是字面常量要么是在表达式求值过程中创建的临时实例没有持久的状态
重点
能取得到地址的变量或者表达式就是左值反之为右值。
那现在我们来看看下面的例子哪个是左值哪个是右值
int a 0;
int b 1;
a;
b;
我来宣布答案a为右值b为左值首先我们先验证一下
int a 0;
int b 1;
a 5; //error: lvalue required as left operand of assignment
b 5;
实验的结果也是正确的那我们来分析一下 对于a 1. a首先产生一个临时变量记录a的值 2. 然后将a1 3. 接着返回临时变量
根据这个过程我们知道 int a 0int c a 的值应该是c为0而a变为了1 所以a此时将临时变量返回给了c那么这个临时变量我们是不能获取地址的也就 是使用“”。所以结论就是a为右值。
对于b 1. 进行了b b 1 2. 返回变量b
根据这个过程我们是可以取b的地址的所以b是左值。
左值右值的本质
int a 5;
int c a a;
a就是左值5就是右值。 a a 表达式中a以右值传入相加之后也以右值返回。
左值就是对一块内存区域的引用(这个并不是c11中的int a 之类的引用) 比如上边的a就对应了一块内存区域(起始地址a大小为sizeof(int) )。
专业的解释 An object is a region of storage that can be examined and stored into. An lvalue is an expression that refers to such an object. An lvalue does not necessarily permit modification of the object it designates. For example, a const object is an lvalue that cannot be modified. 对于每个变量都有2个值与其相关联
数据值存储在某个内存地址中也称为右值右值是被读取的值不可修改。地址值即存储数据值的那块内存地址也称左值。
所以左值既可以当作左值也可以作为右值。就是这么神奇。
引用
在C中有两种对实例的引用左值引用和右值引用。
左值引用
左值引用是常见的引用C中可以使用“”符号定义引用如果一个左值同时也是引用那么就称其为“左值引用”。如
std::string str;
std::string strRef str; // strRef为左值也为引用称其为左值引用
非const左值引用不能使用右值对其赋值
std::string strRef abc; // error: abc字符串为右值假设上面可以的话就会遇到一个问题如何修改右值的值因为引用是可以后续被赋值的。根据上面的定义右值连可被获取的内存地址都没有也就谈不上对其进行赋值。
但是const左值引用就可以使用右值因为常量不能被修改也不存在上面纠结的问题
const std::string strRef abc;
再比如我们经常使用左值作为函数的参数类型可以减少不必要的对象复制
int foo( int number )
{return number;
}main()
{int a foo(1); //错误int b 1;int c foo(b); //通过
}
我们将上面的int number改为 const int number即可。
补充知识 什么是CV限定符(CV-qualified)如果变量声明时类型带有const或者volatile就说此变量类型具有CV限定符。 右值引用
右值引用也是引用但是它只能且必须绑定在右值上。
int a 5;
int b a; // a绑定在左值引用b上
int c a; // errora可以是左值所以不能将它绑定在右值引用上。
int d 30; // 将右值30绑定在右值引用上
int e a * 1 // a * 1的结果是一个临时对象为右值所以可以绑定在右值引用上结论
由于右值引用只能绑定在右值上而右值要么是字面常量要么是临时对象所以
右值引用的对象是临时的即将被销毁 并且右值引用的对象不会在其它地方使用。
移动语句与完美转发
这里涉及前面提过的move和forward两个函数即移动语句和转发。
右值引用 (Rvalue Referene) 是 C 新标准 (C11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和精确传递 (Perfect Forwarding)。它的主要目的有两个方面
消除两个对象交互时不必要的对象拷贝节省运算存储资源提高效率。能够更简洁明确地定义泛型函数。
移动语句
右值引用是用来支持移动语句的。移动语句可以将资源堆系统对象等从一个对象转移到另一个对象这样能够减少不必要的临时对象的创建、拷贝以及销毁能够大幅度提高C应用程序的性能。临时对象的维护创建和销毁对性能有严重影响。
移动语句是和拷贝语句相对的可以类比文件的剪切和拷贝当我们将文件从一个目录拷贝到另一个目录时速度比剪切慢很多。
通过移动语句临时对象中的资源能够转移其他的对象里。
在现有的 C 机制中我们可以定义拷贝构造函数和赋值函数。要实现移动语句需要定义移动构造函数还可以定义移动赋值操作符。对于右值的拷贝和赋值会调用移动构造函数和移动赋值操作符。如果移动构造函数和移动拷贝操作符没有定义那么就遵循现有的机制拷贝构造函数和赋值操作符会被调用。
实现移动构造函数和转移赋值函数
class MyString { public: MyString() { m_data nullptr; m_len 0; }private: char* m_data; size_t m_len; void initData(const char *s) { m_data new char[m_len1]; memcpy(m_data, s, m_len); m_data[m_len] \0; } MyString(const char* p) { m_len strlen (p); initData(p); } MyString(const MyString str) { m_len str.m_len; initData(str.m_data); std::cout Copy Constructor is called! source: str.m_data std::endl; } MyString operator(const MyString str) { if (this ! str) { m_len str.m_len; initData(str.m_data); } std::cout Copy Assignment is called! source: str.m_data std::endl; return *this; } virtual ~MyString() { } }; int main()
{ MyString a; a MyString(Hello); //调用拷贝构造函数MyString(Hello)为临时对象即右值std::vectorMyString vec; vec.push_back(MyString(World));
}运行结果
Copy Assignment is called! source: Hello
Copy Constructor is called! source: World
这个 string 类已经基本满足我们演示的需要。在 main函数中实现了调用拷贝构造函数的操作和拷贝赋值操作符的操作。 MyString(“Hello”) 和 MyString(“World”)都是临时对象也就是右值。虽然它们是临时的但程序仍然调用了拷贝构造和拷贝赋值造成了没有意义的资源申请和释放的操作。如果能够直接使用临时对象已经申请的资源既能节省资源有能节省资源申请和释放的时间。这正是定义转移语义的目的。
MyString(MyString str)
{ std::cout Move Constructor is called! source: str.m_data std::endl; m_len str.m_len; m_data str.m_data; str.m_len 0; str.m_data nullptr;
}MyString operator(MyString str)
{ std::cout Move Assignment is called! source: str.m_data std::endl; if (this ! str) { m_len str.m_len; m_data str.m_data; str.m_len 0; str.m_data nullptr; } return *this;
}增加后的运行结果Move Assignment is called! source: Hello
Move Constructor is called! source: World由此看出编译器区分了左值和右值对右值调用了移动构造函数和移动赋值操作符。节省了资源提高了程序运行的效率。
有了右值引用和移动语义我们在设计和实现类时对于需要动态申请大量资源的类应该设计移动构造函数和转移赋值函数以提高应用程序的效率。
std::move
我们先来看一个例子
void processValue( int value )
{std::cout lvalue process: value std::endl;
}void processValue( int value )
{std::cout rvalue process: value std::endl;
}main()
{int a 0;processValue(a); //传入左值processValue(1); //传如右值
}结果
lvalue process: 0
rvalue process: 1通过这个例子来看processValue函数被重载分别接受左值和右值。由输出结果可以看出临时对象是作为右值处理的。
但是如果临时对象通过一个接受右值的函数传递给另一个函数时就会变成左值因为这个临时对象在传递过程中变成了命名对象。
void processValue( int value )
{std::cout lvalue process: value std::endl;
}void processValue( int value )
{std::cout rvalue process: value std::endl;
}void forwardValue( int value )
{processValue(value);
}main()
{int a 0;processValue(a);processValue(1);forwardValue(2);
}结果
lvalue process: 0
rvalue process: 1
lvalue process: 2我们可以看出最后一个函数调用2是右值可以返回的时候却变成了左值。这里面我们可以使用std::move(var)将变量转移为右值语句。
修改为
...
void forwardValue( int value )
{processValue(std::move(value) );
}
...
既然编译器只对右值引用才能调用转移构造函数和转移赋值函数而所有命名对象都只能是左值引用如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数也就是把一个左值引用当做右值引用来使用怎么做呢标准库提供了函数 std::move这个函数以非常简单的方式将左值引用转换为右值引用。
std::move在提高 swap 函数的的性能上非常有帮助一般来说swap函数的通用定义如下
template class T swap(T a, T b)
{ T tmp(a); // copy a to tmp a b; // copy b to a b tmp; // copy tmp to b
}
有了 std::moveswap 函数的定义变为 :
template class T swap(T a, T b)
{ T tmp(std::move(a)); // move a to tmp a std::move(b); // move b to a b std::move(tmp); // move tmp to b
}
通过 std::move一个简单的 swap 函数就避免了 3 次不必要的拷贝操作。
完美转发Perfect Forwarding
Perfect Forwarding也被翻译成完美转发精准转发等说的都是一个意思。
用于这样的场景需要将一组参数原封不动的传递给另一个函数。
原封不动”不仅仅是参数的值不变在 C 中除了参数值之外还有一下两组属性
左值右值const/non-const。
完美转发就是在参数传递过程中所有这些属性和参数值都不能改变。
重点完美转发仅与类模板或函数模板的环境有关。
示例
class Person
{
public:templatetypename T1, typename T2Person(T1 first, T2 second ) : firstname{std::forwardT1(first)},secondname{std::forwardT2(second)}{}string getName() const{return firstname.getName() secondname.getName(); }
private:Name firstname;Name secondname;
}class Name
{
public:Name( const string aName ): name{aName}{cout Lvalue Name constructor. endl;}Name( string aName ): name{std::move(aName)}{cout Rvalue Name constructor. endl;}const string getName() const { return name; }
private:string name;
}main()
{Person me{string{abc}, string{def}};string first{lll};string second{ggg};Person other{first, second};
}输出结果
Rvalue Name constructor.
Rvalue Name constructor.
Lvalue Name constructor.
Lvalue Name constructor.
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/929816.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!