• C++核心编程 day09 类型转换、异常、输入输出流


    1. 类型转换

    C++中的类型转换有四类,分别是静态转换、动态转换、常量转换、重新解释转换。

    静态转换使用static_cast进行转换。主要用于类层次中的父类和子类之间指针或引用的转换。向上类型转换的时候是安全的,但是向下类型转换的时候由于没有动态类型检查,所有是不安全的。静态转换也支持内置基本的数据类型之间的转换。

    动态类型转换是使用dynamic_cast进行转换。动态类型转换用于类层次之间的向上类型转换和向下类型转换。向上类型转换的时候效果和静态类型转换的效果是一样的。在使用向下类型转换的时候,会比static_cast更加安全。动态类型转换不支持内置基本数据类型转换。

    常量转换是用于修改类型的const属性,使用const_cast来进行转换。需要注意的是常量转换不能直接对非指针和非引用的变量去移除他们的const属性。

    重新解释转换是使用reinterpret_cast来进行转换。这是C++中最不安全的一种转换机制,最有可能出问题。主要用于将一种数据类型转换为另一种数据类型,比如指针可以转换为一个整数,一个整数也可以转换为一个指针。

    关于四种类型转换的示例代码如下所示。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    
    //静态类型转换
    void test01()
    {
    	//允许内置数据类型之间转换
    	char a = 'a';
    	double d = static_cast<double>(a);
    	cout << d << endl;
    }
    
    class Base{ virtual void func(){} };
    class Son : public Base { virtual void func(){} };
    class Other {};
    
    void test02()
    {
    	Base *base = NULL;
    	Son *son = NULL;
    
    	//语法:static_cast<目标数据类型>(原对象)
    	//父子之间的指针或者引用可以转换
    	//将base转为Son * 父转子 向下类型转换 不安全
    	Son *son2 = static_cast<Son *>(base);
    
    	//son转为Base * 子转父 向上类型转换 安全
    	Base *base2 = static_cast<Base *>(son);
    
    	//base转为Other *
    	//Other *other = static_cast(base); //转换无效
    }
    
    // 动态类型转换 dynamic_cast
    void test03()
    {
    	//不允许内置数据类型之间转换
    	//char c = 'c';
    	//double d = dynamic_cast(c);
    }
    
    void test04()
    {
    	Base *base = new Base;
    	Son *son = NULL;
    
    	//将base转为Son * 父转子 不安全 如果发生了多态,那么转换总是安全的
    	Son *son2 = dynamic_cast<Son *>(base);
    
    	//son转为Base * 子转父 安全
    	Base *base2 = dynamic_cast<Base *>(son);
    
    	//base转Other *
    	//Other *other = dynamic_cast(base); //无法转换
    }
    
    
    //常量转换 const_cast
    void test05()
    {
    	//不可以将非指针或者费引用做const_cast转换
    	const int *p = NULL;
    	int *pp = const_cast<int *>(p);
    
    	const int *ppp = const_cast<const int *>(pp);
    
    	//const int a = 10;
    	//int b = const_cast(a);
    
    	int num = 10;
    	int &numRef = num;
    
    	const int &num2 = const_cast<const int &>(numRef);
    }
    
    //重新解释转换 reinterpret_cast 最不安全一种转换,不建议使用
    void test06()
    {
    	int a = 10;
    	int *p = reinterpret_cast<int *>(a);
    
    	Base *base = NULL;
    	//base转Other *
    	Other *other = reinterpret_cast<Other *>(base);
    }
    
    int main()
    {
    	test01();
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    2. 异常

    2.1 异常语法

    异常处理就是处理程序中出现的错误,所谓的错误就是程序运行过程中发生的一些异常事件。比如除0移除、数组下标越界、空指针等错误。在C语言中我们对错误的处理主要是两个方法,第一种是使用整型的返回值标识错误,二是使用errno宏去记录错误。在C++中仍然可以使用这两种方法。

    上述的两种处理方法虽然说可以处理错误,但是最大的缺陷就会出现不一致的错误。比如有些函数返回1表示成功,返回0表示错误;而有些函数返回0表示成功,返回非0表示错误。除此之外,还有一个缺陷就是函数的返回值只有一个,当你通过函数的返回值代表错误代码的时候,那么函数就不能返回其他的值。比如下面的函数你就不能分清该函数返回的-1究竟是程序出错还是程序的执行结果。也就是函数的返回值有多重语义。

    int myDivision(int a, int b)
    {
    	if(b == 0)
    	{
    		return -1;
    	}
    	return a / b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    而我们使用C++的异常处理就不会出现这种情况。在C语言中处理异常的时候可能函数的调用者会忘记处理异常,而C++中如果你没有处理异常程序就会中断。异常的处理也可以在处理跳级,也就是有多个函数在调用栈中出现了某个错误,而C++中只需要在其中的某一处进行处理即可,不需要每一级的函数都进行处理。C++中如果有异常,我们使用throw操作创建一个异常对象并抛出。我们将可能抛出异常的程序段放在try代码块中,如果在try程序段执行期间没有引起异常,那么跟在try代码段后的catch字句就不会执行。catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常,或者也可以继续抛出异常。如果匹配的处理未找到,则会自动调用terminate函数,其缺省功能调用abort终止程序。对于处理不了的异常,也可以在最后一个catch分支使用throw继续上抛。下面给出一段关于异常的基本语法的示例代码。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    #include 
    
    class MyException
    {
    public:
    	void printError()
    	{
    		cout << "我自己的异常" << endl;
    	}
    };
    
    class Person
    {
    public:
    	Person()
    	{
    		cout << "Person的默认构造函数调用" << endl;
    	}
    	~Person()
    	{
    		cout << "Person的析构函数调用" << endl;
    	}
    };
    
    int myDivision(int a, int b)
    {
    	if (b == 0)
    	{
    		//return -1;
    		//throw 1; //抛出int类型异常
    		//throw 'a'; //抛出char类型异常
    		//throw 3.14; //抛出double类型异常
    		//string str = "abc";
    		//throw str;
    
    		//从try代码开始,到throw抛出异常之前,所有栈上的数据都会被释放掉
    		//释放的顺序和创建的顺序相反,这个过程我们称为栈解旋
    		Person p1;
    		Person p2;
    
    		throw MyException(); //抛出MyException的匿名对象
    	}
    	return a / b;
    }
    
    void test01()
    {
    	int a = 10;
    	int b = 0;
    
    	//C语言处理异常有缺陷,返回值不统一,返回值只有一个,无法区分是结果还是异常
    	//int ret = myDivision(a, b);
    	//if (ret == -1)
    	//{
    	//	cout << "异常" << endl;
    	//}
    
    	try
    	{
    		myDivision(a, b);
    	}
    	catch (int)
    	{
    		cout << "int类型异常捕获" << endl;
    	}
    	catch (char)
    	{
    		cout << "char类型异常捕获" << endl;
    	}
    	catch (double)
    	{
    		//捕获到了异常,但是不想处理,继续向上抛出这个异常
    		//异常必须有函数进行处理,如果没有任何处理,程序自动调用terminate函数,让程序中断
    		throw;
    		cout << "double类型异常捕获" << endl;
    	}
    	catch (MyException e)
    	{
    		e.printError();
    	}
    	catch (...)
    	{
    		cout << "其他类型异常捕获" << endl;
    	}
    
    }
    
    int main()
    {
    	try
    	{
    		test01();
    	}
    	catch (double)
    	{
    		cout << "main函数中double类型异常捕获" << endl;
    	}
    	catch (...)
    	{
    		cout << "main函数中其它类型异常捕获" << endl;
    	}
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    C++中异常机制和函数机制并不互相干涉,但是捕获方式是通过严格的类型匹配。

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

    为了加强程序的可读性,我们也可以在函数申明中列出可能抛出的异常的所有类型,例如void func() throw(A, B, C);这个函数func只能抛出类型为A、B、C以及其子类类型的异常。如果throw后的括号为空,则这个函数不能够抛出任何异常。如果函数的声明中没有包含结构声明,则函数可以抛出任意类型的异常。如果一个声明了接口的函数抛出了不允许抛出的异常,则会调用unexpected函数,该函数的默认行为是调用terminate函数中断程序。异常变量也是有声明周期的,声明周期和之前将的普通变量是类似的。异常变量也是有生命周期的,示例代码如下。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    #include 
    
    class MyException
    {
    public:
    	MyException()
    	{
    		cout << "MyException默认构造函数调用" << endl;
    	}
    
    	MyException(const MyException &e)
    	{
    		cout << "MyException拷贝构造函数调用" << endl;
    	}
    
    	~MyException()
    	{
    		cout << "MyException析构函数调用" << endl;
    	}
    };
    
    void doWork()
    {
    	throw new MyException();
    }
    
    int main()
    {
    	try
    	{
    		doWork();
    	}
    	//抛出的是throw MyException(); catch(MyException e) 调用拷贝构造函数,效率低
    	//抛出的是throw MyException(); catch(MyException &e) 只调用默认构造,效率高推荐
    	//抛出的是 throw &MyException(); catch(MyException *e) 对象会提前释放掉,不能在非法操作
    	//抛出的是 new MyException(); catch(MyException *e) 只调用默认构造函数 自己要管理释放
    	catch (MyException *e)
    	{
    		cout << "自定义异常捕获" << endl;
    		delete e;
    	}
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    异常也是有多态的,父类的引用或者指针指向子类的对象。使用的方法和前面类中的多态是一样的,在异常捕获的时候,我们常用这个,示例代码如下。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    
    //异常的基类
    class BaseException
    {
    public:
    	virtual void printError() = 0;
    };
    
    //空指针异常
    class NULLPointerException :public BaseException
    {
    public:
    	virtual void printError()
    	{
    		cout << "空指针异常" << endl;
    	}
    };
    
    //越界异常
    class OutOfRangeException :public BaseException
    {
    public:
    	virtual void printError()
    	{
    		cout << "越界异常" << endl;
    	}
    };
    
    void doWork()
    {
    	//throw NULLPointerException();
    	throw OutOfRangeException();
    }
    
    int main()
    {
    	try
    	{
    		doWork();
    	}
    	catch (BaseException &e)
    	{
    		e.printError();
    	}
    	system("pause");
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    2.2 C++标准异常库

    除了前面的自己编写的异常之外,标准库中也为我们提供了很多的异常类,他们是通过类的继承组织起来的。

    在这里插入图片描述
    所有的异常都继承一个公共的基类exception。每个类都有提供的了构造函数、拷贝构造函数和赋值操作。其中在logic_error类与runtime_error类以及它们的子类都有一个构造函数接收一个string类型参数用于描述异常的信息。所有的异常都有一个what()函数,该函数是一个常函数,返回的是const char *类型的值用于描述异常的信息。所有我们要输出异常的信息可以通过打印异常类中的what()函数的返回值即可。下面给出一些标准异常类的描述:

    异常名称描述
    exception所有标准异常类的基类
    bad_alloc在堆区开辟内存失败的时候,比如使用new或者new[]申请空间
    bad_exception当函数的接口声明了抛出bad_exception异常,而函数中抛出了接口没有声明的异常,调用unexpected函数若抛出异常,则无论什么异常都会被自动替换被bad_exception类型异常
    bad_cast使用动态类型转换引用失败的时候抛出的异常
    ios_base::failureIO操作过程中出现错误
    logic_error逻辑错误,也在运行前检测的错误
    length_error试图生成一个操作该类型最大长度的对象抛出的异常
    domain_error参数的值域错误,主要出现在数学函数中
    out_of_range超出有效范围
    invalid_argument参数不合适
    runtime_error运行时错误,在运行时才能检测到的错误
    range_error计算结果超出了有意义的值域范围
    overflow_error算术计算上溢
    underflow_error算术计算结果下溢

    例如下面代码就是用了系统标准库提供的异常类。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    
    class Person
    {
    public:
    	Person(int age)
    	{
    		if (age < 0 || age > 150)
    		{
    			//throw out_of_range("年龄必须在 0 ~ 150 之间");
    			throw length_error("年龄必须在 0 ~ 150 之间");
    		}
    		else
    		{
    			this->age = age;
    		}
    	}
    
    	int age;
    };
    
    int main()
    {
    	try
    	{
    		Person p(151);
    	}
    	catch (out_of_range &e)
    	{
    		cout << e.what() << endl;
    	}
    	catch (length_error &e)
    	{
    		cout << e.what() << endl;
    	}
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    标准库中的异常类是有限的,有时候我们也需要自己的异常类。在自己写异常类的时候,最好自己写的类要继承标准库中的异常类,同时也应该重载父类中的what函数,示例代码如下。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    #include 
    
    class MyOutOfRangeException :public exception
    {
    public:
    	MyOutOfRangeException(const char *str)
    	{
    		//const char * 可以隐式类型转换为string, 反之不可以
    		this->errorInfo = str;
    	}
    
    	MyOutOfRangeException(string str)
    	{
    		this->errorInfo = str;
    	}
    
    	virtual const char * what() const
    	{
    		//将string转为const char *
    		return this->errorInfo.c_str();
    	}
    
    	string errorInfo;
    };
    
    class Person
    {
    public:
    	Person(int age)
    	{
    		if (age < 0 || age > 150)
    		{
    			throw MyOutOfRangeException("年龄必须在0到150之间");
    		}
    		else
    		{
    			this->age = age;
    		}
    	}
    
    	int age;
    };
    
    int main()
    {
    	try
    	{
    		Person p(1000);
    	}
    	catch (exception &e)
    	{
    		cout << e.what() << endl;
    	}
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    3. 输入输出流

    3.1 输入输出流概念以及流类库

    程序的输入是指从文件将数据输入给程序,而程序的输出是将数据从程序输出给文件。C++中的输入分为三类,分别是标准I/O、文件I/O、串I/O。标准I/O是对系统指定的标准设备的输入与输出,比如鼠标键盘显示器等。文件I/O是以外存磁盘文件为对象进行输入输出。串I/O是对内存中指定的空间进行输入和输出,通常以字符数组作为空间的形式。

    C++中提供了用于标准输入输出的iostream类库,也提供了用于文件输入输出的fstream类库以及串输入输出的strstream类库。

    在这里插入图片描述
    在这里插入图片描述

    我们来详细看一下其中的常见类名。

    类名 |作用 | 头文件
    ios | 抽象基类 | iostream
    istream | 通用输入流和其它输入流的基类 | iostream
    ostream | 通用输出流和其它输出流的基类 | iostream
    iostream | 通用输入输出流和其它输入输出流的基类 | iostream
    ifstream | 输入文件流类 | fstream
    ofstream | 输出文件流类 | fstream
    fstream | 输入输出文件流类 | fstream
    istrstream | 输入字符串流类 | strstream
    ostrstream | 输出字符串流类 | strstream
    strstream | 输入输出字符串流类 | strstream

    iostream类库中的不同的类声明放在不同的头文件中,用户在自己的程序中#include相关的头文件就相当于声明了所需要用到的类。常用的头文件有以下:

    • iostream:包含了对输入输出流进行操作的所需基本信息。
    • fstream:用于用户管理的文件的I/O操作。
    • strstream:用于字符串流I/O。
    • stdiostream:用于混合使用C和C++的I/O机制。
    • iomanip:在使用格式化I/O时应该包含此头文件。

    iostream中有四种流对象,分别如下:

    对象含义设备对应的类C语言中对应的标准文件
    cin标准输入流键盘istream_withassignstdin
    cout标准输出流屏幕ostream_withassignstdout
    cerr标准错误流屏幕ostream_withassignstderr
    clog标准错误流屏幕ostream_withassignstderr

    cout是console output的缩写,意思是在控制台输出。cout不是关键字,而是ostream的一个对象,在iostream中有定义。在cout中重载了左移运算符用于输出。使用<<的时候用户可以不必考虑是什么类型,系统自动判断数据的类型并且根据其类型调用与之匹配的运算符重载函数。而在C语言中输出是非常麻烦的,因为需要记住很多格式字符。cout流在内存中对一个开辟了一个缓冲区,用于存放流中的数据,当cout流插入一个endl时,无论缓冲区是否满,都会立即输出流中的所有数据,然后插入一个换行符并清空缓冲区。

    cerr流对象是标准错误流,cerr流被指定为与显示器相关联。作用是在标准错误设备上输出相关出错信息。cerr与标准输出流cout的作用和用法差不多,但是有一点不同。cout通常是传送到显示屏输出,也可以被重定向输出到磁盘文件,而cerr流中的信息只能在显示器上输出。

    clog流对象也是标准错误流,是console log的缩写。它的作用和cerr的作用相同,都是在显示器终端上输出出错信息。但是不同的是cerr是不经过缓冲区直接向显示器输出有关信息,而clog中的信息放在缓冲区中,缓冲区满后或者遇到endl时向显示器输出。

    3.2 标准输入流

    标准输入流对象是cin,在里面有几个比较重点的函数。

    • cin.get() :一次从输入缓冲区内读取一个字符。
    • cin.get(n):一次从输入缓冲区内读取n个字符。
    • cin.get(buf, n):一次从输入缓冲区内读取长度为n个字符存入buf中。
    • cin.getline(buf, n):一次从输入缓冲区内读取一行的数据将最多n个存入buf中。
    • cin.ignore():忽略一个字符,如果括号有参数就是忽略n个字符。
    • cin.peek():偷窥输入缓冲区的第一个字符。
    • cin.putback(ch):将字符ch放回输入缓冲区。
    • cin.clear():清空缓冲区。
    • cin.sync():重置标志位,标志位为1表示异常,为0表示正常。
    • cin.fail():获取缓冲区标志位,返回的结果为0或者1

    上述标准输入流对象的函数示例如下。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    
    /*
    cin.get() //一次只能读取一个字符
    cin.get(一个参数) //读取一个字符
    cin.get(两个参数) //可以读字符串
    cin.getline()
    cin.ignore()
    cin.peek()
    cin.putback()
    */
    
    void test01()
    {
    	char c = cin.get();
    
    	cout << "c = " << c << endl;
    
    	c = cin.get();
    	
    	cout << "c = " << c << endl;
    
    	c = cin.get();
    	
    	cout << "c = " << c << endl;
    
    	c = cin.get();
    
    	cout << "c = " << c << endl;
    }
    
    void test02()
    {
    	char buf[1024] = { 0 };
    	cin.get(buf, 1024);
    
    	char c = cin.get();
    	//利用cin.get获取字符串的时候,换行符遗留在缓冲区中
    	if (c == '\n')
    	{
    		cout << "换行符遗留在缓冲区" << endl;
    	}
    	else
    	{
    		cout << "换行符不在缓冲区" << endl;
    	}
    	cout << buf << endl;
    }
    
    void test03()
    {
    	char buf[1024] = { 0 };
    	//利用cin.getline获取字符串时候,换行符不会被取走,也不再缓冲区中,而是直接扔掉
    	cin.getline(buf, 1024);
    	
    	char c = cin.get();
    	if (c == '\n')
    	{
    		cout << "换行符遗留在缓冲区" << endl;
    	}
    	else
    	{
    		cout << "换行符不在缓冲区" << endl;
    	}
    	cout << buf << endl;
    }
    
    //cin.ignore忽略 默认忽略一个字符,如果填入参数X,代表忽略X个字符
    void test04()
    {
    	cin.ignore(2);
    	char c = cin.get();
    	cout << "c = " << c << endl;
    }
    
    //cin.peek 偷窥
    void test05()
    {
    	char c = cin.peek();
    	cout << "c = " << c << endl;
    	
    	c = cin.get();
    	cout << "c = " << c << endl;
    
    	c = cin.get();
    	cout << "c = " << c << endl;
    }
    
    //cin.putback() 返回
    void test06()
    {
    	char c = cin.get();
    	cin.putback(c);
    
    	char buf[1024] = { 0 };
    
    	cin.getline(buf, 1024);
    	cout << buf << endl;
    }
    
    //案例1. 判断用户输入的内容是字符串还是数字
    void test07()
    {
    	cout << "请输入一个字符串或者数字" << endl;
    	char c = cin.peek();
    
    	if (c >= '0' && c <= '9')
    	{
    		int num;
    		cin >> num;
    		cout << "您输入的是数字 为: " << num << endl;
    	}
    	else
    	{
    		char buf[1024] = { 0 };
    		cin >> buf;
    		cout << "您输入的是字符串 : " << buf << endl;
    	}
    }
    
    // 案例2 用户输入0~1之间的数字,如果输入有误重新输入
    void test08()
    {
    	cout << "请输入0 ~ 10之间的数字" << endl;
    	int num;
    	while (true)
    	{
    		cin >> num;
    		if (num >= 0 && num <= 10)
    		{
    			cout << "输入正确, 输入的值为: " << num << endl;
    			break;
    		}
    
    		// 清空缓冲区,重置标志位
    		cin.clear();
    		//cin.sync();
    		//cin.ignore(); //vs2013以上版本加入
    		// 如果标志位为0,代表缓冲区正常 如果标志位为1,缓冲区异常
    		cout << "cin.fail() = " << cin.fail() << endl;
    		cout << "输入有误,请重新输入:" << endl;
    	}
    }
    
    
    int main()
    {
    	//test01();
    	//test02();
    	//test03();
    	//test04();
    	//test05();
    	//test06();
    	//test07();
    	test08();
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    3.3 标准输出流

    C++中的输出是通过标准输出流cout完成的。以下是关于标准输出流的函数。

    • cout.flush():刷新缓冲区。
    • cout.put(ch):向缓冲区中写入字符ch
    • cout.write(str, n):向缓冲区中输出str字符串的前n个字符。

    在C语言中我们使用格式字符通过printf去控制输出的格式,在C++中我们通过流对象cout中用于控制输出格式的成员函数来控制输出格式。常见控制输出格式的成员函数如下:

    流成员函数与之相同作用的控制符作用
    precision(n)setprecision(n)设置实数的精度为n位
    width(n)setw(n)设置字段宽度为n位
    fill(c)setfill(c)设置填充字符c
    setf()setiosflags()设置输出格式状态,括号中应给出格式状态,内容与控制符setiosflags括号中的内容相同
    unsetf()resetiosflags()终止已设置的输出格式状态,在括号中应指定内容

    流成员函数setf和控制符setiosflags库昊中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值,因此在引用这些的时候需要加上类名ios以及作用域运算符::,下面是格式标志。

    格式标志作用
    ios::left输出数据在本域宽范围内向左对齐
    ios::right输出数据在本域宽范围内向右对齐
    ios::internal数值的符号为应在域宽内左对齐,数值右对齐,中间由填充字符填充
    ios::dec设置整数的基数为10
    ios::oct设置整数的基数为8
    ios::hex设置整数的基数为16
    ios::showbase强制输出整数的基数(八进制数以0开头,十六进制数以0x开头)
    ios::showpoint强制输出浮点数的小数和尾数0
    ios::uppercase在以科学计数法格式E和以十六进制输出字母时以大写表示
    ios::showpos对正数显示+号
    ios::scientific浮点数以科学计数法格式输出
    ios::fixed浮点数以定点格式(小数形式)输出
    ios::unitbuf每次输出之后刷新所有的流
    ios::stdio每次输出之后清除stdoutstderr

    关于控制符如下:

    控制符作用
    dec设置数值的基数为10
    hex设置数值的基数为16
    oct设置数值的基数为8
    setfill(c)设置填充字符c,c可以是字符常量或者字符变量
    setprecision(n)设置浮点数的精度为n位。在以十进制小数形式输出时,n代表有效数字。在以fixed(固定小数位数)形式和scientific(指数)形式输出时,n为小数位数
    setw(n)设置字段宽度为n位
    setiosflags(ios::fixed)设置浮点数以固定的小数位显示
    setiosflags(ios::scientific)设置浮点数以科学计数法(即指数形式) 显示
    setiosflags(ios::left)输出数据左对齐
    setiosflags(ios::right)输出数据右对齐
    setiosflags(ios::skipws)忽略前导的空格
    setiosflags(ios::uppercase)数据以十六进制形式输出时字母以大写表示
    setiosflags(ios::lowercase)数据以十六进制形式输出时字母以小写表示
    setiosflags(ios::showpos)输出正数时给出+号

    需要注意的是如果使用了控制符,在程序开头还需要添加iomanip.h头文件。成员函数width(n)和控制符setw(n)只对其后面的第一个输出项有效。 在上面设置数值基数的只能选择其中一种来使用,它们是相互排斥的。对于成员函数setf和控制符setiosflags设置的输出格式状态,如果想要改变使用另一个状态,应该调用成员函数unsetf或者控制符resetiosflags终止原来设置的状态。在使用setf函数设置格式状态时,可以包含两个甚至多个状态,这些格式标志在ios类中被定义为枚举值,每一个格式标志都是以一个二进制位表示的,因此可以使用位运算符|组合多个格式标志。

    关于输出流对象的示例代码如下。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    #include  // 控制符格式化输出 头文件
    
    /*
    cout.put() //想缓冲区写入字符
    cout.write() //从buffer中写num个字节到当前输出流中
    */
    void test01()
    {
    	//cout.put('h').put('e');
    
    	char buf[] = "hello world";
    	cout.write(buf, strlen(buf));
    
    	cout << "hello world" << endl;
    }
    
    // 1.通过流成员函数格式化输出
    void test02()
    {
    	int number = 99;
    	cout.width(20); // 指定宽度为20
    	cout.fill('*'); // 填充
    	cout.setf(ios::left); // 左对齐
    	cout.unsetf(ios::dec); // 卸载十进制
    	cout.setf(ios::hex); // 安装十六进制
    	cout.setf(ios::showbase); // 显示基数
    	cout << number << endl;
    }
    
    // 2. 使用控制符格式化输出
    void test03()
    {
    	int number = 99;
    	cout << setw(20)	// 设置宽度
    		<< setfill('~') // 设置填充
    		<< hex // 显示16进制
    		<< setiosflags(ios::showbase) // 显示基数
    		<< setiosflags(ios::left) // 左对齐
    		<< number << endl;
    }
    
    int main()
    {
    	//test01();
    	//test02();
    	test03();
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    3.4 文件读写

    文件读写主要在fstream.h头文件中被定义,这里面定义了三个类,分别是ifstreamofstreamfstream,它们之间的继承关系如下。

    在这里插入图片描述
    进行文件读写的第一步就是要先打开文件,打开文件有两种方法。

    第一种是调用文件流对象的open成员函数,该函数的第一个参数文件路径,第二个参数是打开的方式。第二种方式使用文件流定义的参数构造函数,如下。

    // 第一种方法
    ofstream ofs;
    ofs.open("./demo1.txt", ios::out);
    
    // 第二种方法
    ofstream ofs("./demo1.txt", ios::out);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    文件输入输出的方式设置值如下。

    方式作用
    ios::in以输入方式打开文件
    ios::out以输出方式打开文件(这是默认方式),如果已经有此名字的文件,则将其原有内容直接全部清空
    ios::app以输出方式打开文件,写入的数据添加在文件末尾
    ios::ate打开一个已有的文件,文件指针指向文件末尾
    ios::trunc打开一个文件,如果文件已存在,则删除其中全部数据,若文件不存在,则建立新文件。如已经使用ios::out方式打开,而未指定ios::appios::ateios::in,则同时默认此方式
    ios::binary以二进制方式打开一个文件,如不指定此方式默认为ASCII方式
    ios::nocreate打开一个已有的文件,如文件不存在,则打开失败。
    ios::noreplace如果文件不存在则简历新文件,如果文件已存在则操作失败
    ios::in | ios::out以输入和输出方式打开文件,文件可读可写
    ios::out | ios::binary以二进制方式打开一个输出文件
    ios::in | ios::binary以二进制方式打开一个输入文件

    上面的输入输出方式设置可以使用位运算符中的或|进行组合。如果打开操作失败,open函数的返回值为假,如果是调用构造函数的方式打开文件,则流对象的值为0。

    对文件读写操作完成后,应该关闭相关的文件。关闭用成员函数close完成。关闭就是解除改文件与文件流的关联,同样原来设置的工作方式也会失效,这样就不能再通过文件流对该文件进行输入和输出。

    C++中对ASCII文件读写操作也可以使用左移或者右移运算符。<<表示刘插入运算符,而>>表示流提取运算符。这两用法和cin以及cout是一样的。在文件流中,常使用putgetgetline等成员函数进行字符的输入输出。put()输出单个字符,get()读取一个字符,getline()读取一行字符。

    关于文件读写的示例代码如下。

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    using namespace std;
    #include 
    #include 
    
    void test01()
    {
    	// 写文件 输出
    	ofstream ofs("./test.txt", ios::out | ios::trunc);
    
    	//ofs.open("./test.txt", ios::out | ios::trunc); 设置打开方式 以及路径
    
    	if (!ofs.is_open())
    	{
    		cout << "文件打开失败" << endl;
    		return;
    	}
    
    	ofs << "姓名:孙悟空" << endl;
    	ofs << "年龄:999" << endl;
    	ofs << "性别:女" << endl;
    
    	// 关闭文件
    	ofs.close();
    }
    
    void test02()
    {
    	// 读文件
    	ifstream ifs;
    	ifs.open("./test.txt", ios::in);
    
    	if (!ifs.is_open())
    	{
    		cout << "文件打开失败" << endl;
    		return;
    	}
    
    	// 第一种方式
    	//char buf[1024] = { 0 };
    
    	//while (ifs >> buf)
    	//{
    	//	cout << buf << endl;
    	//}
    		
    	// 第二种方式
    	//char buf[1024] = { 0 };
    	//while (ifs.getline(buf, 1024))
    	//{
    	//	cout << buf << endl;
    	//}
    	
    	// 第三种方式
    	//string buf;
    	//while (getline(ifs, buf))
    	//{
    	//	cout << buf << endl;
    	//}
    
    	// 第四种方式
    	char c;
    	while ((c = ifs.get()) != EOF)
    	{
    		cout << c;
    	}
    }
    
    int main()
    {
    	test01();
    	test02();
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
  • 相关阅读:
    2023年眼镜行业分析(京东眼镜销量数据分析):市场规模同比增长26%,消费需求持续释放
    docker-私有仓库Harbor-个人网盘
    [Hadoop全分布部署]安装JDK、Hadoop
    DNS解析为什么不生效?DNS解析不生效原因分析
    AUTOSAR从入门到精通100讲(150)-SOA架构及应用
    HTML期末大作业(HTML+CSS+JavaScript响应式游戏资讯网站bootstrap网页)
    (附源码)ssm产品裂变管理系统 毕业设计 100953
    zabbix监控Nginx
    七大排序 (9000字详解直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序)
    看完这篇 教你玩转渗透测试靶机Vulnhub——DriftingBlues-7
  • 原文地址:https://blog.csdn.net/qq_42198306/article/details/134447770