二手车网站建站,做微电网的公司网站,中企动力是做什么的公司,怎么做国外的网站推广目录
前言
一、C11的简介
二、C11的小故事。
三、统一的列表初始化
1.列表初始化
2.initializer_list
四、右值引用
1.什么是左值
2.什么是右值
3.右值引用写法
4.右值的分类
5.右值引用的作用
6.STL容器中的右值引用
7.万能引用
总结 前言 C11相较于之C98…目录
前言
一、C11的简介
二、C11的小故事。
三、统一的列表初始化
1.列表初始化
2.initializer_list
四、右值引用
1.什么是左值
2.什么是右值
3.右值引用写法
4.右值的分类
5.右值引用的作用
6.STL容器中的右值引用
7.万能引用
总结 前言 C11相较于之C98能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全不仅功能更强大而且能提升程序员的开发效率公司实际项目开发中也用得比较多。许多功能看起来会很怪效果却很好所以我们要作为一个 重点去学习。 一、C11的简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)使得C03这个名字已经取代了 C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞 进行修复语言的核心部分则没有改动因此人们习惯性的把两个标准合并称为C98/03标准。 从C0x到C11C标准10年磨一剑第二个真正意义上的标准珊珊来迟。 二、C11的小故事。 1998年是C标准委员会成立的第一年本来计划以后每5年视实际需要更新一次标准C国际 标准委员会在研究C 03的下一个版本的时候一开始计划是2007年发布所以最初这个标准叫 C 07。但是到06年的时候官方觉得2007年肯定完不成C 07而且官方觉得2008年可能也 完不成。最后干脆叫C 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的 时候也没完成最后在2011年终于完成了C标准。所以最终定名为C11。 三、统一的列表初始化
1.列表初始化
在C98中标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定如下 C11提出一切都可以使用列表初始化。如下甚至可以把赋值符号省略掉 对于结构体也是如此可以省略赋值符号我们这里看起来似乎用处不怎么大但这种写法是在为后面的内容做准备 上面的是使用的结构体我们再来看看C的class是否也一样。 我们从下图中发现没问题但是不同的写法也是有区别的 像如下这种写法C11也是支持的这样也是挺方便的。 2.initializer_list
再讲解initializer_list之前我们先来看一下下面这个例子Date是上面用到的日期类vector和list是STL库里面的容器。请问他们后面的{1,2,3}是一样的吗 这样乍眼一看好像确实是一样的实际上他们有本质的区别d1后面接的{1,2,3}只是d1的三个参数 他是构造拷贝构造被编译器优化为直接构造并且固定只能写三个参数。
而vectorint v和 listint l 他们两后面接的{1,2,3}是initializer_list他是库里面的容器。 因为vector和list支持了 initializer_list去进行构造因此可以这样玩。同时对于参数的个数是不设限制的。 vector和list如此其他很多容器也是这样,我们随便举个map的例子。 四、右值引用
1.什么是左值
C中值可以分为左值和右值我们之前学的引用都是左值引用如下 这里并不是说在左边的是左值而是可以取地址的才是左值左值引用就是给左值的引用给左值取别名。如下虽然第二行 i 在右边但我们可以给 i 取地址因此 i 是左值 2.什么是右值 右值也是一个表示数据的表达式如字面常量、表达式返回值函数返回值(这个不能是左值引用返回)等等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能取地址。右值引用就是对右值的引用给右值取别名。 举个例子 3.右值引用写法
右值引用是加比左值还多一个如下 左值引用不能给右值取别名除非加const缩小权限。 右值引用不能给左值取别名除非加move()函数将左值转为右值大家记住就好具体我们后续了解 4.右值的分类
内置类型的右值称作纯右值
自定义类型的右值称作将亡值下面会提到
5.右值引用的作用
C11为什么要搞一个右值引用有什么东西是左值引用解决不了的吗
我们模拟实现了一个简易版的string帮助我们打印理解代码如下
namespace bit
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}string(const char* str ):_size(strlen(str)), _capacity(_size){//cout string(char* str) -- 构造 endl;_str new char[_capacity 1];strcpy(_str, str);}// s1.swap(s2)void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string s){cout string(const string s) -- 深拷贝 endl;string tmp(s._str);swap(tmp);}// 赋值重载string operator(const string s){cout string operator(const string s) -- 深拷贝 endl;/*string tmp(s);swap(tmp);*/if (this ! s){char* tmp new char[s._capacity 1];strcpy(tmp, s._str);delete[] _str;_str tmp;_size s._size;_capacity s._capacity;}return *this;}~string(){delete[] _str;_str nullptr;}char operator[](size_t pos){assert(pos _size);return _str[pos];}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}//string operator(char ch)string operator(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str nullptr;size_t _size 0;size_t _capacity 0; // 不包含最后做标识的\0};bit::string to_string(int x){bit::string ret;while (x){int val x % 10;x / 10;ret (0 val);}reverse(ret.begin(), ret.end());return ret;}
}
我们来看下面的例子我们有一个to_string函数可以输入整形返回string我们发现代码竟然发生了两次深拷贝注意这里我们是VS2019使用VS2022编译器优化太厉害结果会不一样 具体流程如下ret将亡值出了作用域就会析构因此如果返回ret的话他在返回时会拷贝生成一个临时对象将这个临时对象进行operator 再赋值给s这样就会进行两次深拷贝。 这里ret是左值但是左值引用解决不了问题因为他出了作用域会析构内容就不存在了右值引用也无法解决问题因为ret为左值无法进行右值引用。就算你改为moveret这样是可以运行但是ret被析构的事实你无法解决你内存都还给编译器了你还想赋值结果肯定不对。 那我们该如何处理呢这就需要提到移动构造了转移将亡值的资源从而避免深拷贝 如果在拷贝构造的地方重写一个右值引用的构造被称作移动构造把将亡值ret的资源转移给那个临时对象临时对象再去调用operator进行赋值拷贝这样是不是可以减少一次深拷贝移动构造的代价很低 移动构造代码如下 string(string s)
{cout string(string s) -- 移动拷贝 endl;swap(s); //swap是类里的函数我们上面的代码中有。
} 我们画图再分析一下如下是没有写移动构造的情况先构造一个ret对象再使用ret对象深拷贝一个临时对象再使用这个临时对象去operator深拷贝赋值给s对象。深拷贝的代价比较大这效率是真的不高。这里三个_str的地址都不一样第一次为构造后两次为深拷贝 如果我们写了string类的移动拷贝具体流程就是如下所示了再ret返回时会进行移动构造而不是拷贝构造因为ret是右值重载的右值引用会更加符合。同时在移动构造内只有swap函数进行资源的交换。这里可以看到下图1和图2string的_str地址都是一样的0x005e0198。因为移动拷贝交换了资源没有深拷贝提高了效率。 那么我们是不是可以按照这个思路函数重载一下operator() 的右值引用版本进行移动构造是不是又可以节省一次深拷贝呢
我们添加如下代码进行operator的移动拷贝
string operator(string s)
{cout string operator(string s) -- 移动拷贝 endl;swap(s);return *this;
} 调试看一看将亡值ret在析构前将资源给到的临时对象将亡值临时对象又再析构前将资源给到了s。因此只进行了两次移动拷贝便完成了操作三次操作_str的地址都是0x010da9a8。 6.STL容器中的右值引用
由于右值引用的效率比深拷贝高很多因此C11在STL容器里很多地方都用到了右值引用
vector构造函数中的的右值引用 push_back和insert也有右值引用版本 其他容器也是如此就不多一一举例了
我们直接使用库里面的vector来看一下效果先reserve(20)是为了防止发生扩容string进行深拷贝影响我们判断。如下发生两个移动拷贝1234转为string类型返回将亡值ret移动构造了临时对象将亡值临时对象再移动构造了string类型最后被添加到 v 后面好理解。 使用自己模拟的vector试一下。如下是我们之前模拟的vector。将他放到vector.h中test.cpp文件包一下头文件。
#pragma once
namespace kky
{templateclass Tclass vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}vector(){}vector(const vectorT v){reserve(v.capacity());for (auto e : v){push_back(e);}}template class InputIteratorvector(InputIterator first, InputIterator last){while (first ! last){push_back(*first);first;}}vector(int n, const T val T()){resize(n, val);}//vector(size_t n, const T val T())//{// resize(n,val);//}vectorT operator(vectorT v){swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);return *this;}~vector(){delete[] _start;_start _finish _endofstorage nullptr;}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}void reserve(size_t n){if (n capacity()){size_t sz size();T* tmp new T[n];if (_start){//memcpy对自定义类型是浅拷贝//memcpy(tmp, _start, sz * sizeof(T));//循环赋值会调用自定义类型的赋值拷贝就是深拷贝for (size_t i 0; i sz; i){tmp[i] _start[i];}delete[] _start;}_start tmp;_finish _start sz;_endofstorage _start n;}}void resize(size_t n, const T val T()){if (n size()){_finish _start n;}else{reserve(n);for (size_t i size(); i n; i){_start[i] val;}_finish _start n;}}void push_back(const T x){if (size() capacity()){size_t cp capacity() 0 ? 4 : capacity() * 2;reserve(cp);}*_finish x;_finish;}iterator insert(iterator pos, const T x){assert(pos _start);assert(pos _finish);if (size() capacity()){size_t len pos - _start;//扩容后_start位置可能会发生改变了因此要记录pos的相对位置改变pos的值size_t cp capacity() 0 ? 4 : capacity() * 2;reserve(cp);pos _start len;}iterator end _finish - 1;while (end pos){*(end 1) *end;end--;}*pos x;_finish;return pos;}iterator erase(iterator pos){assert(pos _start);assert(pos _finish);iterator it pos;while (it _finish - 1){*it *(it 1);it;}_finish--;return pos;}T operator[](size_t pos){assert(pos size());return _start[pos];}//不可修改const T operator[](size_t pos) const{assert(pos size());return _start[pos];}private:iterator _start nullptr;iterator _finish nullptr;iterator _endofstorage nullptr;};
}
测试一下因为我们自己模拟实现的vector还没有添加右值引用版本的push_back因此会发生深拷贝如下 那么我们添加上右值引用的push_back和insert再来试一下
在vector.h添加如下代码代码就是普通版本拷贝过来的修改了参数类型为T x
void push_back(T x)
{if (size() capacity()){size_t cp capacity() 0 ? 4 : capacity() * 2;reserve(cp);}*_finish x;_finish;
}
iterator insert(iterator pos, T x)
{assert(pos _start);assert(pos _finish);if (size() capacity()){size_t len pos - _start;//扩容后_start位置可能会发生改变了因此要记录pos的相对位置改变pos的值size_t cp capacity() 0 ? 4 : capacity() * 2;reserve(cp);pos _start len;}iterator end _finish - 1;while (end pos){*(end 1) *end;end--;}*pos x;_finish;return pos;
} 调试一下发现了问题我们x是右值进入push_back右值引用里没有问题但是在下面赋值的地方竟然会去深拷贝。这是为啥勒string深拷贝的参数不是左值吗 这是因为被右值引用后该值的属性会变为左值。C11为何要这么设计 大家看下面这个代码这是我们之前写过的string的移动拷贝按道理来说s为右值是无法被修改的但这里我们竟然完成了swap交换函数这下是不是可以讲得通了。不这样设计右值引用就无法处理引用来干啥 那么针对不要进行深拷贝要去移动拷贝的问题我们解决起来也很简单我们只需要给x move一下让他再变成右值就好了因为你x本就是右值就是将亡值只是右值引用后变成了左值而已我只是将你变成了本身的状态。 这样就变成了移动拷贝了。 7.万能引用
大家看下面这段代码第一时间感觉就是我们所学的右值引用但其实不然。这里使用了模板如果函数使用了模板参数并且类型为T t。这种类型那么这就是万能引用左值和右值都会来调用这里。 使用下面代码进行测试
void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }
void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }//万能引用
templatetypename T
void PerfectForward(T t)
{Fun(t);
}
int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
我们发现打印的都是左值引用这是因为 t 如果是左值那么左值引用后也是左值t 如果是右值右值引用后也是左值因此打印出来都是左值引用。 我们如何得到想要的效果呢 这需要用到完美转发使用forwadT(记住就好) 因此vector.h的这两个地方也可以修改为forwardT(x)同时如果添加上template就可以删除掉拷贝构造了因为有万能引用跟完美转发了。 总结
我们学习了C11的两个较为重要的特性统一的列表初始化都可以用 {} 去赋值还学习了右值引用这里可能会有点啰嗦因为知识点比较绕也比较难。
最后附上文本的测试代码如果是vs2019及以下大家也可以看看效果vs2022效果不一样
vector.h如下
#pragma once
namespace kky
{templateclass Tclass vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}vector(){}vector(const vectorT v){reserve(v.capacity());for (auto e : v){push_back(e);}}template class InputIteratorvector(InputIterator first, InputIterator last){while (first ! last){push_back(*first);first;}}vector(int n, const T val T()){resize(n, val);}//vector(size_t n, const T val T())//{// resize(n,val);//}vectorT operator(vectorT v){swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);return *this;}~vector(){delete[] _start;_start _finish _endofstorage nullptr;}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}void reserve(size_t n){if (n capacity()){size_t sz size();T* tmp new T[n];if (_start){//memcpy对自定义类型是浅拷贝//memcpy(tmp, _start, sz * sizeof(T));//循环赋值会调用自定义类型的赋值拷贝就是深拷贝for (size_t i 0; i sz; i){tmp[i] _start[i];}delete[] _start;}_start tmp;_finish _start sz;_endofstorage _start n;}}void resize(size_t n, const T val T()){if (n size()){_finish _start n;}else{reserve(n);for (size_t i size(); i n; i){_start[i] val;}_finish _start n;}}templateclass Tvoid push_back(T x){if (size() capacity()){size_t cp capacity() 0 ? 4 : capacity() * 2;reserve(cp);}*_finish forwardT(x);_finish;}templateclass Titerator insert(iterator pos, T x){assert(pos _start);assert(pos _finish);if (size() capacity()){size_t len pos - _start;//扩容后_start位置可能会发生改变了因此要记录pos的相对位置改变pos的值size_t cp capacity() 0 ? 4 : capacity() * 2;reserve(cp);pos _start len;}iterator end _finish - 1;while (end pos){*(end 1) *end;end--;}*pos forwardT(x);_finish;return pos;}iterator erase(iterator pos){assert(pos _start);assert(pos _finish);iterator it pos;while (it _finish - 1){*it *(it 1);it;}_finish--;return pos;}T operator[](size_t pos){assert(pos size());return _start[pos];}//不可修改const T operator[](size_t pos) const{assert(pos size());return _start[pos];}private:iterator _start nullptr;iterator _finish nullptr;iterator _endofstorage nullptr;};
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
#includevector
#includelist
#includestring
#includeassert.h
using namespace std;
#includevector.h
namespace bit
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}string(const char* str ):_size(strlen(str)), _capacity(_size){//cout string(char* str) -- 构造 endl;_str new char[_capacity 1];strcpy(_str, str);}// s1.swap(s2)void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string s){cout string(const string s) -- 深拷贝 endl;string tmp(s._str);swap(tmp);int a 0;}string(string s){cout string(string s) -- 移动拷贝 endl;swap(s);}// 赋值重载string operator(const string s){cout string operator(const string s) -- 深拷贝 endl;/*string tmp(s);swap(tmp);*/if (this ! s){char* tmp new char[s._capacity 1];strcpy(tmp, s._str);delete[] _str;_str tmp;_size s._size;_capacity s._capacity;}return *this;}string operator(string s){cout string operator(string s) -- 移动拷贝 endl;swap(s);return *this;}~string(){delete[] _str;_str nullptr;}char operator[](size_t pos){assert(pos _size);return _str[pos];}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){size_t newcapacity _capacity 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] ch;_size;_str[_size] \0;}//string operator(char ch)string operator(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str nullptr;size_t _size 0;size_t _capacity 0; // 不包含最后做标识的\0};bit::string to_string(int x){bit::string ret;while (x){int val x % 10;x / 10;ret (0 val);}reverse(ret.begin(), ret.end());return ret;}
}int main()
{kky::vectorbit::string v;v.reserve(20);bit::string s1(hello world);v.push_back(s1);cout endl;v.push_back(bit::to_string(1234));
}//void Fun(int x) { cout 左值引用 endl; }
//void Fun(const int x) { cout const 左值引用 endl; }
//void Fun(int x) { cout 右值引用 endl; }
//void Fun(const int x) { cout const 右值引用 endl; }
//
万能引用
//templatetypename T
//void PerfectForward(T t)
//{
// Fun(forwardT(t));
//}
//int main()
//{
// PerfectForward(10); // 右值
// int a;
// PerfectForward(a); // 左值
// PerfectForward(std::move(a)); // 右值
// const int b 8;
// PerfectForward(b); // const 左值
// PerfectForward(std::move(b)); // const 右值
// return 0;
//}
谢谢大家观看