C++:类型转换和IO流

发布时间:2024年01月22日

本篇总结的是类型转换和IO流

C语言的类型转换

在C语言中就有类型转换,对于相近的类型之间可以相互转换,大体分成两种类型转换

  1. 隐式类型转换:编译器在编译阶段自动进行,不能转换会编译失败
  2. 显示类型转换:需要用户自己进行转换

这样做是有问题的,转换形式都基本上一致,对于转换没有一个合适的规则,于是C++就有了一套完整的体系

C++的强制类型转换

C++中有四种强制类型转换的方式,四种方式都有其固定的运用场景

static_cast

主要是用于静态转换,编译器的所有隐式类型转换都可以用,例如:

int main()
{
	double d1 = 1.1;
	int a1 = static_cast<int>(d1);
	cout << a1 << endl;
	return 0;
}

但是对于两个不相关的类型,是不可以进行转换的,例如从int*和int之间的转换就不可以用

reinterpret_cast

主要是用于将一种类型转换成另一种完全不同的类型,例如上述的转换

int main()
{
	double d1 = 1.1;
	int a1 = static_cast<int>(d1);
	cout << a1 << endl;

	int a2 = 10;
	int* p2 = &a2;
	int add = reinterpret_cast<int>(p2);
	cout << add << endl;
	return 0;
}

const_cast

主要是为了修改常变量,例如下面的场景

int main()
{
	const int a = 1;
	// a = 2; 会报错
	int* p = const_cast<int*>(&a);
	*p = 2;
	cout << *p << endl;
}

dynamic_cast

这个转换主要是用于将一个父类对象的指针或引用转换成一个子类对象的指针或者是引用,这当中存在两种类型

  1. 向上转型:子类转换成父类,这是安全的,可以直接转换,遵循赋值兼容规则
  2. 向下转换:父类转换成子类,如果直接转换是不安全的,因此可以使用dynamic_cast

dynamic_cast只能用来转换父类中含有虚函数的类
dynamic_cast转换失败,如果是引用会抛异常,如果是指针会返回空指针

注意

强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会

RTTI

全称是 run time type identitifiction翻译成中文叫做,运行时类型识别,简单来说就是在运行的过程中可以知道这个类型是什么

C++的类型识别有三种

  1. typeid运算符
  2. dynamic_cast运算符
  3. decltype

下面用例子来说明typeid运算符和decltype

int main()
{
	// typeid运算符
	int a = 10;
	cout << typeid(a).name() << endl;

	// decltype
	int* p = &a;
	// 识别为int* pb = nullptr;
	decltype(p) pb = nullptr;
	return 0;
}

关于dynamic_cast:

这是最常用的RTTI组件,它不能回答“指针指向的是哪类对象”这样的问题,但能够回答“是否可以安全地将对象的地址赋给特定类型的指针”这样的问题。

说白了,就是看看这个对象指针能不能转换为目标指针。通常,如果指向的对象(*pt)的类型为Type或者是从Type直接或简介派生而来的类型

流的概念

流的意思就是流动的意思,简单来说就是从某个地方流到另外一个地方,那么才C++中也是如此,如果信息是从计算机的外部设备流入到显示器的过程,那么这个过程就是叫做流

有序连续,具有方向性

为了实现这种流动,在C++的库中定义了I/O标准类库,这些每个类都能叫做流,用来完成一些功能

比如在进行文件读写的时候,数据所在的位置位于内存中,而文件所在的位置在硬盘上,那么现在要做的就是把数据从内存传递到文件中,那么是如何传递的?借助的是上层的应用程序,那么在上层写好的应用程序就可以借助各种方式读取内存,再用文件系统的接口把数据写到文件中

而在文件系统的底层,会调度硬件相关的内容,将软件和硬件联系在一起的内容就叫做驱动,驱动的本质就是软硬相结合,因此驱动就会通过软件层面调度一些内容,来控制硬件

C++中的IO流

在这里插入图片描述
上图展示的是一个完整的C++输入输出流库,其中iOS是基类,以它为起点派生出了庞大的类库

标准IO流

C++标准库提供了四个全局对象,cin,cout,cerr,clog,这四个对象用来进行的是标准输入输出错误日志,而所谓的输出流,简单来说就是把数据从内存流向显示器,这样的过程就叫做是输出流,后面的也基本相同

值得注意的是:

  1. cin是缓冲流,从键盘中输入的数据会被存在缓冲区中,当需要提取的时候会从缓冲区中往出拿取数据,如果一次输入的信息过多,就会留在缓冲区中,等到数据都被获取结束后才会被使用,这也就是为什么在使用的过程中如果输入了多个数据,在下次执行到cin的代码语句时会直接从缓冲区中获取信息,原因就在这里
  2. 输入的数据必须要和提取的数据类型相同,这个还是较为好理解的
  3. cin和cout可以直接作用于内置类型,这是因为在标准库中已经这些和内置类型全部重载过了
    在这里插入图片描述

在这里插入图片描述

多组数据的输入和输出问题

下面展示的是对于多组数据的输入和输出问题:

int main()
{
	int i = 0;
	while (cin >> i)
	{
		// ...
	}

	while (scanf("%d", &i) != EOF)
	{
		// ...
	}
	return 0;
}

上面的代码为什么可以做到对于多组的输入可以有效果呢?原因就在于在istream中存在一个逻辑条件判断值

istream& operator>> (int& val);
explicit operator bool() const;

表面上看,这里是cin >> i,而在底层中调用的是operator >>,返回值是istream对象,因此这里就可以看做是一个逻辑条件值,因为这个对象又会调用operator bool,如果调用的时候返回的是失败,那么就会返回false,导致最后就停止数据的多组输入了

C++中的文件IO流

C++根据文件的内容分成了二进制文件和文本文件,对于文件流操作的对象,大致有下面的步骤

  1. 定义一个文件流对象,表示的是流向
  2. 使用文件流对象的成员函数来打开文件,这样就相当于把文件流对象和磁盘之间建立起来了联系
  3. 对文件进行操作,这样就直接控制了文件的信息
  4. 关闭文件

下面来举个例子,具体来看上述过程

class Date
{
	friend ostream& operator << (ostream& out, const Date& d);
	friend istream& operator >> (istream& in, Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
istream& operator >> (istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
ostream& operator << (ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}

struct ServerInfo
{
	char _address[32];
	int _port;
	Date _date;
};

// 用文件的IO流对指定配置文件进行读写
class BinIO
{
public:
	BinIO(const char* filename)
		:_filename(filename)
	{}
	void Read(ServerInfo& info)
	{
		// 输入流与文件建立联系
		ifstream in(_filename, ios_base::in | ios_base::binary);
		// 从文件中读取信息到下面的变量中
		in.read((char*)&info, sizeof(info));
	}
	void Write(const ServerInfo& info)
	{
		// 输出流与文件建立联系
		ofstream out(_filename, ios_base::out | ios_base::binary);
		// 从变量中读取信息到文件中
		out.write((char*)&info, sizeof(info));
	}
private:
	string _filename;
};

// 用文件的IO流对指定配置文件进行读写
class TextIO
{
public:
	TextIO(const char* filename)
		:_filename(filename)
	{}
	void Read(ServerInfo& info)
	{
		// 输入流与文件建立联系
		ifstream in(_filename);
		// 从文件中读取信息到下面的变量中
		in >> info._address >> info._port >> info._date;
	}
	void Write(const ServerInfo& info)
	{
		// 输出流与文件建立联系
		ofstream out(_filename);
		// 从变量中读取信息到文件中
		out << info._address << " " << info._port << " " << info._date;
	}
private:
	string _filename;
};

int main()
{
	ServerInfo info = { "192.0.0.1", 80, { 2022, 4, 10 } };
	BinIO bio("test.bin");
	bio.Write(info);
	ServerInfo res;
	bio.Read(res);
	cout << res._address << endl;
	cout << res._port << endl;
	cout << res._date << endl;
	return 0;
}

stringstream

给定这样的场景,如果想要把一个整型变量的数据转换成字符串格式,该如何去做?

C语言中有sprintf函数,在C++中提供了一种新的方式,具体样例如下:

在这里插入图片描述
但是这样的写法需要提前设定数组的大小,在面临一些其他情况的时候可能会导致异常出现,因此有下面的写法可以避开这样的问题:

在这里插入图片描述
可以将上述的过程理解为,把n这个变量值放到字符串流当中,再输入给s

int main()
{
	stringstream s;
	s << "这是第一串内容" << " ";
	s << "这是第二串内容" << " ";
	cout << s.str() << endl;
	string str;

	// 下面的过程有点像从缓冲区中拿内容
	s >> str;
	cout << str << endl;
	s >> str;
	cout << str << endl;
	return 0;
}

序列化和反序列化结构数据

用下面一个例子来简单说明这个场景

class Date
{
	friend ostream& operator << (ostream& out, const Date& d);
	friend istream& operator >> (istream& in, Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
istream& operator >> (istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
ostream& operator << (ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day;
	return out;
}

struct ChatInfo
{
	string _name;
	int _id;
	Date _date;
	string _msg;
};

int main()
{
	// 结构信息序列化为字符串
	ChatInfo winfo = { "张三", 135246, { 2022, 4, 10 }, "晚上一起看电影吧" };
	ostringstream oss;
	oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg;
	string str = oss.str();
	cout << str << endl << endl;

	// 经过一些传递,传递到了当前位置,现在要对这个字符串流进行解析
	ChatInfo rInfo;
	istringstream iss(str);
	// 直接从这个字符串流中进行信息的读取和解析
	iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;
	cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") ";
	cout << rInfo._date << endl;
	cout << rInfo._name << ":>" << rInfo._msg << endl;
	return 0;
}
文章来源:https://blog.csdn.net/qq_73899585/article/details/135676898
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。