C++基础篇 第七天 异常、智能指针、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;
}
}