c++11新特性一窥


  c++11在c++的诸多标准中可谓大名鼎鼎,虽然前有c++98,后有c++17、c++20,以下所有内容均基于《c++11新特性的理解》进行总结。

VA_ARGS

  __VA__ARGS__可以在宏定义的实现部分替换省略号所代表的字符串,通常它可以和printf配合使用,示例代码如下:

#include <stdio.h>
#define LOG(...)                                              \
    {                                                         \
        fprintf(stderr, "%s:Line %d:\t", __FILE__, __LINE__); \
        fprintf(stderr, __VA_ARGS__);                         \
        fprintf(stderr, "\n");                                \
    }

int main(int argc, char *argv[])
{
    int x = 3;

    LOG("x = %d",x);

    return 0;
}

noexcept

  在异常处理代码中,我们经常可以看到:

void excpt_func() throw(int,double) {...}
void excpt_func() throw() {...}

  该声明指出了excpt_func()这个函数可能抛出的异常的类型,;而表示函数不会抛出异常,那么以前就用throw();但是上述特性很少用到,所以c++11中被抛弃了,而用于取代throw()的就是noexcept();用法如下:

void excpt_func() noexcept {...}

final/override控制

  为什么要有final?如果一个基类A中声明了某函数为纯虚函数,那么在其派生类B中实现它,即使我们不加关键字virtual,也必须明白该函数仍然是虚函数,在B的派生类C中我们仍然可以实现它,但是有时候我们却不想该实现再被覆盖,包括被类C和被继承类C的派生类覆盖,那么就可以在类B的该函数上加上关键字final。示例代码如下:

struct object{
    virtual void fun() = 0 ;
};

struct Base : public object{
    void fun() final;
};

struct Derived : public Base{
    void fun();
};

  如上所示,类Base中已经声明了fun()函数为final,那么再在Derived中声明fun()函数就会报错。

  为什么要有override?我们很可能碰到这样一种情况:由于类之间的继承,我们很难说明我们写的某个函数是否是继承自祖先类还是一个新的函数,有时候我们可能想实现祖先类的一个函数但一不小心又写错了一个字母;种种情况,对于写的人来说是一种困难,对于读的人来更是一种折磨;于是便有了override,当某个函数明确声明为override时,那么该函数必定是继承于祖先类,如果这个时候函数中某个字母一不小心写错了或者参数不一致怎么办,别担心,编译器此时会报错。使用方法如下:

struct Base
{
    virtual void Turing() = 0;
    virtual void Dijkstra() = 0;
    virtual void VNeumann(int g) = 0;
    virtual void DKnuth() const;
    void Print();
};

struct DerivedMId : public Base
{
    //void VNeumann(double g);
};

struct DerivedTop : public DerivedMId
{
    virtual void Turing() override;
    virtual void Dijkstra() override;         //无法通过编译,拼写错误
    virtual void VNeumann(double g) override; //无法通过编译,参数不一致
    virtual void DKnuth() override;           //无法通过编译,常量性不一致;
    void Print() override;                    //无法通过编译,非虚函数重载
};

右值引用

点击链接访问:右值引用、std::move、引用折叠、std::forward

auto类型推导

  auto声明的变量类型由编译器在编译器时期推导而得;如下:

vector<int> vec;
for(auto i = vec.begin() ; i != vec.end() ;++i)
{
    ...
}

  但是需要注意,auto不支持下述用法:

auto number;

  谁能推导出number到底是什么类型?

decltype

  decltype和auto很类似,也是用于类型推导,通常可以和uisng/typedef配合使用,示例如下:

using size_t = decltype(sizeof(0));
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);

  上述代码中,size_t类型等价于int类型;
  除了和using/typedef 配合使用,也可以在代码中使用,这时候,行为和auto基本相似,示例如下:

int main(int argc, char *argv[])
{
    vector<int> vec;
    typedef decltype(vec.begin()) vectype;
    
    for(vectype i = vec.begin() ;i < vec.end() ;++i)
    {
        //do something
    }

    return 0;
}

  关于decltype,在使用上其实还存在其他一些使用限制,比如:

int i;
decltype(i) a;   //a: int
decltype((i)) b; 无法编译通过

  b无法编译通过,编译器会提示b是一个引用,但没有被赋值。

基于范围的for循环

for(auto e : arr)
 cout << e << "\t";

  第一部分是用于迭代的变量,第二部分则是迭代的范围;但需要注意,如果数组范围不确定的话,是不能使用基于范围的for循环的,如下例:

int arr[] = {1,2,3,4,5};

  此外,如果使用auto在基于范围的for循环中声明迭代对象的话,那么这个对象不是迭代器对象,而是其解引用后的对象,如下例:

int main{
	vector<int> v = {1,2,3,4,5};
	for(auto i = v.begin();i != v.end(); ++i)
		cout << *i << endl;
	
	for(auto e : v)
	    cout << e << endl;
}

强类型枚举

enum Gender{male,Female};
enum {male,Female}; //#2

enum Type{General,Light,Medium,heavy};
enum Category{General,Pistol,MachineGun,Cannon};

  上述代码是普通枚举类型的定义方式,其中#2是匿名枚举类型,普通枚举类型存在以下缺陷:

  1. 普通枚举类型是非强作用域类型,也就是说我们并不必一定要通过“名字::成员名”来引用枚举,这也是为什么会存在匿名枚举原因,但是这样会造成问题,如上述代码中的Type、Category,这两个枚举中都存在General,那么就会产生定义冲突,即使我们为其中一个枚举增加namespace,仍然存在可能在使用过程中成员污染的问题;
  2. 枚举类型在使用过程中可以隐式转换为int类型,同样存在风险,例如上述代码中,如果我们有一个Gender类型,那么即使我们和Category中的某个成员比较也不会产生问题,因为Category中类型被转换为了int类型,而Gender类型接受int类型的比较;

  在以上背景条件下,于是产生了强类型枚举,强类型专门针对以上两个问题进行解决;我们可以像下面这样使用强枚举类型:

#include <iostream>

enum class Type{General ,LIght,Mdeium,Heavy};
enum class Category{General = 1,pistol,MachineGun,Cannon};

using namespace std;
int main(int argc, char *argv[])
{
    Type t = Type::LIght;
    t = General; //编译失败,必须使用强类型名称

    if(t == Category::General) //编译失败,类型不匹配
      ...
    
    return 0;
}

  除此之外,强枚举类型与普通枚举类型相比,还有一点不同,即我们可以指定枚举类型的基本类型,普通枚举默认是int,但是我们可以指定强枚举类型为char,比如:

enum Type:char {General,Light,Mdeium,Heavy};

智能指针

  点击链接访问:智能指针总结!

常量表达式

  有时候,我们可能想为枚举设定初始值,如下:

#define COUNT 1
enum {e1 = COUNT,e2};

  上述代码是可以的,因为COUNT确实是一个常量;但是有时候,我们可能想使用一个函数代替COUNT,于是:

const int GetConst() {return 1;}
enum {e1 = GetConst(),e2};

  很遗憾,无法通过编译,即使我们知道GetConst返回的是一个常量,但是编译器不允许通过,而我们大部分碰到的情况只需要我们确保“运行时常量性”就好了,而现在是“编译时常量性”,因此,constexpr在c++11中出现了,在上述代码中,我们只需要做如下改动即可保证编译通过:

constexpr int GetConst() {return 1;}
enum {e1 = GetConst(),e2};

  在使用常量表达式函数时,我们需要遵循以下几点:

  • 函数体只有单一的return 返回语句;
  • 函数必须又返回值;
  • 在返回前必须已经有定义;
  • return返回语句不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式;

  毫无疑问,对于第一条限制,我们难免会觉得苛刻,所以在C++14中,放宽了这一限定,只保留了“函数的返回值类型及所有形参的类型都是字面值类型”,也就是说,这些值都在编译期能确定了就行。
  上面提到的是常量表达式函数,但是也存在常量表达式值,如下:

constexpr int j = 1; //#1
const int i = 1;

  上面的j就是常量表达式值,那么它和i有什么区别呢?事实上,两者大多数情况下都是没有区别的,只不过如果i在全局空间中,那么编译器一定会为它产生数据,而对于j,如果不是代码显式的调用它的地址,那么编译器可以不为它生成数据,而只是作为编译时期的值。

变长模板

原子类型与原子操作

点击链接访问:原子类型与原子操作

指针为空

lambda函数

点击链接访问:lambda函数!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值