tip——string转char*的库函数方法:
string.c_str();
类型转换
静态转换(static_cast)
使用方式:
static_cast<目标类型>(原始对象)
用法:
- 用于父类和子类之间指针或引用的转换。
- 用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证。
- 没有父子关系的自定义类型不可以转换
补充:
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
- 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
基本数据类型转换的例子:
void test01(){
char a = 'a';
double d = static_cast<double>(a);
cout << "d = " << d <<endl;
}
继承关系指针互相转换
class Base{};
class Child :public Base{};
class Other{};
void test02(){
Base * base = NULL;
Child * child = NULL;
//把base转为 Child*类型 向下 不安全
Child * child2 = static_cast<Child*>(base);
//把child 转为 Base* 向上 安全
Base * base2 = static_cast<Base*>(child);
//转other类型 转换无效
//Other * other = static_cast<Other*>(base);
}
继承关系引用相互转换
class Animal{};
class Dog : public Animal{};
class Other{};
void test03(){
Animal ani_ref;
Dog dog_ref;
//继承关系指针转换
Animal& animal01 = ani_ref;
Dog& dog01 = dog_ref;
//子类指针转成父类指针,安全
Animal& animal02 = static_cast<Animal&>(dog01);
//父类指针转成子类指针,不安全
Dog& dog02 = static_cast<Dog&>(animal01);
}
动态转换(dynamic_cast)——十分严格
使用方式:
dynamic_cast<目标类型>(原始对象)
用法:
- 基础类型不可以转换
- dynamic_cast主要用于类层次间的上行转换和下行转换;
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
- 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全;
- 发生多态,则允许发生向上转换和向下转换。
dynamic_cast 如果发生了多态,那么可以让基类转为派生类 ,向下转换
原因:
创建空间大小的时候,是按照子类的大小进行创建的。
使用样例:
class Animal {
public:
virtual void ShowName() = 0;
};
class Dog : public Animal{
virtual void ShowName(){
cout << "I am a dog!" << endl;
}
};
class Other {
public:
void PrintSomething(){
cout << "我是其他类!" << endl;
}
};
//普通类型转换
void test01(){
//不支持基础数据类型
int a = 10;
//double a = dynamic_cast<double>(a);
}
//继承关系指针
void test02(){
Animal* animal01 = NULL;
Dog* dog01 = new Dog;
//子类指针转换成父类指针 可以
Animal* animal02 = dynamic_cast<Animal*>(dog01);
animal02->ShowName();
//父类指针转换成子类指针 不可以
//Dog* dog02 = dynamic_cast<Dog*>(animal01);
}
//继承关系引用
void test03(){
Dog dog_ref;
Dog& dog01 = dog_ref;
//子类引用转换成父类引用 可以
Animal& animal02 = dynamic_cast<Animal&>(dog01);
animal02.ShowName();
}
//无继承关系指针转换
void test04(){
Animal* animal01 = NULL;
Other* other = NULL;
//不可以
//Animal* animal02 = dynamic_cast<Animal*>(other);
}
常量转换(const_cast)
该运算符用来修改类型的const属性。
使用方式:
- 加上const: const int * newP2 = const_cast<const int *>(p2);
- 去掉const: int * newp = const_cast<int *>(p);
用法:
- 常量引用被转换成非常量引用,并且仍然指向原来的对象;
- 常量指针被转化成非常量指针,并且仍然指向原来的对象;
- 不能直接对非指针和非引用的变量使用const_cast操作符去直接移除它的const.
//常量指针转换成非常量指针
void test01(){
const int* p = NULL;
int* np = const_cast<int*>(p);
int* pp = NULL;
const int* npp = const_cast<const int*>(pp);
const int a = 10; //不能对非指针或非引用进行转换
//int b = const_cast<int>(a); }
//常量引用转换成非常量引用
void test02(){
int num = 10;
int & refNum = num;
const int& refNum2 = const_cast<const int&>(refNum);
}
重新解释转换(reinterpret_cast)——最不安全,不推荐
主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针.
void test06(){
int a = 10;
int * p = reinterpret_cast<int *>(a);
Base * base = NULL;
Other * other = reinterpret_cast<Other*>(base);
//最不安全 ,不推荐
}
异常——将问题检测和问题处理相分离。
什么是异常处理
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)。
引入异常的原因
在C语言的世界中,对错误的处理总是围绕着两种方法:
- 使用整型的返回值标识错误;
- 使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。
C++中仍然是可以用这两种方法的。
这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。
还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。比如返回-1代表除0异常,但是10/(-10)的正确结果也是-1,这就会引发冲突,不能确定你返回的-1是正确的计算结果还是因为发生了异常。
当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
c++异常机制相比C语言异常处理的优势?
- 函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其面的终止或出现错误的结果。
- 整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。
- 整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。
- 异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。
补充——什么是跳级?
比如下图,A调用B,B调用C,在C中发生了除0异常,因此返回值为 -1。 B收到了返回值,误以为- 1 是C的正常计算结果,就将该结果加以计算并将计算结果返回给A。如果A没有发现异常,那么程序就要崩溃了,因此在B没有发现C产生了异常的情况下,允许A来处理异常,这就是跳级。
如果异常没有函数来进行处理,系统会调用terminate函数,是程序中断。
C++异常捕获的小例子
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int myDevide(int a ,int b){
if (b == 0){
//如果b是异常 抛出异常
//return -1;
throw 1; 抛出int类型异常
}
return a / b;
}
void test01(){
int a = 10;
int b = 0;
//int ret = myDevide(a, b); //早期如果返回-1 无法区分到底是结果还是异常
//C++中异常处理
try //试一试
{
myDevide(a, b);
}
catch (int) //捕获异常
{
cout << "int类型异常捕获" << endl;
}
}
int main(){
test01();
system("pause");
return EXIT_SUCCESS;
}
但是类型太多了,总不能针对每种异常都写一个cache吧?可以用"..."符号来解决:
catch (...) {
cout << "其他类型异常捕获" << endl;
}
用一用:
#include "pch.h"
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int myDevide(int a, int b) {
if (b == 0) {
throw 'a';
}
return a / b;
}
void test01() {
int a = 10;
int b = 0;
try {
myDevide(a, b);
}
catch (int) {
cout << "int类型异常捕获" << endl;
}
catch (...) {
cout << "其他类型异常捕获" << endl;
}
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
试一下异常的跳级处理
对于自己不想处理的异常,可以throw出去
int myDevide(int a, int b) {
if (b == 0) {
throw 1;
}
return a / b;
}
void test01() {
int a = 10;
int b = 0;
try {
myDevide(a, b);
}
catch (int) //捕获异常
{
//不想处理该异常,可以将捕获到的异常抛出
throw;
cout << "int类型异常捕获" << endl;
}
}
完整实现代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int myDevide(int a, int b) {
if (b == 0) {
throw 1;
}
return a / b;
}
void test01() {
int a = 10;
int b = 0;
try //试一试
{
myDevide(a, b);
}
catch (int) //捕获异常
{
//不想处理该异常,可以将捕获到的异常抛出
throw;
cout << "int类型异常捕获" << endl;
}
catch (...) {
cout << "其他类型异常捕获" << endl;
}
}
int main() {
try {
test01();
}
catch (...) {
cout << "main函数中的异常捕获" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
自定义异常类
#define _CRT_SECURE_NO_WARNINGS
#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 test01(){
int a = 10;
int b = 0;
try {
myDevide(a, b);
}
catch (int) {
cout << "int类型异常捕获" << endl;
}
catch (double) {
//如果不想处理这个异常 ,可以继续向上抛出
throw;
cout << "double类型异常捕获" << endl;
}
catch (myException e) {
e.printError();
}
catch (...) {
cout << "其他类型异常捕获" << endl;
}
}
int main() {
try {
test01();
}
catch (char) {
cout << "main 函数中 double类型异常捕获" << endl;
}
catch (...) {
cout << "main函数中 其他类型异常捕获" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
总结:
- 若有异常则通过throw操作创建一个异常对象并抛出。
- 将可能抛出异常的程序段放到try块之中。
- 如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
- catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)
- 如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
- 处理不了的异常,可以在catch的最后一个分支,使用throw,向上抛。
c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。
栈解旋(unwinding)
异常被抛出后,从进入try块起,到throw抛出异常之前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class myException //自定义异常类
{
public:
void printError() {
cout << "自定义的异常" << endl;
}
};
class Person {
public:
Person() {
cout << "Person构造" << endl;
}
~Person() {
cout << "Person析构" << endl;
}
};
int myDevide(int a, int b) {
if (b == 0) {
//栈解旋
//从try开始 到 throw 抛出异常之前 所有栈上的对象 都会被释放 这个过程称为栈解旋
//构造和析构顺序相反
Person p1;
Person p2;
throw myException(); //匿名对象
}
return a / b;
}
void test01()
{
int a = 10;
int b = 0;
try {
myDevide(a, b);
}
catch (int) {
cout << "int类型异常捕获" << endl;
}
catch (double) {
//如果不想处理这个异常 ,可以继续向上抛出
throw;
cout << "double类型异常捕获" << endl;
}
catch (myException e) {
e.printError();
}
catch (...) {
cout << "其他类型异常捕获" << endl;
}
}
int main() {
try {
test01();
}
catch (char) {
cout << "main 函数中 double类型异常捕获" << endl;
}
catch (...) {
cout << "main函数中 其他类型异常捕获" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
异常接口声明
- 如果想抛出特定的类型异常 ,可以利用异常的接口声明
- void func() throw ( int) 只能抛出 int类型
- void func() throw() 不抛出任何类型异常
- 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
//可抛出所有类型异常
void TestFunction01(){
throw 10;
}
//只能抛出int char char*类型异常
void TestFunction02() throw(int,char,char*){
string exception = "error!";
throw exception;
}
//不能抛出任何类型异常
void TestFunction03() throw(){
throw 10;
}
int main(){
try{
//TestFunction01();
//TestFunction02();
//TestFunction03();
}
catch (...){
cout << "捕获异常!" << endl;
}
system("pause");
return EXIT_SUCCESS;
}
异常变量的生命周期
- 如果 MyException e,会多开销一份数据 ,调用拷贝构造
- 如果 MyExcepiton *e ,不提前释放对象 new ,自己管理delete
- 推荐 MyException &e 容易些 ,而且就一份数据
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class MyException {
public:
MyException() {
cout << "MyException的默认构造" << endl;
}
MyException(const MyException & e) {
cout << "MyException的拷贝构造" << endl;
}
~MyException() {
cout << "MyException的析构调用" << endl;
}
};
void doWork() {
throw MyException();
}
void test01() {
try {
doWork();
}
catch (MyException e) //MyException e,会多开销一份数据
{
cout << "捕获异常" << endl;
}
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
调用拷贝构造是因为catch的参数是值传递。
catch (MyException e)
为了提高效率,可以改成引用传递
catch (MyException &e)
但是传引用,在catch函数中不能对传入的e进行操作,因为在栈上创建的e已经被析构了。那么就改成在堆上创建e,然后catch传入一个指向e的指针
void doWork() {
throw new MyException();
}
void test01() {
try {
doWork();
}
catch (MyException *e){
cout << "捕获异常" << endl;
delete e; //靠自觉 释放对象
}
}
一定要记得自己new的东西,自己delete
注意!!!如果是栈上创建的e,那么在catch函数中就不要再对e进行任何操作,因为那是野指针,这种操作是很危险的
class MyException {
public:
MyException() {
cout << "MyException的默认构造" << endl;
}
MyException(const MyException & e) {
cout << "MyException的拷贝构造" << endl;
}
~MyException() {
cout << "MyException的析构调用" << endl;
}
void printError() {
this->m_A = 100;
cout << "error" << m_A << endl;
}
int m_A;
};
void doWork() {
throw MyException();
}
void test01() {
try {
doWork();
}
catch (MyException &e) {
//操作野指针
e->printError();
e->printError();
e->printError(); //指向非法内存空间,不应该这么做
cout << "捕获异常" << endl;
}
}
异常的多态使用
- 利用多态来实现 printError同一个接口调用
- 抛出不同的错误对象,提示不同错误
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//异常基类
class BaseException {
public:
virtual void printError() {}
};
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();
}
void test01() {
try {
doWork();
}
catch (BaseException & e) {
e.printError();
}
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
C++标准异常库
标准库介绍
标准异常类的成员:
① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、和赋值操作符重载。
② logic_error类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述
③ 所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息。
标准异常类的具体描述:
异常名称 | 描述 |
exception | 所有标准异常类的父类 |
bad_alloc | 当operator new and operator new[],请求分配内存失败时 |
bad_exception | 这是个特殊的异常,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常抛出列表中没有的异常,这是调用的unexpected函数中若抛出异常,不论什么类型,都会被替换为bad_exception类型 |
bad_typeid | 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常 |
bad_cast | 使用dynamic_cast转换引用失败的时候 |
ios_base::failure | io操作过程出现错误 |
logic_error | 逻辑错误,可以在运行前检测的错误 |
runtime_error | 运行时错误,仅在运行时才可以检测的错误 |
logic_error的子类:
异常名称 | 描述 |
length_error | 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作 |
domain_error | 参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非负数的函数 |
out_of_range | 超出有效范围
|
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
runtime_error的子类:
异常名称 | 描述 |
range_error | 计算结果超出了有意义的值域范围 |
overflow_error | 算术计算上溢 |
underflow_error | 算术计算下溢 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字符不是’0’或’1’的时候,抛出该异常 |
标准库异常函数使用要包含头文件
#include<stdexcept>
out_of_range的使用例子:
#include<iostream>
#include<stdexcept>
using namespace std;
class Person {
public:
Person(int age) {
if (age < 0 || age > 150) {
throw out_of_range("年龄应该在0-150岁之间!");
}
}
public:
int mAge;
};
int main() {
try {
Person p(151);
}
catch (out_of_range& ex) {
cout << ex.what() << endl;
}
system("pause");
return EXIT_SUCCESS;
}
编写自己的异常类
原因:
① 标准库中的异常是有限的;
② 在自己的异常类中,可以添加自己的信息。(标准库中的异常类值允许设置一个用来描述异常的字符串)。
如何写:
① 建议自己的异常类要继承标准异常类。因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
② 当继承标准异常类时,应该重载父类的what函数和虚析构函数。
③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供自己的拷贝构造函数。
先来看看系统的异常基类长啥样(取自VS)
class exception
{
public:
exception() noexcept
: _Data()
{
}
explicit exception(char const* const _Message) noexcept
: _Data()
{
__std_exception_data _InitData = { _Message, true };
__std_exception_copy(&_InitData, &_Data);
}
exception(char const* const _Message, int) noexcept
: _Data()
{
_Data._What = _Message;
}
exception(exception const& _Other) noexcept
: _Data()
{
__std_exception_copy(&_Other._Data, &_Data);
}
exception& operator=(exception const& _Other) noexcept
{
if (this == &_Other)
{
return *this;
}
__std_exception_destroy(&_Data);
__std_exception_copy(&_Other._Data, &_Data);
return *this;
}
virtual ~exception() noexcept
{
__std_exception_destroy(&_Data);
}
virtual char const* what() const
{
return _Data._What ? _Data._What : "Unknown exception";
}
private:
__std_exception_data _Data;
};
string转char*的库函数方法:
string.c_str();
这就是一个string类型的字符串了
string("我自己的年龄越界异常")
完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <string>
using namespace std;
//继承基类 exception
class MyOutOfRangeException :public exception {
public:
MyOutOfRangeException(string errorInfo) {
this->m_ErrorInfo = errorInfo;
}
//重写父类的虚析构函数和what函数
virtual ~MyOutOfRangeException() {}
virtual const char * what() const {
//返回 错误信息
//string 转 char * .c_str()
return this->m_ErrorInfo.c_str();
}
string m_ErrorInfo;
};
class Person {
public:
Person(string name, int age) {
this->m_Name = name;
//年龄做检测
if (age < 0 || age > 200) {
throw MyOutOfRangeException(string("我自己的年龄越界异常"));
}
}
string m_Name;
int m_Age;
};
void test01() {
try {
Person p("张三", 300);
}
catch (MyOutOfRangeException & e) {
cout << e.what() << endl;
}
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
总结:
- 自己的异常类 需要继承于 exception
- 重写 父类的 虚析构和 what函数
- 内部维护以错误信息——字符串
- 构造时候传入错误信息字符串,what返回这个字符串
- string 转 char *的方法—— .c_str();
标准输入输出流
C++输入输出包含以下三个方面的内容:
- 对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称标准I/O。
- 以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件I/O。
- 对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称串I/O。
C++编译系统提供了用于输入输出的iostream类库。iostream这个单词是由3个部 分组成的,即i-o-stream,意为输入输出流。在iostream类库中包含许多用于输入输出的 类。常用的见表
ios是抽象基类,由它派生出istream类和ostream类,两个类名中第1个字母i和o分别代表输入(input)和输出(output)。 istream类支持输入操作,ostream类支持输出操作, iostream类支持输入输出操作。iostream类是从istream类和ostream类通过多重继承而派生的类。其继承层次见上图表示。
C++对文件的输入输出需要用ifstrcam和ofstream类,两个类名中第1个字母i和o分别代表输入和输出,第2个字母f代表文件 (file)。ifstream支持对文件的输入操作, ofstream支持对文件的输出操作。类ifstream继承了类istream,类ofstream继承了类ostream,类fstream继承了 类iostream。见图
I/O类库中还有其他一些类,但是对于一般用户来说,以上这些已能满足需要了。
与iostream类库有关的头文件
iostream类库中不同的类的声明被放在不同的头文件中,用户在自己的程序中用#include命令包含了有关的头文件就相当于在本程序中声明了所需 要用到的类。可以换 —种说法:头文件是程序与类库的接口,iostream类库的接口分别由不同的头文件来实现。常用的有
- iostream 包含了对输入输出流进行操作所需的基本信息。
- fstream 用于用户管理的文件的I/O操作。
- strstream 用于字符串流I/O。
- stdiostream 用于混合使用C和C + +的I/O机制时,例如想将C程序转变为C++程序。
- iomanip 在使用格式化I/O时应包含此头文件。
在iostream头文件中定义的流对象
在 iostream 头文件中定义的类有 ios,istream,ostream,iostream,istream 等。
在iostream头文件中不仅定义了有关的类,还定义了4种流对象,
对象 | 含义 | 对应设备 | 对应的类 | c语言中相应的标准文件 |
cin | 标准输入流 | 键盘 | istream_withassign | stdin |
cout | 标准输出流 | 屏幕 | ostream_withassign | stdout |
cerr | 标准错误流 | 屏幕 | ostream_withassign | stderr |
clog | 标准错误流 | 屏幕 | ostream_withassign | stderr |
在iostream头文件中定义以上4个流对象用以下的形式(以cout为例):
ostream cout ( stdout);
在定义cout为ostream流类对象时,把标准输出设备stdout作为参数,这样它就与标准输出设备(显示器)联系起来,如果有
cout <<3;
就会在显示器的屏幕上输出3。
在iostream头文件中重载运算符
“<<”和“>>”本来在C++中是被定义为左位移运算符和右位移运算符的,由于在iostream头文件中对它们进行了重载, 使它们能用作标准类型数据的输入和输出运算符。所以,在用它们的程序中必须用#include命令把iostream包含到程序中。
#include <iostream>
- >>a表示将数据放入a对象中。
- <<a表示将a对象中存储的数据拿出。
标准I/O流
标准I/O对象:cin,cout,cerr,clog
cout流对象
cout是console output的缩写,意为在控制台(终端显示器)的输出。强调几点。
1) cout不是C++预定义的关键字,它是ostream流类的对象,在iostream中定义。 顾名思义,流是流动的数据,cout流是流向显示器的数据。cout流中的数据是用流插入 运算符“<<”顺序加入的。如果有:
cout<<"I "<<"study C++ "<<"very hard. << “hello world !";
按顺序将字符串"I ", "study C++ ", "very hard."插人到cout流中,cout就将它们送到显示器,在显示器上输出字符串"I study C++ very hard."。cout流是容纳数据的载体,它并不是一个运算符。人们关心的是cout流中的内容,也就是向显示器输出什么。
2) 用“cout<<”输出基本类型的数据时,可以不必考虑数据是什么类型,系统会判断数据的类型,并根据其类型选择调用与之匹配的运算符重载函数。这个过程都是自动的,用户不必干预。如果在C语言中用prinf函数输出不同类型的数据,必须分别指定相应 的输出格式符,十分麻烦,而且容易出错。C++的I/O机制对用户来说,显然是方便而安全的。
- cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插人一个endl时,不论缓冲区是否已满,都立即输出流中所有数据,然后插入一个换行符, 并刷新流(清空缓冲区)。注意如果插人一个换行符”\n“(如cout<<a<<"\n"),则只输出和换行,而不刷新cout 流(但并不是所有编译系统都体现出这一区别)。
- 在iostream中只对"<<"和">>"运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。如果用户声明了新的类型,并希望用"<<"和">>"运算符对其进行输入输出,按照重运算符重载来做。
cerr流对象
cerr流对象是标准错误流,cerr流已被指定为与显示器关联。cerr的作用是向标准错误设备(standard error device)输出有关出错信息。cerr与标准输出流cout的作用和用法差不多。但有一点不同:cout流通常是传送到显示器输出,但也可以被重定向输出到磁盘文件,而cerr流中的信息只能在显示器输出。当调试程序时,往往不希望程序运行时的出错信息被送到其他文件,而要求在显示器上及时输出,这时 应该用cerr。cerr流中的信息是用户根据需要指定的。
clog流对象
clog流对象也是标准错误流,它是console log的缩写。它的作用和cerr相同,都是在终端显示器上显示出错信息。区别:cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。
标准输入流
标准输入流对象cin,重点掌握的函数
cin.get() //一次只能读取一个字符
cin.get(一个参数) //读一个字符
cin.get(两个参数) //可以读字符串
cin.getline()
cin.ignore()
cin.peek()
cin.putback()
cin.get(一个参数)的测试——输入as
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;
}
因为输入的有换行符,因此输出的如上图所示。
cin.get(两个参数) //可以读字符串——输入 hello world
cin.get(两个参数)读取字符串时,不会把换行符拿走,遗留在缓冲区中
void test02()
{
//cin.get(两个参数) //可以读字符串
char buf[1024];
cin.get(buf, 1024);
char c = cin.get();
if (c == '\n'){
cout << "换行还在缓冲区" << endl;
}else{
cout << "换行不在缓冲区" << endl;
}
cout << buf << endl;
}
cin.getline()测试
//cin.getline 把换行符读取,并且扔掉
void test03()
{
char buf[1024];
cin.getline(buf, 1024);
char c = cin.get();
if (c == '\n')
{
cout << "换行还在缓冲区" << endl;
}
else
{
cout << "换行不在缓冲区" << endl;
}
}
cin.ignore()测试
没有参数 代表忽略一个字符 ,带参数N,代表忽略N个字符
void test04()
{
cin.ignore(2); //没有参数 代表忽略一个字符 ,带参数N,代表忽略N个字符
char c = cin.get();
cout << "c = " << c << endl;
}
cin.peek() 测试
void test05()
{
//输入as 偷看一眼 a,然后再放回缓冲区 缓冲区中还是as
char c = cin.peek();
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];
cin.getline(buf, 1024);
cout << buf << endl;
}
总结:
- cin.get 缓冲区中读取一个字符
- cin.get(两个参数) 不读换行符
- cin.getline () 读取换行 并且扔掉
- cin.ignore 忽略 (N) N代表忽略字符数
- cin.peek 偷窥 偷看1个字符然后放回去
- cin.putback 放回 把字符放回缓冲区
输入流案例
判断用户输入的是数字还是字符串
//案例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];
cin >> buf;
cout << "您输入是字符串 :字符串为" << buf << endl;
}
}
让用户输入 1 到 10 的数字 ,如果输入有误 重新输入
缓冲区有一个标志位,如果你输入的类型与标志位不符,则缓冲区中就一直是那个类型不符的值。因此需要对缓冲区进行清空处理。
cin.fail() //看标志位 0正常 1不正常
cin.clear()//重置标志位
cin.syne() //清空缓冲区
完整测试案例
//案例2 让用户输入 1 到 10 的数字 ,如果输入有误 重新输入
void test08(){
int num;
cout << "请输入一个1到10的数字:" << endl;
while (true){
cin >> num;
if (num > 0 && num <= 10){
cout << "输入的数字为" << num << endl;
break;
}
cout << "对不起,请重新输入" << endl;
//重置标志位
cin.clear();
cin.sync(); //清空缓冲区
cout << "标志位: " << cin.fail() << endl; //标志位 0 正常的 1 不正常
}
}
如果上面的代码没有充值标志位和清空缓冲区操作,当你输入的不是int类型的值,而是一个字符a时, 则会程序如下的运行结果
标准输出流
cout.flush() //刷新缓冲区 Linux下有效
cout.put() //向缓冲区写字符
cout.write() //从buffer中写num个字节到当前输出流中。
//cout.flush 刷新缓冲区,linux下有效
void test01(){
cout << "hello world";
//刷新缓冲区
cout.flush();
}
//cout.put 输出一个字符
void test02(){
cout.put('a');
//链式编程
cout.put('h').put('e').put('l');
}
//cout.write 输出字符串 buf,输出多少个
void test03(){
//char* str = "hello world!";
//cout.write(str, strlen(str));
char* str = "*************";
for (int i = 1; i <= strlen(str); i ++){
cout.write(str, i);
cout << endl;
}
for (int i = strlen(str); i > 0; i --){
cout.write(str, i);
cout << endl;
}
}
格式化输出
在输出数据时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出,如要求以十六进制或八进制形式输出一个整数,对输出的小数只保留两位小数等。有两种方法可以达到此目的。
1)使用控制符的方法;
2)使用流对象的有关成员函数。
使用流对象的有关成员函数
通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数如下:
流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符“::”。格式标志见表13.5。
void test02()
{
//通过流成员函数
int number = 99;
cout.width(20);//输出18个空格,后面跟着99
cout.fill('*');//把空格换成*
cout.setf(ios::left); //设置输出格式状态,输入内容左对齐
cout.unsetf(ios::dec); //卸载十进制
cout.setf(ios::hex); //安装16进制
cout.setf(ios::showbase); // 强制输出整数基数 0 0x
cout.unsetf(ios::hex);
cout.setf(ios::oct);
cout << number << endl;
}
控制符格式化输出
如果想使用控制符,则需要引入头文件
#include <iomanip> //使用控制符的头文件
C++提供了在输入输出流中使用的控制符(有的书中称为操纵符)。
//控制符的方式显示
void test03() {
int number = 99;
cout << setw(20) //20个空格
<< setfill('~') //空格换成~
<< setiosflags(ios::showbase) //基数
<< setiosflags(ios::left) //左对齐
<< hex // 十六进制
<< number
<< endl;
}
文件读写
输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。
和文件有关系的输入输出类主要在fstream.h这个头文件中被定义,在这个头文件中主要被定义了三个类,由这三个类控制对文件的各种输入输出操作,他们分别是ifstream、ofstream、fstream,其中fstream类是由iostream类派生而来,他们之间的继承关系见下图所示:
由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在fstream头文件中是没有像cout那样预先定义的全局对象,所以我们必须自己定义一个该类的对象。ifstream类,它是从istream类派生的,用来支持从磁盘文件的输入。ofstream类,它是从ostream类派生的,用来支持向磁盘文件的输出。
fstream类,它是从iostream类派生的,用来支持对磁盘文件的输入输出。
打开文件的方式
//以输出的方式打开文件 如果想配合使用,要用 |
ofstream ofs("./test.txt", ios::out | ios::trunc);
另一种打开文件的方式
//后期指定打开方式
ofstream ofs;
ofs.open("./test.txt", ios::out | ios::trunc);
写文件的例子:
- ofstream ofs
- open 指定打开方式
- isopen 判断是否打开成功
- ofs << “数据” 写入数据
- ofs.close 关闭文件
#define _CRT_SECURE_NO_WARNINGS
#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 << "年龄:100" << endl;
ofs << "性别:男" << endl;
ofs.close();
}
读文件的例子
- ifstream ifs
- ios::in 指定打开方式
- isopen 判断是否打开成功
- 三种方式读取数据
//读文件
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;
}
ifs.close();
}
完整例子:
class Person{
public:
Person(char* name,int age){
strcpy(this->mName, name);
this->mAge = age;
}
public:
char mName[64];
int mAge;
};
int main(){
char* fileName = "person.txt";
//二进制模式读写文件
//创建文件对象输出流
ofstream osm(fileName, ios::out | ios::binary);
Person p1("John",33);
Person p2("Edward", 34);
//Person对象写入文件
osm.write((const char*)&p1,sizeof(Person));
osm.write((const char*)&p2, sizeof(Person));
//关闭文件输出流
osm.close();
//从文件中读取对象数组
ifstream ism(fileName, ios::in | ios::binary);
if (!ism){
cout << "打开失败!" << endl;
}
Person p3;
Person p4;
ism.read((char*)&p3, sizeof(Person));
ism.read((char*)&p4, sizeof(Person));
cout << "Name:" << p3.mName << " Age:" << p3.mAge << endl;
cout << "Age:" << p4.mName << " Age:" << p4.mAge << endl;
//关闭文件输入流
ism.close();
system("pause");
return EXIT_SUCCESS;
}