C++11新特性(语法糖,新容器)

简介: C++11新特性(语法糖,新容器)

距离C++11版本发布已经过去那么多年了,为什么还称为新特性呢?因为笔者前面探讨的内容,除了auto,范围for这些常用的,基本上是用着C++98的内容,虽说C++11已经发布很多年,却是目前被使用最广泛的版本。因为新版本的发布,编译器要很长时间才能支持新语法新特性,有一句话说得挺好,C/C++之所以强大,是因为它们的编译器很强大。C++23版本已经发布,但真想普遍使用不知道还得多少年呢?尤其是C/C++这种基本用于底层架构的语言,底层代码不敢改,也不愿意改。但不管怎么说,日后的开发是离不开C++11的,C++11可以说是C++历年来最大的版本更新,多了很多非常强大的东西,但也多了很多鸡肋,内容太多太多,不可能全部提完。故而笔者着重讲述必须要掌握的,用处一般的就简单提提,剩下的,大家碰到了再查,C++11新特性部分将分为多篇内容,让我们开始吧


array & forward_list

C++11新更新了两个容器,分别是array,forward_list

array本质上是对C语言的数组进行了一层封装,加上了模板,迭代器等功能

array的定义如上,它也是一个静态数组,声明时需要指定类型和大小,整体使用下来并不比C语言的数组强多少,唯一比C语言数组好用的就是会检查下标,我们知道C语言的数组越界读是不会报错的,越界写也仅是抽查,所以不小心写错下标可能会造成一些问题

但是vector也能检查下标呀,我为何不使用vector呢,况且vector功能比array强大多了,所以array算是C++11里比较鸡肋的一个

forward_list是新推出的单链表,在C++推出STL库时,只有list,list的底层实现我们前面也探讨过了,本质是双链表。forward_list算是list的青春版,因为双链表的节点要链接前后,所以会比单链表多存储一个指针大小,如果在内存空间特别紧凑,并且单链表足够满足使用场景,可以考虑使用forward_list,其基础功能如下,只能头插和尾插

auto,typeid,decltype

auto,typeid,decltype都是C++11推出的和类型相关的特性功能

auto是语法糖,为什么叫语法糖,因为让人用的舒服,auto是很好用的,在一些场景下,有的类型被层层封装,导致其类型名很长,有了auto就不用我们自己去推断类型

这仅是auto应用场景之一,后面我们会见到大量使用auto的场景,例如范围for就是使用auto来推导要迭代的类型,auto是根据初始值来进行类型推导的,也就是说你要使用auto推导必须给出一个初始值,否则没有意义

auto test;
//没有初始值,无意义的推导

如果你想把某个变量或者表达式的类型给打印出来看看,那么就可以使用typeid,具体用法如下图,typeid是一个类,把想知道类型的变量或者表达式传过去,调用name

调用name之后,会返回一个字符串,字符串的内容即是推导出的类型

如果现在提一个过分一点的要求,我不仅要你推导出类型,还要用你推导的结果再声明出同类型的对象,使用typeid是做不到了,因为它返回的是字符串,不可能作为类型声明符,但是decltype可以做到,看看decltype是如何使用的

上图用decltype推导出test_1的类型,并用推导结果声明了变量test_2

这里的应用场景看着比较傻,类型推导在模板里应用比较广泛,如下

按照常规方法是不好解决的,因为我们不明确 T1 和 T2的类型到底是什么,就没有办法给变量ret_val确定类型,但是现在我们有了auto 和 decltype就很容易解决这个问题

范围for

范围for也是C++11中使用体验不错的语法糖,范围for本质就是循环遍历对象,范围for的出现帮我们节省了不少时间,如下列程序

不仅内置类型可以用,容器也是可以使用范围for的,如下图程序

只要容器支持迭代器,那么它就可以使用范围for,因为容器使用范围for本质还是在调用容器中实现的迭代器 ,但是范围for使用起来方便不少,使用汇编可以一窥细节

//测试代码
int main()
{
  vector<int> test;
  for (int i = 1; i <= 10; i++)
  {
    test.push_back(i);
  }
  //迭代器
  auto it = test.begin();
  while (it != test.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  //范围for
  for (auto& t : test)
  {
    cout << t << " ";
  }
  return 0;
}

可以看出两者都是在调用迭代器,我们手动调用的begin()和end()是经过封装过的,范围for则直接调用迭代器的底层实现,在容器中范围for本质和迭代器没区别,但是用的更省心

使用范围for时,如果auto推导类型后跟上&,就是引用调用,可以读写原数据

如果没有跟上&,那么就是传值调用,修改并不会影响原数据

如果只想读不想写的话推荐 const auto & 这种写法,减少拷贝消耗

统一的列表初始化

平时我们给C语言的数组进行初始化操作时,可以使用一对花括号进行赋值" { } "

//使用花括号给数组进行初始化赋值
int arr[] = {1, 2, 3, 4, 5, 6};

其实这样的初始化还挺好用的,我们平时使用vector进行赋值时就没那么方便,如果赋值有顺序还好,没顺序的话还要自己手动push_back,于是C++11提出了统一初始化列表,也就是说让STL中的容器也够支持使用" { } "来进行赋值,如下图

可不仅仅是只有vector能使用,其它容器也是支持的,如下图的list和map

你甚至可以直接不写赋值符号 "=" ,同样能完成初始化,不仅可以用于数组,容器,对于单个内置类型也是可以使用{}来初始化的,如下图

除了内置类类型和STL库中的容器支持这种初始化,自定义类型也是支持的

需要注意的是,使用{}初始化自定义类型,是去调用自定义类型的构造函数,由此看来,C++11之后确实可以统一使用{ }来初始化,这也是为什么叫统一的列表初始化

统一的列表初始化原理

像数组可以使用{}进行初始化可以理解,毕竟原生的编译器就支持,但是容器也支持{}初始化是怎么做到的呢? 其实实现原理也不难,我们以vector为例,既然是初始化,那我们就紧盯构造函数,打开资料库查查C++11的构造函数有没有发生变化

果然,我们发现,构造函数中多了一个initializer list,而这就是统一列表初始化实现的秘密,可以看出该构造函数使用的是initializer_list,我们查一下这是个什么东西


我们大概能理解,使用{ }时,会将里面的内容放到initializer_list容器中存放起来,然后把该容器内的值拷贝给vector,如此就完成了初始化操作,知道原理了,我们可以自己尝试给之前写的vector也添加这么个功能

vector(std::initializer_list<T> _lt)
{
  _begin = new T[_lt.size()];
  _finish = _begin + _lt.size();
  _end = _begin + _lt.size();
  auto _vtp = _begin;
  auto _ltp = _lt.begin();
  while (_ltp != _lt.end())
  {
    *_vtp = *_ltp;
    _vtp++;
    _ltp++;
  }
}

这个只能构造,若想拷贝赋值的话,可以重载一个operator=( std::initializer_list<T> _lt)

具体定义如下,别忘了包含头文件initializer_list

vector<T>& operator=(std::initializer_list<T> _lt)
{
  vector<T> tmp(_lt);
  std::swap(_start, tmp._start);
  std::swap(_finish, tmp._finish);
  std::swap(_endofstorage, tmp._endofstorage);
  return *this;
}

nullptr

出现nullptr是因为C++错误的将库中的NULL定义为0,这就会导致很多问题,因为NULL不仅表示一个空指针,还是一个字面常量值0,如下述代码

简单写个代码验证其中的危害性

int main()
{
  int p = NULL;
  cout << p << endl;
  int t = 0;
  if (t == NULL) cout << "t是一个空指针" << endl;
  return 0;
}

运行结果如上,t被错误的判断为一个空指针,事实上,t连指针都不是。错已经错了,直接改NULL会影响原先的代码,为了解决这个问题,C++又推出了nullptr来代替NULL

nullptr则是正确的定义 (void*)0,所以C++中请使用nullptr来表示空指针

至此,本篇文章就结束了,总体下来还算轻松,很多内容都是见过多次的老朋友了,其中也多了不少好用的特性,像统一初始化列表,范围for这种就快快用起来吧

目录
相关文章
|
7天前
|
C++ 容器
C++中自定义结构体或类作为关联容器的键
C++中自定义结构体或类作为关联容器的键
13 0
|
1天前
|
安全 NoSQL Redis
C++新特性-智能指针
C++新特性-智能指针
|
8天前
|
C++ 容器
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树
18 5
|
7天前
|
存储 C++ 索引
|
28天前
|
存储 C++ 容器
开发与运维数组问题之C++标准库中提供数据容器作为数组的替代如何解决
开发与运维数组问题之C++标准库中提供数据容器作为数组的替代如何解决
37 5
|
5天前
|
安全 编译器 容器
C++STL容器和智能指针
C++STL容器和智能指针
|
7天前
|
存储 缓存 NoSQL
【C++】哈希容器
【C++】哈希容器
|
8天前
|
关系型数据库 C++ 容器
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
15 0
|
1月前
|
安全 程序员 C++
C++一分钟之-C++中的并发容器
【7月更文挑战第17天】C++11引入并发容器,如`std::shared_mutex`、`std::atomic`和线程安全的集合,以解决多线程中的数据竞争和死锁。常见问题包括原子操作的误用、锁的不当使用和迭代器失效。避免陷阱的关键在于正确使用原子操作、一致的锁管理以及处理迭代器失效。通过示例展示了如何安全地使用这些工具来提升并发编程的安全性和效率。
21 1
|
1月前
|
存储 C++ 索引