c++新特性总结一

C++演化是从c++98(1.0)到c++03到c++11(2.0)到c++14,当然后面不断更新。从1.0到2.0的变化比较重要。
一 2.0的新增头文件
比如2.0新增的头文件有:

#include <type_traits>
#include <unordered_set>
#include <forward_list>
#include <array>
#include <tuple>
#include <regex>
#include <thread>

这些头文件的命名空间都是std
c++的头文件可以不用写.h,比如c中的#include<stdio.h>,在c++中可以写成

#include<cstdio>

在这里插入图片描述

二. 不同版本对应的__cplusplus宏

#include<iostream>
using namespace std;
int main()
{
    cout << __cplusplus <<endl;//两个下划线
    return 0;
}

首先通过默认的g++编译

aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ g++ main.cpp -o main
aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ ./main 
199711

然后指定c++11

aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ g++ main.cpp -std=c++11 -o main
aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ ./main 
201103

当然也可以指定c++14

aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ g++ main.cpp -std=c++14 -o main
aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ ./main
201402

二. 可变参数模板
variadic templates用与数量不定的模板参数,用三个点…来表示,…其实就是一个包(pack)。
当…用在模板参数,就叫模板参数包;
如下面的template<typename T, typename… Types>
当…用在函数参数类型,叫做函数参数类型包;
如下面的void print(const T& firstArgs, const Types&… args)
当…用在函数参数,叫做函数参数包;
如下面的 print(args…);。
这个用来做递归比较方便。
例如:

#include <iostream>
#include <bitset> //for use bitset template
using namespace std;
void print()
{
}

template<typename T, typename... Types>
void print(const T& firstArgs, const Types&... args)
{
    cout <<"len of aegs is "<<sizeof...(args)<<endl;
    cout << firstArgs <<endl;// print first argument
    print(args...);// call print for remaining arguments
}

int main()
{
    print(7.5, "hello", bitset<16>(377), 42);
    return 0;
}

运行:

aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ g++ variadicTemplate.cpp -std=c++11 
aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ ./a.out 
len of aegs is 3
7.5
len of aegs is 2
hello
len of aegs is 1
0000000101111001
len of aegs is 0
42

其中bitset的用法参考https://www.cnblogs.com/magisk/p/8809922.html
这两行

template<typename T, typename... Types>
void print(const T& firstArgs, const Types&... args)

是告诉编译器,可以传入任意类型的参数,而且参数的个数也是任意的。
再看下面的程序,两个模板函数竟然可以共存,没有报错

#include <iostream>
#include <bitset> //for use bitset template
using namespace std;
void print()
{
}

template<typename T, typename... Types>
void print(const T& firstArgs, const Types&... args)
{
    cout <<"len of aegs is "<<sizeof...(args)<<endl;
    cout << firstArgs <<endl;// print first argument
    print(args...);// call print for remaining arguments
}

template<typename... Types>
void print(const Types&... args)
{
    cout << "called"<<endl;
}

int main()
{
    print(7.5, "hello", bitset<16>(377), 42);
    return 0;
}

运行后和上面的结果完全相同。实际上两种都符合,但是第一种比较特化,第二种比较泛话,所以选择特化的。

实际上这种变长模板非常适合用于递归函数调用。
再举个例子,看下图,这是一个万能的散列函数:

在这里插入图片描述

观察这个图的调用顺序,从自定义的类中的hash_val,怎么开始调用的。

除了完成递归函数调用外,变长模板还可以完成递归继承,比如tuple的实现就是这样的,自己继承自己
在这里插入图片描述

三. 模板套模板不用空格了
两个模板嵌套使用的时候不用加空格了,以前需要加个空格。
在这里插入图片描述

四. nullptr
c++11推荐使用nullptr代表一个空指针,在之前我们都是用NULL表示空指针,但是NULL实际上就是0,所以有歧义,这里用nullptr专门表示空指针,编译器看到nullptr就知道你想传递的是空指针而不是0。
举例:

#include<iostream>
using namespace std;
void f(int)
{
   cout <<"int function called" << endl; 
}

void f(void*)
{
   cout <<"void* function called" << endl; 
}

int main()
{
    f(0);//call f(int)
    //f(NULL);// 这个时候不知道应该调用f(int)还是f(void*),报错:error: call of overloaded ‘f(NULL)’ is ambiguous
    f(nullptr); // call f(void*)
    cout << NULL << endl;
    return 0;
}

运行后:

aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ g++ nullptr.cpp -std=c++11 
nullptr.cpp: In function ‘int main()’:
nullptr.cpp:18:13: warning: passing NULL to non-pointer argument 1 of ‘std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]’ [-Wconversion-null]
     cout << NULL << endl;
             ^
aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ ./a.out 
int function called
void* function called
0

调用f(NULL)的时候会报错,因为不知道调用哪个函数。f(0)知道0是一个int,f(nullptr)知道了你想传递一个空指针。把上面的f(int)或者f(void*)删掉其中任意一个,f(NULL)就能调用成功,比如:

#include<iostream>
using namespace std;

void f(void*)
{
    cout <<"void* function called" << endl;
}

int main()
{
    f(NULL);// call f(int) if NULL is 0,ambiguous otherwise
    f(nullptr); // call f(void*)
    cout << NULL << endl;
    return 0;
}

结果为:

void* function called
void* function called
0

nullptr能自动转成任意类型的空指针,nullptr的类型是std::nullptr_t,在头文件文件中
在这里插入图片描述

四. auto关键字
auto自动推导类型,在lambda表达式中使用非常方便,因为有些lambda表达式的类型太长或者太复杂,不好写。但是也要注意,不要太偷懒写auto,除非类型很长(比如一些迭代器),尽量还是自己写。auto实在编译期就能推导出来,而不是运行过程中,所以不会影响到性能。
举例使用auto

#include<iostream>
using namespace std;

int main()
{
    // auto for iterator
    string str="hello world";
    for (auto item:str) {
        cout << item<<endl;
    }

    // auto for lambda
    auto fun = [](int x)->bool {
        if (x>100) {
            return true;
        }
        return false;
    };
    bool result = fun(0);
    cout <<"result=" << result<<endl;
 }

运行结果为:

aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ g++ auto.cpp -std=c++11 
aitian@aitian-CW65S:~/at/cpp_project/c11/day1$ ./a.out 
h
e
l
l
o
 
w
o
r
l
d
result=0

五. 统一用大括号初始化
在c++11之前,初始化的方法各种各样,比如有用小括号初始化的,用大括号和等于号初始化的,为了能够统一,c++11之后可以直接在变量名后面用大括号进程初始化,比如

#include <iostream>
#include <set>
#include <vector>
#include <complex>

using namespace std;
struct Rect {
    int a;
    int b;
    int c;
    int d;
    Rect(int a_,int b_,int c_,int d_){
        a = a_;
        b = b_;
        c = c_;
        d = d_;
    }
};
int main()
{
    //before c++ 11
    //braces and assignment operations
    int array[6] = {27,210,12,47,109,83}; 
    //parentheses
    Rect r1(3, 7, 20, 25);
    Rect r2 = {2, 4, 6, 8};
    
    // after c++11
    int value[] {1,2,3};
    vector<int> v {2,3,5,6,11,13,17};
    vector<string> cities {"Berlin", "New York", "London"};
    Rect r3  {1, 4, 6, 8};
    complex<double> c{4.0, 3.0};
    return 0;
}

可以看出,在变量后面加一个大括号就是进行初始化的动作,甚至针对自己定义的类型(Rect)也可以这么用,对c++自带的那些类型,更加可以了。
实现用大括号的原理实际上用到了initializer_list和array,这两个东西也都是c++11提供的。当编译器看到了{t1,t2,t3,…,tn}的时候,会构造一个initializer_list,它会关联到一个array<T,n>,其中T是类型,n是元素的个数。调用函数的时候,array里面的元素可被编译器分解逐一传给函数。不过,如果函数参数是一个initializer_list的时候,那么就不能以这种分散的方式写,需要自己写一个initlizer_list。

比如上面的:

vector<string> cities {"Berlin", "New York", "London"};

编译器会先形成一个initlizer_list,背后有一个array<string,3>,调用构造函数时,编译器找到一个vectorctor接收initlizer_list,所有的容器都有这个ctor。

再比如上面的:

complex<double> c{4.0, 3.0};

编译器会先形成一个initlizer_list,背后有一个array<double,2>,调用complexctor时,由于complex并没有任何构造函数接受initlizer_list类型,所以complexctor内的两个元素被分解传送给ctor

六 总结
本节讲解:
宏,变长模板,去空格,nullptr,auto,initializer_list

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涤除而玄览

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值