c++学习笔记(七、异常和I/O)

异常,不知道c++中的异常怎么样,之前学过Java中的异常。

7.1 异常的概念

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。

异常的好处:

  1. 函数返回值可以忽略,但异常不可忽略。
  2. 整形返回值没有任何语义信息。而异常缺包含语义信息,有时你从类名就能够体现出来。
  3. 整形返回值缺乏相关的上下文信息。
  4. 异常处理可以跳级。(这个还不懂)

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++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
在这里插入图片描述
下表是对上面层次结构中出现的每个异常的说明:

异常描述
std::exception该异常是所有标准 C++ 异常的父类。
std::bad_alloc该异常可以通过 new 抛出。
std::bad_cast该异常可以通过 dynamic_cast 抛出。
std::bad_exception这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid该异常可以通过 typeid 抛出。
std::logic_error理论上可以通过读取代码来检测到的异常。
std::domain_error当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument当使用了无效的参数时,会抛出该异常。
std::length_error当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
std::runtime_error理论上不可以通过读取代码来检测到的异常。
std::overflow_error当发生数学上溢时,会抛出该异常。
std::range_error当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error当发生数学下溢时,会抛出该异常。

参考文章: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/

如果要看看文件指针操作,可以看看上面链接的博客,讲的很不错。这里就不讲了。

还有就是把类转化成二进制的方式传输或者保存,这个就是所谓的序列化和反序列化了,这个以后传输的时候讲。

序列化 对象数据写成二进制的方式传输。
反序列化 通过二进制数据返回一个类的对象数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值