C/C++教程

C++中的抛出异常

本文主要是介绍C++中的抛出异常,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

以下为本人大一时阅读《C++ Primer Plus》中关于抛出异常章节所做的笔记

目录

梗概(下文会有详细内容):

try、catch的使用:

throw:

组成和继承的概念

类的作用域与类成员的访问:

访问函数(access fnction)和工具函数(utility function):

使用设置函数、获取函数的好处:

析构函数(destructor):

exit函数与abort函数:

左值:

const对象和const成员函数:

组成(composition):将对象作为类的成员

friend函数(友元函数)和friend类:

重载友元函数:

this指针:

static类成员:


与结构体类似,对象的名称或者对象的引用可以和原点成员选择符(.)一起使用,而对象的指针则需要和箭头成员选择运算符(->)一起使用

梗概(下文会有详细内容):

判定函数(predicate function):用于测试条件是真还是假

工具函数(也称为助手函数):类的private成员函数,目的是支持类的public成员函数的操作,并非为类的客户使用而准备的

析构函数(destructor):在类的对象撤销之前,用于完成对该对象的“扫尾工作”。

防止一个头文件在一个程序中被多次包含:

#ifndef TIME_H
#define TIME_H
  ...
#endif

ifndef:if no define(如果没有定义)

如果之前没有在文件中包含此头文件,那么TIME_H这个名字将被#define指令定义,并且包含该头文件的语句;

如果之前已包含此头文件,那么将不再包含该头文件

try、catch的使用:

try
{
    ...
}
catch( 指定的错误类型 &e)
{
    cerr<<"Exception: "<<e.what()<<endl;
    //(catch语句块内部也可以写其它的指令)
}

注意:代码中指定类型的异常为其它头文件中的能指定错误的类型,例如在头文件<stdexcept>中的out_of_range、invalid_argument等,后面的 &e 是声明一个接收引用的异常形参,用来实现与捕捉到的异常对象的交互,e.what()是通过调用它的what成员函数打印异常的错误信息。catch语句块可以处理指定类型的异常。

当执行到以上步骤时,运行try中的语句,若存在异常,则运行catch中的语句(一般是用cerr输出关于错误的信息),然后再接着运行catch之后的程序;若不存在异常,直接跳过catch语句。一个try可以有多个catch块来处理不同的异常。

注意:在try语句块中声明的任何变量在catch语句块中都超出了它们的作用域,也就是说在catch语句块中都不可访问。

throw:

黏性设置与非黏性设置:

类定义体内的成员函数被隐式地声明为inline:

组成和继承的概念

类的作用域与类成员的访问:

访问函数(access fnction)和工具函数(utility function):

具有默认实参的构造函数:

注意:每个类最多只有一个默认构造函数(但可以有多个构造函数)

与一般函数一样,构造函数也可以有默认实参:

Time类:

public:
  explicit Time(int=0,int=0,int=0);

Time类的成员函数定义:

Time::Time(int hour,int minute,int second)
{
    cin>>hour>>minute>>second;
}

初始化3个Time对象:

Time t1;
Time t2(2);
Time t3(12,25,42);

在C++11中可以使用列表初始器调用构造函数:

Time t1;
Time t2{2};
Time t3{12,25,42};

或者写成

Time t1;
Time t2={2};
Time t3={12,25,42};

上述三种初始化都实现了:t1使用了3个默认实参,t2指定了一个实参(按形参的顺序传递,之传入一个实参时是传给了hour),t3指定了三个实参

C++11:重载的构造函数和委托构造函数:

使用设置函数、获取函数的好处:

类的成员函数可以直接访问类的private数据,但一般都调用设置函数(如setTime)和获取函数(如getTime)来修改和访问类的private数据,这样做的好处是当需要修改设置和获取数据的方式时,只需修改设置函数和获取函数的内容,而不需要对所有涉及到操作和获取private数据的内容都进行修改。

析构函数(destructor):

命名:在类名之前添加(~)

当对象撤销时,类的析构函数会隐式地调用。

析构函数本身并不释放对象占用的内存空间,它只是在系统收回对象的内存空间之前执行扫尾工作,这样内存可以重新用于保存新的对象。

如果没有显式地提供析构函数,编译器会隐式生成一个“空的”析构函数

exit函数与abort函数:

exit函数迫使程序立即结束,不执行自动对象的析构函数。当程序检测到输入中有错误,或者程序要处理的文件不能打开时,常常使用这个函数来终止程序。

abort函数的执行情况与exit函数类似,但是迫使程序立即终止,不允许调用任何对象的析构函数

总结:abort函数立即终止程序,exit函数终止程序前还进行一些清理工作

对象的存储类别对调用析构函数的顺序的影响:

全局作用域内对象:在文件内任何其他函数(包括main函数)开始执行之前调用构造函数、在main函数执行结束时调用析构函数

局部对象:当程序的执行每次进入或者离开自动对象的作用域时,自动对象的构造函数或者析构函数会被调用。(若程序的终止是由调用exit函数或者abort函数完成的,那么自动对象的析构函数将不被调用)

static局部对象:构造函数只被调用一次,即在程序第一次执行到该对象的定义处时,而相应的析构函数的调用发生在main函数结束或者程序调用exit函数时。全局或static对象的撤销顺序与它们建立的顺序正好相反。如果用abort函数的调用终止程序,那么static对象的析构函数将不被调用。

左值:

=是赋值运算符,它的作用是将一个表达式的值赋给一个左值。一个表达式或者是一个左值,或者是一个右值。所谓左值是指一个能用于赋值运算左边的表达式。左值必须能够被修改,不能是常量。这里是用变量作左值,指针和引用也可以作左值。

如果函数返回的是一个const引用,那么这个引用不能用作可修改的左值。

返回private数据成员的引用或指针:

对象的引用就是该对象名称的别名,可以在赋值语句的左边使用。

在类的定义中:

public:
    int &badSetHour(int);
private:
    int hour;

在类的成员函数定义中:

int &Time::badSetHour(int hh)
{
    hour=hh;
    return hour;
}
int Time::getHour()
{
    return hour;
}

在main函数中:

int main()
{
    Time t;
    int &hourRef=t.badSetHour(20);
    cout<<t.getHour()<<endl;
    hourRef=30;
    cout<<t.getHour()<<endl;
    t.badSetHour(12)=74;
    cout<<t.getHour()<<endl;
}

输出:

20

30

74

总结:通过引用hourRef来修改private中的数据,其中hourRef在声明的同时已用调用t.badSetHour(20)返回的引用来进行初始化。在这个过程中展示了hourRef如何破坏了类的封装性——main函数中的语句不应该有访问该类的private数据的权利。最后使用badSetHour函数调用本身作为左值,将74赋给该函数返回的引用。这个过程中,hour的值先是被赋值为传入badSetHour函数的参数12,然后被返回为引用,再被修改为74

默认的逐个成员赋值:

赋值运算符(=)可以将一个对象赋给另一个类型相同的对象,默认情况下是将=号右边对象的每个数据成员逐个赋值给左边对象同一个数据成员。对于每个类,编译器都提供了一个默认的复制构造函数,可以将原始对象的每个成员复制到新对象的相应成员中。对象可以作为函数的实参进行传递,也可以由函数返回。这种传递和返回默认情况下是以按值传递的方式执行的。

const对象和const成员函数:

修改const对象的任何企图在编译时就会被发现,而不是等到执行期才导致错误

以下调用是允许的:

1.对非const对象调用非const成员函数

2.对非const对象调用const成员函数

3.对const对象调用const成员函数

注意:对const对象调用非const成员函数是不想允许的

组成(composition):将对象作为类的成员

以下这句话差不多可以跟函数的声明顺序一样理解

创建顺序、撤销顺序:

由内而外创建、由外而内撤销(例如,Date成员对象作为Employee成员对象中的成员,则Date成员对象先创建,Employee成员对象后创建,同时Date成员对象在Employee对象撤销后再撤销)

friend函数(友元函数)和friend类:

类的friend函数在类的作用域之外定义,却具有访问类的非public(以及public)成员的权限。单独的函数、整个类或其他类的成员函数都可以被声明为另一个类的友元。

friend的声明:

使用friend函数修改类的private数据;

友元声明可以出现在类的任何地方。按照惯例,友元声明首先出现在类的定义中。

class Count
{
    friend void setX(Count &,int); //***
public:
    Count():x(0){}
private:
    int x;
};

void setX(Count &c,int val)
{
    c.x=val;
}
int main()
{
    Count counter;
    setX(counter,1);
}

上面的程序中使用了友元函数setX将类成员对象counter中的private成员x进行了修改。如果将标记了***行中的友元声明去掉,就会出现错误信息(一般类之外的函数不能对类成员对象中的private成员进行修改)

(实验课后的总结:友元函数的编写要注意编写程序的顺序,详细可以参考C++文件中实验课的2.1.6和2.1.7以及https://blog.csdn.net/jw903/article/details/38864769)

重载友元函数:

this指针:

每个对象都可以使用this指针来访问自己的地址

对象的 this指针不是指针本身的一部分,占用的内存大小不会反应在对对象进行sizeof运算得到的结果中。

this指针作为一个隐式的参数(被编译器)传递给对象的每个非static成员函数

使用this指针来避免名字冲突:
一个常用的this指针的explicit应用是用来避免类数据成员和成员函数参数之间的名字冲突。

在下面的例子中,设类Time有数据成员hour:

void Time::setHour(int hour)
{
    this->hour=hour;
}

上面的程序将传进setHour函数的hour参数赋值给数据成员hour

this指针的类型:

隐式和显式使用this指针来访问对象的数据成员:

void Test::print() const
{
    cout<<x<<endl; // 1
    cout<<this->x<<endl; // 2
    cout<<(*this).x<<endl; // 3
}

上述输出的都是Test成员对象中的数据成员x的值,第一个隐式地使用this指针,仅仅指明该数据成员的名称,第二个和第三个使用不同的表示法通过this指针访问x。第三个请注意:*this必须用括号括起来,后面再跟随圆点成员选择运算符( . ),这样的原因是圆点运算符具有比*运算符更高的优先级(也即是*this.x的含义与(*this).x的含义不同)

使用this指针实现串联的函数调用:

串联的函数调用也就是多个函数在同一条语句中被调用

下面给个例子:

Time类的声明中:

public:
    Time &setTime(int,int,int);
    Time &setHour(int);

Time类成员函数定义:

Time &Time::setTime(int h,int m,int s)
{
    ...
    return *this;
}
Time &Time::setHour(int h)
{
    ...
    return *this;
}

main函数中调用串联的成员函数:

int main()
{
    Time t;
    t.setHour(18).setMinute(30).setSecond(22);
}

串联的成员函数调用过程解读:

t.setHour(18).setMinute(30).setSecond(22);

先求t.setHour(18)的值,返回对对象t的引用,作为此函数调用的值。

接下来转化为:

t.setMinute(30).setSecond(22);

再接着转化为:

t.setSecond(22);

static类成员:

对于类的每个对象来说一般都各自拥有类所有数据成员的一份副本。但是static数据成员仅有变量的一份副本供类的所有对象共享。

这篇关于C++中的抛出异常的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!