C++11标准中新增的一些语法特性(上)

目录

前言

 一、统一的列表初始化

1、{}初始化

2、initiallizer_list

二、声明

1、auto

2、decltype

 3、nullptr

三、范围for循环

四、左值引用(C++98)

五、 右值引用


前言

相比于C++98标准和C++03标准,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言, C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以学习C++11标准中的新增的语法特性是很有很有必要的。

 一、统一的列表初始化

1、{}初始化

在C++98标准中,允许使用花括号{}数组或者结构体元素进行统一的列表初始值设定

C++11标准中,扩大了用大括号括起的列表(初始化列表)的使用范围, 使其可用于所有的内置类型和用户自 定义的类型使用初始化列表时,可添加等号 (=) ,也可不添加
int main()
{
	//在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	A a = { 1, 2 };//初始化结构体变量(在C语言中就支持这种初始结构体的方法)

	//C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
	//定义的类型,使用初始化列表时,可添加等号(= ),也可不添加。
	int x1 = 1;
	int x2{ 2 };
	int array1[]{ 1, 2, 3, 4, 5 };
	int array2[5]{ 0 };
	A a{ 1, 2 };
	// C++11中列表初始化也可以适用于new表达式中
	int* pa = new int[4]{ 0 };

	return 0;
}

 创建自对象时也可以使用列表初始化方式调用构造函数初始化 。

我们先写一个简单的日期类:

#include <iostream>
using namespace std;

class Date
{
public:
 Date(int year=0, int month=0, int day=0)
 :_year(year)
 ,_month(month)
 ,_day(day)
 {
 cout << "Date(int year, int month, int day)" << endl;
 }
Date(const Date& d)
{
cout<<"Date(const Date& d)"<<endl;
}
~Date()
{
cout<<"~Date()"<<endl;
}
private:
 int _year;
 int _month;
 int _day;
};

 

int main()
{
 Date d1(2022, 1, 1); //调用Data的构造函数传统初始化方法

 // C++11支持的列表初始化,这里会调用构造函数初始化
 Date d2{ 2022, 1, 2 };
 Date d3 = { 2022, 1, 3 };//一般建议把=号加上,因为可能存在特殊场合不加=的可能产生误解
 return 0;
}

2、initiallizer_list

d75782ccf6d047a9b61e2a8b1277ef71.png

4458c71405d94ad3a418daaf16f00c68.png

 initializer_list是C++11标准提供的一种类模板,头文件是<initializer_list>,其内部存储的什么类型的数据取决于模板参数T。

std::initializer_list 使用场景:
std::initializer_list 一般是作为构造函数的参数,C++11对STL中的不少容器就增std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就以使用{}对STL中的容器进行初始化。
int main()
{
 auto il = { 10, 20, 30 };//系统会默认将il识别为initializer_list类型
 cout << typeid(il).name() << endl;
 return 0;
}

5014f817a3d34a76aed1942bfa203ebb.png

 接下来我们来了解一下STL中的vector,list等利用 {}初始化 与自定义类型对象利用 {}初始化 的不同之处。

我们以vector的内部模拟实现为例,实现一下以initializer_list为参数的vector的构造接口和operator=接口,如下:

vector(initializer_list& il)
{
 reserve(il.size());
 for(auto& e : il)
 {
 push_back(e);
 }
}

vector<T>& operator=(initializer_list& il)
{
 //首先用il构造一个新的vector再将这个新vector和目标赋值vector的成员变量交换
 vector<T> tmp(il);
 std::swap(_start, tmp._start);
 std::swap(_finish, tmp._finish);
 std::swap(_endofstorage, tmp._ebdofstorage);
}
int main()
{
 Date d = {2024, 5, 1};//利用{}初始化时,{}里的值跟对应构造函数的参数个数匹配
                 //多参数构造类型转换,{2024,5,1}首先构造成一个Date类型的临时对象
                  //再将其拷贝构造给对象d,编译器优化:构造+拷贝构造->直接构造

 vector<int> v = { 1,2,3,4 };//{1,2,3,4}就是initializer_list类型的对象,对象v再调用
                         //自己内部以initializer_list类型对象为参数的构造函数构造出v
 // 使用大括号对容器赋值
 v = {10, 20, 30};//v调用以initializer_list类型的对象为参数的operator=

 list<int> lt = { 1,2 }; //和vector一样

 // 这里{"sort", "排序"}会先初始化构造一个pair对象
 map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//此时initializer_list类型对象里存储的数据类型是pair类型,和map里储存的数据类型一样。
}

 

二、声明

1、auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。 C++11 中废弃 auto 原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
int main()
{
int a = 3;
auto p = &a;
auto il = {1, 2, 3};
cout << typeid(p).name() << endl;
cout << typeid(il).name() << endl;

map<string, string> dict = { {"aaa", "bbb"}, {"ccc", "ddd"} };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();//map储存的数据类型复杂,这种用auto表示类型的要谨慎使用。
                       //避免混淆

return 0;
}

96a6d9135c5f4fdbafafdf3c8b77f0a9.png

2、decltype

关键字decltype将变量的类型声明为表达式指定的类型。
int main()
{
int i = 1;
double d = 5.5;

//类型以字符串形式获取到
cout << typeid(i).name();//typeid(i).name()是一个字符串int
                         //不可以将其当作其当作一个类型

auto ret = i * d;//ret的类型是double
decltype(ret) x;//delctype(ret)在定义对象x
vector<decltype(ret)> v;//用decltype(ret)来实例化vecto容器的参数



}

 3、nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

三、范围for循环

int main()
{
//使用范围for遍历数组
int arr[] = {1, 2, 3, 4, 5};
for(auto e : arr)
cout << e << " ";

//使用范围for遍历对象
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
for(auto e : arr)
cout << e << " ";
                   
return 0;
}

遍历对象时如果对象里存储的数据类型是自定义类型的数据最好将auto写为auto&,这样可以省去在范围for遍历对象时对对象里的自定义类型的数据拷贝。

四、左值引用(C++98)

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。 无论左值引用还是右值引用,都是给对象取别名
 
左值是一个表示数据的表达式(如变量名或解引用的指针), 我们可以获取它的地址 + 可以对它赋 值,左值可以出现赋值符号的左边和右边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给它赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

 

int main()
{
// 以下的p、a、b、*p都是左值  *p是一个表达式
int* p = new int(3);
int a = 1;
const int b = 2;//b是常变量 不可以给b再赋值,但是可以取b的地址
int c = a;//a是左值出现在了赋值符号的右边

// 以下几个是对上面左值的左值引用
int*& rp = p;
int& ra = a;
const int& rb = b;
int& pvalue = *p;
return 0;
}

五、 右值引用

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等, 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。
int main()
{
double x = 1.1, y = 2.2;

// 以下几个都是常见的右值
10;
x + y;//表达式返回值 其实表达式的返回值是一个临时变量
fmin(x, y);//函数返回值也是一个临时变量

// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修rr1,如果不想rr1被修改,可以用const int&& rr1 去引用。实际中右值引用的场景并不是这个,这个性质也不重要。
代码如下:
int main()
{
 double x = 1.1, y = 2.2;
 int&& rr1 = 10;
 const double&& rr2 = x + y;
 rr1 = 20;
 rr2 = 5.5;  // 报错
 return 0;
}

 

 

 

 

 

  • 41
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值