网站建设中数据安全研究,海拉尔网站建设,做网站建设的公司有哪些内容,哪个网站可以做名片C11相比于C98增加以许多新特性#xff0c;让C语言更加灵活好用#xff0c;但是貌似也增加了许多学习的难度#xff0c;现在先看第一部分。 一、右值引用和移动语义 1.右值引用和左值引用 在C中#xff0c;值可以大致分为右值和左值#xff0c;左值大概是哪些已经被定义的变… C11相比于C98增加以许多新特性让C语言更加灵活好用但是貌似也增加了许多学习的难度现在先看第一部分。 一、右值引用和移动语义 1.右值引用和左值引用 在C中值可以大致分为右值和左值左值大概是哪些已经被定义的变量或者对象它一般具有持久性它可以出现在赋值符号的左边也可以出现在赋值符号的右边右值一般指的是临时变量、字面常量等等。一般来说区分左右值的方法可以用是否可以取地址来判断左值可以取地址右值不可以被取地址。 下面让我们简单看一段代码吧
void Func(int a)
{cout 左值引用 endl;
}void Func(int a)
{cout 右值引用 endl;
}int main()
{int a 1;Func(1);Func(a);return 0;
} 其中a是一个普通变量为左值而1是一个字面常量为右值那么最后的输出结果因该是“右值引用”、“左值引用”。让我们看一看结果 2.右值引用引用左值和左值引用引用右值 右值可以被左值引用引用因为右值一般具有常性所以可以通过在左值引用前加上“const”来使得左值引用可以引用右值而右值引用引用左值则需要使用“move”函数来改变左值的属性。
int main()
{int a 2;const int L 1;int R move(a);cout L endl R endl;return 0;
} 3.右值引用存在的意义
a.延长临时对象的生命周期 右值引用可以延长临时对象的生命周期比如
int main()
{string(123456789);return 0;
} 此代码中string的生命周期仅限于这一行倘若使用右值引用来引用那么就可以延长它的生命周期
int main()
{string S string(123456789);cout S endl;return 0;
} 同样的表达式相加的临时对象函数返回时的临时对象都可以使用右值引用来延长它的生命周期。
b.移动语义 当然了右值引用存在的意义可不是为了简单的延长生命周期而是为了转移临时对象的数据这使得数据的转移更加高效和安全这也正是移动语义的的机制。在此之前还需要了解的是虽然右值是不可以被改变但是右值引用是具有左值属性的也就是说被右值引用引用的右值是可以被修改的。 拿拷贝构造来说我们会将它的它的参数写为const类型的在保证不修改实参的情况下还能够接收右值。但是无论是左值还是右值在有资源的情况下需要进行大量的复制行为特别是在右值的情况下因为右值的生命周期即将结束还得进行一次复制这样会造成效率的低下。为了解决这个问题这个时候就需要介绍一下移动构造和移动赋值了。 移动构造和移动赋值旨在将临时对象右值的资源转移到我们的类中由于右值的生命周期即将结束秉承着趁你病要你命的原则我是可以在虚弱的时候掠夺你的资源。这个时候我就可以直接把我的没用的东西和你的资源进行交换这样做的效率可不是一般的高因为避免了大量的数据拷贝。 移动构造和移动赋值同普通的构造和赋值函数一样只是参数变为了右值并且函数内部进行资源的互换。 就用我们的自己写的string类为例。此处在linux环境下测试因为VS的编译器会进行优化
#pragma once
#includeiostreamusing namespace std;namespace Mynamespace
{class string{friend ostream operator(ostream _cout, const Mynamespace::string s);friend istream operator(istream _cin, Mynamespace::string s);public:typedef char* iterator;public:string(const char* str ){_str new char[strlen(str)1];char* der _str;const char* sour str;while (*sour ! \0){*(der) *(sour);}*der \0;_size strlen(str);_capacity _size;}string(const string s){_str new char[s._capacity];char* der _str;const char* sour s._str;while (*sour ! \0){*(der) *(sour);}*der \0;_size s._size;_capacity s._capacity;}string(string s){cout string(string s) 移动构造 endl;swap(s);}~string(){delete[] _str;_capacity 0;_size 0;}string operator(const string s){delete[] _str;_str new char[strlen(s._str) 1];char* der _str;const char* sour s._str;while (*sour ! \0){*(der) *(sour);}*der \0;_size s._size;_capacity s._capacity;return *this;}// 移动赋值string operator(string s){cout string operator(string s) -- 移动赋值 endl;swap(s);return *this;}//// iteratortypedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}/// modifyvoid push_back(char c);string operator(char c);void append(const char* str);string operator(const char* str);void clear();void swap(string s);const char* c_str()const;/// capacitysize_t size()const;size_t capacity()const;bool empty()const;void resize(size_t n, char c \0);void reserve(size_t n);/// accesschar operator[](size_t index);const char operator[](size_t index)const;///relational operatorsbool operator(const string s){return strcmp(_str, s._str) 0;}bool operator(const string s){return *this s || *this s;}bool operator(const string s){return !(*this s);}bool operator(const string s){return !(*this s);}bool operator(const string s){return strcmp(_str, s._str) 0;}bool operator!(const string s){return !(*this s);}// 返回c在string中第一次出现的位置size_t find(char c, size_t pos 0) const;// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos 0) const;// 在pos位置上插入字符c/字符串str并返回该字符的位置string insert(size_t pos, char c);string insert(size_t pos, const char* str);// 删除pos位置上的元素并返回该元素的下一个位置string erase(size_t pos, size_t len);private:char* _str;size_t _capacity;size_t _size;static const size_t npos -1;};
}int main()
{Mynamespace::string mystring1 Mynamespace::string(123456789);return 0;
} 可能是编译器的版本的问题在我的linux环境中以上代码即使关闭了优化也仍然进行直接构造而不是构造移动构造
cxUbuntu-Linux:~/study$ g test.cpp -fno-elide-constructors
cxUbuntu-Linux:~/study$ ./a.outstring(const char* str ) 有条件的小伙伴可以自己试一试其中“-fno-elide-constructors”或者“-O0”可以关闭优化。
所以这里先测试一下移动赋值
int main()
{Mynamespace::string mystring1;mystring1 Mynamespace::string(123456789);return 0;
}
cxUbuntu-Linux:~/study$ g test.cpp -fno-elide-constructors
cxUbuntu-Linux:~/study$ ./a.outstring(const char* str )string(const char* str )
string operator(string s)可以看到最后调用了一次移动赋值。
二、引用折叠 在C11中新增加了引用折叠的特性引用折叠针对的是引用的引用但是不可以显式的写出来引用的引用比如
int main()
{int a;int b a;return 0;
} 但是可以隐式的引用typedef后的引用
int main()
{typedef int L;typedef int R;int a;L b a;R c a;R d 1;L e a;return 0;
} 引用折叠理解起来有点像与门在这里左值引用代表着0右值引用代表着1当左值引用右值引用同时存在的时候就是左值引用只有当两个引用都为右值的时候才为右值引用。 就拿以上代码为例 L b a; 里b的类型为int R c a; 里c的类型为intR d 1; 里d 的类型为int L e a; 里 e为左值。 1.万能引用 引用折叠的用途在哪里呢实际上它可以用来在函数模板中实现万能引用。请看以下函数模板
template class T
void func(T n)
{cout void func(int n) endl;
}int main()
{int a 1;func(1);func(a);return 0;
} 已知字面常量1是一个右值变量a是一个左值由于引用折叠第一次调用的是右值引用版本的func第二次是左值引用的func。最后的运行结果如下 这样可以用一个模板实现左值引用和右值引用的两个版本。 2.完美转发 先看以下代码
void F(int a)
{cout 左值引用 endl;}
void F(int a)
{cout 右值引用 endl;}
void F(const int a)
{cout 左值引用 endl;
}template class T
void func(T n)
{F(n);
}int main()
{int a 1;func(a);func(2);return 0;
} 我们想要的输出结果是“左值引用”、“右值引用”那么先看一下运行结果 哎。为什么是两个左值引用呢 由于右值引用的属性为左值当我们想连续传递一个右值的时候在第一次传递后这个值就已经变为左值属性了为了解决这个问题C11引入了完美转发它的目得是为了在右值连续传递的过程中不改变右值的属性。 完美转发的本质是一个函数模板forward它的底层是一个强转左值引用和右值引用的本质还是指针只是在语义上不同。它的用法是在传递参数的地方加上模板的类型后边紧接着参数是传递的值
void F(int a)
{cout 左值引用 endl;}
void F(int a)
{cout 右值引用 endl;}
void F(const int a)
{cout 左值引用 endl;
}template class T
void func(T n)
{F(forwardT(n));
}int main()
{int a 1;func(a);func(2);return 0;
} 运行结果 我们可以分析一下 a.传递的参数为a的时候a为左值func发生了引用折叠编译器推导出T为int那么forward强转n为左值并返回。 b.传递的参数为字面常量1的时候1为右值func没有发生引用折叠编译器推导出T为int那么forward强转n为右值并返回。 总的来说完美转发就是根据T的类型来推导最后返回的类型如果T是右值那么最后返回的就是右值属性的对象如果T为左值那么最后返回的就是左值属性的对象。