[C++] IO流,文件输入输出,string流,类型转换运算符

类型转换运算符(循环输入原理)

循环输入

有些oj题会要求我们实现多组输入,也就是像下面这样要在外面套一层循环。

循环输入时,在vs编译器下输入 ctrl + z 然后回车结束输入。

while (cin >> a)
{

}

while (cin >> a >> b >> c)
{

}

while (cin >> str)
{

}

在我们输入 ctrl + z 之后循环是怎么停止的呢?

我们知道,string 类 重载了 >> 运算符:

istream& operator>> (istream& is, string& str);

实际上,当它返回 istream& 类型后会发生隐式类型转换,将 istream 转换为 bool 类型。这种将类类型转换为内置类型的操作可以通过定义类型转换运算符来实现。

**类型转换运算符(conversion operator)**是类的一种特殊成员函数,它负责将一个类类型转换成其他类型。类型转换函数的一般形式如下所示:

operator type() const;

其中 type 是任意可以作为函数返回类型的类型(除了 void 之外)

类型转换运算符既没有显式的返回类型,也没有形参,而且必须定义成类的成员函数,类型转换运算符通常不应该改变待转换对象的内容,所以一般要用 const 修饰


例子:

给日期类定义类型转换运算符,如果是2022年的对象就转换成false,否则转换成true

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)
	{}
	operator bool() const
	{
		// 如果是2022年就返回false,否则返回true
		if (_year == 2022)
			return false;
		else
			return true;
	}
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;
}

int main()
{
	Date d(2022, 10, 12);
	if (d) cout << "不是2022";
	else cout << "是2022";
	return 0;
}
// 结果:是2022

Date 也可以转换为 int,怎么转换可以自己设计:

operator int()
{
	return _year + _month + _day;
}

Date d(2022, 10, 12);
int sum = d;
cout << sum;
//结果:2044

所以 istream 类内部一定是实现了 operator bool,能够保证在接收到 ctrl+z 时返回 falseios::operator bool - C++ Reference (cplusplus.com)

文件输入输出

简介

头文件 fstream 定义了三个类型来支持文件IO:

  • ifstream 从一个给定文件读取数据(只读)
  • ofstream 向一个给定文件写入数据(只写)
  • fstream 可以读写给定文件(读写)

这些类型提供的操作和我们之前已经使用过的对象 cincout操作一样。比如,我们可以用IO运算符(<<>>)来读写文件,可以用 getline 从一个 ifstream 读取数据。

当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来,每个文件流类都定义了一个名为 open 的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。

  • 创建文件流对象时,如果提供了文件名,则 open 会被自动调用。
  • 当一个文件流对象被销毁时,会自动调用 close 关闭文件。
ifstream in(ifile); // 构造一个 ifstream 并打开给定文件
ofstream out;		// 输出文件流为关联到任何文件

文件名 ifile 既可以是 string 对象,也可以是 C 风格字符数组。

例子

读取 test.txt 文件内容并打印

int main()
{
	ifstream ifs("test.txt");
	while (ifs)
	{
		char ch = ifs.get();
		cout << ch;
	}
	return 0;
}

文件模式

fstream fstrm(s, mode);

创建一个文件流对象 fstrm,其中s是文件名,mode为文件模式。

每个流都有一个关联的文件模式,用来指出如何使用文件,如果要指定多个文件模式,就将不同的代表文件模式的变量按位或,然后再传入。

文件模式及含义:

  • in 以读方式打开
  • out 以写方式打开
  • app 每次写操作前均定位到文件末尾
  • ate 打开文件后立即定位到文件末尾
  • trunc 截断文件
  • binary 以二进制方式进行IO

这些变量可以在对应的文件流类型中找到。

注意

  • ifstream 会隐式指定 in 模式,我们不能给它设定 out 模式,ofstream 会隐式指定 out 模式,不能给它设定 in 模式
  • out 模式打开文件会丢弃已有数据,这一点和C语言指定 "w" 是一样的,如果要保留已有数据,那么可以显式指定 appin 模式。

具体用例参考下方的二进制读写

二进制读写与文本文件读写

我们知道,函数形参如果是基类,那么调用函数可以传入其对应的派生类对象。

由于 fstreamiostream 的派生类,所以在任何接受 iostream 类型引用(或指针)参数的函数,可以用一个对应的 fstream (或 sstream)类型来调用,如果有一个函数接受 ostream& 参数,可以传递 ofstream 对象,对 istream&ifstream 也类似。

<<>> 运算符重载就是这样的函数,所以文件流对象可以像 cincout 一样使用

二进制读写,与文本文件读写:

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

struct ConfigManager
{
public:
	ConfigManager(const char* filename)
		:_filename(filename)
	{}
	void WriteBin(const ServerInfo& info)
	{
		ofstream ofs(_filename, ios_base::out | ios_base::binary);
		ofs.write((const char*)&info, sizeof(info));
	}
	void ReadBin(ServerInfo& info)
	{
		ifstream ifs(_filename, ios_base::in | ios_base::binary);
		ifs.read((char*)&info, sizeof(info));
	}
	void WriteText(const ServerInfo& info)
	{
		ofstream ofs(_filename);
		ofs << info._address << " " << info._port << " " << info._date;
	}
	void ReadText(ServerInfo& info)
	{
		ifstream ifs(_filename);
		ifs >> info._address >> info._port >> info._date;
	}
private:
	string _filename; // 配置文件
};

string 流

sstream 头文件定义了三个类型来支持内存 IO,这些类型可以向 string 写入数据,读取数据,就像 string 是一个 IO 流一样。

  • istringstream 从 string 读取数据
  • ostringstream 向 string 写入数据
  • stringstream 既可以从 string 读取数据,也可以向 string 写入数据

与 fstream 类似,它们也继承自 iostream 头文件中定义的类型。

除了继承来的操作,stringstream 还有一些特有的操作:

sstream strm; // 创建一个未绑定的string流对象,sstream是上述三个类型之一
sstream strm(s); // s是string对象,strm保存s的一个拷贝
strm.str() // 返回strm所保存的string的拷贝
strm.str(s) // 将string s拷贝到strm中,返回void

istringstream 使用

使用 istringstream 可以方便地处理整行文本内的单个单词。

假设我们有这样一段信息:

列出一些人的名字和他们的电话号码,一个人可能有多个电话号码——家庭电话,移动电话等

morgan 2015552368 8625550123
drew 9735550130
lee 6095550132 2015550175 8005550000

对这些信息进行处理并保存:

// 一个人的信息包括名字和电话
struct PersonInfo
{
	string name;
	vector<string> phones;
};

int main()
{
	string line, word;				// 分别保存输入的一行和单词
	vector<PersonInfo> people;		// 保存所有信息
	while (getline(cin, line))		// 从标准输入流中按行读取
	{
		PersonInfo info;
		istringstream record(line);	// 将string流record绑定到一行的信息
		record >> info.name;		// 像cin一样使用
		while (record >> word)
		{
			info.phones.push_back(word);
		}
		people.push_back(info);
	}
	return 0;
}

ostringstream 使用

对上一个例子,我们想逐个验证电话号码,并改变格式。如果所有号码都是有效的,则将它们改变格式后的信息输出到一个新的文件。如果出现无效号码,则打印一条包含人名和无效号码为的错误信息。

假定我们已经实现验证号码valid和改变格式format的功能

for (const auto& entry : people)	// 对每个人
{
	ostringstream formatted, badNums;		// 分别存放有效号码和无效号码
	for (const auto& nums : entry.phones)	// 对每个号码
	{
		if (!valid(nums))					// 如果是无效号码
		{
			badNums << " " << nums;			// 将号码存入badNums
		}
		else								// 如果是有效号码
		{
			formatted << " " << format(nums);// 格式化后存入formatted
		}
	}
	if (badNums.str().empty())	// 如果没有错误的号码
		os << entry.name << " " << formatted.str() << endl; // 打印名字和格式化的号码
	else
	{
		cerr << "input error: " << entry.name << " invalid number(s) " << badNums.str() << endl; // 打印错误信息
	}
}

总结:文件流对象和string流对象的使用和cin、cout的使用是差不多的,它们最大的区别就是去向不同,cin、cout是从键盘读,输出到屏幕,文件流对象是从文件读写,string流对象是从string读写。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 对于您的问题,我可以回答。将C结构体转换为二进制,您可以使用以下方法: 1.使用memcpy()函数将结构体变量的内容复制到一个字符数组中。然后使用fwrite()函数将该字符数组写入文件或套接字。 例如: ```c struct MyStruct { int a; float b; char c; }; //将结构体变量转换为二进制 struct MyStruct s; char buffer[sizeof(struct MyStruct)]; memcpy(buffer, &s, sizeof(struct MyStruct)); //将二进制写入文件 FILE *fp; fp = fopen("output.bin", "wb"); fwrite(buffer, sizeof(struct MyStruct), 1, fp); fclose(fp); ``` 2.使用一个打包库,如Google的protobuf或Apache Thrift,可以将结构体序列化为二进制,并且可以跨语言进行交互。 例如,使用protobuf库: ```c //定义protobuf消息 message MyMessage { int32 a = 1; float b = 2; string c = 3; } //将结构体转换为protobuf消息 struct MyStruct s; MyMessage msg; msg.set_a(s.a); msg.set_b(s.b); msg.set_c(string(1, s.c)); //将protobuf消息序列化为二进制 string output; msg.SerializeToString(&output); ``` 希望这些信息可以帮助到您。 ### 回答2: 在C语言中,通过使用struct结构体可以定义一种数据结构,该结构体可以包含不同类型的成员变量。当需要将这个结构体转换成二进制时,可以使用以下步骤: 1. 创建一个struct结构体对象,并对其成员变量进行赋值。 2. 创建一个指向该结构体对象的指针。 3. 使用sizeof运算符来获取结构体对象的大小,这将用于后续的二进制缓冲区的分配。 4. 分配一个大小为结构体大小的二进制缓冲区,可以使用malloc函数动态分配内存。 5. 将struct结构体指针转换为一个指向无类型(void)的指针。 6. 使用memcpy函数将struct结构体指针所指的内存块中的数据拷贝到二进制缓冲区中。 7. 现在,二进制已经存储在缓冲区中,可以对其进行读写或者进行网络传输等操作。 8. 在结束使用后,记得使用free函数释放之前动态分配的内存空间,以避免内存泄露。 总结:通过以上步骤,我们可以将struct结构体转换成二进制。建立结构体对象,指向它的指针,用sizeof运算符获取大小,动态分配缓冲区,使用memcpy函数拷贝数据,把结构体转换为二进制。最后进行相应的操作后,使用free函数释放内存。 ### 回答3: 在C语言中,可以使用`struct`结构体来定义一组相关的变量,并将它们作为一个整体进行处理。而将结构体转换为二进制,在网络编程中是非常常见的操作。 要将`struct`结构体转换为二进制,可以使用`memcpy`函数来实现。首先,我们可以定义一个结构体类型,例如: ```c typedef struct { int id; char name[20]; float score; } Student; ``` 接下来,我们可以创建一个`Student`类型的结构体变量,并给其成员赋值。然后,可以通过`memcpy`函数将结构体变量的数据拷贝到一个字节数组中,即转换为二进制: ```c Student student; student.id = 1; strcpy(student.name, "Tom"); student.score = 90.5; char buffer[sizeof(Student)]; memcpy(buffer, &student, sizeof(Student)); ``` 上述代码中,`buffer`是一个与结构体大小相等的字节数组。`memcpy`函数将`student`变量的数据拷贝到`buffer`数组中。 如果需要将二进制转换回`struct`结构体,可以使用相反的步骤。先创建一个目标结构体类型的变量,再通过`memcpy`函数将二进制的数据拷贝到该变量中: ```c Student student2; memcpy(&student2, buffer, sizeof(Student)); printf("Student ID: %d\n", student2.id); printf("Student Name: %s\n", student2.name); printf("Student Score: %.1f\n", student2.score); ``` 通过上述代码,我们可以将二进制再转换回原来的结构体变量并打印出来。 总之,通过使用`memcpy`函数,我们可以在C语言中方便地将结构体转换为二进制,并在需要时将其转换回来。这在网络传输、文件IO等场景中都非常有用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值