(黑马C++)L09 C++类型转换 异常 输入输出流

一、C++类型转换

类型转换(cast)是将一种数据类型转换成另一种数据类型,一般情况下要尽量少的去使用类型转换,除非解决非常特殊的问题。

(1)静态转换(static_cast)

static_cast使用方式:static_cast<目标类型>(原始对象);

  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
  • 没有父子关系的自定义类型不能进行转换。
#include <iostream>
using namespace std;

class Base{};
class Child : public Base{};

void test() {
    Base* base = NULL;
    Child* child = NULL;

    //把base转成child,向下类型转换,不安全
    Child* child2 = static_cast<Child*> (base);

    //把child转成base,向上类型转换,安全
    Base* base2 = static_cast<Base*> (child);
}


int main() {
    test();
    system("pause");
    return 0;
}
  • 用于基本数据类型之间的转换,如把int转换成char,或者把char转换成int。
#include <iostream>
using namespace std;

void test() {
    char a = 'a';
    double d = static_cast<double>(a);
    cout << "d = " << d <<endl;
}


int main() {
    test();
    system("pause");
    return 0;
}

(2)动态转换(dynamic_cast)

  • 动态转关非常严格,失去精度或者不安全都不可以转换。
  • 没有发生多态的情况下,可以用于子类转基类,但是不能用于基类转子类。
  • dynamic_cast如果发生了多态,可以让基类转为派生类,向下转换。
#include <iostream>
using namespace std;

class Base{
public:
    virtual void func() {};
};

class Child : public Base{
    virtual void func() {};
};

void test() {
    Base* base = NULL;
    Child* child = NULL;

    Base* base2 = new Child;
    Child* child = dynamic_cast<Child*> (base2);
}


int main() {
    test();
    system("pause");
    return 0;
}

(3)常量转换(const_cast)

用于修改类型的const属性。

  • 常量指针被转化为非常量指针,并且仍然指向原来的对象。
  • 常量引用被转换成非常量引用,并且仍然指向原来的对象。
  • 注意:不能直接对非指针和非引用的变量使用const_cast操作符直接移除它的const。
#include <iostream>
using namespace std;

void test() {
    //去除const
    const int * p = NULL;
    int * newp = const_cast<int*> (p);

    //加上const
    int * p2 = NULL;
    const int * newp2 = const_cast<const int*> (p2);
}


int main() {
    test();
    system("pause");
    return 0;
}

(4)重新解释转换(reinterpret_cast)

  • 最不安全的转换机制,最有可能出现问题,不推荐使用。
  • 主要用于将一种数据类型转换为另一种类型,可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针。

二、C++异常

(1)异常的基本使用

  • 异常处理就是处理程序中的错误,即程序运行过程中发生的一些异常事件(如:除0溢出,数组下标越界,读取的文件不存在。空指针,内存不足等)。
  • 在C语言中对错误的处理有两种方法:一是使用整型的返回值标识错误,二是使用errno宏,用不同的数字变量返回错误,该方法的局限是会出现不一致的问题,可能无法判断是返回值还是异常。
int myDevide(int a, int b) {
    if(b == 0) return -1;
    return a/b;
}
  • C++的异常机制:使用抛出异常和处理异常,抛出的异常必须进行处理,否则程序会出错。
  • 如果异常种类较多,都要进行处理,也可以使用以下方式:
catch(...) { //捕获异常
        cout << "其他类型异常捕获" << endl;
    }
#include <iostream>
using namespace std;

int myDevide(int a, int b) {
    if(b == 0) {
        throw -1; //抛出int类型的异常
    }
    return a/b;
}

void test() {
    int a = 10;
    int b = 0;
    try{
        myDevide(a, b);
    }
    catch(int) { //捕获异常
        cout << "int类型异常捕获" << endl;
    }
}

int main() {
    test();
    system("pause");
    return 0;
}
  • 异常处理可以在调用跳级,假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求在每一级函数中都进行处理,而使用异常处理的栈展开机制,只需要在一处进行处理就行,不需要每级函数都处理。
  • 以下代码会打印 double类型异常捕获,但是不想处理时可以继续向上抛出
  • 如果异常没有处理,会调用terminate函数,使程序中断
#include <iostream>
using namespace std;

int myDevide(int a, int b) {
    if(b == 0) {
        throw 3.14;
    }
    return a/b;
}

void test() {
    int a = 10;
    int b = 0;
    try{
        myDevide(a, b);
    }

    catch(int) { //捕获异常
        cout << "int类型异常捕获" << endl;
    }

    catch(double) { //捕获异常
        //如果不想处理这个异常需要向上抛出
        //throw; 此时会返回main函数中double类型异常捕获
        cout << "double类型异常捕获" << endl;
    }
}

int main() {
    try{
        test();
    }
    catch(double) {
        cout << "main函数中double类型异常捕获" << endl;
    }
    system("pause");
    return 0;
}

(2)对自定义异常进行捕获

可以抛出自定义对象,捕获自定义异常。

#include <iostream>
using namespace std;

//自定义异常
class myException {
public:
    void printError() {
        cout << "自定义异常" << endl;
    }
};

int myDevide(int a, int b) {
    if(b == 0) {
        throw myException(); //匿名对象
    }
    return a/b;
}

void test() {
    int a = 10;
    int b = 0;
    try{
        myDevide(a, b);
    }

    catch(myException e) { //捕获异常
       e.printError();
    }
}

int main() {
    test();
    system("pause");
    return 0;
}

(3)栈解璇

异常被抛出后,从进入try块起,到异常被抛出前,这期间在栈上构造的所有对象,都会被自动析构,析构的顺序与构造的顺序相反,这一过程称为栈的解璇。

(4)异常的接口声明

  • 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,例如:void func() throw(A, B, C) } {};这个函数func只能抛出类型A, B, C及其子类型的异常。
  • 如果函数声明中没有包含异常接口声明,则此函数可以抛出任何类型的异常。
  • 一个不抛出任何类型异常的函数可声明为void func() throw() {}。
  • 如果一个函数抛出了它的异常接口声明不允许抛出的异常,unexcepted函数会被调用,该函数默认调用terminate函数中断程序。

(5)异常变量生命周期

#include <iostream>
using namespace std;

class myException {
    myException() {
        cout << "默认构造函数调用" <<endl;
    }

    myException(const myException& e) {
        cout << "拷贝构造函数调用" <<endl;
    }

    ~myException() {
        cout << "析构函数调用" <<endl;
    }
};

void doWork() {
    throw myException();
}

void test() {
    try{
        doWork();
    }
    catch(myException e) {
        cout << "捕获异常" << endl;
    }
}

int main() {
    test();
    system("pause");
    return 0;
}

以上函数会输出以下结果,因为myException()调用默认构造,myException e调用拷贝构造,此时拷贝构造会多花费一份开销,所以一般写成myException &e,此时不会再调用拷贝构造

默认构造函数调用
拷贝构造函数调用
捕获异常
析构函数调用
析构函数调用

 当异常写成以下形式时,会先析构,执行结果为默认构造函数调用,析构函数调用,捕获异常。

#include <iostream>
using namespace std;

class myException {
    myException() {
        cout << "默认构造函数调用" <<endl;
    }

    myException(const myException& e) {
        cout << "拷贝构造函数调用" <<endl;
    }

    ~myException() {
        cout << "析构函数调用" <<endl;
    }
};

void doWork() {
    throw &myException();
}

void test() {
    try{
        doWork();
    }
    catch(myException* e) {
        cout << "捕获异常" << endl;
    }
}

int main() {
    test();
    system("pause");
    return 0;
}

(6)异常的多态使用

利用多态可以实现printError同一个接口调用,抛出不同的错误对象。

#include <iostream>
using namespace std;

//异常基类
class BaseException{
public:
    virtual void printError() {
        
    }
};

class NullPointerException : public BaseException {
public:
    virtual void printError() {
        cout << "空指针异常" << endl;
    }
};

void doWork() {
    throw NullPointerException();
}

void test() {
    try{
        doWork();
    }
    catch(BaseException& e) { //父类的引用调用子类的对象
        e.printError();
    }
}

int main() {
    test();
    system("pause");
    return 0;
}

(7)C++标准异常库

标准库中提供了很多的异常类,它们是通过类继承组织起来的,异常类继承层级结构图如下:

  •  在上述继承中,每个类都提供了构造函数,拷贝构造函数,析构函数和赋值运算符的重载;
  • logic_error类以及其子类、runtime_error类以及其子类,他们的构造函数接收一个string类型的参数,用于异常信息的描述;
  • 所有的异常都有一个what方法,返回 const char* 类型的值,描述异常信息。

 exception的直接派生类:

 logic_error的派生类:

 runtime_error 的派生类:

 以out_of_range异常为例,看一下它具体的使用方法,使用时需要包含头文件<stdexcept>

#include <iostream>
#include <string>
using namespace std;
//系统提供的异常
#include <stdexcept>

class Person {
public:
    Person(string name, int age) {
        this->m_Name = name;
        if(age < 0 || age > 200) {
            //抛出越界异常
            throw out_of_range("年龄越界!");
        }
        else {
            this->m_Age = age;
        }
    }

    string m_Name;
    int m_Age;
};

void test01() {
    try{
        Person p("张三", 300);
    }
    catch(out_of_range& e) {
        cout << e.what() << endl;
    }
}

int main() {
    test01();
    system("pause");
    return 0;
}

三、C++输入与输出流

程序的输入指的是从输入文件将数据传送给程序,程序的输出指的是从程序将数据传送给输出文件。

C++的输入输出包含三种:

标准I/O:从键盘输入数据,输出到显示器屏幕

文件I/O:以外存磁盘文件为对象进行输入和输出

串I/O:对内存中指定的空间进行输入和输出,通常指定字符数组作为存储空间进行信息存储

(1)标准输入流

  • 缓冲区:输入和输出的所有数据,都不是直接放在程序中,而是先放在缓冲区中,然后再从缓冲区中取出数据。
  • cin.get():一次读取一个字符,输入abc 输出a
void test01()
{
    char ch;
    ch = cin.get(); //输入as
    cout << "ch = " << ch << endl; //输出a

    ch = cin.get();
    cout << "ch = " << ch << endl; //输出s

    ch = cin.get();
    cout << "ch = " << ch << endl; //输出换行

    ch = cin.get();
    cout << "ch = " << ch << endl; //等待下一次输入
}
  • cin.get(一个参数):读一个字符,与上面效果一样
  • cin.get(两个参数):可以读字符串
void test() {
    char buf[1024];
    //cin >> buf;  //采用>>运算符输入字符窜,遇到空格就会停止
    //cout << buf << endl;
    
    cin.get(buf, 1024); //不会拿走换行,换行还在缓冲区
    cout << buf<< endl;
}
  • cin.getline():读取一行字符串
void test() {
    char buf[1024];
    cin.getline(buf, 1024); //会把换行符也读取并扔掉换行符
    cout << buf<< endl;
}
  • cin.ignore():默认忽略缓冲区的一个字符,带参数N,代表忽略N个字符
void test() {
    cin.ignore(); //输入as
    char c = cin.get();
    cout << c << endl; //输出s
}
  • cin.peek():从缓冲区查看一个字符,但不会从缓冲区取走
void test()
{
    char c = cin.peek();  //输入as
    cout <<"c = "<< c <<endl; //输出a

    ch = cin.get();
    cout << "c = " << c << endl; //输出a
}  
  • cin.pushback():将缓冲区取出的字符放回原位置
void test()
{
    char c = cin.get();  //输入helloworld  h被拿走
    cin.putback(c); //h被放回

    char buf[1024];
    cin.getline(buf, 1024); //读取字符串
    cout << "buf = " << buf << endl;
}
  • 标准输入流案例
  • (1)判断用户输入的是字符串还是数字
void test() {
    cout << "请输入一串数字或者字符串" << endl;
    //判断第一个字母(peek)
    char c = cin.peek();
    if(c >= '0' && c <= '9') {
        int num;
        cin >> num; //在缓冲区读取
        cout << "您输入的是数字,数字为:" << num <<endl;
    }
    else {
        char buf[1024];
        cin >> buf;
        cout << "您输入的是字符串,字符串为:" << buf <<endl;
    }
}
  • (2)让用户输入1-10的数字,如果输入有误,重新输入
void test() {
    int num;
    while(true) {
        cout << "请输入数字:" << endl;
        cin >> num; //如果输入的是char形,标志位会被修改
        if(num > 0 && num <= 10) {
            cout << "输入的数字为:" << num << endl;
            break;
        }
        else {
            cout << "对不起,请重新输入!" << endl;
            cin.clear(); //重置标志位
            cin.sync();  //清空缓冲区
            //cout <<"标志位:" << cin.fail() << endl; //标志位0正常 1不正常
        } 
    }
    
}

(2)标准输出流

  • cout.flush():刷新缓冲区,Linux下有效
  • cout.put():向缓冲区写字符
void test() {
    cout.push('a').put('b'); //输入ab
}
  • cout.write():从buffer中写num个字节到当前的输出流中
void test() {
    char buf[1024] = "helloworld";
    cout.write(buf, strlen(buf));
}
  •  格式化输出:使用控制符方法;使用流对象的有关成员函数
  • 使用成员函数
void test()
{
    int num = 99;
    cout.width(20);  //设置宽度,前面加18个空格
    cout.fill('*');  //设置填充,将空格填充为*
    cout.setf(ios::left); //左设置对齐  set format
    cout.unsetf(ios::dec); //卸载十进制
    cout.setf(ios::hex);  //安装十六进制
    cout.setf(ios::showbase);//显示进制基数,如果是十六进制,前面加上0x
    cout.unsetf(ios::hex);  //卸载十六进制
    cout.setf(ios::oct);  //安装八进制
    cout << num << endl;
}
  • 使用控制符(包含头文件iomanip)
void test03()
{
    int num = 99;
    cout << setw(20)                   //设置宽度
         << setfill('~')               //设置填充
         << setiosflags(ios::showbase) //显示进制基数
         << setiosflags(ios::left)     //左对齐
         << hex                        //安装16进制
         << num << endl;
}

(3)文件的读写操作

文件读写定义了三个类,分别是ofstream、ifstream、fstream。

  • 打开文件

用一个流对象打开一个文件的成员函数是open (filename, mode);其中 filename 是一个字符串,表示要打开的文件的名称,mode 是一个可选参数,由以下标志组合而成:

  • 写文件操作示例如下:
#include <iostream>
using namespace std;
//文件读写头文件
#include <fstream>

//写文件
void test01() {
    //以输出方式打开文件
    ofstream ofs("./test.txt", ios::out | ios::trunc);
    
    //后期指定打开方式
    ofstream ofs;
    ofs.open("./test.txt", ios::out | ios::trunc);

    //判断是否打开成功
    if(!ofs.is_open()) {
        cout << "打开失败!" << endl;
    }
    
    //输入内容
    ofs << "姓名:abc" <<endl;
    ofs << "年龄:20" <<endl;
    ofs << "性别:男" <<endl;

    //写入完毕后关闭文件
    ofs.close();
}
  • 读文件操作示例如下:
void test02() {
    ifstream ifs;
    ifs.open("./test.txt", ios::in);

    //判断是否打开成功
    if(!ifs.is_open()) {
        cout << "打开失败" << endl;
    }
    
    //第一种方式 -- 用数组存储
    char buf[1024];
    while(ifs >> buf) { //按行读取
        cout << buf <<endl;
    }

    //第二种方式
    char buf2[1024];
    while(!ifs.eof()) { //eof读到文件的尾部
        ifs.getline(buf2, sizeof(buf2));
        cout << buf2 << endl;
    }

    //第三种方式 -- 按单个字符读取(不推荐)
    char c;
    while((c = ifs.get()) != EOF) { //EOF 文件尾
        cout << c;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值