程序设计与算法(三)C++面向对象程序设计笔记 第十周 C++11新特性和C++高级主题

笔记按照中国大学MOOC上北京大学郭炜老师主讲的程序设计与算法(三)C++面向对象程序设计所作,B站上也有资源。原课程链接如下:

程序设计与算法(三)C++面向对象程序设计

其他各章节链接如下:

程序设计与算法(三)C++面向对象程序设计笔记 第一周 从C到C++

程序设计与算法(三)C++面向对象程序设计笔记 第二周 类和对象基础

程序设计与算法(三)C++面向对象程序设计笔记 第三周 类和对象提高

程序设计与算法(三)C++面向对象程序设计笔记 第四周 运算符重载

程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承

程序设计与算法(三)C++面向对象程序设计笔记 第六周 多态

程序设计与算法(三)C++面向对象程序设计笔记 第七周 输入输出和模板

程序设计与算法(三)C++面向对象程序设计笔记 第八周 标准模板库STL(一)

程序设计与算法(三)C++面向对象程序设计笔记 第九周 标准模板库STL(二)

程序设计与算法(三)C++面向对象程序设计笔记 第十周 C++11新特性和C++高级主题

其他各科笔记汇总

C++11 新特性和 C++ 高级主题

C++11 新特性

统一的初始化方法
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};
struct A {
    int i,j; 
    A(int m,int n):i(m),j(n) { }
};
A func(int m,int n ) { return {m,n}; }
int main() { A * pa = new A {3,7}; }
成员变量默认初始值
class B
{
    public:
    int m = 1234;
    int n;
};
int main()
{
    B b;
    cout << b.m << endl; //输出 1234
    return 0;
}
auto 关键字

用于定义变量,编译起可以自动判断变量的类型

定义变量时必须对变量进行初始化

auto i = 100;     // i 是 int
auto p = new A(); // p 是 A *
auto k = 34343LL; // k 是 long long

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

 

class A { };
A operator + ( int n,const A & a)
{
    return a;
}

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
auto k = add(100,A()); // d是A类型

C++11 里面可以把函数的返回值写在函数的后面,通过 -> 后面写一个比如 int 之类来指明函数的返回值类型

-> decltype(x + y) 告诉编译器 add 函数的返回值类型是 decltype(x + y),decltype(x + y) 代表 x + y 表达式的类型。编译器以后从 add 模板实例化出 add 函数时会根据 x + y 表达式的类型去推断出函数返回值类型

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&
智能指针 shared_ptr

shared_ptr 是一个类模板

 

头文件: <memory>

通过 shared_ptr 的构造函数,可以让 shared_ptr 对象托管一个 new 运算符返回的指针,写法如下:

shared_ptr<T> ptr(new T); //T可以是int, char, 类名等各种类型

此后 ptr 就可以像 T* 类型的指针一样来使用,即 *ptr 就是用 new 动态分配的那个对象,而且不必操心释放内存的事

多个shared_ptr对象可以同时托管一个指针,这个指针必须是 new 出来的,系统会维护一个托管计数。当无shared_ptr 托管该指针时,delete 该指针

shared_ptr 对象不能托管指向动态分配的数组的指针,否则程序运行会出错

#include <memory>
#include <iostream>
using namespace std;
struct A {
    int n;
    A(int v = 0):n(v){ }
    ~A() { cout << 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(); //p指向A(2)
    cout << "2)" << p->n << endl
        
    sp3 = sp1; //sp3也托管A(2)
    cout << "3)" << (*sp3).n << endl; //输出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
    shared_ptr<A> sp4(sp1); //sp4托管A(3)
    shared_ptr<A> sp5;
    sp1.reset(); //sp1放弃托管A(3)
    cout << "before end main" <<endl;
    sp4.reset(); //sp4放弃托管 A(3)
    cout << "end main" << endl;
    return 0; //程序结束,会delete掉A(2)
}

输出结果:
1)2,2
2)2
3)2
4)sp1 is
null
5)3
before end
main
3
destructor

 

sp2 是用复制构造函数初始化的

A * p = sp1.get() 取得 share_ptr 对象 sp1 托管的指针,p 指向 A(2)

用 !sp1 判断是否托管指针

new 出来的东西交给 shard_ptr 对象托管,后来这些对象又放弃托管,导致 new 出来的东西已经没有人托管,这个 new 出来的东西就会被自动 delete

只要一个 shared_ptr 对象消亡,它托管的那个指针就有可能被 delete。程序结束时所有的 shared_ptr 对象都消亡

 

#include <iostream>
#include <memory>
using namespace std;
struct A{
~A() { cout << "~A" << endl; }
};
int main()
{
    A * p = new A();
    shared_ptr<A> ptr(p);
    shared_ptr<A> ptr2;
    ptr2.reset(p); //并不增加ptr中对p的托管计数
    cout << "end" << endl;
    return 0;
}

输出:
end
~A
~A
之后程序崩溃,因 p 被 delete 两次

在整个系统看来,ptr2 托管了一个指针,ptr 也托管了一个指针,虽然这两个指针是同一个地址,但是系统不这么认为,它并不知道上面的 p 跟 下面的 p 是相同的,认为 ptr2 和 ptr 分别托管不同的指针

空指针nullptr
#include <memory>
#include <iostream>
using namespace std;
int main() {
    int* p1 = NULL;
    int* p2 = nullptr;
    shared_ptr<double> p3 = nullptr;
    if(p1 == p2)
        cout << "equal 1" <<endl;
    if( p3 == 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

 

p3 == NULL 这里的”==“肯定经过了某种形式的重载

nullptr 可以被自动转换成一个 bool 类型变量

基于范围的 for 循环
#include <iostream>
#include <vector>
using namespace std;
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)
        cout << e << ",";
    cout << endl;
    
    vector<A> st(ary,ary+5);
    for( auto & it: st)
        it.n *= 10;
    for( A it: st)
        cout << it.n << ",";
    return 0;
}

输出:
10,20,30,40,50,
100,200,300,400,500,

 

for(int & e: ary) 对于 ary 数组里面每一个元素的引用 e 依次进行,不想修改 ary 的值可以不写引用

for(auto & it: st) it 代表 st 里面每一个元素的引用

右值引用和 move 语义

如果一个表达式能够出现在赋值号的左边就是左值,不能就是右值。之前的引用都是左值引用,只能引用左值,不能引用右值

 

 

右值:一般来说,不能取地址的表达式,就是右值,能取地址的,就是左值

class A { };
A & r = A();    //error, A()是无名变量,是右值
A && r = A();   //ok, r是右值引用

主要目的是提高程序运行的效率,减少需要进行深拷贝的对象进行深拷贝的次数

如果一个类里面有一个指针成员变量,这个指针成员变量指向一片动态分配的存储空间,这种情况下类的复制构造函数或者赋值号被调用进行对象复制时一般不会用浅拷贝简简单单地把两个指针变成一模一样,而会用深拷贝,把原对象里面的指针指向的存储空间里面的内容复制到新对象里面的那个指针所指向的存储空间里面

 

#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;
    }
    
    //move constructor
    String(String && s):str(s.str) {
        cout << "move constructor called"<<endl;
        s.str = new char[1];
        s.str[0] = 0;
    }
    
    //move assigment
    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; }
};

template <class T>
void MoveSwap(T& a, T& b) {
    T tmp(move(a));//std::move(a)为右值,这里会调用move constructor
    a = move(b);   //move(b)为右值,因此这里会调用move assigment
    b = move(tmp); //move(tmp)为右值,因此这里会调用move assigment
}

int main()
{
    //String & r = String("this"); // error
    String s;
    s = String("ok"); // String("ok")是右值
    cout << "******" << endl;
    String && r = String("this");
    cout << r.str << endl;
    String s1 = "hello",s2 = "world";
    MoveSwap(s1,s2);
    cout << s2.str << endl;
    return 0;
}

输出:
move operator= called
******
this
move constructor called
move operator= called
move operator= called
hello

 

有了右值引用以后可以新写一个参数是一个右值引用 String && s 的移动构造函数(move constructor),直接把被初始化对象的 str 指向 s.str 指向的地方,让 s.str 指向一片新的存储空间,存储空间里面放着一个空串

同理还要写一个移动的赋值号(move assignment)

 

T tmp(move(a)) 对 tmp 进行初始化时写的是 move(a),如果不写 move(a) 直接写 a 则 tmp 用复制构造函数初始化,复制构造函数里可能进行深拷贝操作

move 是在 STL 里面定义好的一个函数模板,把一个左值变成右值,move(a) 返回值是一个右值。普通的复制构造函数参数是左值引用,移动构造函数的参数是一个右值引用,右值引用可以和右值匹配,所以这里会调用移动构造函数

a = move(b) 不会调用传统的参数为左值引用的 operator= 而是调用参数为右值引用的移动的赋值号

无序容器(哈希表)

unordered_map 的功能和用法跟 map 一样,区别在于 map 内部用平衡二叉树实现,unordered_map 用哈希表实现,时间效率更高,但是需要更多的内存空间

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main()
{
    unordered_map<string,int> turingWinner; //图灵奖获奖名单
    turingWinner.insert(make_pair("Dijkstra",1972));
    turingWinner.insert(make_pair("Scott",1976));
    turingWinner.insert(make_pair("Wilkes",1967));
    turingWinner.insert(make_pair("Hamming",1968));
    turingWinner["Ritchie"] = 1983;
    string name;
    cin >> name; //输入姓名
    
    auto p = turingWinner.find(name);
    //据姓名查获奖时间
    if( p != turingWinner.end())
        cout << p->second;
    else
        cout << "Not Found" << endl;
    return 0;
}

哈希表插入和查询的时间复杂度几乎是常数

正则表达式

正则表达式描述的是字符串的一种模式

#include <iostream>
#include <regex>       //使用正则表达式须包含此文件
using namespace std;
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");
    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, 失败
}

 

regex reg("b.?p.*k");

regex 是正则表达式类,reg 是正则表达式对象,用字符串 “b.?p.*k” 进行初始化。reg 作为正则表达式代表了一种字符串的模式,字符串的模式由 “b.?p.*k” 说明,它代表某一类字符串

这类字符串必须以 ‘b’ 开头,‘.’ 代表任意字符,也就是 ‘b’ 后面跟任意字符,‘?‘表示刚才这个任意字符应该出现零次或一次。’*’ 说明刚才这个任意字符可以出现零次或者任意多次

定义了一个正则表达式以后就可以用 regex_match 函数判断某一个指定的字符串是否能和正则表达式相匹配,符合这个正则表达式所规定的模式

 

regex reg2("\\d{3}([a-zA-Z]+).(\\d{2}|N/A)\\s\\1");

C++ 里面的字符串要一个斜杠被表示成两个斜杠,故 ‘\\d’ 代表 ‘\d’,‘\d’ 代表数字0~9,‘{3}’ 代表刚才那个东西应该出现3次,也就是说模式所对应的字符串应该以3个数字打头

‘()’ 代表里面的内容是一个项,这个项在后面可能还会引用到,‘()’ 本身并不需要和任何东西匹配,它只指明这里要出现里面这一项。项 ‘[a-zA-Z]+’ 由字母出现一次或若干次组成,‘+’ 代表前面的东西必须出现一次或者任意多次,‘[a-zA-Z]’ 是英文字母

‘.’ 代表这个地方可以出现任意一个字符

接下来是第二项,第二项中间的 ‘|’ 是或的意思,就是第二项可以是 ‘\\d{2}’ 代表两个数字,也可以是 ‘N/A’ 字符串

‘\\s’ 是空格。‘\\1’ 代表第一项,在这个地方出现的东西必须跟刚才的第一项一模一样,这也是 incorrect 无法跟 reg2 匹配的原因

Lambda 表达式

只使用一次的函数对象,能否不要专门为其编写一个类?

只调用一次的简单函数,能否在调用时才写出其函数体?

 

lambda 表达式本质上就是一个函数

形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1T6fPgki-1666239501189)(C++ 面向对象程序设计.assets/image-20221019192604373.png)]

lambda 表达式一般出现在一个函数内部,只要不是在 lambda 表达式里面定义的变量都称为外部变量。外部变量可以是全局变量,也可以是调用了这个 lambda 表达式的那个函数里面定义的局部变量

使用外部变量可以有两种方式,一种是传值,一种是传引用。如果是传值方式使用,lambda 表达式不允许在语句组里面修改外部变量的值,此外如果外部变量是个对象,有可能引发复制构造函数的调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBR0m1Sz-1666239501192)(C++ 面向对象程序设计.assets/image-20221019192732702.png)]

“->返回值类型”也可以没有, 没有则编译器自动判断返回值类型

 

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;
}

输出:
3.7
100
225
201,301

 

可以把 lambda 表达式整个赋值给一个变量 ff,赋值语句并不导致 lambda 表达式被调用。ff(15) 调用 lambda 表达式,有点像函数指针的用法

 

 

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

 

 

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
    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 << " ";});
    return 0;
}

程序输出结果:
10
2 4 6 8

 

 

实现递归求斐波那契数列第n项:

function<int(int)> fib = [&fib](int n)
{ return n <= 2 ? 1 : fib(n-1) + fib(n-2);};

cout << fib(5) << endl; //输出5

function<int(int)> 表示返回值为 int,有一个 int 参数的函数。用 lambda 表达式对 fib 变量进行初始化

不可以用 auto 让编译器自动判断 fib 的类型

多线程
#include <iostream>
#include <thread>
using namespace std;

struct MyThread {
    void operator () () {
        while(true)
            cout << "IN MYTHREAD\n";
    }
};

void my_thread(int x)
{
    while(x)
        cout << "in my_thread\n";
}

int main()
{
    MyThread x;    //对x的要求:可复制
    thread th(x);  //创建线程并执行
    thread th1(my_thread, 100);
    while(true)
        cout << "in main\n";
    return 0;
}

输出:
in my_thread
in my_thread
IN MYTHREAD
IN MYTHREAD
in main
in my_thread
IN MYTHREAD
IN MYTHREAD
in main
in main
in my_thread
IN MYTHREAD
…….

类型强制转换

static_cast、interpret_cast、const_cast 和 dynamic_cast

 

static_cast

static_cast 用来进用行比较“自然”和低风险的转换,比如整型和实数型、字符型之间互相转换

static_cast不能来在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,也不能用于不同类型的引用之间的转换

#include <iostream>
using namespace std;
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 用来进行各种不同类型的指针之间的转换、不同类型的引用之间转换、以及指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作

#include <iostream>
using namespace std;
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);
    //两个不同类型的函数指针之间可以互相转换
}

输出结果:
200,100
400
5678abcd

 

r 引用 a,因为 r 是 int &,实际上就等于引用了 a 前面的4B,r = 200 往 r 引用的4B的地址空间里面写200

pa->i = 400 pa 实际指向的是 n,但编译器还是以为这块空间是 pa 指向的 A 对象 的 i

typedef void (* PF1) (int) PF1这种类型是一个函数指针类型,指针指向的函数参数类型是 int,返回值类型是 void

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 *

之后可以通过 p 和 ps 修改 s 的内容

dynamic_cast

经常会做基类指针到派生类指针的强制转换,强制转换是否安全取决于这个基类指针是否指向一个派生类对象

包含虚函数的基类就叫做多态基类

 

dynamic_cast 专门用于将多态基类的指针或引用,强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针

dynamic_cast 通过多态类里的虚函数表的指针判断一个被转换的指针指向的对象到底是基类还是派生类对象,因此不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用

#include <iostream>
#include <string>
using namespace std;
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)
    //此处pd不会为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;
}

输出结果:
unsafe dynamic_cast1

 

基类的对象也可以看作基类的引用

 

 

Derived & r = dynamic_cast<Derived&>(b);

那该如何判断该转换是否安全呢?

答案:不安全则抛出异常

异常处理

程序运行异常

程序运行中总难免发生错误

  • 数组元素的下标超界、访问 NULL 指针
  • 除数为0
  • 动态内存分配 new 需要的存储空间太大

 

引起这些异常情况的原因:

  • 代码质量不高,存在 BUG
  • 输入数据不符合要求
  • 程序的算法设计时考虑不周到

 

我们总希望在发生异常情况时

  • 不只是简单地终止程序运行
  • 能够反馈异常情况的信息:哪一段代码发生的、什么异常
  • 能够对程序运行中已发生的事情做些处理:取消对输入文件的改动、释放已经申请的系统资源 …

 

通常的做法:在预计会发生异常的地方,加入相应的代码,但这种做法并不总是适用的

......//对文件A进行了相关的操作
fun(arg,......);  //可能发生异常
......
  • caller 该如何知道 fun(arg, …) 是否发生异常
    • 没有发生异常,可以继续执行
    • 发生异常,应该在结束程序运行前还原对文件A的操作
  • fun(arg, …) 是别人已经开发好的代码
    • fun(arg, …) 的编写者不知道其他人会如何使用这个函数
    • fun(arg, …) 会出现在表达式中,通过返回值的方式区分是否发生异常
      • 不符合编写程序的习惯
      • 可能发生多种异常,通过返回值判断也很麻烦

 

需要一种手段

  • 把异常与函数的接口分开,并且能够区分不同的异常
  • 在函数体外捕获所发生的异常,并提供更多的异常信息
用 try、catch 进行异常处理
#include <iostream>
using namespace std;
int main()
{
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0)
            throw -1; //抛出int类型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d << endl;
    }
    catch(int e) {
        cout << "catch(int) " << e << endl;
    }
    cout << "finished" << endl;
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B5r2dN4G-1666239501194)(C++ 面向对象程序设计.assets/image-20221020003008010.png)]

如果抛出异常,异常没有被任何一个 catch 块捕获,程序就会异常中止

catch 块是从上到下依次匹配的,最先匹配到哪一个 catch 块就执行哪一个

 

 

#include <iostream>
using namespace std;
int main()
{
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0 )
            throw -1; //抛出整型异常
        else if( m == 0 )
            throw -1.0; //抛出double型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d << endl;
    }
    catch(...) {
        cout << "catch(...) " << endl;
    }
    cout << "finished" << endl;
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xDG9n4nR-1666239501195)(C++ 面向对象程序设计.assets/image-20221020003132320.png)]

catch(…) 能够捕获任何类型的异常

注意:try 块中定义的局部对象,发生异常时会析构!

异常的再抛出

如果一个函数在执行的过程中,抛出的异常在本函数内就被 catch 块捕获并处理了,那么该异常就不会抛给这个函数的调用者(也称“上一层的函数”);如果异常在本函数中没被处理,就会被抛给上一层的函数

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

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;
}

输出结果:
salary < 0
tax counted
devided by zero
f=1.2
finished

 

因为 CountTax(-1) 运行过程中抛出的异常自己处理掉了,它的调用者 main 里面就感知不到 CountTax(-1) 运行期间抛出的异常

因为这里不是传引用,e 是刚才 throw 出来的对象的复制品

C++ 标准异常类

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00MYKyo3-1666239501197)(C++ 面向对象程序设计.assets/image-20221020003952398.png)]

bad_typeid —— 类型判断

bad_cast —— 类型转换

bad_alloc —— new 运算符分配不成功

ios_base::failure —— 输入输出产生错误

out_of_range —— 数组、容器下标越界

bad_cast

在用 dynamic_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;
}

输出结果:
Bad dynamic_cast!

 

e.what() 是一个字符串,能够显示一些不正常现象的一些信息,具体文字信息是什么样的,不同编译器会有所不同

bad_alloc

在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常

#include <iostream>
#include <stdexcept>
using namespace std;
int main ()
{
    try {
        char * p = new char[0x7fffffff];
        //无法分配这么多空间,会抛出异常
    }
    catch (bad_alloc & e) {
        cerr << e.what() << endl;
    }
    return 0;
}

输出结果:
bad allocation

out_of_range

用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界,就会抛出此异常。例如:

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
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;
}

输出结果:
invalid vector<T> subscript
invalid string position

运行时类型检查

C++ 运算符 typeid 是单目运算符,可以在程序运行过程中获取一个表达式的值的类型。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
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值