C++基础篇 第七天 异常、智能指针、nullptr,auto

目录

异常

异常概念:

C和C++中异常的区别

异常处理

1. throw抛出异常

2. try...catch 进行异常处理

3.try...catch抛出自建类的异常

4.自建类的异常延伸

捕获的扩展

提高异常捕获的概率

智能指针

auto_ptr(已废弃,只做了解)

auto_ptr智能指针的方法

弃用原因(了解):

unique_ptr

shared_ptr

weak_ptr

nullptr

auto 自动推导(基础)



异常

异常概念:

C++的异常是指程序在运行期间出现的问题,编译可以通过,说明代码出现了逻辑问题,而不是语法问题。当程序运行的过程中出现了C++标准库中预定义的异常现象时,程序会抛出一个异常对象,此对象需要正确地处理,否则会导致运行终止。

C和C++中异常的区别

1. 异常的语法:C++中具有原生的异常处理机制,使用`try-catch`块来捕获和处理异常。而C语言并没有内置的异常处理机制,需要使用其他方式来处理错误,如返回错误码、使用全局变量等。

2. 异常类型:C++中的异常可以是任何类型的对象,包括内置类型、自定义类型、标准库类型等。而C语言中没有内置的异常类型概念,错误通常以错误码或特定的返回值表示。

3. 异常的传播:在C++中,异常可以在调用栈上进行传播,即从抛出异常的地方一直传播到能够处理异常的地方。而在C语言中,错误处理通常是通过返回错误码或特定值来传递的。

4. 异常处理的开销:由于C++中的异常处理机制涉及到对象的构造和析构、栈的展开等操作,因此异常处理可能会带来一定的性能开销。而C语言中的错误处理通常更加轻量级,不会引入额外的开销。

#include <iostream>
using namespace std;
int main()
{
    string s="hello";
    cout<<s.at(0)<<endl;
    cout<<s.at(10)<<endl; 
    cout<<"程序继续运行。。"<<endl;
}

正确的捕获方式

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

int main()
{
    string s="hello";
    cout<<s.at(0)<<endl;
    try{
        cout<<s.at(10)<<endl;
    }catch(out_of_range a){
        cout<<a.what()<<endl;
    }
    cout<<"程序继续运行。。"<<endl;
}

异常处理

1. throw抛出异常

可以抛出任意类型的异常,抛给程序调用者,如果出现异常,调用者需要处理,否则程序就会终止。

#include <iostream>
using namespace std;
void division(double a,double b)
{
    if(b==0){
        throw 'e'; 
    }
    cout<<a/b<<endl;
}
int main()
{
    double a=3;
    double b=2;
    division(a,b);
    b=0;
    division(a,b);
    cout<<"程序继续运行"<<endl;
}


2. try...catch 进行异常处理

使用try-catch代码块进行异常的捕获,try代码块表示尝试执行可能出现异常的代码,catch块表示如果try块中出现了异常,则与catch块捕获的异常类型进行匹配,如果匹配成功,则进入catch块,如果匹配失败,程序仍然终止。

#include <iostream>
using namespace std;
void division(double a,double b){
    if(b==0){
        throw 'e'; 
    }
    cout<<a/b<<endl;
}
int main()
{
    double a=3;
    double b=2;
    division(a,b);
    b=0;
    try{
        division(a,b);
    }catch(char e){
        cout<<"异常已经处理,可以继续运行"<<endl;
    }
    cout<<"程序继续运行。。"<<endl;
}

3.try...catch抛出自建类的异常
#include <iostream>
using namespace std;
class DivisionByZero{
private:
    string message;
public:
    DivisionByZero(string message){
        this->message=message;
    }
    void getMsg(){
        cout<<message<<endl;
    }
};
void division(double a,double b){
    if(b==0){
        throw DivisionByZero("你除0出现了错误!");
    }
    cout<<a/b<<endl;
}
int main()
{
    double a=3;
    double b=2;
    division(a,b);
    b=0;
    division(a,b);
    cout<<"程序继续运行。。"<<endl;
}

4.自建类的异常延伸
#include <iostream>
using namespace std;
class DivisionByZero{
private:
    string message;
public:
    DivisionByZero(string message){
        this->message=message;
    }
    void getMsg(){
        cout<<message<<endl;
    }
};
void division(double a,double b){
    if(b==0){
        //throw 'a';
        throw DivisionByZero("你除0出现了错误!");
    }
    cout<<a/b<<endl;
}
int main()
{
    double a=3;
    double b=2;
    division(a,b);
    b=0;
    try{
        division(a,b);
    }catch(DivisionByZero e){
        e.getMsg();
        cout<<"这里是处理的方式。。。"<<endl;
    }
    cout<<"程序继续运行。。"<<endl;
}

捕获的扩展


提高异常捕获的概率

1. 准确标识可能引发异常的代码块:
在编写代码时,要仔细考虑可能引发异常的地方,并将其放置在适当的`try`块中。这样可以确保异常被正确捕获,并且避免在不应该捕获异常的地方捕获它们。

2. 使用更具体的异常类型:
在抛出异常时,尽量使用更具体的异常类型,而不是通用的`std::exception`。这样可以使异常处理更加精确和灵活,以便针对不同类型的异常采取不同的处理方式。

3. 捕获异常的顺序:

在使用多个`catch`块捕获异常时,应该将最具体的异常类型的`catch`块放在前面,而将更通用的异常类型的`catch`块放在后面。这样可以确保异常被正确地捕获,并避免被更通用的异常类型所捕获。

4. 使用异常基类捕获多个异常

如果有多个异常类型具有相似的处理方式,可以使用异常基类来捕获它们。这样可以减少重复的代码,并提高异常捕获的概率。

#include <iostream>
#include <stdexcept>
using namespace std;
class DivisionByZero{
private:
    string message;
public:
    DivisionByZero(string message){
        this->message=message;
    }
    void getMsg(){
        cout<<message<<endl;
    }
};
void division(double a,double b){
    if(b==0){
        throw DivisionByZero("你除0出现了错误!");
    }
    cout<<a/b<<endl;
}
int main()
{
    double a=3;
    double b=2;
    division(a,b);
    b=0;
    try{
        division(a,b);
    }catch(const length_error e ){
        cout<<1<<endl;
    }catch(const logic_error e){
        cout<<2<<endl;
    }catch(const exception e){
        cout<<3<<endl;
    }catch(...){
        cout<<4<<endl;
    }
    cout<<"程序继续运行。。"<<endl;
}

智能指针

C++智能指针的存在可以提供自动化的内存管理、异常安全性、简化代码、防止悬空指针以及共享所有权等好处,使得C++编程更加方便、安全和高效。

智能指针主要用于管理堆内存对象的生命周期,本身指针对象是位于栈内存的对象。

智能指针需要引入头文件#include <memory> ,智能指针管理的new关键字创建的堆内存对象

auto_ptr(已废弃,只做了解)

#include <iostream>
#include <memory>
using namespace std;
class Test{
private:
    string str;
public:
    Test(string s):str(s){}
    ~Test(){
        cout<<str<<"销毁了"<<endl;
    }
};
int main()
{
    {
       Test t("A");
       Test * t2=new Test("B");
//       传统手动销毁对象内存
//       delete t2;
//       t2=NULL;
       auto_ptr <Test> ap1(t2); //智能指针自动管理
    }
}
auto_ptr智能指针的方法
#include <iostream>
#include <memory>
using namespace std;
class Test{
private:
    string str;
public:
    Test(string s):str(s){}
    ~Test(){
        cout<<str<<"销毁了"<<endl;
    }
    void show(){
        cout<<"str的值是:"<<str<<endl;
    }
};
int main()
{
    {
        //第一种构造方式
       Test * t1=new Test("B");
       auto_ptr <Test> ap1(t1); //智能指针自动管理
       //第二种构造方式
       auto_ptr <Test> ap2(new Test("C"));
       //get()方法可以得到管理的堆内存对象的地址
       cout<<ap1.get()<<" "<<t1<<endl;  //0xee17c0 0xee17c0
       ap2.get()->show(); //str的值是:C
       //reset()方法  放弃之间对象的管理权,会把之前对象的内存释放
       ap2.reset(new Test("D"));
       cout<<"------------"<<endl;
       //release()方式 释放的管理权.但是管理对象内存不会释放
       ap1.release();
       delete t1;
       t1=NULL;
    }
}

这个警告说明auto_ptr已经被废弃了

弃用原因(了解):

1. 潜在的资源所有权转移问题:`std::auto_ptr` 允许资源的所有权在拷贝或赋值时进行转移。这意味着当您将一个 `std::auto_ptr` 赋值给另一个 `std::auto_ptr` 或者将其作为函数参数传递时,原始指针的所有权会被转移给新的 `std::auto_ptr`。这可能导致潜在的所有权混乱和资源释放问题。

2. 缺乏拷贝语义:`std::auto_ptr` 不支持拷贝构造函数和拷贝赋值运算符。这意味着无法直接通过拷贝 `std::auto_ptr` 对象来创建新的对象,限制了它的使用场景。

3. 不适用于标准容器:由于 `std::auto_ptr` 的所有权转移特性,它不能与标准容器(如 `std::vector` 或 `std::map`)一起使用。将 `std::auto_ptr` 存储在容器中会导致资源的所有权混乱和未定义行为。

#include <iostream>
#include <memory>
using namespace std;
class Test{
private:
    string str;
public:
    Test(string s):str(s){}
    ~Test(){
        cout<<str<<"销毁了"<<endl;
    }
    void show(){
        cout<<"str的值是:"<<str<<endl;
    }
};
int main()
{
    {
        //第一种构造方式
       Test * t1=new Test("B");
       auto_ptr <Test> ap1(t1); //智能指针自动管理
       cout<<t1<<endl;
       cout<<ap1.get()<<endl;    //0x6e17c0

       auto_ptr <Test> ap2(ap1); //拷贝构造
       cout<<ap1.get()<<" "<<ap2.get()<<endl; //0 0x6e17c0

       auto_ptr<Test> ap3=ap2;  //隐式的拷贝构造
       cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<endl;

       auto_ptr<Test> ap4;
       ap4=ap3;  //等号赋值
       cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<" "<<ap4.get()<<endl;

    }
}

unique_ptr

解决了auto_ptr复制语义发生控制权转移的问题,如需转移可以使用move的方式

#include <iostream>
#include <memory>
using namespace std;
class Test{
private:
    string str;
public:
    Test(string s):str(s){}
    ~Test(){
        cout<<str<<"销毁了"<<endl;
    }
    void show(){
        cout<<"str的值是:"<<str<<endl;
    }
};
int main()
{
    {
        //第一种构造方式
       Test * t1=new Test("B");
       unique_ptr <Test> ap1(t1); //智能指针自动管理
       cout<<t1<<endl;
       cout<<ap1.get()<<endl;

       //unique_ptr <Test> ap2(ap1); //拷贝构造
       unique_ptr <Test> ap2(move(ap1));
       cout<<ap1.get()<<" "<<ap2.get()<<endl;

       //unique_ptr<Test> ap3=ap2;  //隐式的拷贝构造
       unique_ptr<Test> ap3=move(ap2);
       cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<endl;

       unique_ptr<Test> ap4;
       //ap4=ap3;  //等号赋值
       ap4=move(ap3);
       cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<" "<<ap4.get()<<endl;

    }
}

shared_ptr

一个资源在多个指针之间共享。

每当有一个智能指针管理资源的时候 ,使用计数加1,当一个智能智能释放管理权的时候,使用计数就减1

当使用计数的为0的时候,此时资源没有被使用,这时才会销毁

#include <iostream>
#include <memory>
using namespace std;
class Test{
private:
    string str;
public:
    Test(string s):str(s){}
    ~Test(){
        cout<<str<<"销毁了"<<endl;
    }
    void show(){
        cout<<"str的值是:"<<str<<endl;
    }
};
int main()
{
    {
        //第一种构造方式
       Test * t1=new Test("B");
       shared_ptr <Test> sp1(t1);
       cout<<sp1.get()<<endl; //0x10217c0
       cout<<sp1.use_count()<<endl; //1
       shared_ptr <Test> sp2(sp1);
       cout<<sp1.use_count()<<" "<<sp2.use_count()<<endl;//2 2

       shared_ptr <Test> sp3=sp2;
       cout<<sp1.use_count()<<" "<<sp2.use_count()<<" "<<sp3.use_count()<<endl; //3 3 3

       shared_ptr <Test> sp4=sp3;
       cout<<sp1.use_count()<<" "<<sp2.use_count()
          <<" "<<sp3.use_count()<<" "<<sp4.use_count()<<endl; //4 4 4 4

       sp4.reset(); //sp4释放管理权 使用计数减1
       cout<<sp1.use_count()<<" "<<sp2.use_count()
          <<" "<<sp3.use_count()<<" "<<sp4.use_count()<<endl;//3 3 3 0
    }
}

weak_ptr

用于解决 `shared_ptr` 的循环引用问题,并提供了对被 `shared_ptr` 管理的对象的弱引用

#include <iostream>
#include <memory>
using namespace std;
class Test {
private:
    string str;
public:
    Test(string s) : str(s) {}
    ~Test() {
        cout << str << "销毁了" << endl;
    }
    void show() {
        cout << "str的值是:" << str << endl;
    }
};

int main() {
    {
        shared_ptr<Test> sp1 = make_shared<Test>("B");
        cout << sp1.get() << endl; // 输出:0x10217c0
        cout << sp1.use_count() << endl; // 输出:1
        shared_ptr<Test> sp2 = sp1;
        cout << sp1.use_count() << " " << sp2.use_count() << endl; // 输出:2 2
        shared_ptr<Test> sp3 = sp2;
        cout << sp1.use_count() << " " << sp2.use_count() << " " << sp3.use_count() << endl; // 输出:3 3 3
        weak_ptr<Test> wp1 = sp3;
        cout << sp1.use_count() << " " << sp2.use_count() << " " << sp3.use_count() << " " << wp1.use_count() << endl; // 输出:3 3 3 3
        shared_ptr<Test> sp4 = wp1.lock();
        cout << sp1.use_count() << " " << sp2.use_count() << " " << sp3.use_count() << " " << wp1.use_count() << endl; // 输出:4 4 4 4
        sp4.reset(); // 释放 sp4 的管理权,使用计数减 1
        cout << sp1.use_count() << " " << sp2.use_count() << " " << sp3.use_count() << " " << wp1.use_count() << endl; // 输出:3 3 3 3
    }
}

nullptr

` nullptr` 是 C++11 引入的空指针常量。它是一个特殊的关键字,用于表示一个空指针。

为什么引入nullptr

在早期的 C++ 版本中,通常使用宏 `NULL` 或整数常量 0 来表示空指针。然而,这种表示方式容易引起一些问题,因为整数类型可以隐式转换为指针类型,可能会导致错误的解释。

为了解决这个问题,并提供更明确和类型安全的空指针表示,C++11 引入了 `nullptr`。它是一个特殊的关键字,表示一个空指针常量,具有明确的空指针类型。

#include <iostream>
#include <memory>
using namespace std;
void test(char * ch){
    cout<<"1"<<endl;
}
void test(int n){
    cout<<"2"<<endl;
}
int main()
{
    char * a=NULL;
    test(a);   			//1
    test(NULL); 		//2

    char * b=nullptr; 	//1
    test(b);
    test(nullptr); 		//1
}

auto 自动推导(基础)

auto不能做参数类型的推导,也不能推断表达式的类型和数组

#include <iostream>
#include <memory>
#include <map>
using namespace std;

int main()
{
    int a=10;
    auto b=10;
    cout<<a<<" "<<b<<endl;

    map<string,int> mp;
    mp.insert(pair<string,int>("age",20));
    mp.insert(pair<string,int>("height",170));
    mp.insert(pair<string,int>("weight",60));
    //map<string,int>::iterator ite=mp.begin();
    auto ite=mp.begin();
    for(;ite!=mp.end();ite++){
        cout<<ite->first<<" "<<ite->second<<endl;
    }
}