C++学习-入门到精通【15】异常处理深入剖析

Qt C++开发学习路线 Qt C++学习路线 阅读详情

C++学习-入门到精通【15】异常处理深入剖析



一、实例:处理除数为0的异常处理

通常对于浮点数除法,如果除数为0,在不少C++的实现版本中都是被允许的,计算结果为正或负的无穷大,相应输出为inf或-inf。

下面的程序中我们定义了一个名为quotient的函数,该函数接收用户输入两个整数,然后用第一个int类型的参数除以第二个int类型的参数。在执行除法之前,该函数会将第一个int类型参数的值强制类型转换为double类型,如此在计算过程中,第二个参数也会被强制类型转换为double类型,所以该函数实际上执行的是两个double类型值的除法,并返回一个double类型的结果。

虽然除数为0的浮点数除法是被允许的,但是现在规定将任何以0为除数的行为都视为错误。因此quotient函数在执行除法之前,需要检查它的第二个参数,以确保它不为0。如果为0,则抛出异常,来向调用它的函数表明执行出现的问题。接着,调用函数将处理这个异常,同时允许用户重新输入两个值。

定义一个异常类,描述可能出现的问题类型

下面代码中我们定义类DivideByZeroException,该类是标准库runtime_error(在头文件<stdexcept>中定义)的一个派生类。类runtime_error是标准库exception类(在头文件<exception>)的派生类,它是C++用于描述运行时错误所创建的标准基类。而类exception是C++标准库中描述所有异常而建立的标准基类。

一个从runtime_error类派生出来的典型异常类只定义了一个构造函数,这个构造函数将带有错误信息的字符串传递给基类runtime_error的构造函数。所有的异常类都直接或间接地从含有虚函数what的exception类派生而来,而what函数返回一个异常对象的错误信息。

DivideByZeroException.h

#pragma once
#include <stdexcept>

class DivideByZeroException : public std::runtime_error
{
public:
	DivideByZeroException()
		: std::runtime_error("attempted to divide by zero") {}
};

异常处理演示

#include <iostream>
#include "DivideByZeroException.h"
using namespace std;

double quotient(int numerator, int denominator)
{
	if(denominator == 0)
		throw DivideByZeroException();
	
	return static_cast<double>(numerator) / denominator;
}

int main()
{
	int number1;
	int number2;

	cout << "Enter two integers (end-of-file to end): ";

	while (cin >> number1 >> number2)
	{
		try
		{
			double result = quotient(number1, number2);
			cout << "The quotient is: " << result << endl;
		}
		catch (DivideByZeroException& ex)
		{
			cerr << "Exception occured: "
				<< ex.what() << endl;
		}

		cout << "\nEnter two integers (end-of-file to end): ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

try语句块中封装的代码

try语句块可以进行异常处理,try语句块中包含着可能引起异常的语句和在异常发生时应该跳过的语句。

异常可能发生在try语句块中明确提及的代码中,也可能出现在try语句块中的代码对其他函数的调用和深层嵌套的函数调用中。

定义一个catch处理器处理异常DivideByZeroException

在之前的学习中,我们是使用catch处理器来进行处理的。在每个try语句块后面至少应该立即跟着一个catch处理器。

每个catch处理器都是由关键字catch开始,并且在圆括号里面的异常参数应该被声明为一个异常类型的引用,而这个异常类型正是该catch处理器所能处理的异常类型。使用一个引用的好处在于当此异常被捕获时,避免了对异常对象的复制,同时还使相应的catch处理器能够正确地捕获派生类的异常。

当一个try语句块中的异常发生时,那么将要执行就是第一个能够匹配这个异常类型的catch处理器(也就是catch块中的类型恰好匹配了抛出的异常类型,或者是它的直接或间接基类)。如果一个异常参数含有一个可选的参数名,那么catch处理器就能使用该参数名与catch处理器体中捕获到的异常对象进行交互。catch处理器的体要用一对花括号{}括起来。

提示

  • 在try语句块和相应的catch处理器之间,或者几个catch处理器之间放入代码是一个语法错误;
  • 一个catch处理器只有一个参数——指定用逗号隔开的异常参数列表是一个语法错误;
  • 在一个try语句块后的多个不同catch处理器中捕获相同的异常类型是一个编译错误;

异常处理的终止模式

如果一个try语句块中的一条语句发生了异常,那么这个try语句块就会终止。然后,程序搜索能够处理已发生的异常的第一个catch处理器。程序通过将抛出的异常的类型与每一个catch处理器的异常参数类型进行比较来找到匹配的catch,并定位那里。如果抛出的异常类型和异常参数的类型完全相同,或者异常参数类型是抛出的异常类型的直接或间接基类类型,那么就匹配成功。

当匹配成功时,该catch处理器内部的代码将会被执行,当运行到该catch处理器的右花括号}时,catch处理器执行结束,异常被认为已经处理,同时在catch处理器体内定义的局部变量(包括catch的参数)就出了作用域。程序将会从try语句块后的最后一个catch处理器之后的第一条语句开始继续执行。

注意,一些程序设计语言使用异常处理的恢复模式,在这种模式下,当一个异常处理完毕,将会在异常抛出点后继续执行。

如果假设在一个异常处理结束后,控制将回到抛出点后的第一条语句,那么这将是一个逻辑错误

如果try语句块成功地执行了,那么程序将忽略catch处理器,并且程序控制将从try语句块的最后一个catch处理器体之后的第一条语句开始。

如果异常发生在try语句块中,且没有与之相匹配的catch处理器,或者发生异常的语句并不属于try语句,则含有这条语句的函数将立即终止,并且程序将试图定位调用函数中封装的try语句块。这个过程被称为堆栈展开。

异常抛出

在quotient函数中,当输入的分母是0时,此时函数会使用关键字throw来抛出一个异常。该关键字后面跟着一个代表抛出的异常类型的操作数。一般情况下,throw语句指定一个操作数(也可以使用没有指定操作数的throw语句)。一个throw语句的操作数可以是任何类型(但是必须是可以被构造复制的)。如果操作数是一个对象,就称之为异常对象;操作数也可以是其他值,比如是一个并不产生类对象的表达式的值(例如,throw x > 5)或者是一个int值(例如,throw 5)。

一般情况下,应该只抛出异常类类型的对象

作为抛出异常的一部分,throw的操作数将会被创建并用来初始化catch处理器中的参数。在上面的代码中,throw语句创建了一个DivideByZeroException类的对象。当抛出该异常时,函数quotient立即结束,因为如果继续执行就会产生错误。这是异常处理的一个重要特征:程序必须在错误有可能发生之前显式地抛出异常。

将每种运行时错误与一个有相应名称的异常类型联系在一起,可以提高程序的清晰度

二、重新抛出异常

异常处理器可以在接收到异常时,通过语句throw;重新抛出异常。无论处理器能否处理异常,处理器都可以为了在处理器外更进一步进行处理而重新抛出异常。下一个封装的try语句块将检测这个重新抛出的异常(异常类型与该catch处理器处理的异常类型相同),而列在该封装的try语句之后的一个catch处理器将试图处理该异常。

下面给出了一个重新抛出异常的示例代码:

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

void throwException()
{
	try
	{
		cout << "  Function throwException throws an exception\n";
		throw exception(); // 抛出一个exception类的对象
	}
	catch (exception&) // 不显式指定异常的参数名,所以在内部无法使用该异常对象获取信息
	{
		cout << "  Exception handled in function throwException\n"
			<< "  Function throwException rethrows exception";
		throw; // 重新抛出该异常对象
	}

	cout << "This also should not print\n";
}

int main()
{
	try
	{
		cout << "main invokes function throwException\n";
		throwException(); // 该函数中会抛出异常,后续的代码都不会被执行
		cout << "This should not print\n";
	}
	catch (exception&)
	{
		cout << "\n\nException handled in main\n";
	}

	cout << "Program control continues after catch in main\n";
}

运行结果:

在这里插入图片描述

三、堆栈展开

当异常被抛出但没有在一个特定的作用域内被捕获时,函数调用堆栈就会展开,并试图在下一个外部的try...catch语句内捕获这个异常。

展开函数调用堆栈意味着如果一个函数中出现的异常没有被捕获,那么这个函数将会结束,此函数中已经完成初始化的所有局部变量都将被销毁,并且控制将返回到最初调用该函数的语句。如果该语句被一个try语句块封装,那么该try语句块对应的catch处理器就会试图捕获该异常。如果该语句没有被一个try语句封装堆栈展开将再次发生,就算有try语句进行了封装,但是没有相应的catch处理器能够捕获该异常,那么堆栈展开也会发生。如果没有任何一个catch处理器能够捕获该异常,那么程序将会结束。

下面的程序就给出了一个堆栈展开的示例:

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

void function3()
{
	cout << "In function 3" << endl;

	// 不在try语句块或catch语句块中,直接结束函数
	throw runtime_error("runtime_error in function3");
}

void function2()
{
	cout << "function3 is called inside function2" << endl;
	function3();
}

void function1()
{
	cout << "function2 is called inside function1" << endl;
	function2();
}

int main()
{
	try
	{
		cout << "function1 is called inside main" << endl;
		function1();
	}
	catch (runtime_error& error)
	{
		cout << "Exception occured: " << error.what() << endl;
		cout << "Exception handled in main" << endl;
	}
}

运行结果:

在这里插入图片描述

四、何时使用异常处理

异常处理是用来处理同步错误的,这些错误发生在一个语句正在执行的时候。常见例子有数组下标越界、运算溢出、除数为0、无效的函数参数和失败的内存分配。异常处理并不处理相关的异步事件(例如,磁盘I/O操作的完成、网络消息的到达、鼠标的点击和键盘的击键等),这些事件与程序的控制流并行且互相独立。

异常处理提供一个单独的、统一的处理问题的技术。这有助于一个大型项目中的程序员互相了解各自的错误处理代码。

异常处理机制对于处理程序与软件元素交互时发生的问题也十分有用。软件元素包括成员函数、构造函数、析构函数和类,等等。这些软件元素通常在问题发生时使用异常来通知程序。这样使程序员能够为每个应用程序定制相应的错误处理操作。

一个复杂的应用程序常常由两类组件构成,这两类组件是预定义的软件组件和使用预定义的软件组件的应用程序特定的组件。当预定义组件发生问题时,该组件就需要一种机制,来与应用程序特定的组件进行通信,因为预定义的组件无法知道每个应用程序将如何处理所发生的问题(同样的问题不同程序中的处理方式不同,而一个预定义的组件中可能发生的错误相同)。

C++11:声明不会抛出异常的函数

对于C++11,如果一个函数不会抛出任何异常,也不会调用任何会抛出异常的函数,那么程序员应该显式地说明它不会抛出异常。

只需要在该函数的原型和定义中的参数列表的右侧添加noexcept关键字。对于声明为const的成员函数,应该将noexcept放在const之后。

如果一个声明为noexcept的函数调用另一个抛出一个异常的函数,或者调用另一个执行一条throw语句的函数,那么程序将终止。

五、构造函数、析构函数和异常处理

大家有没有考虑过如果一个构造函数中检测到一个错误时会发生什么呢?例如,当一个对象的构造函数没有接收到有效的数据时,构造函数应该如何响应?大家都知道,构造函数是没有返回值,所以它也不能通过返回一个值来指示错误,所以必须要有一种可行的方式来指出这个对象没有被正确的创建。

一种方案是返回这个未正确构造的对象,并希望使用它的人会对它进行一些测试,从而判定它处于错误状态。
另一种方案是在构造函数之外设置一些变量。

而还有一种更好的办法,就是要求构造函数抛出包含错误信息的异常,这样做可以为程序提供一个处理失败的机会。

在构造函数抛出异常之前,作为所构造对象一部分的成员对象的析构函数将会被调用。在异常被捕获之前,try语句块中构造的每一个自动对象,它们的析构函数都将被调用。在异常处理器开始执行的时候,必须保证堆栈展开已经完成(有一个catch语句捕获到该异常)。如果是由于堆栈展开而调用的析构函数抛出一个异常,那么程序将结束。这可能会导致各种各样的安全攻击。

析构函数应该捕获异常,以防止程序意外终止(因为当析构函数被调用时通常时是在堆栈展开的过程中,此时已经存在了一个异常未被捕获,如果此时析构函数再次抛出一个未被捕获的异常,那么程序就会直接结束。)
不要由具有静态存储期的对象的构造函数抛出异常,因为这样的异常无法捕获。(无法捕获的原因是,这种具有静态存储期的对象可能在main函数调用之前就已经创建,如果在这种情况下,创建失败,那么此时程序中还没有合适的catch处理器来处理该异常,所以说这样的异常无法捕获。)

如果一个对象拥有成员对象,并且如果在这种外部对象被完全构造前抛出了异常,那么对于在异常抛出前已经构造好了的成员对象,其析构函数将被调用。如果在异常发生时一个对象数组只是部分被构造,那么只有数组中已经完成构造的对象的析构函数才会被调用。

对于在构造函数和析构函数中的异常处理,要点在于构造函数过程中发生异常,此构造函数要构造的对象并没构造完成,所以它的析构函数不会被调用,只有该对象内部的已经被构造完成的成员对象需要调用对应的析构函数。
如果在调用析构函数时出现异常,应该就在本析构函数中进行处理,不要将异常留到当前函数栈外。

初始化局部对象获取资源

异常可能阻止正常释放资源(例如内存资源或文件资源)的代码的执行,这将导致资源泄漏,妨碍其他程序获取资源。解决这个问题的一个方法是初始化一个局部变量来获取资源。当异常发生时,将调用该对象的析构函数并可以释放资源。

例如,现在有一个程序要打开一个文件进行操作,在操作的过程中可能会抛出异常,如果此处没有对该异常进行捕获,那么就会进行堆栈展开,当前函数中后续的用来释放资源的语句并没有被执行。就算在异常抛出之后,马上进行了处理,那么也会导致代码冗余,且处理复杂,容易出错。

如果我们使用一个对象来获取资源,我们在创建一个对象时,系统会为它的数据成员分配对应资源,当将该对象为一个自动存储期的变量时,当它离开当前作用域时会自动调用析构函数进行销毁,此时对象中获取的资源就会自动释放,且我们只需要在该对象的析构函数中释放资源即可,减少了代码的冗余,也使得逻辑更加清晰,不易出错。

六、异常与继承

各种各样的异常类可以从公共的基类派生出来,就像之前使用的DivideByZeroException这个例子一样。根据我们之前对多态的学习,我们知道一个基类的引用或指针也可以用来指向一个派生类的对象,那么在catch处理器中,我们可以使其捕获一个基类类型的异常对象的一个引用,使得它同样可以捕获该基类的public派生类的所有对象的一个引用,如此就可以使用多态性来处理相关的异常。

七、处理new失败

当new运算符操作失败时,它会抛出bad_alloc异常(在头文件<new>中定义)。下面将给出两个例子,一个是new失败时抛出bad_alloc的版本,另一个是使用set_new_handler函数来处理new失败。

new失败时抛出bad_alloc异常

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

int main()
{
	double *ptr[50]; // 创建一个内置的指针数组

	try
	{
		for (size_t i = 0; i < 50; ++i)
		{
			ptr[i] = new double[500000000]; // 使用new运算符分配大量空间,该行为可能会导致异常发生
			cout << "ptr[" << i << "] points to 500,000,000 new doubles\n";
		}
	}
	catch (exception& ex)
	{
		cerr << "Exception occured: "
			<< ex.what() << endl; // 使用多态性,利用基类指针调用派生类的虚函数
	}
}

运行结果:

在这里插入图片描述
这一过程会非常卡顿,因为该例子中分配了大量内存。

new在失败后返回nullptr
在旧版本的C++中,当运算符new在分配内存失败时,将返回一个nullptr。在头文件<new>中定义了对象nothrow(其类型为nothrow_t),用法如下:double *ptr = new (nothrow) double[500000000];

建议使用抛出异常的new版本。

使用函数set_new_handler处理new失败

处理new失败的另一种方法是使用函数set_new_handler(原型在标准头文件<new>中),这个函数的参数是一个函数指针,指向的函数没有参数且返回值类型为void。该指针指向的函数在new失败时被调用。这给程序员提供了一个统一处理所有new失败的方法,不管这种失败发生在程序的什么位置,一旦set_new_handler在程序中注册了new处理器,那么在失败时new运算符不会抛出bad_alloc异常,它会将错误推给new处理器函数(set_new_haneler的参数指定的函数)来处理。

如果new成功地分配了内存,它将返回一个指向该内存的指针;如果new分配内存失败并且set_new_handler函数没有注册new处理器函数,那么new将抛出一个bad_alloc异常;如果在new分配内存失败时,已经注册了new处理器函数,那么将调用该处理器函数。new处理器函数应该完成下列列出的一个任务:

  • 通过释放其他动态分配的内存来增加更多的可用内存(或者告诉用户关掉其他应用程序),并且返回到运算符new来尝试再次分配内存;
  • 抛出一个bad_alloc类型的异常;(异常从new中被抛出转移到在处理器函数中抛出)
  • 调用函数abort(异常/强制的结束程序)或exit(正常/有序的结束程序)(在头文件<cstdlib>中定义)来结束程序;

示例代码:

#include <iostream>
#include <new>
#include <cstdlib>
using namespace std;

// 该处理器函数选择终止程序
void customNewHandler()
{
	cerr << "customNewHandler was called";
	abort();
}

int main()
{
	double *ptr[50]; // 创建一个内置的指针数组
	
	set_new_handler(customNewHandler);

	for (size_t i = 0; i < 50; ++i)
	{
		ptr[i] = new double[500000000]; // 使用new运算符分配大量空间,该行为可能会导致异常发生
		cout << "ptr[" << i << "] points to 500,000,000 new doubles\n";
	}
}

运行结果:

在这里插入图片描述

八、类unique_ptr和动态分配内存

之前我们提到过了使用这初始化局部对象的方法来获取资源以防止当异常发生在释放资源之前,而导致的资源泄漏问题。这里的unique_ptr就是用来处理动态分配的内存资源在异常发生时可能出现的泄漏问题。C++在头文件<memory>中提供了类模板unique_ptr来处理这种情况。

一个unique_ptr类对象维护了一个指向动态分配内存的指针。当一个unique_ptr对象的析构函数被调用时,它将对其指针数据成员执行delete操作。由于unique_ptr类模板中提供了重载的运算符*和->,所以unique_ptr的对象可以像一般的指针变量一样使用。

在这里插入图片描述

示例代码:

Integer.h

#pragma once
class Integer
{
public:
	Integer(int i = 0); // 默认构造函数
	~Integer(); // 析构函数
	void setInteger(int i);
	int getInteger() const;
private:
	int value;
};

Integer.cpp

#include <iostream>
#include "Integer.h"
using namespace std;

Integer::Integer(int i)
	: value(i)
{
	cout << "Constructor for Integer " << value << endl;
}

Integer::~Integer()
{
	cout << "Destructor for Integer " << value << endl;
}

void Integer::setInteger(int i)
{
	value = i;
}

int Integer::getInteger() const
{
	return value;
}

test.cpp

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

#include "Integer.h"

int main()
{
	cout << "Creating a unique_ptr object that points to an Integer\n";

	// 创建一个unique_ptr的对象,该对象的类型为unique_ptr类模板的Integer类型特化版
	unique_ptr<Integer> ptrToInteger(new Integer(7)); // 该对象管理一个动态分配的Integer对象

	cout << "\nUsing the unique_ptr to manipulate the Integer\n";
	ptrToInteger->setInteger(99); // 就像使用一个Integer类型的指针一样,使用unique_ptr对象来操作管理的对象

	cout << "Integer after setInteger: " << (*ptrToInteger).getInteger()
		<< "\n\nTerminating program" << endl;
}

运行结果:

在这里插入图片描述

使用unique_ptr的注意事项

unique_ptr类之所以这么命名是因为,对于一个动态分配的对象在同一时间只能有一个unique_ptr对象可以指向它。通过unique_ptr类的重载赋值运算符或拷贝构造函数,可以使一个unique_ptr类对象转让它管理的动态内存的所有权。最后一个维护指向动态内存的指针的unique_ptr类对象将负责回收内存。当一个unique_ptr类对象在客户代码中退出作用域时,unique_ptr的析构函数将销毁动态分配的对象并回收它的内存。

指向内置数组的unique_ptr

程序员也可以使用一个unique_ptr对象来管理动态分配的内置数组,例如:
unique_ptr<string[]> ptr(new string[10]);
这条语句动态分配了一个由unique_ptr对象ptr管理的具有10个string元素的数组。类型string[]表明被管理的内存是一个包含string元素的内置数组,当管理一个数组的unique_ptr离开它的作用域时,它通过delete[]来回收内存。

同时,unique_ptr类中提供重载的运算符[],所以该对象可以通过下标运算符来访问数组元素。

九、标准库的异常层次结构

C++标准库中包含一个异常类的层次结构,其中的一部分如下图所示:

在这里插入图片描述
该层次结构的最上层是基类exception(在头文件<exception>中定义),它包含了虚函数what,exception的派生类可以通过重载what来发布合适错误信息。

基类exception的直接派生类包括runtime_error和logic_error(在头文件<stdexcept>中定义),其中每一个都有若干个派生类。此外,由C++运算符抛出的异常也直接由exception派生。例如,bad_alloc异常是由new抛出的,bad_cast上由dynamic_cast抛出的,以及bad_typeid异常。

注意,将捕获基类对象的catch处理器放在捕获该基类的派生类对象的catch处理器前面是一个逻辑错误。基类的catch处理器捕获所有由基类派生的类对象,所以派生类catch处理器永远不会被执行。

logic_error类是一些用来表明程序逻辑错误的标准异常类的基类。例如,invalid_argument类表明一个函数接收了一个非法参数。length_error类表明对对象使用的长度超过了该对象所允许的最大长度。out_of_range类表明了一个值越过所允许的取值范围。

而runtime_error的派生类,overflow_error描述了算术上溢错误,underflow_error描述了算术下溢错误。

为了捕获由一个try语句抛出的所有异常,可以使用catch(...),使用这种方式捕获异常有两个缺点,一个是捕获到的异常的类型是未知的,另外一个缺点是没有命名的参数,在catch处理器中无法引用该异常对象。

对于catch(...)可以用来执行不依赖于异常类型的恢复操作(例如,释放公共资源)。异常可以被重新抛出,由其他更特定的catch处理器来处理。

初学者必备:C++经典入门详细教程 下面的是学C++时要注意的。  1.把C++当成一门新的语言学习(和C没啥关系!真的。);  2.看《Thinking In C++》,不要看《C++变成死相》;  3.看《The C++ Programming Language》和《Inside The C++ Object  Model》,不要因为他们很难而我们自己是初学者所以就不看;  4.不要被VC、BCB、BC、MC、TC等词汇所迷惑— 阅读详情

相关推荐

C++编程入门到精通课件PPT完整版

语句推荐程度使用建议break高用于switch或单层循环退出continue中清晰表达跳过条件,避免深层嵌套goto低仅限资源清理、异常退出等极少数结构化需求滥用goto会导致“面条代码”(spaghetti code),难以维护。现代C++应优先采用RAII和异常处理替代。Start[开始循环] --> Condition{满足继续条件?Condition -- 是 --> Body[执行循环体]

weixin_42601702的博客 1030

C++异常处理

异常处理的概念 在进行团队开发的过程中,可以通过对异常处理机制来降低产生错误的可能性,从而提高程序的可靠性。 仅仅用if语句的判断,并不能够将所有出现异常的可能性都包括,如果开发较大规模的程序,就会导致正常的逻辑代码和处理异常的代码混淆在一起,增加了程序维护的难度。 通过异常处理机制,在整个程序段发生异常后都不至于导致程序出错,而是将异常抛出。 案例一(除数为0): #include <iostream> using namespace std; double fuc(double x, do

heifengli6666的博客 457

C++从入门到精通经典四部曲实战指南

在C++中,变量是程序中用于存储数据的基本单元。每个变量都必须有一个唯一的名称,并遵循特定的命名规则。变量命名规则如下:- 名称只能由字母(a-z, A-Z)、数字(0-9)和下划线(_)组成;- 名称不能以数字开头;- 名称不能是C++的关键字(如intreturnclass等);- 名称应具有可读性,建议使用有意义的英文单词组合。声明变量的语法:数据类型 变量名;示例代码:int age;char grade;

weixin_42579969的博客 905

c++异常处理(以处理除0错误为例)

一、概念异常处理的优势: 将程序逻辑&错误处理逻辑混在一起,会降低程序性能。如果没有异常发生,异常处理代码对性能的影响很小。更有效。异常处理将错误处理从程序执行的“主线”中剥离出来,提高程序的清晰性,增强可修改性。程序员可以处理任何异常——所有异常、某种类型的所有异常 或 一组相关类型的所有异常(即通过继承层次相联系的异常类型)。减少错误的可能性,是程序更健壮。异常处理必需品:

中山大学 Winnie Zha(C/C++) 8092

一个专门处理c++异常的类和例子

CExceptionLogger,是一个可以免费使用的C++类,用它可以截获未处理异常,如:非法存取、栈溢出、被零除等,并可以将异常详细信息记录到日志文件......

ROS 2 Humble + Gazebo Fortress 搭建AGV小车仿真环境(保姆级避坑指南)

本文详细介绍了如何使用ROS 2 Humble和Gazebo Fortress搭建AGV小车仿真环境,包括环境准备、URDF模型构建、ROS 2控制集成、传感器配置等关键步骤。特别针对Gazebo Fortress版本差异提供了保姆级避坑指南,帮助开发者快速解决常见问题并优化性能。

weixin_29044157的博客 200

C++除零异常

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34025051的博客 1315

Midas Civil实战避坑指南:从建模报错到施工阶段分析

本文详细解析了Midas Civil在桥梁建模与施工阶段分析中的实战避坑技巧,涵盖从基础建模到高级边界条件设置的全流程。针对截面定义、节点创建、施工荷载区分等常见问题提供专业解决方案,帮助工程师避免计算误差,提升结构分析效率。特别强调施工阶段荷载与恒荷载的正确处理,以及弹性连接的合理应用。

weixin_30319153的博客 349

c++处理除零错误

继承自 C 的优良传统, C++ 也是一门非常靠近底层的语言, 可是实在是太靠近了, 很多问题语言本身没有提供解决方案, 可执行代码贴近机器, 运行时没有虚拟机来反馈错误, 跑着跑着就毫无征兆地崩溃了, 简直比过山车还刺激.     虽然 C++ 加入了异常机制来处理很多运行时错误, 但是异常机制的功效非常受限, 很多错误还没办法用原生异常手段捕捉, 比如整数除 0 错误. 下面这段代码 #i

hhhbbb的专栏 3213

告别代码移植烦恼:STM32CubeMX 6.4.0 + STM32F407ZGT6 + YT8512C PHY芯片的LWIP网络配置全攻略

本文详细介绍了如何使用STM32CubeMX 6.4.0配置STM32F407ZGT6与YT8512C PHY芯片的LWIP网络适配,包括硬件特性解析、STM32CubeMX基础配置、专用驱动适配和LWIP协议栈定制化配置,帮助开发者解决PHY芯片差异导致的网络问题。

weixin_28718793的博客 290

windows 下AD9361的 no-os master 工程搭建

参考网站: https://wiki.analog.com/resources/fpga/docs/build#windows_environment_setup https://wiki.analog.com/resources/eval/user-guides/ad-fmcomms2-ebz/software/no-os-setup 参考博客: https://blog.youkuaiyun.com...

Bronceyang的博客 7294

【C++】异常处理(一)

介绍下C++处理异常的方法

矢车菊二十七号 676

自动驾驶算法-滤波器系列(三)——不同运动模型(CV、CA、CTRV、CTRA)的建模和推导

CV & CA & CTRV & CTRA1. CV模型2. CA模型3. CTRV模型4. CTRA模型 1. CV模型 2. CA模型 3. CTRV模型 4. CTRA模型

weixin_41480034的博客 2万+

C++新手入门学习教程(完整版)

C++新手入门学习教程(完整版)

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 3万+

网表导入virtuoso后发现pg pin忘记connect_pg_net/globalNetConnect怎么办?

数模混合项目中经常需要一些需要到virtuoso去连接的线,比如IO上的pg和一些信号线,除了在pr工作中设置skip route之外还需要做好net的赋值,告诉工具虽然我没在物理上有连接,但是实际上应该连什么net,这部分提现在网表里。在Default Net Name中也填写net name,因为这个net需要做global操作,即在顶层定义这个net,所以需要在net name后加个!打开schematic,删除指定cell pin所接net的label。

m0_61544122的博客 969

2022年华为ICT大赛 全球总决赛!中文综合任务书-网络赛道真题!

2022年华为ICT大赛 全球总决赛!综合任务书-网络赛道真题-中文翻译版

君逍遥o 2404

C++协程从入门到精通

协程(coroutine)是一种特殊的函数,它可以被暂停(suspend)、恢复执行(resume),并且一个协程可以被多次调用。C中的协程属于stackless协程,即协程被suspend时不需要堆栈。C20开始引入协程,围绕协程实现的相应组件较多,如co_wait、co_return、co_yield,promise,handle等组件,灵活性高,组件之间的关系也略复杂,这使得C++协程学习起来有一定难度。协程与传统函数不同,普通函数是线程相关的,函数的状态跟线程紧密关联;而协程是线程无关的,它的状态与

Small world 1135

【软考中级网络工程师】知识点之 ICMP 协议

本文聚焦软考中级网络工程师必备的 ICMP 协议知识,先概述其在 TCP/IP 协议族网络层的地位与无连接特性,再阐述差错报告、网络诊断等功能。解析报文格式,详解差错与查询类报文类型及工作机制,还介绍其在 Ping、Traceroute 命令中的应用,分析常见攻击与防范措施,最后总结核心要点,为网络工程师学习和应用 ICMP 协议提供全面指导。

邓邓子的博客 1403

15大章节全解析:C++核心指南的隐藏学习地图cpp-core-guidelines-cheatsheet完整路线图

**cpp-core-guidelines-cheatsheet** 是一个 **C++核心指南速查表** 开源项目,把 C++ 发明者 Bjarne Stroustrup 与 Herb Sutter 制定的《C++ Core Guidelines》拆解为 **15 大章节**,配套可下载的速查表图片(PDF + 高清 PNG),把 C++ 编码的规范、规则与最佳实践浓缩成一张随身携带的"学习地图

gitblog_00109的博客 705

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

c++从入门到精通(学习路线)

学习C++是一条充满挑战和乐趣的道路。通过持续的学习和实践,你将逐渐掌握这门强大的编程语言。记住,不要害怕犯错,因为每个错误都是进步的机会。祝你在C++的学习之旅上一帆风顺!

2301_81770430的博客 569

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

如何成为C++ QT开发大牛,从入门到精通的完整学习路线

1.【基础专栏]1.1 Qt C++语言编程基础1.1.1安装1.1.2 C++语言基础概述1.1.3 C++指针与引用1.1.4C++类与对象(一)1.1.5 C++类与对象(二)1.1.6类的基它特性1.1.7 构造函数 &析构函数 &拷贝构造函数1.1.8 C++友元与动态内存1.1.9函数重载与运算符重载1.2 Qt C++语言进阶1.2.1单继承1.2.2 多继承1.2.3虚继承及其它特性1.2.4虚函数与抽象类1.2.5 命名空间与模板。

一个程序员的博客 1056

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【C++进阶学习】第十二弹——C++ 异常处理:深入解析与实践应用

【玩转C++】重要一步——C++异常处理:深入解析和应用C++异常处理

2301_80220607的博客 5071

Java学习教程,Java基础教程(从入门到精通)

原文链接 Java学习教程,Java基础教程(从入门到精通) Java学习教程,Java基础教程(从入门到精通) qq_38433066 2019-03-19 12:32:06 26941 收藏 1222 文章标签: java 入门 教程 学习 路线 你好,欢迎来到C语言中文网阅读"Java 教程",你将享受到免费的 Java 学习资料,以及良好的阅读体验。 这套教程适用于没有任何Java 基础,或者基础薄弱的学员,它通俗易懂,并且非常全面,30 天从入门到精通不是神话。 关.

u013328649的博客 1580

《C/C++从入门到精通》目录

嵌入式面试大全 954

c++学习笔记(15) 异常处理

异常处理概述: 异常是用一个throw语句抛出,同时用try-catch来捕获,例如一个简单的例子: #include &lt;iostream&gt; using namespace std; int main(int argc, char *argv[]) { cout &lt;&lt; "Enter two number: " &lt;&lt; endl; int number...

开到荼蘼的博客 659

java自学教程_Java学习教程,Java基础教程(从入门到精通)

原文链接:https://www.bilibili.com/video/BV1rE411F7j5/Java是一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念。Java 不但可以用来开发网站后台、PC 客户端和 Android APP,还在数据分析、网络爬虫、云计算领域大显身手。从学术的角度讲,初学者需要花费不少时间来熟悉面向对象的概念、语...

weixin_31682257的博客 610
上一篇: C++学习-入门到精通【14】标准库算法
下一篇: C++学习-入门到精通【16】自定义模板的介绍
YKPG
博客等级 码龄6年 344粉丝 37原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值