C++高级主题系列篇

2022-06-16 14:45:59 浏览数 (1)

主要包括:

异常处理

强制转换

智能指针

auto,decltype

lambda表达式

1.异常处理

具体异常情况:

  • 做除法的时候除数为 0;
  • 用户输入年龄时输入了一个负数;
  • 用 new 运算符动态分配空间时,空间不够导致无法分配;
  • 访问数组元素时,下标越界;打开文件读取时,文件不存在。

异常处理机制:

函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。

拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。

如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。

具体实现方法:

通过 throw 语句和 try...catch 语句实现对异常的处理。

代码语言:javascript复制
throw  表达式;

该语句拋出一个异常。

异常是一个表达式,其值的类型可以是基本类型,也可以是类

代码语言:javascript复制
try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}

catch 可以有多个,但至少要有一个。

  • 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;
  • 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。

应用举例展示:

代码语言:javascript复制
    #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;
    }

结果显示:

如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:

由于catch(...)能匹配任何类型的异常,它后面的 catch 块实际上就不起作用,因此不要将它写在其他 catch 块前面。

代码语言:javascript复制
catch(...) {
    ...
}
代码语言:javascript复制
    #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;
    }

结果显示:

特殊处理:

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

如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。

代码语言:javascript复制
    #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;
    }

结果显示:

虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。

代码语言:javascript复制
    #include <iostream>
    #include <string>
    using namespace std;
    int CountTax(int salary)
    {
        try {
            if( salary < 0 )
                throw string("zero salary");
            cout << "counting tax" << endl;
        }
        catch (string s ) {
            cout << "CountTax error : " << s << endl;
            throw; //继续抛出捕获的异常
        }
        cout << "tax counted" << endl;
        return salary * 0.15;
    }
    int main()
    {
        double f = 1.2;
        try {
            CountTax(-1);
            cout << "end of try block" << endl;
        }
        catch(string s) {
            cout << s << endl;
        }
        cout << "finished" << endl;
        return 0;
    }

结果显示:

C 标准异常类

1) bad_typeid

使用 typeid 运算符时,如果其操作数是一个多态类的指针

2) bad_cast

在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。

代码语言:javascript复制
    #include <iostream>
    #include <stdexcept>
    #include <typeinfo>       // std::bad_cast
    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;
    }

结果显示:

3) bad_alloc

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

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

结果显示:

4) out_of_range

用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界。

代码语言:javascript复制
    #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;
    }

结果显示:

2.强制转换

强制类型风险:

如把整型数值转换成指针,把基类指针转换成派生类指针,把一种函数指针转换成另一种函数指针,把常量指针转换成非常量指针等。

强制转换用法:

代码语言:javascript复制
强制类型转换运算符 <要转换到的类型> (待转换的表达式)
代码语言:javascript复制
double d = static_cast <double> (3*5);  //将 3*5 的值转换成实数

1)static_cast

static_cast 用于: 风险低的转换,如整型和浮点型、字符型之间的互相转换。

如果对象所属的类重载了强制类型转换运算符 T(如 T 是 int、int* 或其他类型名),则 static_cast 也能用来进行对象到 T 类型的转换。

代码语言:javascript复制
    #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 char*,p 的值变为 NULL
        n = static_cast <int> (p);  //编译错误,static_cast不能将指针转换成整型
        p = static_cast <char*> (n);  //编译错误,static_cast 不能将整型转换成指针
        return 0;
    }

结果显示:

2)reinterpret_cast

reinterpret_cast 用于: 各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。

代码语言:javascript复制
    #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); //两个不同类型的函数指针之间可以互相转换
    }

结果显示:

3) const_cast

const_cast 运算符仅用于:进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。

代码语言:javascript复制
    const string s = "Inception";
    string& p = const_cast <string&> (s);
    string* ps = const_cast <string*> (&s);  // &s 的类型是 const string*

4) dynamic_cast

用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但不检查转换后的指针是否确实指向一个派生类对象。

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

代码语言:javascript复制
    #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;
    }

结果显示:

那该如何判断该转换是否安全呢?不存在空引用,因此不能通过返回值来判断转换是否安全。

C 的解决办法是:dynamic_cast 在进行引用的强制转换时,如果发现转换不安全,就会拋出一个异常,通过处理异常,就能发现不安全的转换

3.智能指针

实现原理:

只要将 new 运算符返回的指针 p 交给一个 shared_ptr 对象“托管”,托管 p 的 shared_ptr 对象在消亡时会自动执行。

该 shared_ptr 对象能像指针 p —样使用,即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。

代码语言:javascript复制
 shared_ptr<T> ptr(new T);  // T 可以是 int、char、类等各种类型
代码语言:javascript复制
    #include <iostream>
    #include <memory>
    using namespace std;
    class A
    {
    public:
        int i;
        A(int n):i(n) { };
        ~A() { cout << i << " " << "destructed" << endl; }
    };
    int main()
    {
        shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
        shared_ptr<A> sp2(sp1);       //A(2)同时交由sp2托管
        shared_ptr<A> sp3;
        sp3 = sp2;   //A(2)同时交由sp3托管
        cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
        A * p = sp3.get();      // get返回托管的指针,p 指向 A(2)
        cout << p->i << endl;  //输出 2
        sp1.reset(new A(3));    // reset导致托管新的指针, 此时sp1托管A(3)
        sp2.reset(new A(4));    // sp2托管A(4)
        cout << sp1->i << endl; //输出 3
        sp3.reset(new A(5));    // sp3托管A(5),A(2)无人托管,被delete
        cout << "end" << endl;
        return 0;
    }

结果显示:

应用范围: 只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。

代码语言:javascript复制
A* p = new A(10);shared_ptr <A> sp1(p), sp2(p);

问题所在: sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。

4.Lambda

思考:对于只使用一次的函数对象类,能否直接在使用它的地方定义呢?L

优势:Lambda 表达式可以减少程序中函数对象类的数量,使得程序更加优雅。

代码语言:javascript复制
 [外部变量访问方式说明符] (参数表) -> 返回值类型
 {
    语句块
 }

外部变量访问方式说明符:

”可以是=&,表示{}中用到的、定义在{}外面的变量在{}中是否允许被改变。=表示不允许,&表示允许。当然,在{}中也可以不使用定义在外面的变量。

“-> 返回值类型”可以省略。

代码语言:javascript复制
 [=] (int x, int y) -> bool {return x < y; }
代码语言:javascript复制
    int a[4] = {11, 2, 33, 4};
    sort(a, a 4, [=](int x, int y) -> bool { return x < y; } );
    for_each(a, a 4, [=](int x) { cout << x << " ";} );

输出结果:11 2 33 4

代码语言:javascript复制
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int main()
    {
        int a[4] = { 1, 2, 3, 4 };
        int total = 0;
        for_each(a, a   4, [&](int & x) { total  = x; x *= 2; });
        cout << total << endl;  //输出 10
        for_each(a, a   4, [=](int x) { cout << x << " "; });
        return 0;
    }

结果显示:

“外部变量访问方式说明符”有更加复杂和灵活的用法:

  • [=, &x, &y]表示外部变量 x、y 的值可以被修改,其余外部变量不能被修改;
  • [&, x, y]表示除 x、y 以外的外部变量,值都可以被修改。
代码语言:javascript复制
    #include <iostream>
    using namespace std;
    int main()
    {  
        int x = 100,y=200,z=300;
        auto ff  = [=,&y,&z](int n) {
            cout <<x << endl;
            y  ; z  ;
            return n*n;
        };
        cout << ff(15) << endl;
        cout << y << "," << z << endl;
    }

结果显示:

5.auto和decltype

定义:用 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。

代码语言:javascript复制
纯文本复制

int i;double t;

struct A { double x; };

const A* a = new A();

decltype(a) x1; //x1 是 A*

decltype(i) x2; //x2 是 int

decltype(a -> x) x3; // x3 是 double

特别注意:函数返回值若为 auto,则需要和 decltype 配合使用

代码语言:javascript复制
    #include <iostream>
    using namespace std;
    struct A {
        int i;
        A(int ii) : i(ii) {}
    };
    A operator   (int n, const A & a)
    {
        return A(a.i   n);
    }
    template <class T1, class T2>
    auto add(T1 x, T2 y) -> decltype(x   y) {
        return x   y;
    }
    int main() {
        auto d = add(100, 1.5);  // d 是 double 类型,d = 101.5
        auto k = add(100, A(1));  // k 是 A 类型,因为表达式“100 A(1)”是A类型的
        cout << d << endl;
        cout << k.i << endl;
        return 0;
    }

结果显示:

0 人点赞