当前位置: 首页 > news >正文

c++现代特性

@c++现代特性

迈向现代c++

1.1被弃用的c++

注意∶弃用并非彻底不能用,只是用于暗示程序员这些特性将从未来的标准中消失,应该尽量避免使用。但是,已弃用的特性依然是标准库的一部分,并且出于兼容性的考虑,大部分特性其实会『永久』保留。

(1)不再允许字符串字面值常量赋值给一个char 。如果需要用字符串字面值常量赋值和初始化一个char ,应该使用const char 或者auto cpp,
char str = “hello world!”;//l将出现弃用警告
(2)C++98异常说明、unexpected_handler、 set_unexpected()等相关特性被弃用,应该使用noexcept;
(3)auto_ptr被弃用,应使用
unique_ptr*;
(4)register关键宇被弃用,可以使用但不再具备任何实际含义。bool类型的+操作被弃用。;
(5)如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了;
(6)C语言风格的类型转换被弃用(即在变量前使用(convert_type)),应该使用static_cast、reinterpret_cast、const_cast来进行类型转换。
(7)特别地,在最新的C++17标准中弃用了一些可以使用的C标准库,例如、. 与等

1.2与C的兼容性

从现在开始,你的脑子里应该树立『C++不是C的一个超集』这个观念(而且从一开始就不是,后面的进一步阅读的参考文献中给出了C++98和C99之间的区别)。在编写C++时,也应该尽可能的避免使用诸如void*之类的程序风格。而在不得不使用C时,应该注意使用extern "c”这种特性,将C语言的代码与C++代码进行分离编译,再统一链接这种做法,例如:

// foo.h
extern "C"
{
#endif
int add(int x,int y);
#ifdef __cplusplus
}
#endif
// foo.c
int add(int x,int y)
{
return x+y;
}
//1.2cpp
#include "foo.h"
#include <iostream>
#include <functional>
int main()
{
[out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){
out.get()<<".\n";}()
return 0;
}
//跟我一起学习后续看

编译

gcc foo.c -o foo.o
g++ 1.2.cpp foo.o -std=c++2a -o 1.2

语言可用性

当我们声明、定义一个变量或者常量,对代码进行流程控制、面向对象的功能、模板编程等这些都是运行时之前,可能发生在编写代码或编译器编译代码时的行为。为此,我们通常谈及语言可用性,是指那些发生在运行时之前的语言行为。

2.1常量

nullptr(代替NULL)
nullptr 出现的目的是为了替代 NULL。在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void)0),有些则会直接将其定义为0;
C++ 不允许直接将 void * 隐式转换到其他类型但如果编译器尝试把 NULL 定义为 ((void)0),那么在下面这句代码中

const *char=NULL;
//没有了 void * 隐式转换的 C++ 只好将 NULL 定义为 0。而这依然会产生新的问题,将 NULL 定义成 0 将导致 C++ 中重载特性发生混乱。考虑下面这两个 foo 函数:
//
#include <iostream>
#include <type_traits>

void foo(char*);
void foo(int);

int main()
{
if(if (std::is_same<decltype(NULL), decltype(0)>::value)
std::cout << "NULL == 0" << std::endl;
if (std::is_same<decltype(NULL), decltype((void*)0)>::value)
std::cout << "NULL == (void *)0" << std::endl;
if (std::is_same<decltype(NULL), std::nullptr_t>::value))
std::cout << "NULL == nullptr" << std::endl;
foo(0); // 调用 foo(int)
// foo(NULL); // 该行不能通过编译
foo(nullptr); // 调用 foo(char*)
return 0;
}
void foo(char *) {
std::cout << "foo(char*) is called" << std::endl;
}
void foo(int i) {
std::cout << "foo(int) is called" << std::endl;
}

foo(int) is called
foo(char*) is called

此外,在上面的代码中,我们使用了 decltype 和 std::is_same 这两个属于现代 C++ 的语法,简单来说,decltype 用于类型推导,而 std::is_same 用于比较两个类型是否相等

constexpr
C++ 本身已经具备了常量表达式的概念,比如 1+2, 3*4 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:

#include<iostream>
#define LEN 10


int len_foo()
{
    int i=2;
    return i;
}
constexpr int len_foo_constexpr()
{
    return 5;
}
constexpr int fibonacci(const int n)
{
    return n==1||n==2?1: fibonacci(n-1)+fibonacci(n-2);//递归算法
}
int main()
{
    char arr_1[10];
    char arr_2[LEN];

    int len=10;
    //char arr_3[len];//非法
    const int len_2=len+1;
    constexpr int len_2_constexpr=1+2+3;

    char arr_4[len_2];//在vscode支持这个是合法的
    char arr_4[len_2_constexpr];

    char arr_6[len_foo_constexpr()+1];
    //char arr_6[len_foo()+1];//非法

    std::cout << fibonacci(10) << std::endl;
// 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
}

的例子中,char arr_4[len_2] 可能比较令人困惑,因为 len_2 已经被定义为了常量。为什么char arr_4[len_2] 仍然是非法的呢?这是因为 C++ 标准中数组的长度必须是一个常量表达式,而对
于 len_2 而言,这是一个 const 常数,而不是一个常量表达式,因此(即便这种行为在大部分编译器中都支持,但是)它是一个非法的行为,我们需要使用接下来即将介绍的 C++11 引入的 constexpr 特性来解决这个问题;而对于 arr_5 来说,C++98 之前的编译器无法得知 len_foo() 在运行期实际上是返回一个常数,这也就导致了非法的产生

2.2变量及其初始化

if/switch 变量声明强化
在传统 C++ 中,变量的声明虽然能够位于任何位置,甚至于 for 语句内能够声明一个临时变量int,但始终没有办法在 if 和 switch 语句中声明一个临时的变量。例如:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<int>vec={1,2,3,4};
    //c++ 17以前
    const std::vector<int>::iterator itr=std::find(vec.begin(),vec.end(),2);
    if(itr!=vec.end())
    {
      *itr=3;
    }
    // 需要重新定义一个新的变量
     const std::vector<int>::iterator itr2 = std::find(vec.begin(), vec.end(), 3);
     if (itr2 != vec.end()) 
     {
       *itr2 = 4; 
     }
     // 将输出 1, 2, 3, 4
     for (std::vector<int>::iterator element = vec.begin(); element != vec.end(); ++element)
     std::cout << *element << std::endl;
     
}

在上面的代码中,我们可以看到 itr 这一变量是定义在整个 main() 的作用域内的,这导致当我们需要再次遍历整个 std::vectors 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 if(或 switch)中完成这一操作

if (const std::vector<int>::iterator itr = std::find(vec.begin(), vec.end(), 3);
itr != vec.end()) {
*itr = 4; }

初始化列表
初始化是一个非常重要的语言特性,最常见的就是在对象进行初始化时进行使用。在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、POD (Plain Old Data,即没有构造、析构和虚函数的类或结构体)类型都可以使用 {} 进行初始化,也就是我们所说的初始化列表。而对于类对象的初始
化,要么需要通过拷贝构造、要么就需要使用 () 进行。这些不同法都针对各自对象,不能通用

例如

#include <iostream>
#include <vector>
#include<iostream>
class Foo {
public:
int value_a;
int value_b;
Foo(int a, int b) : value_a(a), value_b(b) {}
};
int main() {
// before C++11
int arr[3] = {1, 2, 3};
Foo foo(1, 2);
std::vector<int> vec = {1, 2, 3, 4, 5};
std::cout << "arr[0]: " << arr[0] << std::endl;
std::cout << "foo:" << foo.value_a << ", " << foo.value_b << std::endl;
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {

std::cout << *it << std::endl;
}
return 0; }

//C++ 新特性 Foo foo {2,3}
为了解决这个问题,C++11 首先把初始化列表的概念绑定到了类型上,并将其称之为 std::initializer_list,
允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初
始化方法提供了统一的桥梁,例如:

#include<initializer_list>
#include<vector>
#include<iostream>

class MagicFoo
{
public:
std::vector<int>vec;
MagicFoo(std::initializer_list<int> list)
{
    for(std::initializer_list<int>::iterator it=list.begin();
    it!=list.end();++it)
    vec.push_back(*it);
}
void foo(std::initializer_list<int> list)
{
    std::cout<<"magicFoo:";
    for(std::initializer_list<int>::iterator itr=list.begin();
    itr!=list.end();itr++)
    {
        std::cout<<*itr<<",";
    }
    std::cout<<std::endl;
}

};
int main()
{
    MagicFoo magicfoo={1,2,3,4,5};
    std::cout<<"magicFoo:";
    for(std::vector<int>::iterator itr=magicfoo.vec.begin();
    itr!=magicfoo.vec.end();itr++)
    {
        std::cout<<*itr<<",";
    }
    std::cout<<std::endl;
    magicfoo.foo({6,7,8,9});
    return 0;

}

结构化绑定
结构化绑定提供了类似其他语言中提供的多返回值的功能。在容器一章中,我们会学到 C++11 新增了 std::tuple 容器用于构造一个元组,进而囊括多个返回值。但缺陷是,C++11/14 并没有提供一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 std::tie 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型,非常麻烦。

#include<iostream>
#include<tuple>
#include<typeinfo>

std::tuple<int,double,std::string> f()
{
    return std::make_tuple(1,2.3,"456");
}
int main()
{
  auto  [x, y, z] = f();
  std::cout << x << ", " << y << ", " << z << std::endl;
  return 0;
}

// 虽然有错误 但是能运行!!!(只需声明-std=c++11)

2.3类型推导

auto(只对变量)
auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并
存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着
register 被弃用(在 C++17 中作为保留关键字,以后使用,目前不具备实际意义),对 auto 的语义变
更也就非常自然了。

#include <initializer_list>
#include <vector>
#include <iostream>
class MagicFoo {
public:
std::vector<int> vec;
MagicFoo(std::initializer_list<int> list) {
// 从 C++11 起, 使用 auto 关键字进行类型推导
for (auto it = list.begin(); it != list.end(); ++it) {
vec.push_back(*it);
} }
};
int main() {
MagicFoo magicFoo = {1, 2, 3, 4, 5};
std::cout << "magicFoo: ";
for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) {
std::cout << *it << ", "; }
std::cout << std::endl;
return 0; }

*注意:auto 不能用于函数传参,因此下面的做法是无法通过编译的(考虑重载的问题,我们应该使用模板)
auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型

decltype
decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和ypeof 很相似:
decltype(表达式)
用例:
auto x = 1;
auto y = 2;
decltype(x+y) z;

你已经在前面的例子中看到 decltype 用于推断类型的用法,下面这个例子就是判断上面的变量 x,y, z 是否是同一类型:

if (std::is_same<decltype(x), int>::value)
std::cout << "type x == int" << std::endl;
if (std::is_same<decltype(x), float>::value)
std::cout << "type x == float" << std::endl;
if (std::is_same<decltype(x), decltype(z)>::value)
std::cout << "type z == type x" << std::endl;

尾返回类型推导
你可能会思考,在介绍 auto 时,我们已经提过 auto 不能用于函数形参进行类型推导,那么 auto能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:

template<typename T, typename U>
auto add(T x, U y) {
return x+y
}
auto q = add3<double, int>(1.0, 2);
std::cout << "q: " << q << std::endl;

2.4控制流

if_constexpr
正如本章开头出,我们知道了 C++11 引入了 constexpr 关键字,它将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高?C++17 将 constexpr 这个关键字引入到 if 语句中,允许在代码中声明常量
表达式的判断条件,考虑下面的代码:

#include<iostream>
#include<typeinfo>
#include <type_traits> 

template<typename T>
auto print_type_info(const T&t)
{
    if constexpr(std::is_integral<T>::value)
    {
        return t+1;
    }
    else
    {
        return t+0.0001;
    }
}
int main()
{
  std::cout<<print_type_info(5)<<std::endl;
  std::cout<<print_type_info(3.14)<<std::endl;
  return 0;
}

实际流程

int print_type_info(const int& t) {
return t + 1; }
double print_type_info(const double& t) {
return t + 0.001; }

区间for迭代

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> vec={1,2,3,4};
    for(auto element:vec)
    {
        std::cout << element << std::endl; // read only
    }
    for (auto &element : vec) {
element += 1; // writeable

}
for (auto element : vec)
std::cout << element << std::endl; // read only
}

相关文章:

  • 【学习笔记】go协程和通道
  • 十二、bootstrap前端开发框架
  • USACO Training 1.3 Milking Cows
  • < Linux > 进度条小程序 + git三板斧
  • 惊险的十天
  • 【数据结构初阶】堆堆的实现堆排序TOP-K
  • 自动驾驶技术综述1:自动驾驶算法软件架构介绍
  • PySpark数据分析基础:pyspark.mllib.regression机器学习回归核心类详解(一)+代码详解
  • 线程池-手写线程池
  • TCP延申
  • C进阶——指针详解
  • Serverless 架构下的 AI 应用开发:入门、实战与性能优化
  • 网络整体框架介绍
  • 利用MCU实现制作一台蓝牙控制小车方法
  • pythonGUI(二)基本元素之一
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • 【Linux系统编程】快速查找errno错误码信息
  • 【知识碎片】第三方登录弹窗效果
  • 〔开发系列〕一次关于小程序开发的深度总结
  • C++类中的特殊成员函数
  • ECMAScript入门(七)--Module语法
  • JavaScript设计模式系列一:工厂模式
  • JS题目及答案整理
  • Mybatis初体验
  • Python进阶细节
  • WinRAR存在严重的安全漏洞影响5亿用户
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 百度地图API标注+时间轴组件
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 从零搭建Koa2 Server
  • 电商搜索引擎的架构设计和性能优化
  • 读懂package.json -- 依赖管理
  • 翻译--Thinking in React
  • 马上搞懂 GeoJSON
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 微服务核心架构梳理
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 我的面试准备过程--容器(更新中)
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • 一份游戏开发学习路线
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • ​Java并发新构件之Exchanger
  • #define、const、typedef的差别
  • #vue3 实现前端下载excel文件模板功能
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (4)STL算法之比较
  • (poj1.2.1)1970(筛选法模拟)
  • (十)T检验-第一部分
  • (未解决)macOS matplotlib 中文是方框
  • (转)C#调用WebService 基础
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET 事件模型教程(二)
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .NET中两种OCR方式对比