异常,不知道c++中的异常怎么样,之前学过Java中的异常。
7.1 异常的概念
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常的好处:
- 函数返回值可以忽略,但异常不可忽略。
- 整形返回值没有任何语义信息。而异常缺包含语义信息,有时你从类名就能够体现出来。
- 整形返回值缺乏相关的上下文信息。
- 异常处理可以跳级。(这个还不懂)
7.2 异常的基本语法
异常的基本语法:
//下面就是个除数的运算,但是除数为0时,抛出一个异常。
//用throw这个关键字抛异常
double division(int x, int y)
{
if(y == 0)
throw y; //抛异常
return (x/y);
}
既然异常可以抛出,就可以处理,c++中处理异常是用捕获:
int main(int argc, char **argv)
{
try{
division(10, 0);
} catch(int e){ //这是捕获异常
printf("异常 %d\n", e); //这里是处理异常,我这里就是打印
}
return 0;
}
c++语法有点放飞自我,异常可以跳级的,我们看看例子:
double division(int x, int y)
{
if(y == 0)
throw y;
return (x/y);
}
double division2(int x, int y)
{
division(x, y);
}
int main(int argc, char **argv)
{
try{
division2(10, 0);
} catch(int e){
printf("异常 %d\n", e);
} catch(...) { //这个可以捕获所有异常
printf("其他异常 %d\n", e);
}
return 0;
}
c++语法中支持,如果上一次的函数没做处理,就会默认继续往上级抛异常,但是Java的话是要处理的,有两种处理方式一种是抛,一种是捕获,c++默认会抛,直到上级能捕获这个异常,才行,如果都没有的话,程序挂死。
7.3 异常的接口声明
为了增强程序的可读性,可以在函数声明的时候列出可能抛出异常的所有类型。
//1.这个函数有且只能抛出 int char型异常,如果有需要可以再添加
double division(int x, int y) throw(int, char)
{
if(y == 0)
throw y;
return (x/y);
}
//2.如果函数中没有包含异常的接口说明,则此函数可以抛出所有异常
double division(int x, int y)
{
if(y == 0)
throw y;
return (x/y);
}
//3.如果列表中没有,就说明此函数不抛任何异常
double division(int x, int y) throw()
{
if(y == 0)
throw y;
return (x/y);
}
7.4 异常对象的生命周期
异常对象也是存在生命周期的,c++就比较难受,有引用也有指针,我们一个一个分析:
值传递
class MyException
{
public:
MyException()
{
printf("构造函数\n");
}
MyException(const MyException& ex)
{
printf("拷贝构造函数\n");
}
void watch() {
printf("未定义异常\n");
}
~MyException()
{
printf("析构函数\n");
}
private:
};
double division(int x, int y) throw(MyException)
{
if(y == 0)
throw MyException(); //构造一个匿名对象,无参构造函数
return (x/y);
}
double division2(int x, int y)
{
division(x, y);
}
int main(int argc, char **argv)
{
try{
division2(10, 0);
} catch(MyException e){ // MyException e = MyException()
//这里是调用拷贝构造函数
e.watch();
//析构的时候,不是很明白那个匿名对象为什么不先析构,要一起析构
}
return 0;
}
引用转递
int main(int argc, char **argv)
{
try{
division2(10, 0);
} catch(MyException& e){ // MyException& e = MyException()
//这里就直接返回那个临时变量的引用,并没有重新构造。
e.watch();
}
return 0;
}
指针转递
double division(int x, int y) throw(MyException)
{
if(y == 0)
//throw MyException(); //构造一个匿名对象,无参构造函数
//构造一个匿名对象,无参构造函数,对象的内存存储到堆里,有点像Java的感觉了
throw new MyException();
return (x/y);
}
int main(int argc, char **argv)
{
try{
division2(10, 0);
} catch(MyException* e){ // MyException* e = new MyException()
//申请一个指针指向MyException()对象
e->watch();
delete e; //因为对象是堆里的,所以需要delete
}
return 0;
}
7.5 C++ 标准的异常
C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
下表是对上面层次结构中出现的每个异常的说明:
参考文章:https://www.runoob.com/cplusplus/cpp-exceptions-handling.html
下面自定义一个异常类:
class MyException : public exception
{
public:
MyException()
{
printf("构造函数\n");
}
MyException(const MyException& ex)
{
printf("拷贝构造函数\n");
}
//重写wat函数
const char * what () const throw ()
{
return "C++ Exception";
}
~MyException()
{
printf("析构函数\n");
}
private:
};
double division(int x, int y)
{
if(y == 0)
throw MyException();
return (x/y);
}
double division2(int x, int y)
{
division(x, y);
}
int main(int argc, char **argv)
{
try{
division2(10, 0);
} catch(exception& e){
printf("异常 %s\n", e.what());
}
return 0;
}
这种写法和之前的写法差不多,就是把自己的写的异常类继承了c++标准异常类,这样才能正规一点。竟然是继承的话,就需要重写what方法,这个在父类中是一个纯虚函数,因为这个方法每一个都不一样。
7.6 标准输入
预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:
int main(int argc, char **argv)
{
char hello[100] = {0};
char hello1[100] = {0};
cout << "请输入" << endl;
cin >> hello >> hello1;
cout << "输出" << hello << hello1 <<endl;
return 0;
}
cin获取键盘的输入,可以通过空格隔开:
>> 这个操作是是重载后的,我们之前学过了操作符重载。(是阻塞的)
这个是简单的使用方法,下面看看几个常用的cin函数:
下面函数都是操作输入缓冲区的,输入数据有一个缓冲区存储,行缓冲。
int main(int argc, char **argv)
{
char hello[100] = {0};
char hello1[100] = {0};
cout << "请输入" << endl;
char ch, ch1;
//cin.get 阻塞函数
ch = cin.get(); //一次只能读取一个字符
cin.get(ch1); //读一个字符
cin.get(hello, 100); //可以读取字符串
//cin.getline也是阻塞函数
cin.getline(hello, 100); //读取一行数据,不读取\n
//也是阻塞
cin.ignore(); //忽略当前字符
cin.ignore(4); //忽略当前4个字符
//如果提前遇到\n,就忽略\n前面的,如果没 遇到,就忽略4个字符
cin.ignore(4, '\n');
//偷窥缓冲区第一个字符,也是阻塞的
ch = cin.peek();
cin.putback(ch); //把字符ch放回缓冲区原来的位置
cout << "输出" << ch << " ch1 " << ch1 << " hello " << hello <<endl;
return 0;
}
7.7 标准输出
预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的,如下所示:
int main(int argc, char **argv)
{
char hello[100] = {0};
char hello1[100] = {0};
cout << "hello c++" << endl;
return 0;
}
ostream对象已经把<<这个符号重载了,所以可以直接作为输出。但是这个标准输出也是有缓冲区的,也是行缓冲,每一行的结尾都需要一个回车,才能显示到屏幕上,如果没有就缓冲到缓冲区中,下面有跟标准输出有关的函数:
#include <iomanip>
int main(int argc, char **argv)
{
char hello[100] = {0};
char hello1[100] = {0};
cout << "hello c++" << endl;
cout << "hello c++ two";
cout.flush(); //刷新缓冲区
//往缓冲区写字符
cout.put('g').put('g').put('l');
//输出字符串
cout.write("gggl", strlen("gggl"));
//格式化输出 这个吃饱了闲的,还不如c的printf
int num = 10;
cout << num << endl;
cout.unsetf(ios::dec); //卸载当前默认的10进制输出方式
cout.setf(ios::oct); //八进制输出
cout.setf(ios::showbase); //输出表示进制的字符
cout << num << endl;
cout.width(10); //设置宽度
cout.fill('*'); //填充字符
cout.setf(ios::left); //设置左对齐
cout << num << endl;
//通过控制符输出
cout << hex
<< setiosflags(ios::showbase)
<< setw(10)
<< setfill('*')
<< setiosflags(ios::left)
<< num
<< endl;
//while(1);
return 0;
}
不过感觉比较闲,还是用c语言的printf好,比较实在。
7.8 文件操作
c++中的文件操作涉及到了文件有关的类,就在标准库 fstream,它定义了三个新的数据类型:
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
7.8.1 打开文件
//输入文件留
//1.可以通过构造函数打开文件
ifstream in("test.c", ios::in);
//2.也可以用open函数打开
ifstream in1;
in1.open("test.c", ios::in);
打开文件有两种方式,上面说了,比较建议用构造函数,入乡随俗。
open的参数和c语言的参数是一样的,第一个是文件路径,第二个就是权限,但是c++的权限有点改变了:
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
ios::binary | 二进制方式 |
这些模式标志,可以多个的,中间用|。
in1.open(“test.c”, ios::in | ios::out);
7.8.2 关闭文件
这个关闭文件最简单了,直接调用类的成员函数。
in.close();
7.8.3 操作文件示例
#include <fstream>
int main(int argc, char **argv)
{
char temp[1024] = {0};
int ret = -1;
//输入文件留
//1.可以通过构造函数打开文件
ifstream in("test.c", ios::in);
if(!in) {
return -1;
}
//2.也可以用open函数打开
//ifstream in1;
//in1.open("test.c", ios::in);
//输出文件
ofstream out("test.c.bak", ios::out);
if(!out) {
return -2;
}
//判断是否读取完
while(!in.eof()) {
//通过read读取
in.read(temp, 1024);
//写入新的文件
//out.write(temp, 1024);
out << temp;
//cout << temp << endl;
cout << ret << endl;
memset(temp, 0, 1024);
}
in.close();
out.close();
return 0;
}
目前功力不行,没有更深入了解,c++文件操作,只能先写成这样了,最简单的就是c++已经把<< >>这两个符号重载了,所以写入到文件时候,我就用了,也可以用函数的方式,函数方法有点不好,就是不知道read函数读取多少个字节,还是了解不多,以后再补补。
状态标志符的验证(Verification of state flags)
除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):
-
bad()
如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。 -
fail()
除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。 -
eof()
如果读文件到达文件末尾,返回true。 -
good()
这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。
要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。
这篇博客写的不错,可以参考参考
https://blog.csdn.net/kingstar158/article/details/6859379/
如果要看看文件指针操作,可以看看上面链接的博客,讲的很不错。这里就不讲了。
还有就是把类转化成二进制的方式传输或者保存,这个就是所谓的序列化和反序列化了,这个以后传输的时候讲。
序列化 对象数据写成二进制的方式传输。
反序列化 通过二进制数据返回一个类的对象数据。