C++课程学习笔记第十周:C++11新特性

前言:本文主要是根据MOOC网北大课程——《程序设计与算法(三):C++面向对象程序设计》内容整理归纳而来,整理的课程大纲详见 https://www.cnblogs.com/inchbyinch/p/12398921.html

本文介绍了C++11的新特性,包括初始化、默认值、auto/decltype/nullptr/for/typeid关键字,哈希表、正则表达式、多线程,Lambda表达式,右值引用和move语义,智能指针,四种类型强制转换运算符,异常处理。

1 初始化、默认值、auto/decltype/nullptr/for

//提供了花括号式的初始化方法
int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string> mp{{1, "a"}, {2, "b"}};
string str{"Hello World"};
int * p = new int[20]{1,2,3};


//可以设置类的成员变量默认初始值
class B{
public:
    int m = 1234;
    int n;
};
int main(){
    B b;
    cout << b.m << endl; //输出 1234
    return 0;
}


//auto关键字,用于定义变量,编译器可以自动判断变量的类型
//示例1
map<string, int, greater<string> > mp;
for( auto i = mp.begin(); i != mp.end(); ++i)
    cout << i->first << "," << i->second ;
//i的类型是:map<string, int, greater<string> >::iterator
//示例2
template <class T1, class T2>
auto add(T1 x, T2 y) -> decltype(x + y) {
    return x+y;
}
auto d = add(100,1.5); // d是double d=101.5


//decltype关键字,求表达式的类型
int i;
double t;
struct A { double x; };
const A* a = new A();
decltype(a) x1;   // x1 is A *
decltype(i) x2;   // x2 is int
decltype(a->x) x3;  // x3 is double
decltype((a->x)) x4 = t;  // x4 is double&


//nullptr关键字。C++中宏定义NULL为0,定义nullptr为空指针的关键字
//nullptr可以转换为其他类型指针和布尔类型,但不能转为整数
//详见 https://www.cnblogs.com/developing/articles/10890886.html
int main() {
    int* p1 = NULL;
    int* p2 = nullptr;
    shared_ptr<double> p3 = nullptr;
    if(p1 == p2)
        cout << "equal 1" <<endl;
    if( p3 == nullptr) //智能指针可以直接与NULL、nullptr比较
        cout << "equal 2" <<endl;
    //if( p3 == p2) ; //error,智能指针会做类型检查
    if( p3 == NULL)
        cout << "equal 4" <<endl;
    bool b = nullptr; // b = false
    //int i = nullptr; //error,nullptr不能自动转换成整型
    return 0;
}
//输出:equal 1[回车]equal 2[回车]equal 4[回车]


//基于范围的for循环
struct A { int n; A(int i):n(i) { } };
int main() {
    int ary[] = {1,2,3,4,5};
    for(int & e: ary)
        e*= 10;
    for(int e : ary)    //输出10,20,30,40,50,
        cout << e << ",";
    cout << endl;   
    vector<A> st(ary,ary+5);
    for( auto & it: st)
        it.n *= 10;
    for( A it: st)   //输出100,200,300,400,500,
        cout << it.n << ",";
    return 0;
}

2 运行时类型检查运算符typeid

C++运算符typeid是单目运算符,可以在程序运行过程中获取一个表达式的值的类型。typeid运算的返回值是一个type_info类的对象,里面包含了类型的信息。

//typeid和type_info用法示例
#include <iostream>
#include <typeinfo> //要使用typeinfo,需要此头文件
using namespace std;
struct Base { }; //非多态基类
struct Derived : Base { };
struct Poly_Base {virtual void Func(){ } }; //多态基类
struct Poly_Derived: Poly_Base { };
int main(){
    long i; int * p = NULL;
    cout << "1) int is: " << typeid(int).name() << endl; //输出 1) int is: int
    cout << "2) i is: " << typeid(i).name() << endl; //输出 2) i is: long
    cout << "3) p is: " << typeid(p).name() << endl; //输出 3) p is: int *
    cout << "4) *p is: " << typeid(*p).name() << endl ; //输出 4) *p is: int
    
    //非多态类型
    Derived derived;
    Base* pbase = &derived;
    cout << "5) derived is: " << typeid(derived).name() << endl;
    //输出 5) derived is: struct Derived
    cout << "6) *pbase is: " << typeid(*pbase).name() << endl;
    //输出 6) *pbase is: struct Base
    cout << "7) " << (typeid(derived)==typeid(*pbase) ) << endl;//输出 7) 0
    
    //多态类型
    Poly_Derived polyderived;
    Poly_Base* ppolybase = &polyderived;
    cout << "8) polyderived is: " << typeid(polyderived).name() << endl;
    //输出 8) polyderived is: struct Poly_Derived
    cout << "9) *ppolybase is: " << typeid(*ppolybase).name() << endl;
    //输出 9) *ppolybase is: struct Poly_Derived
    cout << "10) " << (typeid(polyderived)!=typeid(*ppolybase) ) << endl;
    //输出 10) 0
}    

3 哈希表、正则表达式、多线程

//无序容器<unordered_map>,用法同<map>一样
//实现:哈希表;插入和查找速度几乎为常数,但也更耗空间。
int main(){
    unordered_map<string,int> turingWinner; //图灵奖获奖名单
    turingWinner.insert(make_pair("Dijkstra",1972));
    turingWinner.insert(make_pair("Scott",1976));
    turingWinner["Ritchie"] = 1983;
    string name;
    cin >> name; //输入姓名来查找
    unordered_map<string,int>::iterator p =turingWinner.find(name);
    if( p != turingWinner.end())
        cout << p->second;
    else
        cout << "Not Found" << endl;
    return 0;
}


//正则表达式
//可用于:有效性验证、从文本中抓取感兴趣信息、分词(Tokenize)、替换等等
//C++11以后支持原生字符,C++03之前不支持;可用assert(...)
//https://www.cnblogs.com/coolcpp/p/cpp-regex.html
int main(){
    regex reg("b.?p.*k");
    cout << regex_match("bopggk",reg) <<endl; //输出 1, 表示匹配成功
    cout << regex_match("boopgggk",reg) <<endl; //输出 0, 匹配失败
    cout << regex_match("b pk",reg) <<endl ; //输出 1, 表示匹配成功
    regex reg2("\\d{3}([a-zA-Z]+).(\\d{2}|N/A)\\s\\1");
    //可用原生字符如 std::regex reg1(R"(<(.*)>.*</\1>)");
    string correct="123Hello N/A Hello";
    string incorrect="123Hello 12 hello";
    cout << regex_match(correct,reg2) <<endl; //输出 1,匹配成功
    cout << regex_match(incorrect,reg2) << endl; //输出 0, 失败
}


//多线程
// https://blog.csdn.net/lizun7852/article/details/88753218
// https://blog.csdn.net/lijinqi1987/article/details/78396512
// https://www.cnblogs.com/wangguchangqing/p/6134635.html
// https://liam.page/2017/05/16/first-step-on-multithread-programming-of-cxx/

void GetSumT(vector<int>::iterator first,vector<int>::iterator last,int &result){
    result = accumulate(first,last,0); //调用C++标准库算法
}

int main(){   //主线程
    int result1, result2, result3;
    vector<int> largeArrays;
    for(int i=0; i<60000000; i++){
        if(i%2==0)
            largeArrays.push_back(i);
        else
            largeArrays.push_back(-1*i);
    }
    thread first(GetSumT,largeArrays.begin(),
        largeArrays.begin()+20000000,std::ref(result1)); //子线程1
    thread second(GetSumT,largeArrays.begin()+20000000,
        largeArrays.begin()+40000000,std::ref(result2)); //子线程2
    thread thrid(GetSumT,largeArrays.begin()+40000000,
        largeArrays.end(),std::ref(result3)); //子线程3

    first.join(); //主线程要等待子线程执行完毕
    second.join();
    third.join();
    
    int resultSum = result1+result2+result3+result4+result5; //汇总各个子线程的结果
    return 0;
}

4 Lambda表达式

为了避免在主函数体外写过多的函数体或类,对于只使用一次的简单函数或者函数对象,可以采用匿名函数。

//形式:
[外部变量访问方式说明符](参数表)->返回值类型 { 语句组 }
  • [] 不使用任何外部变量
  • [=] 以传值的形式使用所有外部变量
  • [&] 以引用形式使用所有外部变量
  • [x, &y] x 以传值形式使用,y 以引用形式使用
  • [=,&x,&y] x,y 以引用形式使用,其余变量以传值形式使用
  • [&,x,y] x,y 以传值的形式使用,其余变量以引用形式使用
  • “->返回值类型”也可以没有,没有则编译器自动判断返回值类型。
//示例1
int main(){
    int x = 100,y=200,z=300;
    cout << [ ](double a,double b) { return a + b; }(1.2,2.5) << endl;
    auto ff = [=, &y, &z](int n) { //可以赋给一个名字,便于重复或简明使用
        cout << x << endl;
        y++; z++;
        return n*n;
     };
    cout << ff(15) << endl;
    cout << y << "," << z << endl;
}

//示例2
int a[4] = { 4,2,11,33 };
sort(a, a+4, [](int x, int y)->bool {return x%10 < y%10; });
for_each(a, a+4, [](int x) {cout << x << " " ;});  //11 2 33 4

//示例3
vector<int> a { 1,2,3,4};
int total = 0;
for_each(a.begin(), a.end(), [&](int& x){ total += x; x *= 2; });
cout << total << endl; //输出 10
for_each(a.begin(), a.end(), [](int x){ cout << x << " ";});

//示例4:实现递归求斐波那契数列第n项
//function<int(int)> 表示返回值为int,有一个int参数的函数
function<int(int)> fib = [&fib](int n)  //实现递归求斐波那契数列第n项
    { return n <= 2 ? 1 : fib(n-1) + fib(n-2); };
cout << fib(5) << endl; //输出5

5 右值引用和move语义

参考:

左值和右值

在C语言中:

  • 左值就是既可以出现在赋值表达式左边,也可以出现在赋值表达式右边的表达式e;
  • 右值就是只能出现在赋值表达式右边的表达式。

在C++中:

  • 左值:指向一块内存,并且允许通过 & 操作符获取该内存地址的表达式;
  • 右值:非左值的即为右值。

move语义

move语义就是在复制过程中使用浅拷贝,然后把被复制对象所持的资源所有权注销,并不涉及到复制和销毁所持有的资源。

在类的复制构造函数和赋值操作符重载函数中,浅拷贝会带来一系列问题,深拷贝则又太耗资源,因此在一些特殊的场合(比如赋值后右值就不再使用),可以使用move语义来避免深拷贝。

类的复制构造函数在三个地方使用:用同类对象初始化、函数参数传值、函数返回对象。函数返回对象时调用复制构造函数的行为已被现代编译器优化;函数参数传值可以通过改为传引用来规避;用同类对象来初始化时调用复制构造函数的行为可以通过move语义来规避(仅限于不再使用这个同类对象的情况,详见swap示例)。

move语义的实现:通过右值引用再一次重载复制构造函数和赋值操作符。当赋值操作的右边是一个左值时,编译器调用常规赋值函数(进行深拷贝);当右边是一个右值时,编译器调用右值引用的赋值函数(进行浅拷贝)。换句话说,通过重载,左值选择常规引用,右值选择move语义。

//示例1:右值赋值
#include <iostream>
#include <cstring>
using namespace std;

class A{
    int* i;
public:
    A():i(new int[500]){ cout<<"class A construct!"<<endl; }
    A(const A &a):i(new int[500]){
      memcpy(a.i,i,500*sizeof(int));
      cout<<"class A copy!"<<endl;
     }
    ~A(){ delete []i; cout<<"class A destruct!"<<endl; }
};

A get_A_value(){ return A(); }
int main(){
    A a = get_A_value();
    return 0;
}

右值引用

右值引用,就是不同于A&左值引用的引用,其表现形式为A&&。右值引用的行为类似于左值引用,但有几点显著区别,最重要的区别是:当函数重载决议时,左值倾向于左值引用,右值倾向于右值引用。

//示例
void foo(A &a);		//lvalue reference overload
void foo(A &&a);	//rvalue reference overload
A a;
A foobar(){};
foo(a);				//argument is lvalue: calls foo(A&)
foo(foobar());		//argument is rvalue: calls foo(A&&)

注意:

  • 若只实现void foo(A&),而不实现void foo(A&&),则foo只能左值调用。
  • 若只实现void foo(const A&),而不实现void foo(A&&),则foo能同时被左值和右值调用,但是左值右值无任何语义区别(因为左值和右值均可以赋给const A&类型)。
  • 若只实现void foo(A &&),而不实现void foo(A&)或void foo(const A&),则foo仅能被右值调用,如果被左值调用会触发编译错误。
  • 变量被声明为右值引用后,既可以为左值也可以为右值,比如在重载函数void foo(A && a)中,a被默认当成左值用,需要强制转义才能做右值。区别左值和右值的标准是:它是否有名字,有名字就是左值,无名字就是右值。

强制move语义

C++11允许程序员不仅仅在右值上使用move语义,同样也允许程序员在左值上使用move语义,只需要用move函数即可将左值强制转换为右值(move类似一个类型强制转换符)。

//示例:swap函数
template<class T> 
void swap(T& a, T& b) { 
    T tmp(std::move(a));
    a = std::move(b); 
    b = std::move(tmp);
}

A a, b;
swap(a, b);

移动构造函数和移动赋值函数

重载复制构造函数和赋值函数,当传入左值时选择常规引用,传入右值时选择move语义。

#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class String{
public:
    char * str;
    String():str(new char[1]) { str[0] = 0;} //无参构造函数
    String(const char * s) {
        str = new char[strlen(s)+1];
        strcpy(str,s);
    }
    String(const String & s) {  //复制构造函数
        cout << "copy constructor called" << endl;
        str = new char[strlen(s.str)+1];
        strcpy(str,s.str);
    }
    String & operator=(const String & s) { //赋值函数
        cout << "copy operator= called" << endl;
        if( str != s.str) {
            delete [] str;
            str = new char[strlen(s.str)+1];
            strcpy(str,s.str);
        }
        return * this;
    }
    String(String && s):str(s.str) {  //移动复制构造函数
        cout << "move constructor called"<<endl;
        s.str = new char[1]; //释放掉被拷贝对象的资源所有权
        s.str[0] = 0;
    }
    String & operator = (String && s) {  //移动赋值函数
        cout << "move operator= called"<<endl;
        if (str!= s.str) {
            delete [] str;
            str = s.str;
            s.str = new char[1];
            s.str[0] = 0;
        }
        return *this;
    }
    ~String() { delete [] str; }
};

6 智能指针

参考:

为啥需要智能指针?

  • 为了处理用动态内存时的内存泄露和悬空指针的问题;
  • 内存泄露的两种情况:忘记delete,或者抛出异常时没有继续走下面的delete语句;
  • 智能指针把动态内存的指针封装成一个类,使用上像一个指针,但实际上是一个对象,利用对象生命周期结束后自动消亡这一特点,在析构函数中调用delete,达到自动回收内存的效果。

智能指针有哪几类?为了解决啥问题?

  • C++98提供了第一种智能指针:auto_ptr,C++11又增加了三种:unique_ptr、shared_ptr、weak_ptr,之后auto_ptr被摒弃。均在头文件< memory >中。
  • 所有的智能指针类都有一个explicit构造函数,以指针作为参数,因此不能自动将指针转换为智能指针对象,必须显式调用。(只接受动态分配内存的指针。)
  • 用智能指针对象来托管一个动态内存指针需要解决对象赋值的问题(赋值后会产生对象多次析构delete的问题),通常有两种解决方案,一是资源独享(auto_ptr和unique_ptr采用的策略),二是通过计数来保证多个对象共同托管一个指针时只delete一次(shared_ptr采用的策略)。
  • auto_ptr虽然保证了资源独享,但是将对象p1赋值给p2后,对象p1为空指针,后续若不小心用 * 号访问其指向的内存时,程序会崩溃。相比之下,unique_ptr更为严格,它在进行赋值操作时会做检查,若右边为右值时可正常赋值,若为左值时则编译报错,提醒程序员此处有风险。即用unique_ptr替代auto_ptr的好处是可以避免潜在的内存崩溃问题,更安全。(若unique_ptr实在需要赋值,则可用move)
//示例1:智能指针需要显式调用构造函数
shared_ptr<double> pd; 
double * p_reg = new double;
//pd = p_reg;                // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg);   // allowed (explicit conversion)
//shared_ptr<double> pshared = p_reg;    // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg);     // allowed (explicit conversion)

//示例2:为了避免赋值后访问空指针的风险,unique_ptr要求赋值时右边为右值
auto_ptr<string> p1(new string ("auto")); 
auto_ptr<string> p2;            
p2 = p1;             //编译允许,但后续若不小心使用*p1时程序崩溃
unique_ptr<string> p3 (new string ("auto"));  
unique_ptr<string> p4;         
//p4 = p3;     //编译出错,以免后续使用*p3
p4 = move(p3);  //用move转为右值,需要特别小心,在更新p3前使用*p3会程序崩溃
p3 = unique_ptr<string>(new string("another")); //更新后可以继续使用

//示例3:shared_ptr用法
#include <memory>
#include <iostream>
using namespace std;
struct A {
    int n;
    A(int v = 0):n(v){ }
        ~A() { cout << "id " << n << " destructor" << endl; }
};
int main(){
    shared_ptr<A> sp1(new A(2)); //sp1托管A(2)
    shared_ptr<A> sp2(sp1); //sp2也托管 A(2)
    cout << "1)" << sp1->n << "," << sp2->n << endl; //输出1)2,2
    shared_ptr<A> sp3;
    A * p = sp1.get(); //获取智能指针对象内部托管的指针
    cout << "2)" << p->n << endl;
    sp3 = sp1; //sp3也托管 A(2)
    cout << "3)" << (*sp3).n << endl; //输出1)2
    sp1.reset(); //sp1放弃托管 A(2)
    if( !sp1 ) cout << "4)sp1 is null" << endl; //会输出
    A * q = new A(3);
    sp1.reset(q); // sp1托管q
    cout << "5)" << sp1->n << endl; //输出 3
    cout << "end main" << endl;
    return 0; //程序结束,会delete 掉A(2)
}

7 四种类型强制转换运算符

参考:

将类型名作为强制类型转换运算符的做法是C语言的老式做法,C++为保持兼容而予以保留。但有一些缺陷如下:

  • 没有从形式上体现转换功能和风险的不同。
  • 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
  • 难以在程序中寻找到底什么地方进行了强制类型转换,难以debug。

为此,C++引入了四种功能不同的强制类型转换运算符以进行强制类型转换:static_cast,reinterpret_cast,const_cast,dynamic_cast。形式如下:

强制类型转换运算符 <要转换到的类型> (待转换的表达式)

static_cast

static_cast用来进用行比较“自然”和低风险的转换,比如整型和实数型、字符型之间互相转换。
static_cast不能来在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,也不能用于不同类型的引用之间的转换。

//用法示例
class A{
public:
    operator int() { return 1; }
    operator char * (){ return NULL; }
};
int main(){
    A a;
    int n; char * p = "New Dragon Inn";
    n = static_cast<int>(3.14); // n 的值变为 3
    n = static_cast<int>(a); //调用a.operator int, n的值变为 1
    p = static_cast<char*>(a); //调用a.operator int *,p的值变为 NULL
    //n = static_cast<int>(p); //编译错误,static_cast不能将指针转换成整型
    //p = static_cast<char*>(n); //编译错误,static_cast不能将整型转换成指针
    return 0;
}

reinterpret_cast

reinterpret_cast用来进行各种不同类型的指针之间的转换、不同类型的引用之间转换、以及指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作。(不做安全检查,风险大。)

//用法示例(虽然编译正常,但实际不可这么瞎搞)
class A{
public:
    int i; int j;
    A(int n):i(n),j(n) { }
};
int main(){
    A a(100);
    int& r = reinterpret_cast<int&>(a); //强行让 r 引用 a
    r = 200; //把 a.i 变成了 200
    cout << a.i << "," << a.j << endl; // 输出 200,100
    int n = 300;
    A * pa = reinterpret_cast<A*>( & n); //强行让 pa 指向 n
    pa->i = 400; // n 变成 400
    pa->j = 500; //此条语句不安全,很可能导致程序崩溃
    cout << n << endl; // 输出 400
    long long la = 0x12345678abcdLL;
    pa = reinterpret_cast<A*>(la); //la太长,只取低32位0x5678abcd拷贝给pa
    unsigned int u = reinterpret_cast<unsigned int>(pa);
    //pa逐个比特拷贝到u
    cout << hex << u << endl; //输出 5678abcd
    typedef void (* PF1) (int);
    typedef int (* PF2) (int,char *);
    PF1 pf1; PF2 pf2;
    pf2 = reinterpret_cast<PF2>(pf1); //两个不同类型的函数指针之间可以互相转换
}    
    

const_cast

用来进行去除const属性的转换。将const引用转换成同类型的非const引用,将const指针转换为同类型的非const指针时用它。有风险。

//用法示例
const string s = “Inception”;
string & p = const_cast<string&>(s);
string * ps = const_cast<string*>(&s); // &s的类型是const string *
    

dynamic_cast

  • dynamic_cast专门用于将多态基类的指针或引用,强制转换为派生类的指针或引用,而且能够检查转换的安全性。
  • 对于不安全的指针转换,转换结果返回NULL指针。对于不安全的引用转换则抛出异常。
  • dynamic_cast不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用。
//用法示例
class Base{ //有虚函数,因此是多态基类
public:
    virtual ~Base() { }
};
class Derived:public Base { };
int main(){
    Base b;
    Derived d;
    Derived * pd;
    pd = reinterpret_cast<Derived*>(&b);
    if(pd == NULL) //不会为NULL。reinterpret_cast不检查安全性,总是进行转换
        cout << "unsafe reinterpret_cast" << endl; //不会执行
    pd = dynamic_cast<Derived*>(&b);
    if(pd == NULL) //结果会是NULL,因为&b不是指向派生类对象,此转换不安全
        cout << "unsafe dynamic_cast1" << endl; //会执行
    pd = dynamic_cast<Derived*>(&d); //安全的转换
    if(pd == NULL) //此处pd不会为NULL
        cout << "unsafe dynamic_cast2" << endl; //不会执行
    return 0;
}

8 异常处理

参考:

程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:

  • 语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。
  • 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。
  • 运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。

注意:

  • 异常机制实际上就是定义一个异常类,通过异常对象来通信。需include < stdexcept >
  • 如果一个函数在执行的过程中,抛出的异常在本函数内就被catch块捕获并处理了,那么该异常就不会抛给这个函数的调用者(也称“上一层的函数”);如果异常在本函数中没被处理,就会被抛给上一层的函数。(可以根据需要来决定是在函数内部处理异常,还是把异常抛给调用者。)
  • 在函数内部发生异常后返回到上一层函数时,该函数在栈中被清除,里面的局部变量也会消亡。不管是否把异常抛给上一层函数,try块中定义的局部对象在发生异常时都会消亡。
//用法示例:自定义异常类
class CException{
public:
    string msg;
    CException(string s):msg(s) { }
};
double Devide(double x, double y){
    if(y == 0)
        throw CException("devided by zero"); //把异常抛给调用者
    cout << "in Devide" << endl;
    return x / y;
}
int CountTax(int salary){
    try {
        if(salary < 0)  throw -1;
        cout << "counting tax" << endl;
    }
    catch (int) {   //函数内部处理异常
        cout << "salary < 0" << endl;
    }
    cout << "tax counted" << endl;
    return salary * 0.15;
}
int main(){
    double f = 1.2;
    try {
        CountTax(-1);
        f = Devide(3,0);
        cout << "end of try block" << endl;
    }
    catch(CException e) {
        cout << e.msg << endl;
    }
    cout << "f=" << f << endl;
    cout << "finished" << endl;
    return 0;
}

C++标准库中有一些类代表异常,这些类都是从exception类派生而来。

//示例1:在用dynamic_cast进行从多态基类对象到派生类的引用的强制类型转换时,如果转换是不安全的,则会抛出bad_cast异常
#include <iostream>
#include <stdexcept>
#include <typeinfo>
using namespace std;
class Base{ virtual void func(){} };
class Derived : public Base{
public:
    void Print() { }
};
void PrintObj( Base & b){
    try {
        Derived & rd = dynamic_cast<Derived&>(b); 
        //此转换若不安全,会抛出bad_cast异常
        rd.Print();
    }
    catch (bad_cast& e) {
        cerr << e.what() << endl;
    }
}
int main (){
    Base b;
    PrintObj(b);
    return 0;
}

//示例2:在用new运算符进行动态内存分配时,如果没有足够的内存,则会引发bad_alloc异常
int main (){
    try {
        char * p = new char[0x7fffffff]; //无法分配这么多空间,会抛出异常
    }
    catch (bad_alloc & e) {
        cerr << e.what() << endl;
    }
    return 0;
}

//示例3:用vector或string的at成员函数根据下标访问元素时,如果下标越界,就会抛出out_of_range异常
int main (){
    vector<int> v(10);
    try {
        v.at(100)=100; //抛出out_of_range异常
    }
    catch (out_of_range& e) {
        cerr << e.what() << endl;
    }
    string s = "hello";
    try {
        char c = s.at(100); //抛出out_of_range异常
    }
    catch (out_of_range& e) {
        cerr << e.what() << endl;
    }
    return 0;
}    
posted @ 2020-03-02 23:40  天地辽阔  阅读(438)  评论(0编辑  收藏  举报