先来个面试题:设计一个算法实现1+2+…+n不能使用+、-运算符 且时间复杂度为o(n)
解决方案:
#define _CRT_SECURE_NO_WARNINGS 0;
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<windows.h>
using namespace std;
template<size_t N>
int func()
{
/*char arr1[N>>1][N + 1];
char arr2[N][(N + 1) >>1];
int n1 = sizeof(arr1);
int n2 = sizeof(arr2);
return n1 > n2 ? n1 : n2;*/
if (N % 2 == 0)
{
char a[N >> 1][N + 1];
return sizeof(a);
}
else
{
char a[N][(N + 1)>>1];
return sizeof(a);
}
}
int main()
{
cout << func<11>() << endl;
system("pause");
return 0;
}
一、程序运行成功或失败的原因
1、一般的程序都是通过返回错误码来告诉你程序运行的成功和失败。但是这个远远不够,因为你不知道是什么原因导致的。
2、解决方法
找到对应错误码值所对应的具体含义就可以知道原因,但这个方法太过繁琐。
二、错误处理技术
1、传统错误处理办法
- 终止程序。(如段错误等)
- 返回错误码。
- 返回合法值,让程序处于某种非法的状态。(坑货)
- 调用一个预先设置的出现错误时调用的函数。–回调函数
2、异常处理
这个异常是申请失败:
原因:
申请太大,没有那么多内存给你
new的底层实现是operator new
异常的抛出和捕获
概念:
当一个函数发现自己无法处理的错误时抛出异常,让函数的调用者直接或 间接的处理这个问题。
异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
int f()
{
try
{
Array<> a;
a[10];
}
catch (Exception& e)
{
cout << e.What() << endl;
}
return 0;
}
int main()
{
try
{
f();
}
catch (Exception& e)
{
cout << e.What() << endl;
}
system("pause");
return 0;
}
int f()
{
try
{
Array<> a;
a[10];
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
int main()
{
try
{
f();
}
catch (Exception& e)
{
cout << e.What() << endl;
}
system("pause");
return 0;
}
- 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对 象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
class Exception
{
public:
Exception(const string& msg, int id)
:_msg(msg)
, _id(id)
{}
const char* What() const
{
return _msg.c_str();
}
private:
string _msg;
int _id;
};
template<size_t N=10>
class Array
{
public:
int& operator[](size_t pos)
{
if (pos >= N)
{
throw Exception("下标不合法", 1);
}
return a[pos];
}
protected:
int a[N];
};
int f()
{
try
{
Array<> a;
a[10];
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
int main()
{
try
{
f();
}
catch (const Exception& e)
{
cout << e.What() << endl;
}
system("pause");
return 0;
}
throw出了作用域后就i消失了,它抛出的异常会产生一个临时变量,再传给对应的catch,得到错误的原因。
栈展开
抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。 首先检查throw本身是否在catch块内部,如果是再查找匹配的catch语句。 如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查 找。 不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。 上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
三、异常捕获的匹配规则
异常对象的类型与catch说明符的类型必须完全匹配。 只有以下几种情况例外
- 允许从非const对象到const的转换。
- -
允许从派生类型到基类类型的转换。
- 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
异常捕获程序会终止。
正确捕获异常的代码:
#define _CRT_SECURE_NO_WARNINGS 0;
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<windows.h>
using namespace std;
class Exception
{
public:
Exception(const string& msg, int id)
:_msg(msg)
, _id(id)
{}
const char* What() const
{
return _msg.c_str();
}
private:
string _msg;
int _id;
};
void Send(const char* msg)
{
srand(time(0));
int num = rand();
if (num % 2 == 0)
{
cout << msg << endl;
}
else if (num % 3 == 0)
{
throw Exception("已经不是微信好友",1);
}
else if (num % 5 == 0)
{
throw exception("非法言论",2);
}
else if (num % 7 == 0)
{
throw bad_alloc();
}
}
int main()
{
while (1){
try {
Send("微信发消息");
}
catch (Exception& e)
{
cout<<e.What()<<endl;
}
catch (exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "非法错误" << endl;
}
Sleep(1000);
}
system("pause");
return 0;
}
结果:
服务器中的异常捕获(可以做成多态形式)
#define _CRT_SECURE_NO_WARNINGS 0;
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
#include<windows.h>
using namespace std;
class Exception
{
public:
Exception(const string& msg, int id)
:_msg(msg)
, _id(id)
{}
virtual const char* What() const
{
return _msg.c_str();
}
const char* What() const
{
return _msg.c_str();
}
private:
string _msg;
int _id;
};
class SqlException :public Exception
{
public:
SqlException(const string& msg, int id)
:Exception(msg, id)
{}
virtual const char* What() const
{
cout << "log一条sql错误" << endl;
return _msg.c_str();
}
string sql;
};
class NetworkException : public Exception
{
public:
NetworkException(const string& msg, int id)
:Exception(msg, id)
{}
};
void Send(const char* msg)
{
srand(time(0));
int num = rand();
if (num % 2 == 0)
{
cout << msg << endl;
}
else if (num % 3 == 0)
{
throw Exception("已经不是微信好友",1);
}
else if (num % 5 == 0)
{
throw NetworkException("网络超时", 2);
}
else if (num % 7 == 0)
{
throw SqlException("数据库连不上", 2);
}
}
int main()
{
while (1){
try {
Send("微信发消息");
}
catch (NetworkException& e)//对网络异常进行特殊处理
{
cout << "Send Email"<<e.What() << endl;
}
catch (Exception& e)
{
cout<<e.What()<<endl;
}
catch (exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "非法错误" << endl;
}
Sleep(1000);
}
system("pause");
return 0;
}
Vector下operator[]和v.at()的区别
try
{
/*char *p1 = new char[0x7fffffff];*/
vector<int> v;
v.push_back(1);
v.push_back(2);
cout << v[1] << endl;
cout << v.at(1) << endl;
cout << v.at(2) << endl;
}
catch (exception& e)
{
cout << e.what() << endl;
}
system("pause");
return 0;
}
- v[]越界会报错(断言错误)
- v.at()越界会抛异常,但不会报错。
四、异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再 交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上 层的函数进行处理。
go to、异常都会打断执行流
五、异常与构造函数&析构函数
- 构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
- 析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
总结:
优点:
- 1、比返回错误码的传统方式更清晰的表示程序错误。
- 2、很多c++的第三方库使用异常,使用异常能更好的用这些库
- 3、引入异常能更好的测试代码,因为如测试框架使用异常
缺点;
- 1:执行乱跳,不方便调试跟踪代码。
- 2、异常安全的问题/需要更好使用RAII编写异常安全的代码,如智能指针。