前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++高级主题系列篇

C++高级主题系列篇

作者头像
用户9831583
发布2022-06-16 14:45:59
4540
发布2022-06-16 14:45:59
举报
文章被收录于专栏:码出名企路

主要包括:

异常处理

强制转换

智能指针

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%10 < y%10; }
代码语言:javascript
复制
    int a[4] = {11, 2, 33, 4};
    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

代码语言: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;
    }

结果显示:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码出名企路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.异常处理
  • 2.强制转换
  • 3.智能指针
  • 4.Lambda
  • 5.auto和decltype
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档