11月30日笔记C++6_异常 exception,运算符重载 ,

9 篇文章 0 订阅

异常 exception
    string at()
    string s = "hello";
    s.at(10) == out_of_range

    new/delete 
    new char[-54] == bad_alloc

    我们在编写程序的过程中,会碰到各种错误
    如,未声明、未定义、重定义、模糊不清、        语法错误
    如,段错误、核心已转储...                                  异常
    
    通常把 编译时就能检测出来的这些错误,称为语法错误
    而把那些 程序执行时出现的各种错误,称为异常。


    C++提供了新的错误处理机制:异常处理
    异常处理的基本思想是把错误检测与错误处理分离开来。
    异常处理机制主要包括:
        1、throw表达式
        2、try{}catch(){}语句
        3、一套异常类型

    throw表达式,异常检测部分使用throw表达式来表示它遇到了无法处理的问题,

                          一般称为throw引发或抛出了异常。
    例:

    void foo(int i)
    {
        if (i < 0)
        {
            throw 1; // 抛出了一个int类型的异常,异常值为1
        }
        cout << "i = " << i << endl;
    }


    throw语句后 所有的代码都不会再执行,同时在当前函数中查找异常处理代码,
    如果当前函数中没有进得异常处理,throw会自动把 异常抛出给 当前函数的调用者,重复此过程,直到被处理。
    如果异常最终抛给了操作系统,操作系统会自动调用terminate函数,终止进程。

    try语句块
        一般语法格式:

        try 
        {
            可能发生异常的代码块
        }
        catch(异常类型1 形参)
        {
            处理异常
        }
        catch(异常类型n 形参)
        {
            处理异常
        }
        catch(...)
        {
            处理任意异常
        }

    说明
        try语句块中的代码可能发生异常,如果没有发生异常,则catch语句不起作用
        如果发生了异常,异常发生点 后面的代码不会再执行,程序流程进入catch语句
        catch用来捕获异常,根据异常类型进行匹配,异常类型匹配时不会发生类型转换。
        catch语句可以有多个
        如果不在意抛出的异常类型,可以使用 ... 来表示 捕获任意类型的异常,...这种写法只能出现在catch语句的最后面
    
    例: 

    int main()
    {
        try
        {
            foo(-100);// 异常发生点
            foo(100); // 此行代码不会执行。
        }
        catch(int e)
        {
            cerr << "捕获到int类型的异常" << endl;
            ...
        }
        catch(...)
        {
            cerr << "捕获到其他类型的异常" << endl;
            ...
        }
    }

    标准异常类
        C++标准库提供了一组异常类型,用来说明 库函数 中遇到了问题,这些异常类型可以程序中直接使用
        这些异常类型定义于 头文件 <stdexcept>
        常用异常类型:

        std::logic_error
        std::runtime_error
        std::out_of_range
        std::bad_alloc    

        这组异常类型都提供了一个成员函数 what(), 此函数返回一个const char*类型的错误信息。    例: 

        void foo(int i)
        {
            if (i < 0)
                throw std::logic_error("i < 0");
        }    

        int main()
        {
            try
            {
                foo(-1);
            }
            catch(std::logic_error e)
            {
                cerr <<  e.what() << endl;
            }
        }

    应用场合
        析构函数 不能抛出异常,析构函数的声明中默认包括关键字 noexcept (noexcept表示该函数不会抛出异常,如果抛出了异常,程序终止)
        构造函数 中如果遇到了无法解决的问题,推荐使用异常
        其他函数,根据需要选择即可。
    
 


运算符重载        (本质是函数重载)
    C++中的运算符可以作用于基本类型(如int,double等)、标准库类型(如string)及类类型的对象。
    基本类型 与 标准库中的类型的运算符功能已经由 语言及标准库实现了。
    而自定义的类型的运算符功能则 需要通过 运算符重载 来实现。

    所谓 运算符重载,就是给已有的运算符 添加 新功能,使得运算符能作用于 类类型 的对象上。
    如:可以使用cout输出整数、小数、字符串,也可以输出一个点、矩形等
    class Point
    {
    public:
    private:
        int x;
        int y;
    };
    
    Point pos;
    cout << pos << endl;

    C++中的运算符是用函数实现的,与其它的函数一样,重载的运算符函数也包括 返回类型、函数名、参数列表及函数体。
    一般形式如下:

    返回类型 operator运算符(参数列表);

    说明: 
        重载的运算符是 具有特殊名字的 函数,由关键字operator和要重载的运算符组合而成。
        例: 加法运算符重载函数名为 operator+

        运算符函数既可以是 类的成员函数,也可以是 普通的全局函数
        如果是 普通全局函数,则一元运算符函数有一个参数,二元运算符函数有两个参数
        如果是 类的成员函数,则一元运算符没有参数,二元运算符函数有一个参数

    运算符->运算符函数:
    运算符 对应 函数名
    操作数 对应 参数列表 
    操作结果 对应 函数返回值    典型的双目运算符:+ - * / == != > < <= >= 的重载方式类似,以为例:

    class Complex // 复数
    {
    public:

        Complex(r,i):real(r),imag(i) {}
    private:
        double real; // 实部
        double imag; // 虚部
    };
    Complex operator+(Complex c1, Complex c2)
    {
        //Complex c3;
        double real = c1.real + c2.real
        double imag = c1.imag + c2.imag;
        return Complex(real,imag);
    }

    Complex c1(1,2);
    Complex c2(2,3);
    Complex c3 = c1 + c2; //c3 operator+(c1, c2)
    

    输入输出运算符
        输出运算符函数 一般如下:

        std::ostream& operator<<(std::ostream& out, 类型名 形参);

        ostream的参数与返回类型都必须为非const引用(ostream类没有拷贝构造函数,被删除了)
        
        输入输出运算符必须重载为友元函数。不能重载为类的成员函数。


    拷贝赋值运算符
        如果类中没有显式的定义赋值运算符函数,编译器会自动生成,自动生成的赋值运算符执行浅拷贝
        如果一类拥有资源,则需要 自定义 析构函数、拷贝构造函数 和 赋值运算符函数
        赋值运算符函数只能重载为类的成员函数
        赋值运算符重载的基本步骤
        1、防止自赋值
        2、释放原有资源
        3、重新分配新的资源
        4、返回自引用
        例: 

        string& string::operator=(const string& rhs)
        {        
            cout << "赋值运算符重载函数" << endl;
            // 防止自赋值
            if (this == &rhs)
                return *this;
            // 释放原有空间            
            delete [] _data;
            // 重新分配资源
            _data = new char[rhs.size()+1];
            strcpy(_data, rhs._data);
            // 返回自引用
            return *this;
        }

    移动赋值运算符
        与移动构造函数类似,用于移动对象的资源
        一般的形式如下:

        类名& operator=(类名&&);

        移动赋值运算符实现:
        1、移动资源
        2、释放右侧对象对资源的控制权
    例: 

        string& string::operator=(string&& rhs)
        {
            // 防止自赋值
            if (this == &rhs)
                return *this;

            // 释放原有空间
            delete[] _data;

            // 资源转移
            _data = rhs._data;
            rhs._data = nullptr;

            // 返回自引用
            return *this;                
        }

    注:
        C++标准库提供了一个move()函数,用来获取给定参数的右值引用
        例: 
            int i = 10;
            int && r = std::move(i);


静态成员+运算符重载 => 单例模式
单例模式:(这个唯一的实例一只只使用一个唯一的地址,只能一次一次的用,不能同时使用)
    在程序执行过程中,一个类只能有一个实例
    在C++中的基本实现步骤:
        1、隐藏构造函数
        2、提供一个公有的静态成员函数,以创建那唯一的实例
        3、删除拷贝构造函数、拷贝赋值运算符函数

    例: 

    class Singleton
    {
    public:
        void setValue(int x)
        {
            this->x = x;
        }
        int getValue() const
        {
            return x;
        }
        // 提供一个全局的接口,以获取唯一的那个实例
        static Singleton& getInstance()
        {
            static Singleton s(0); // 实例化一个静态局部对象
            return s;
        }
        Singleton(const Singleton&) = delete; // 删除拷贝构造函数
        Singleton& operator=(const Singleton&) = delete; // 删除拷贝赋值运算符
    private:
        Singleton(int x):x(x) {} // 隐藏构造函数
        int x;
    };
    int main()
    {
        Singleton& s = Singleton::getInstance();
        s.setValue(100);
        cout << s.getValue() << endl;
    }


单目运算符
    ++
    自增运算符既可以重载为 友元函数,也可以重载为 类的成员函数
    自动运算符分两种
        前++

            类名& operator++(类名&);    // 友元函数
            类名& operator++();             // 成员函数

        后++

            类名 operator++(类名&, int);    

                                 // 友元函数,多了一个int类型的参数,称为占位符参数,不参与运算
            类名 operator++(int);    

                                 // 成员函数,多了一个int类型的参数,称为占位符参数,不参与运算

    例: 

        class Complex {};

        Complex& operator++(Complex& c)        //前++
        {
            ++c.real;
            ++c.imag;
            return c;
        }

        Complex operator++(Complex& c, int)        //后++
        {
            Complex t = c;
            ++c;
            return t;
        }

        int main()
        {
            Complex c(0,0);
            Complex c1 = ++c;
            cout << c << endl;  // Complex(1,1)
            cout << c1 << endl; // Complex(1,1)

            c = Complex(2,2);
            Complex c2 = c++;
            cout << c << endl;  // Complex(3,3)
            cout << c2 << endl; // Complex(2,2)
        }


函数调用运算符 ()
    如果一个类 重载了调用运算符,则该类型的对象,称为函数对象(仿函数)   
    例: 

    class Print
    {
    public:
        void operator()(int i)
        {
            cout << "i = " << i << endl;
        }
        void operator()(std::string s)
        {
            cout << s << endl;
        }
    private:
        ...
    };

    int main()
    {
        Print p;
        p(100); // p是一个函数对象,可以调用

        p("hello");
    }


自动类型转换
    在C++中,一个表达式中,如果类型不匹配,编译器会尝试自动类型转换
    构造函数转换:
        如果定义了一个构造函数,这个构造函数能把另一个类型的对象作为它的单参数,则这种构造函数允许编译器执行自动类型转换
    例:

    class Demo
    {
    public:
        Demo(int i): i(i) {}
    private:
        int i;  
    };

    void foo(Demo d)
    {}

    foo(100); // Demo d = 100; =>  Demo tmp(100);  Demo d = tmp;  Demo(100)

            如果不希望发生这种隐式的类型转换,可以在单参数的构造函数前,加一个新的关键字 explicit  
    explicit 用来阻止编译器做隐式转换

    class Demo
    {
    public:
       explicit Demo(int i): i(i) {}
    private:
        int i;  
    };

    void foo(Demo d)
    {}

    foo(100);              // error 
    foo(Demo(100)); // ok

    运算符转换
        类型转换运算符 是类的一种特殊的成员函数,它负责将一个类类型的值转换成其它类型的值
        一般形式: 

        operator type() const;

        说明: 
            1、type 表示类型名,待转换的目标类型名,只要该类型能作为函数的返回类型(void除外)
            2、这种函数没有显式的返回类型,也没有形参,它的返回类型就是type类型
            3、必须是成员函数
            4、通常不应该修改待转换的对象,所以要加 const 进行说明
    例:

    class Demo
    {
    public:
       explicit Demo(int i): i(i) {}
       operator int() const 
       {
           return i;
       }
    private:
        int i;  
    };

    Demo d(100);
    int i = d; // 自动把d转成100

小结:
    1、可以重载大多数运算符,但有些不能,如:
                .   ?:   ::   sizeof
    2、只能重载已有运算符,不能创建新的运算符 

    3、重载的运算符应该要与它的原始语义保持一致,如加法运算中不能实现减,不能修改操作数等

    4、以下运算符只能重载为类的成员函数
        =  ()  []  ->

    5、输入输出运算符只能重载为友元函数

    6、只有当操作数中至少有一个自定义类型时,才需要重载运算符。



    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值