C++11实用特性---稳定性和兼容性
  5Z6Aj0LQwRjK 2023年11月13日 23 0


1、原始字面量

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

特性一(不需要额外做转义)

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string str1 = "D:\hello\world\test.text";
    cout << str1 << endl;

    string str2 = R"(D:\hello\world\test.text)";
    cout << str2 << endl;

    string str3 = R"luffy(D:\hello\world\test.text)luffy";
    cout << str3 << endl;

    //string str4 = R"luffy(D:\hello\world\test.text)robin";	// 语法错误,编译不通过
    //cout << str4 << endl;


    return 0;
}

C++11实用特性---稳定性和兼容性_开发语言


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

特性二(不需要额外做连接)

C++11实用特性---稳定性和兼容性_开发语言_02

2、long long 类型

++11 标准要求 long long 整型可以在不同平台上有不同的长度,但至少有64位。
long long 整型有两种∶

  • long long - 对应类型的数值可以使用 LL (大写) 或者 ll (小写) 后缀
  • unsigned long long - 对应类型的数值可以使用 ULL (大写) 或者 ull (小写) 或者 Ull、uLL (等大小写混合)后缀

对于有符号类型的 long long和以下三种类型等价

  • long long int
  • signed long long
  • signed long long int

对于无符号类型的unsigned long long 和unsigned long long int是等价的

同其他的整型一样,要了解平台上 long long大小的方法就是查看(或<limits. h> )中的宏与long long整 型相关的一共有3个:
LLONG_MIN - 最小的long long值
LLONG_MAX - 最大的long long 值
ULLONG MAX - 最大的 unsigned long long 值

#include <iostream>
using namespace std;

int main()
{
    long long max = LLONG_MAX;
    long long min = LLONG_MIN;
    unsigned long long ullMax = ULLONG_MAX;

    cout << "Max Long Long value: " << max << endl
        << "Min Long Long value: " << min << endl
        << "Max unsigned Long Long value: " << ullMax << endl;
    return 0;
}

C++11实用特性---稳定性和兼容性_初始化_03


可以看到 long long 类型能够存储的最大/最小值还是非常大/小的,但是这个值根据平台不同会有所变化,原因是因为C++11标准规定该类型至少占8字节,它占的字节数越多,对应能够存储的数值也就越大。

3、类成员的快速初始化

1. C++98 标准的类成员初始化

在C++98中,支持了在类声明中使用等号 = 加初始值 的方式,来初始化类中非静态成员常量 。这种声明方式我们也称之为”就地”声明。而静态成员变量的初始化则必须类内声明类外初始化。
C+±–静态成员全局变量VS静态变量

2. C++11 标准的类成员初始化

2.1 初始化类的非静态成员

在进行类成员变量初始化的时候,C++11标准对于C++98做了补充,允许在定义类的时候在类内部直接对非静态成员变量进行初始化,在初始化的时候可以使用等号 = 也可以使用花括号 {} 。

#include <iostream>
using namespace std;

class Test
{
public:
    int a = 9;
    int b = { 5 };
    int c{ 12 };
    double array[4] = { 3.14, 3.15, 3.16, 3.17 };
    double array1[4]{ 3.14, 3.15, 3.16, 3.17 };
    //string s1("hello");     // error
    string s2{ "hello, world" };
};

int main() {
    Test t;
    cout << t.a<<endl;
    cout << t.array[0];

    return 0;
}

C++11实用特性---稳定性和兼容性_字符串_04


可以看到如果使用花括号 {}的方式对类的非静态成员进行初始化,等号是可以省略不写的。

第9行:错误,不能使用小括号() 初始化对象,应该使用花括号{}

4、final

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

C++11实用特性---稳定性和兼容性_字符串_05

5、模板的优化

1、模板的右尖括号

在泛型编程中,模板实例化有一个非常繁琐的地方,那就是连续的两个右尖括号(>>)会被编译器解析成右移操作符,而不是模板参数表的结束。

C++11实用特性---稳定性和兼容性_c++_06


根据错误提示中描述模板的两个右尖括之间需要添加空格,这样写起来就非常的麻烦,C++11改进了编译器的解析规则,尽可能地将多个右尖括号(>)解析成模板参数结束符,方便我们编写模板相关的代码。

上面的这段代码,在支持C++11的编译器中编译是没有任何问题的。

2、默认模板参数

在C++98/03标准中,类模板可以有默认的模板参数,但是不支持函数的默认模板参数,在C++11中添加了对函数模板默认参数的支持。

C++11实用特性---稳定性和兼容性_c++_07

6、数值类型和字符串之间的转换

1. 数值转换为字符串

使用to_string()方法可以非常方便地将各种数值类型转换为字符串类型,这是一个重载函,函数声明位于头文件中,函数原型如下:

// 头文件 <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);

2. 字符串转换为数值

由于C++中的数值类型包括整形和浮点型,因此针对于不同的类型提供了不同的函数,通过调用这些函数可以将字符串类型转换为对应的数值类型。

// 定义于头文件 <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,则为十六进制,否则为十进制。
这些函数虽然都有多个参数,但是除去第一个参数外其他都有默认值,一般情况下使用默认值就能满足需求。关于函数的使用也给大家提供了一个例子,示例代码如下:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string str1 = "45";
    string str2 = "3.14159";
    string str3 = "9527 with words";
    string str4 = "words and 2";

    int myint1 = stoi(str1);
    float myint2 = stof(str2);
    int myint3 = stoi(str3);
    // 错误: 'invalid_argument'
    // int myint4 = stoi(str4);

    cout << "std::stoi(\"" << str1 << "\") is " << myint1 << endl;
    cout << "std::stof(\"" << str2 << "\") is " << myint2 << endl;
    cout << "std::stoi(\"" << str3 << "\") is " << myint3 << endl;
    // cout << "std::stoi(\"" << str4 << "\") is " << myint4 << endl;
}

C++11实用特性---稳定性和兼容性_开发语言_08


从上述测试程序可以得出这样的结论,在C++11提供的这些转换函数将字符串转换为数值的过程中:

  • 如果字符串中所有字符都是数值类型,整个字符串会被转换为对应的数值,并通过返回值返回
  • 如果字符串的前半部分字符是数值类型,后半部不是,那么前半部分会被转换为对应的数值,并通过返回值返回
  • 如果字符第一个字符不是数值类型转换失败

7、断言

1、运行时动态断言

C++11实用特性---稳定性和兼容性_抛出异常_09

2、静态断言

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

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

C++11实用特性---稳定性和兼容性_字符串_10

8、noexcept

1、异常

1.1基本语法

C++11实用特性---稳定性和兼容性_开发语言_11

int main()
{ 
    try
    {
        throw -1; 
    } 
    catch (int e)
    { 
        cout << "int exception, value: " << e << endl; 
    } 
    cout << "That's ok!" << endl; 
    return 0; 
}

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋。

1.2异常接口声明

可以在函数声明中列出可能抛出的所有异常类型,常用的有如下三种书写方式:

1、显示指定可以抛出的异常类型
struct MyException
{
    MyException(string s) :msg(s) {}
    string msg;
};

double divisionMethod(int a, int b) throw(MyException, int)
{
    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;
}

第7行代码在divisionMethod函数后添加了throw异常接口声明,其参数表示可以抛出的异常类型,分别为int 和MyException 类型。

2、抛出任意异常类型

第7行代码在divisionMethod 没有添加异常接口声明,表示在该函数中可以抛出任意类型的异常。

3、不抛出任何异常

C++11实用特性---稳定性和兼容性_字符串_12

第7行代码在divisionMethod 函数后添加了throw异常接口声明,其参数列表为空,表示该函数不允许抛出异常。

温馨提示:以上程序在VS上的测试结果和在Linux上基于G++的测试结果是不同的,如果违反了规则VS只会给出警告,而G++则会直接终止程序的运行。(PS:VS使用的不是G++编译器)

2. noexcept

上面的例子中,在 divisionMethod 函数声明之后,我们定义了一个动态异常声明 throw(MyException, int),该声明指出了divisionMethod可能抛出的异常的类型。事实上,该特性很少被使用,因此在C++11中被弃用了 ,而表示函数不会抛出异常的动态异常声明 throw() 也被新的 noexcept 异常声明所取代。

noexcept 形如其名,表示其修饰的函数不会抛出异常 。不过与 throw()动态异常声明不同的是,在 C++11 中如果 noexcept 修饰的函数抛出了异常,编译器可以选择直接调用 std::terminate() 函数来终止程序的运行,这比基于异常机制的 throw() 在效率上会高一些。这是因为异常机制会带来一些额外开销,比如函数抛出异常,会导致函数栈被依次地展开(栈解旋),并自动调用析构函数释放栈上的所有对象。
因此对于不会抛出异常的函数我们可以这样写:

double divisionMethod(int a, int b) noexcept
{
    if (b == 0)
    {
        cout << "division by zero!!!" << endl;
        return -1;
    }
    return a / b;
}

从语法上讲,noexcept 修饰符有两种形式:
简单地在函数声明后加上 noexcept 关键字
可以接受一个常量表达式作为参数,如下所示∶

double divisionMethod(int a, int b) noexcept(常量表达式);

常量表达式的结果会被转换成一个bool类型的值:
值为 true,表示函数不会抛出异常
值为 false,表示有可能抛出异常这里
不带常量表达式的noexcept相当于声明了noexcept(true),即不会抛出异常。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月13日 0

暂无评论

推荐阅读
5Z6Aj0LQwRjK