您的位置:首页 > 游戏 > 手游 > 微信小程序网站建设公司_大连网建科技_2023北京封控了_网络营销方案设计范文

微信小程序网站建设公司_大连网建科技_2023北京封控了_网络营销方案设计范文

2025/4/19 6:07:25 来源:https://blog.csdn.net/2401_87944878/article/details/147116766  浏览:    关键词:微信小程序网站建设公司_大连网建科技_2023北京封控了_网络营销方案设计范文
微信小程序网站建设公司_大连网建科技_2023北京封控了_网络营销方案设计范文

目录

引入

C++11新特性

统一的初始化列表

一切皆可{}初始化

std::initializer_list

统一的声明

auto

decltype

nullptr

范围for

STL新增容器

STL新增容器接口

左值引用和右值引用

左值和右值

左值引用和右值引用

右值引用的优势(移动语义)

右值引用的使用场景

move谨慎使用

模板中的万能引用

完美转发

类型转换

C++类型转化

static_cast

reinterpret_cast

const_cast

dynamic_cast


引入

C++11是C++的一个大变革,增加了大约有140种新特性,其中有不少对于我们的代码编写效率帮助很大,也有一些特性很鸡肋;C++11是委员会经过了8年积淀才发行的,其中添了不少C++的“坑”,此篇文章将对C++11的新特性进行介绍和剖析。

C++11新特性

统一的初始化列表

一切皆可{}初始化

C++98规定了可以使用{}对内置类型或结构体元素进行初始化,在C++11后将{}的适用范围进行了扩充,允许{}对所有内置类型和自定义类型进行初始化,并且如果使用{}进行初始化可以不加赋值符号=。

struct Point
{int x;int y;
};int a = 1;
int a1 = { 1 };
int a2{ 1 };int arr[]{ 1,2,3,4 };Point p1 = { 1,1 };
Point p2{ 1,1 };

以上初始化的方式都是被允许的。

std::initializer_list

initializer_list是C++中引入的一个新特性,其保证了一切皆可{}初始化。可以理解为代码中的{}会被识别为initializer_list类型,然后通过该类型再去进行赋值或构造

为了验证{}的类型是initializer_list类型,可以使用typeid(e).name()来打印类型。

auto e = { 1,2,3,4,5,6,7 };
cout << typeid(e).name() << endl;

C++11为每个容器的构造函数都增加了用initializer_list作为参数进行构造,这也是为什么容器可以使用{}进行初始化。

vector:

set:

map:

对于自定义类型用{}进行初始化,以及vector用{}进行初始化的原理是不一样的,vector的{}中元素个数是可以不固定的,而对于一些我们自己实现的自定义类型的{}中元素的个数是固定的;比如一下Point类型。

struct Point
{int x;int y;
};Point p1 = { 1,1 };
vector<int> v={1,2,3,4,5,6,7};

p1的{}是隐式类型转化,先用{}中的参数进行构造Point对象,再进行拷贝构造,编译器会将两次构造优化为一次;v是用initialiter_list进行构造的,不是使用拷贝构造。


统一的声明

auto

C++11对auto的作用进行了修改,auto可以实现类型推断,变量的类型需要显示的写,让编译器自己推。

不论是内置类型,自定义类型,还是函数返回值auto都能推导。但是注意:auto不能作为函数的参数。 

auto a = new int(1);
auto b = &a;
auto c = strcpy;

decltype

decltype(expression)

decltype是C++11中新增的关键字,其能够获得表达式的类型。表达式可以是变量,也可以是表达式。

decltype(&x) aa;  //aa的类型是int*
decltype(x * y) bb; //bb的类型是doouble

nullptr

C++11引入了nullptr来代替NULL;NULL实际上是0,是整形,这也就导致了在进行函数传参的时候NULL会匹配整形而不是指针。引入nullptr就不会出现分歧了,nullptr本质是(void*)0


范围for

C++引入范围for这一新概念,对于数组以及部分容器的遍历的方式进行了简化。

int arr[] = { 1,2,3,4,5,6,7 };
for (auto e : arr)
{cout << e << endl;
}vector<int> v = { 1,2,3,4,5,6,7,8,8 };
for (auto e : v)
{cout << e << endl;
}

范围for的底层还是依靠迭代器来实现的,所以没有实现迭代器的容器是不能使用范围for的。


STL新增容器

以上用红色框起来的都是新增容器,其中unordered_set和unordered_map底层是哈希表,关于哈希表的底层实现在《哈希表的实现》中已经进行了深度剖析,此处就不再赘述了。

哈希表(闭散列)的实现-CSDN博客文章浏览阅读921次,点赞43次,收藏44次。通过除留余数法确定数据位置,再根据闭散列的方法解决哈希冲突。本篇博客对哈希表闭散列的实现进行细致剖析,帮助读者理解哈希表性能的优缺点。 https://blog.csdn.net/2401_87944878/article/details/146922720哈希表(开散列)的实现-CSDN博客文章浏览阅读518次,点赞24次,收藏26次。通过除留余数法确定数据位置,再根据开散列的方法解决哈希冲突。本篇博客对哈希表开散列的实现进行细致剖析,帮助读者理解哈希表性能的优缺点。 https://blog.csdn.net/2401_87944878/article/details/146923387array容器,其底层就是数组,只是对数组进行了封装。

int arr1[10] = { 0 };
array<int, 10> a2{ 0 };

以上arr1和arr2是完全一样的,唯一区别就是array对越界的检查更加严格,这个类在我们实际写代码中用的很少,没有数组的简洁,直观,也没有vector丰富的接口,array的使用很鸡肋。

forward_list容器,其底层是单链表,这个容器也很鸡肋,库中forward_list不支持尾插和任意位置前面插入,因为这两个函数实现的时间复杂度是O(N);forward_list的使用不如list,所以这个容器的使用也是比较少的。

STL新增容器接口

C++11为每个容器的迭代器增加了新接口:cbegin()和cend();

这俩个接口专门用来返回const迭代器,但是begin()中已经重载了返回const迭代器的接口,所以cbegin和cend的引入是完全没有必要的。


左值引用和右值引用

左值和右值

C++11引入了左值和右值的概念;

左值:可以获取地址的值被称为左值,左值可以在赋值符号=的左边也可以在右边;

右值:不能获取地址的值被称为右值,右值只能在赋值符号=的右边;右值通常有常量,表达式,函数返回值...

例外:常量字符串属于左值,其可以取地址。

const char* arr = "hello world";

左值引用和右值引用

我们通常使用的都是左值引用。

左值引用:在类型后加&即可。

int a = 10;
int& b = a;
int* p = new int(1);
int*& pp = p;

右值引用:在类型后加&&即可。

double x = 1.8, y = 2.3;
double&& sum = x + y;

左值引用可以引用右值:使用const&;

const int& a1 = 10;
double x = 1.8, y = 2.3;
const double& sum = x + y;

右值引用可以引用左值:move()仿函数,将左值转化为右值;

double x = 1.8, y = 2.3;
double&& x1 = move(x);
double&& y1 = move(y);

右值引用的优势(移动语义)

右值引用作为参数和返回值可以提高效率。

当需要将一个自定义类型的对象返回并接受的时候,使用右值引用可以减少拷贝。

以上代码编译器会优化:将ret视为右值,因为其是将亡值,即将被销毁。 

根据以上图可以看出,如果对自定义类型进行返回的时候会先进行拷贝再去赋值。

上面代码中返回的ret即将被销毁,所以ret也被称为将亡值。ret是将亡值,能否直接用ret来进行赋值呢??不去创建临时对象,这样也能大大提高效率。

此时就可以使用右值引用,通过引用将亡值来避免再创建临时对象,而是直接进行赋值,这也种方法被称为移动赋值。

对赋值函数进行重载即可,将参数修改为右值引用,代码模拟实现如下。

//赋值重载函数
string& operator=(const string& s)
{cout << "赋值" << endl;string tmp(s);swap(tmp);return *this;
}//移动赋值
string& operator=(string&& s)
{cout << "移动赋值" << endl;swap(s);return *this;
}

有移动赋值,同时也有移动构造。

//拷贝构造
string(const string& s):_str(nullptr)
{cout << "深拷贝" << endl;string tmp(s._str);swap(tmp);
}//移动拷贝构造
string(string&& s):_str(nullptr)
{cout << "移动拷贝" << endl;swap(s);
}

左值引用的核心是:减少拷贝,提高效率;

右值引用的核心是:进一步减少拷贝,解决左值引用没有解决的传值返回问题。

右值引用的使用场景

自定义类型中需要深拷贝的类,且有对该类型进行船只返回的场景。

浅拷贝的类不需要实现移动语义,编译器会自动生成;而且浅拷贝的拷贝代价也不大,没有意义。

右值引用的核心在于:不进行深拷贝,而是进行已有资源的交换。

库中的容器里,基本上每一个插入,构造,赋值都会重载移动语义来提高效率; 

move谨慎使用

库中会对移动语义进行重载,那使用move将左值变为右值不就可以提高效率吗??

理论上是这样的,但是被move且被使用的参数会自动销毁。

string s1 = "hello";
vector<string> l;
l.push_back(move(s1));

以上代码,确实提高了list插入的效率,但是插入后,s1会调用析构函数。


模板中的万能引用

template<typename T>
void Func(T&& val)
{cout << val << endl;
}

模板中的&&并不仅仅表示右值引用,其表示万能引用,既可以引用左值又能引用右值。

实参是左值,T&&实例化成左值引用,这一现象称为折叠;

实参是右值,T&&实例化成右值引用。

万能引用也包含const 引用。


完美转发

在C++11中规定了:右值引用变量的属性会被编译器识别为左值。

void Func1(int&& val)
{cout << val << endl;
}

意思就是,上面代码中的val不再是右值,而是左值。

如果希望保持val的右值属性,在使用val的时候,使用forward<T>(val)替代,这样向下一个函数传递的时候依旧保留的右值的属性。

完美转化常用于:在多个函数之间相互调用的时候,需要保留参数的右值属性。

比如:在list的插入中,如果传过去的是右值属性的自定义类型,进行构造的时候还需要保持其右值属性,此时就可以使用完美转化来实现。

//尾插
void push_back(T&& val)
{ListNode* newnode = new ListNode(std::forward<T>(val));newnode->_next = _phead;newnode->_prev = _phead->_prev;_phead->_prev->_next = newnode;_phead->_prev = newnode;
}

对ListNode进行构造时,仍然需要保留右值属性,从而调用string的移动构造。

//初始化节点
ListNode(T&& val):_val(std::forward<T>(val)), _next(this), _prev(this)
{}

tips:只要是右值变量需要往下传,且要保留右值属性就可以使用完美转发来实现。

类型转换

类型转换分为显式类型转化和隐式类型转化。

1)有关联的类型之间可以转化;

2)指针可以转为整形,地址也只是一个编号;

3)单参数构造支持隐式类型转化;

4)自定义类型能否隐式类型转换看构造函数,如果不希望进行隐式类型转化可使用explict进行修饰。

void test_07()
{const int a = 10;const int* pa = &a;int* paa = (int*)pa;*(paa) = 20;cout << *paa << endl;cout << a << endl;
}

上图是对const类型进行隐式类型转化,来实现对const类型变量的修改。

如图是代码运行的结果,并没有看到a发生了改变。这是因为a实际上改变了,但是编译器在第一次拿到a的时候认为其不会改变,将其放在寄存器中存储,每次也从寄存器中拿。

要先让编译器从a存放的地址处那,可以使用volatile对const类型进行修饰。

void test_07()
{volatile const int a = 10;volatile const int* pa = &a;int* paa = (int*)pa;*(paa) = 20;cout <<"*paa: " << *paa << endl;cout << "a: " << a << endl;
}

C++类型转化

C++对类型转换进行了分类,分为4种;每种使用一个关键字实现转化,使得类型转化有了可视性。

static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。

	double d = 1.2;int x = static_cast<int>(d);

指针和整形不能进行static_cast转化。

reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。

可用于不相关类型的转化。

int a = 10;
int* pa = &a;
int b = reinterpret_cast<int>(pa);

const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值。

volatile const int r = 10;
int* pr = const_cast<int*>(&r);

dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)。

用派生类指针可以转化为基类指针,则被称为向上转化,遵循复制兼容原则。

用基类指针可以转化为派生类类指针,则被称为向下转化,只有当基类指针指向派生类的时候才能实现转化。

当对基类指针和派生类指针进行转化的时候,建议使用dynamic_cast。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com