C++11

原始字面量

C++11 添加了定义原始字符串的字面量,定义方式为:R “xxx(原始字符串)xxx” 其中()两边的字符串可以省略。原始字面量 R 可以直接表示字符串的实际含义,而不需要额外对字符串做转义或连接等操作。

	std::string str = R"xxx(hello)xxx";
	std::cout << str;
    //输出结果为hello

使用原始字面量 R “xxx (raw string) xxx”,()两边的字符串在解析的时候是会被忽略的,因此一般不用指定。如果在()前后指定了字符串,那么前后的字符串必须相同,否则会出现语法错误 。

指针空值类型 nullptr

c98中的NULL

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

C++中将NULL定义为字面常量0,在有些场景下不能正常工作。

 

#include <iostream>
using namespace std;

void func(char *p)
{
    cout << "void func(char *p)" << endl;
}

void func(int p)
{
    cout << "void func(int p)" << endl;
}

int main()
{
    func(NULL);   // 想要调用重载函数 void func(char *p)
    func(250);    // 想要调用重载函数 void func(int p)

    return 0;
}

输出结果:

void func(int p)
void func(int p)

 nullptr可以隐式转换成任何指针类型。

实现方式大概为:

struct nullptr
{
    void operator&() const = delete;  // Can't take address of nullptr

    template <class T>
    inline operator T* () const
    {
        return 0;
    }

    template <class C, class T>
    inline operator T C::* () const
    {
        return 0;
    }
};

常量表达式修饰符 constexpr

在 C++11 之前只有 const 关键字,从功能上来说这个关键字有双重语义:变量只读修饰常量

void func(const int num)
{
    const int count = 24;
    int array[num];            // error,num是一个只读变量,不是常量
    int array1[count];         // ok,count是一个常量

    int a1 = 520;
    int a2 = 250;
    const int& b = a1;
    b = a2;                         // error
    a1 = 1314;
    cout << "b: " << b << endl;     // 输出结果为1314
}

 在 C++11 中添加了一个新的关键字 constexpr,这个关键字是用来修饰常量表达式的。所谓常量表达式,指的就是由多个(≥1)常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。

编译器如何识别表达式是不是常量表达式呢?在 C++11 中添加了 constexpr 关键字之后就可以在程序中使用它来修改常量表达式,用来提高程序的执行效率。在使用中建议将 const 和 constexpr 的功能区分开,即凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr

对于 C++ 内置类型的数据,可以直接用 constexpr 修饰,但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。

struct Test
{
    int id;
    int num;
};

int main()
{
    constexpr Test t{ 1, 2 };
    constexpr int id = t.id;
    constexpr int num = t.num;
    // error,不能修改常量
    t.num += 100;
    cout << "id: " << id << ", num: " << num << endl;

    return 0;
}

自动类型推导

final和override

C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写,如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面。

override 关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和 final 一样这个关键字要写到方法的后面。

数值类型和字符串类型的转换

在 C++11 中提供了专门的类型转换函数,程序猿可以非常方便的使用它们进行数值类型和字符串类型之间的转换。

// 头文件 <string>
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);
// 定义于头文件 <string>
int       stoi( const std::string& str, std::size_t* pos = 0, int base = 10 );
long      stol( const std::string& str, std::size_t* pos = 0, int base = 10 );
long long stoll( const std::string& str, std::size_t* pos = 0, int base = 10 );

unsigned long      stoul( const std::string& str, std::size_t* pos = 0, int base = 10 );
unsigned long long stoull( const std::string& str, std::size_t* pos = 0, int base = 10 );

float       stof( const std::string& str, std::size_t* pos = 0 );
double      stod( const std::string& str, std::size_t* pos = 0 );
long double stold( const std::string& str, std::size_t* pos = 0 );
  • str:要转换的字符串
  • pos:传出参数,记录从哪个字符开始无法继续进行解析,比如: 123abc, 传出的位置为 3
  • base:若 base 为 0 ,则自动检测数值进制:若前缀为 0 ,则为八进制,若前缀为 0x 或 0X,则为十六进制,否则为十进制

静态断言static_assert 

断言就是将一个返回值总是需要为真的判断表达式放在语句中,用于排除在设计的逻辑上不应该产生的情况。

如果我们要在 C++ 程序中使用断言,需要在程序中包含头文件 <cassert> 或 <assert.h>,头文件中为我们提供了 assert 宏,用于在运行时进行断言。举例说明

#include <iostream>
#include <cassert>
using namespace std;

// 创建一个指定大小的 char 类型数组
char* createArray(int size)
{
    // 通过断言判断数组大小是否大于0
    assert(size > 0);	// 必须大于0, 否则程序中断
    char* array = new char[size];
    return array;
}

int main()
{
    char* buf = createArray(0);
    // 此处使用的是vs提供的安全函数, 也可以使用 strcpy
    strcpy_s(buf, 16, "hello, world!");
    cout << "buf = " << buf << endl;
    delete[]buf;
    return 0;
}

assert 是一个运行时断言,也就是说它只有在程序运行时才能起作用 

静态断言 static_assert,所谓静态就是在编译时就能够进行检查的断言,使用时不需要引用头文件。静态断言的另一个好处是,可以自定义违反断言时的错误提示信息。静态断言使用起来非常简单,它接收两个参数:

参数1:断言表达式,这个表达式通常需要返回一个 bool值
参数2:警告信息,它通常就是一段字符串,在违反断言(表达式为false)时提示该信息

static_assert(sizeof(long) == 8, "错误, 不是64位平台...");

noexception

异常通常用于处理逻辑上可能发生的错误,在 C++98 中为我们提供了一套完善的异常处理机制,我们可以直接在程序中将各种类型的异常抛出,从而强制终止程序的运行。

#include <string>
#include <iostream>

using namespace std;

struct MyException
{
    MyException(string s) :msg(s) {}
    string msg;
};
//divisionMethod 函数后添加了 throw 异常接口声明,其参数列表为空,表示该函数不允许抛出异常。
//divisionMethod 没有添加异常接口声明,表示在该函数中可以抛出任意类型的异常
//divisionMethod 函数后添加了 throw 异常接口声明,其参数表示可以抛出的异常类型,分别为 int 和 MyException 类型
double divisionMethod(int a, int b) throw(MyException, int, std::invalid_argument)
{
    if (b == 0)
    {
        throw MyException("division by zero!!!");
        // throw 100;
    }
    return a / b;
}

int main()
{
    try
    {
        double v = divisionMethod(100, 0);
        cout << "value: " << v << endl;
    }
    catch (int e)
    {
        cout << "catch except: " << e << endl;
    }
    catch (MyException e)
    {
        cout << "catch except: " << e.msg << endl;
    }
    return 0;
}

divisionMethod 函数后添加了 throw 异常接口声明,其参数列表为空,表示该函数不允许抛出异常。
divisionMethod 没有添加异常接口声明,表示在该函数中可以抛出任意类型的异常。
divisionMethod 函数后添加了 throw 异常接口声明,其参数表示可以抛出的异常类型,分别为 int 和 MyException 类型。

在 divisionMethod 函数声明之后,定义了一个动态异常声明 throw(MyException, int),该声明指出了 divisionMethod 可能抛出的异常的类型,在 C++11 中被弃用了 ,而表示函数不会抛出异常的动态异常声明 throw() 也被新的 noexcept 异常声明所取代。不过与 throw ()动态异常声明不同的是,在 C++11 中如果 noexcept 修饰的函数抛出了异常,编译器可以选择直接调用 std::terminate () 函数来终止程序的运行,这比基于异常机制的 throw () 在效率上会高一些。

noexcept修饰符有两种形式:

  1. 简单地在函数声明后加上 noexcept 关键字
  2. 可以接受一个常量表达式作为参数,如下所示∶
double divisionMethod(int a, int b) noexcept(常量表达式);

常量表达式的结果会被转换成一个 bool 类型的值:

  1. 值为 true,表示函数不会抛出异常
  2. 值为 false,表示有可能抛出异常这里
  3. 不带常量表达式的 noexcept 相当于声明了 noexcept(true),即不会抛出异常。

右值

左值是指存储在内存中、有明确存储地址(可取地址)的数据

右值是指可以提供数据值的数据(不可取地址)

比较方便的区分左值和右值的方法是:可以对表达式取地址(&)就是左值,否则为右值 .

 所有有名字的变量或对象都是左值,而右值是匿名的。

C++11 中右值可以分为两种:一个是将亡值( xvalue, expiring value),另一个则是纯右值( prvalue, PureRvalue):

  • 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和 lambda 表达式等
  • 将亡值:与右值引用相关的表达式,比如,T&& 类型函数的返回值、 std::move 的返回值等。

纯右值 (pure rvalue) 指的是一个表达式,它没有名称、没有地址,且其值不能被修改(例如一个字面常量 `5` 或一个返回右值引用的函数调用)。在 C++11 中,纯右值可以被移动但不能被拷贝。

将亡值 (xvalue,全称为 eXpiring Value) 是一个表达式,它将会被移动(move)或取消引用(dereference)后被销毁。在 C++11 中,将亡值是一种特殊的右值,其可以被移动且也可以被拷贝。常见的将亡值包括返回右值引用的函数调用,以及使用 `std::move` 转换后的左值。

#include <iostream>
using namespace std;

class Test
{
public:
    Test()
    {
        cout << "construct: my name is jerry" << endl;
    }
    Test(const Test& a)
    {
        cout << "copy construct: my name is tom" << endl;
    }
};

Test getObj()
{
    return Test();
}

int&& value = 520;//520是纯右值,value是对字面量520这个右值的引用

int main()
{
    int a1;
    int&& a2 = a1;        // a1是左值,左值不能初始化一个右值引用
    Test& t = getObj();   // 右值不能给普通的左值引用赋值
    Test&& t = getObj();  // getObj() 返回的临时对象被称之为将亡值,t 是这个将亡值的右值引用。
    const Test& t = getObj();//常量左值引用是一个万能引用类型,它可以接受左值、右值、常量左值和常量右值。
    return 0;
}

&&的特性

并不是所有情况下 && 都代表是一个右值引用,具体的场景体现在模板和自动类型推导中,如果是模板参数需要指定为 T&&,如果是自动类型推导需要指定为 auto &&,在这两种场景下 && 被称作未定的引用类型。外还有一点需要额外注意 const T&& 表示一个右值引用,不是未定引用类型。

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    // 添加移动构造函数
    Test(Test&& a) : m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct: my name is sunny" << endl;
    }

    ~Test()
    {
        delete m_num;
        cout << "destruct Test class ..." << endl;
    }

    int* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    int x = 520, y = 1314;
    auto&& v1 = x;  //v1是左值引用
    auto&& v2 = 250;//v2是右值引用
    decltype(x) && v3 = y;   //  decltype(x)&& 等价于 int&& 是一个右值引用,无法将左值绑定到右值上
    cout << "v1: " << v1 << ", v2: " << v2 << endl;
    return 0;
};


T&& 或者 auto&& 这种未定引用类型,当它作为参数时,有可能被一个右值引用初始化,也有可能被一个左值引用初始化,在进行类型推导时右值引用类型(&&)会发生变化,这种变化被称为引用折叠。

const auto&& x = 5;     //x 为右值引用,不需要推导,只能通过右值初始化
const int&& s2 = 100;
auto&& ee = s2;         //s2 为常量右值引用,推导出的 ee 为常量左值引用类型

int&& a1 = 5;
auto&& bb = a1;//a1 为右值引用,推导出的 bb 为左值引用类型

转移和完美转发

不能使用左值初始化右值引用,如果想要使用左值初始化一个右值引用需要借助 std::move () 函数。使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。从实现上讲,std::move 基本等同于一个类型转换:static_cast<T&&>(lvalue);

list<string> ls;
ls.push_back("hello");
ls.push_back("world");

list<string> ls1 = ls;        // 需要拷贝, 效率低
list<string> ls2 = move(ls);

如果不使用 std::move,拷贝的代价很大,性能较低。使用 move 几乎没有任何代价,只是转换了资源的所有权。如果一个对象内部有较大的堆内存或者动态数组时,使用 move () 就可以非常方便的进行数据所有权的转移。另外,我们也可以给类编写相应的移动构造函数(T::T(T&& another))和和具有移动语义的赋值函数(T&& T::operator=(T&& rhs)),在构造对象和赋值的时候尽可能的进行资源的重复利用,因为它们都是接收一个右值引用参数。

forward

一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值,并不是原来的类型了。如果需要按照参数原来的类型转发到另一个函数,可以使用 C++11 提供的 std::forward () 函数,该函数实现的功能称之为完美转发。

可调用对象

可调用对象

int print(int a, double b)
{
    cout << a << b << endl;
    return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;

强枚举类型

具名(有名字)的enum类型的名字,以及 enum 的成员的名字都是全局可见。

enum China {Shanghai, Dongjing, Beijing, Nanjing};
enum Japan {Dongjing, Daban, Hengbin, Fudao};

编译器会报错重定义。因为Dongjing是全局可见的。

针对枚举的缺陷,C++11 标准引入了一种新的枚举类型,即枚举类,又称强类型枚举(strong-typed enum)。 声明强类型枚举非常简单,只需要在 enum 后加上关键字 class。

强类型枚举具有以下几点优势∶


  • 强作用域,强类型枚举成员的名称不会被输出到其父作用域空间。
  • 强类型枚举只能是有名枚举,如果是匿名枚举会导致枚举值无法使用(因为没有作用域名称)。
  • 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换。

  • 可以指定底层类型。强类型枚举默认的底层类型为 int,但也可以显式地指定底层类型,
具体方法为在枚举名称后面加上∶type,其中 type 可以是除 wchar_t 以外的任何整型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值