C++11—给时代一个新选择

C++11是新时代的C++亦称为modern C++,是对C++98扩展。C++11旨在手写简便与提高效率。C++11实现了more concise ,more effective。(更简洁,更高效)。

前面的C++文章中也部分涉及了C++11,这里单独拿出来总结一下。这里往下都是C++11的新特新,有必要掌握一下

More concise

1、nullptr

nullptr 是用于解决 NULL 和 0 的有疑义关系的。 NULL 通常被义为(void*)0。但是在一些应用中会引发歧义,所以才会引入nullptr。

#include <iostream>
using namespace std;
void f(int){}
void f(bool){}
void f(void*){}
int main()
{
    f(0); // calls f(int), not f(void*)
    f(NULL); // might not compile, but typically calls f(int). Never calls f(void*)
    f(nullptr); // calls f(void*) overload
}

2、override

override 是辅助你检查是否继承了想要虚继承的函数。关键字 override 则指明, 此种覆写关系, 若此关系不成立, 则以报错的形式提示给用户。

3、final

关键字 final 有两个用途。 第一, 它阻止了从类继承; 第二, 阻止一个虚函数的覆写。

4、=default =delete

C++ 的类有四类特殊成员函数, 它们分别是: 默认构造函数、 析构函数、 拷贝构造函数以及拷贝赋值运算符,这些类的特殊成员函数负责创建、 初始化、 销毁, 或者拷贝类的对象。如果程序员没有显式地为一个类定义某个特殊成员函数, 而又需要用到该特殊成员函数时, 则编译器会隐式的为这个类生成一个默认的特殊成员函数。

#include <iostream>
using namespace std;
class A
{ 
public:
    A() = default;//设置构造器为默认
    A(int x ):_x(x)
    {}
private:
    int _x;
};
int main()
{
    A a;
    return 0;
}

为了能够让程序员显式的禁用某个函数, C++11 标准引入了一个新特性: "=delete"函数。 程序员只需在函数声明后上“=delete;”, 就可将该函数禁用。

class Singleton
{ 
public:
    static Singleton* getInstance()
    {
        if(_ins == nullptr)
        _ins = new Singleton;
        return _ins;
    } 
    Singleton(Singleton &) = delete;//拷贝构造器设置为禁用
    Singleton& operator=(Singleton &) = delete;
private:
    Singleton(){}
    static Singleton* _ins;
};
    Singleton* Singleton::_ins = nullptr;
int main()
{
    Singleton *ps = Singleton::getInstance();
    Singleton ps(*ps);
    *ps = Singleton::getInstance();
    return 0;
}

5、Raw String Literals

C/C++中提供了字符串, 字符串的转义序列, 给输出带来了很多不变, 如果需要原生义的时候, 需要反转义, 比较麻烦。C++提供了 R"()", 原生字符串, 即字符串中无转义, 亦无需再反义。 但是注意()中的)"会导至提前结束。

#include <iostream>
using namespace std;
string path = "C:\Program Files (x86)\alipay\aliedit\5.1.0.3754";
string path2= "C:\\Program Files (x86)\\alipay\\aliedit\\5.1.0.3754";
string path3= R"(C:\Program Files (x86)\alipay\aliedit\5.1.0.3754)";
string path4= R"(C:\Program "Files" (x86)\\alipay\aliedit\5.1.0.3754)";//这个才是对的
int main(int argc, char *argv[])
{
    cout<<path<<endl;
    cout<<path2<<endl;
    cout<<path3<<endl;
    cout<<path4<<endl;
    return 0;
}

6、Range-Based for

这个的for循环和之前的for看起来似乎一点都不一样,这里的for是基于区间的for循环,比如vector,他是有begin和end的,就在这个begin和end的区间上进行循环。所以说下面注释的那些行是不能用这种for循环的,但是数组arr是可以的,因为arr底层其实也被搞成有begin和end的,这就是原因所在.

#include <iostream>
using namespace std;
int main()
{
    //char *p = "aksdfjlaskdf";
    //for(auto &i: p)
    //{
    //    cout<<i<<endl;
    //} 
    int arr[10] = {1,2,3,4,5,6,7};
    for(auto &i: arr) //使用引用可以改变数组中的内容
    {
        cout<<i<<endl;
    } 
    string str = "china";
    for(auto& ch: str)
    {
        cout<<ch<<endl;
    } 
    return 0;
}

7、初始化列表 initializar list {}

这个初始化列表其实在前面的STL中使用过。这个就是那个,这是C++11的新特性

#include <iostream>
#include <list>
#include <vector>
#include <map>
using namespace std;
int main(int argc, char *argv[])
{
    vector<int> vi = {1,2,3,4,5};
    list<int> li = {1,2,3,4,5};
    map<int,string> mis =
    {
        {1,"c"}, {2,"c++"},
        {3,"java"},{4,"scala"},{5,"python"}
    };
    mis.insert({6,"ruby"});
    for(auto &is: mis)
    {
        cout<<is.first<<is.second<<endl;//注意这里用的是.
    } 
    return 0;
}

initializer_list

在 C++11 可以使用 {} 而不是 () 来调用类的构造函数。因为template< class T > class initializer_list; C++11 中提供了新的模板类型initializer_list。initializer_list 对象只能用大括号{}初始化,其内有元素都是 const 的。 常用于构造器和普通函数参数

#include <iostream>
using namespace std;
double sum(const initializer_list<double> &il);
double average(const initializer_list<double> &ril);
int main()
{
    auto il = {1,2,3,4,5};
    double s = sum({1,2,3,4,5})-
    cout<<"sum = "<<s<<endl;
    double avg = average({1,2,3,4,5});
    cout<<"average = "<<avg<<endl;
    return 0;
} 
double sum(const initializer_list<double> &il)
{
    double s = 0;
    for(auto d:il)
    s += d;
    return s;
} 
double average(const initializer_list<double> &ril)
{
    double s = 0;
    for(auto d:ril)
    s += d;
    double avg = s/ril.size();//是有size这个函数的
    return avg;
}

在构造器中也是一样的使用

8、auto

auto 能够实现类型的自我推导, 并不代表一个实际的类型声明。 auto 只是一个类型声明的占位符。auto 声明的变量, 必须马上初始化, 以让编译器推断出它的实际类型, 并在编译时将auto 占位符替换为真正的类型。注意:auto 不能用于函数参数(C++14 可以通过)

9、decltype

decltype获取表达式类型 ,decltype(expr); 所推导出来的类型, 完全与 expr 类型一致。 同 auto 一样, 在编译期
间完成, 并不会真正计算表达式的值 ,关于函数的类型推导, 不可以直接使用函数名字, 可以使用函数的类型生成的对象推导 。

int main()
{
    double a = 10;
    decltype(a) b;
    cout<<sizeof(b)<<endl;
    cout<<sizeof(decltype(a))<<endl;
    
    decltype("abcde") ds = "china"; //必须是刚好5个字符,否则报错 
    cout<<ds<<endl;
    
    char *p = "abc";
    decltype(p) pp = "12345";
    cout<<sizeof(pp)<<endl;
    cout<<pp<<endl;
    return 0;
}

组合使用:

推导类型(decltype) 重定义 typedef

对于推导出来的类型进行重定义, 是一种不错的选择。 比宏更准确, 因为发生在编译阶段。

typedef decltype(map<int,string>::value_type()) Int2String;

返回类型推导 auto -> decltype

->decltype()放在函数的结尾, 称为尾推导。 返回值类型用 auto 来配合使用, 是一种绝佳组合。

#include <iostream>
using namespace std;
template<typename T1, typename T2,typename T3>
T1 add(T2 a, T3 b)
{
    return a+b;
} 

template<typename T1, typename T2>
auto add2(T1 a, T2 b)->decltype(a+b)//注意这里,两数相加可能返回值是T2类型,也可能是T3,这里使用自动推导
{
    return a+b;
} 
int main(int argc, char *argv[])
{
    int a = 1;
    float b = 1.1;
    auto ret = add<decltype(a+b),int,float>(a,b);
    cout<<ret<<endl;
    auto ret2 = add2<int,float>(a,b);
    cout<<ret2<<endl;
    return 0;
}

10、Lambda

简短函数, 就地书写, 调用, 即 Lambda 存在的意义, 常用于取代作回调用的简短函数指针与仿函数。就地书写, 因只有函数体, 即无函数名, 也称匿名函数。

lambda 本质就是匿名的仿函数

格式如下:

[capturelist] (parameterlist) mutable ->return type { function body } 

闭包[]与 mutable

lambda 函数能够捕获 lambda 函数外的具有自动存储时期的变量。函数体与这些变量的集合合起来叫闭包。闭包的概念在 lambda 中通过[]来体现出来

#include <iostream>
#include <vector>
#include <list>
#include <stdlib.h>
#include <time.h>
#include <algorithm>
#include <functional>
using namespace std;
bool Compare(int i, int j)
{
    return i<j;
} 

int main(int argc, char *argv[])
{
    vector<int> vi;
    srand(time(NULL));
    
    for(int i=0;i <10; i++)
    {
        vi.push_back(rand()%100);
    }
    
    // sort(vi.begin(),vi.end(),Compare);
    sort(vi.begin(),vi.end(),[](int x, int y){return x<y;});//sort函数使用Lambda
    for_each(vi.begin(),vi.end(),[](int i){cout<<i<<endl;});
    return 0;
}

&为引用,=就是复制,本身是const,想改就加mutable。

&和=是全部引进,杀伤力太大,看下面:

[]的捕获规则

形式语意
[]不截取任何变量。
[bar]仅对外部变量 bar 值传递在函数体中使用。
[&bar]仅对外部变量 bar 引用传递在函数体中使用。
[x, &y]仅 x 按值传递, y 按引用传递在函数体中使用。
[&]截取外部作用域中所有变量, 并作为引用传递在函数体中使用。
[=]截取外部作用域中所有变量, 并按值传递在函数体中使用。
[=, &foo]截取外部作用域中所有变量, 并值传递在函数体中使用, 但是对 foo 变量使用引用传递。
[&, =foo]截取外部作用域中所有变量, 在函数体中作引用传递使用, 但是对 foo 变量作值传递。 =foo -> foo

上述, 中涉及到值传递要发生拷贝行为, 而引用传递则不会发生拷贝行为。 捕获列表中不允许重复。 比如: [=, a] [&,&this]。

11、enum

前面有说过enum在C语言与C++中的区别,这里介绍一下C++11的新特性

#include <iostream>
using namespace std;

class Painter
{ 
public:
    enum Color { red, green, yellow};
public:
    Painter(){} 

    void dis(Color c)
    {
        cout<<c<<endl;
    }
};
int main()
{ 
    // Painter p;
    // p.dis(red);
    Painter p;
    p.dis(Painter::red);
    return 0;
}
#include <iostream>
using namespace std;
enum class Color { red, green, yellow};
class Painter
{ 
public:
    Painter(){}
    void dis(Color c)
    {
        cout<<static_cast<int>(c)<<endl;
    }
};

int main()
{
    Painter p;
    // p.dis(red);
    p.dis(Color::red);
    return 0;
}

C++11之前我们像上面来使用,在C++11我们还可以这样用指定enum中的类型,我们知道enum默认为int类型,我们可以改成char型,应该是只能改成整形相关的。

#include <iostream>
using namespace std;
enum class color:int { red, green, yellow};
enum class colorX:char { red, green, yellow };
int main()
{
    //使用域运算符访问枚举体成员, 强转后打印
    std::cout << static_cast<int>(color::red) << std::endl;
    std::cout << static_cast<int>(colorX::red) << std::endl;
    return 0;
}
Last modification:October 11th, 2019 at 09:14 pm
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment

One comment

  1. 一博的女朋友 ViVO Android 9 中国 吉林

    非常有用