C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。
注意宽度输出和精度输出控制。C语言借助了相应的缓冲区来进行输入与输出。如下图所示:
这里的设备除了终端或者是控制台之外,还可能是磁盘文件,或者是网卡。
对输入输出缓冲区的理解:
1.可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏
蔽这部分的差异,可以很容易写出可移植的程序。
2.可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”。
printf/scanf
fprintf/fscanf
sprintf/sscanf
int main()
{int a=0;
printf("%d",a);
scanf("%d",&a);
printf("%d\n",a);
}
二、C++中的IO流
istream:流提取
ostream:流输出
因为提取和输出都要写一个类,非常麻烦,就写了一个特iostream,继承了这两个类的功能
(但是因为这里存在菱形继承,所以非常复杂)
int main()
{int i=0;
double j=2.2;
cout<
这是因为库底层都对其进行了函数的重载。
istream和ostream可以更好地支撑自定义类型对象的流插入和流提取。
自定义类型可以自己重载,控制流提取和流插入的方式。
(string,日期类都可以进行流插入和流提取的重载)
#includeint main()
{int year ,month ,day;
//输入多个值默认输入是用空格或者换行分隔开来的。
cin>>year>>month>>day;
scanf("%d%d%d",&year,&month,&day);
scanf("%d %d %d",&year,&month,&day);//不需要去加空格
//如果日期是20221128这样输入的
//我们可以用这种方式将我们的日期进行分隔
scanf("%4d%2d%2d",&year,&month,&day);
cout<>str;
year=stoi(str.substr(0,4));
month=stoi(str.substr(4,2));
day=stoi(str.substr(6,2));
cout<
多行测试用例如何写输入int main()
{int year ,month ,day;
string str;
//使用了operate bool
//怎么终止?ctrl+c直接kill -9终止进程。
//ctr+z给一个流结束的标志。
while(cin>>str)
{year=stoi(str.substr(0,4));
month=stoi(str.substr(4,2));
day=stoi(str.substr(6,2));
cout<
返回值是cin类型的对象。
一般是整型指针,比较运算符表达式道德结果可以做逻辑条件判断,0就是假,非0就是真,自定义类型一般是不能做while的条件类型判断的,这里存在隐式类型转换
想要让自定义类型能够进行转换,我们需要将其进行强制类型转换,但是一般用于类型转换的()被占用了,所以我们就用operator bool,将我们的类型转化成一个bool值,并将这个bool值进行返回。
这就类似于我们下面的将我们的自定义类型a赋值给内置类型int的过程。
class A
{public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{//内置类型转换成自定义类型
//隐式类型转换
A aa1=1;//用1构造A的临时对象,再拷贝构造aa1,优化后直接构造aa1
}
如何让我们的自定义类型转换成内置类型
class A
{public:
A(int a)
:_a(a)
{}
//重载运算符
operator int()
{return _a;
}
private:
int _a;
};
int main()
{//自定义类型转换成内置类型1
int i=aa1;
cout<
三、文件流
ifstream读取文件int main()
{ifstream ifs("/Users/Test.cpp");
//读取一个字符
char ch=ifs.get();
while(ifs)
{cout<
读取文件中不同类型的数据这是我们test.txt中的内容
可以更好地兼容自定义类型的读写
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()
{// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
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()
{ifstream ifs("/Users/test.txt");
//可以使用c语言的方式从文件中读取不同类型的数据
// fscanf("%d%s%f",)
//在c++中使用ifstream会方便一些
int i;
string s;
double d;
//对于自定义类型也是可以的
//当然,可以的前提是日期类对象重载了流提取
Date de;
//流提取
ifs>>i>>s>>d>>de;
cout<
二进制读写和文本读写二进制读写:就是文件在内存中是如何存储的,就怎样写到磁盘文件中
文本读写:序列化(json,xml),将对象数据按照某种格式进行转换,序列化成字符串,写入磁盘当中。读取回来的也是字符串,反序列化,再转换成对象数据。
二进制文件的写出二进制读写:
优点:快
缺点:写出去的内容看不见
(打开就大概率是乱码了,比方说存了一个100,但是二进制读写出来是100的补码)
文本读写:
优点:可以看见写出去的是什么
缺点:存在一个转换的过程,要慢一些
#include#includeusing namespace std;
struct ServerInfo
{char _address[32];
int _port;
};
struct ConfigureManager
{public:
ConfigureManager(const char* filename="./server.config")
:_filename(filename)
{}
void WriteBin (const ServerInfo& info)
{//默认的写都是覆盖的模式进行写入
//但是app就是追加写,也就是append
ofstream ofs(_filename,ios_base::out|ios_base::binary);
//需要传入一个char*的字符串,然后第二个参数是要读取的数据的大小
ofs.write((char*)&info,sizeof(info));
}
private:
string _filename;
};
int main()
{ServerInfo info={"127.0.0.1",888};
ConfigureManager cm;
cm.WriteBin(info);
return 0;
}
这里我们的文本是不支持直接查看二进制文件的,所以我们这里的除了数字,别的我们文本的编码都是不可见的。
#include#includeusing namespace std;
struct ServerInfo
{char _address[32];
int _port;
};
struct ConfigureManager
{public:
ConfigureManager(const char* filename="./server.config")
:_filename(filename)
{}
void ReadBin (const ServerInfo& info)
{//默认的写都是覆盖的模式进行写入
//但是app就是追加写,也就是append
ifstream ifs(_filename,ios_base::in|ios_base::binary);
//需要传入一个char*的字符串,然后第二个参数是要读取的数据的大小
ifs.read((char*)&info,sizeof(info));
}
private:
string _filename;
};
int main()
{//二进制的读取
ServerInfo rinfo;
ConfigureManager cm;
cm.ReadBin(rinfo);
cout<
二进制读写的坑我们将这里的char*的address改成string,并且将这里的address的地址改成我们博客的连接
#include#includeusing namespace std;
struct ServerInfo
{//char _address[32];
string _address;
int _port;
};
struct ConfigureManager
{public:
ConfigureManager(const char* filename="./server.config")
:_filename(filename)
{}
void WriteBin (const ServerInfo& info)
{//默认的写都是覆盖的模式进行写入
//但是app就是追加写,也就是append
ofstream ofs(_filename,ios_base::out|ios_base::binary);
//需要传入一个char*的字符串,然后第二个参数是要读取的数据的大小
ofs.write((char*)&info,sizeof(info));
}
void ReadBin (const ServerInfo& info)
{//默认的写都是覆盖的模式进行写入
//但是app就是追加写,也就是append
ifstream ifs(_filename,ios_base::in|ios_base::binary);
//需要传入一个char*的字符串,然后第二个参数是要读取的数据的大小
ifs.read((char*)&info,sizeof(info));
}
private:
string _filename;
};
int main()
{//二进制写出去
// ServerInfo info={"https://editor.csdn.net/md/?articleId=128086656",888};
// ConfigureManager cm;
// cm.WriteBin(info);
//二进制的读取
ServerInfo rinfo;
ConfigureManager cm;
cm.ReadBin(rinfo);
cout<
我们先将数据写出去,然后重新读取进来。
(如果我们的address比较短的话,是不会报下列的错误的,但是如果address比较长的话,就会报下面的错误)
在windows下的string,短的时候存的就·直接用共这里的_buff[16]将其进行存储,如果这里的字符串太长了,就用string中的_ptr指向这个string的地址。
_buff[16]
_ptr
_size
_capacity
_port
ServerInfo info={"https://editor.csdn.net/md/?articleId=128086656",888};
字符串是存在我们的堆上面的,我们将我们的字符串写出去,也就是调用write写出去的时候,调用的是这个字符串的地址。这里我们的是分两次运行的,也就是我们写二进制文件的时候,这个字符串的地址。当我们的读的进程结束之后,我们再运行读取的进程,我们将这个指针读取回来的时候,这个指针已经不是我们刚刚那个string的指针了!它已经是一个野指针了!!
文本的方式写和读取所以二进制读写的时候,一定要用数组,char[],不要使用string!!
#include#includeusing namespace std;
struct ServerInfo
{string _address;
int _port;
};
struct ConfigureManager
{public:
ConfigureManager(const char* filename="./server.config")
:_filename(filename)
{}
void WriteText(const ServerInfo& info)
{//这里我们将指定的文件进行写的操作
ofstream ofs(_filename, ios_base::out);
//我们需要传入一个写入的子串和这个字符串的大小
ofs.write(info._address.c_str(), info._address.size());
//然后我们可以用换行将我们的字符串和后面的内容进行分隔
//当然这里传入一个换行符的话,我们可以使用上面的write,或者是下面的这种简单形式的put
ofs.put('\n');
//将我们的_port,(原本是整型)转换成字符串的类型
const string str = to_string(info._port);
//然后将我们的_port的字符串输出,第一个人参数是字符串,第二个参数是字符串的大小
ofs.write(str.c_str(), str.size());
}
void ReadText(ServerInfo& info)
{//读取指定的文件
ifstream ifs(_filename, ios_base::in);
//创建一个字符串缓冲区
char buff[128];
//读取一行,放入我们上面刚刚创建的buffer当中
ifs.getline(buff, 128);
//然后将我们的address中的内容赋值成buff
info._address = buff;
//然后我们再读取进来一行数据
ifs.getline(buff, 128);
//然后将我们的_port,因为它原本是整型,我们需要将其重新转换成整型然后赋值给_port
info._port = stoi(buff);
}
private:
string _filename;
};
int main()
{//文本方式写出去
ServerInfo info={"127.0.0.1",888};
ConfigureManager cm;
cm.WriteText(info);
//文本方式读进来
ServerInfo rinfo;
// ConfigureManager cm;
cm.ReadText(rinfo);
cout<
可以成功将文本写出,并且正常读取
可以读取到我们的文本中的内容
文件流写和读(c++)但是这样的的方式非常麻烦,我们需要在写入的时候手动成员的每一个参数转换成字符串类型,再读取的时候,还要将读取到的字符串转换成对应的类型放入我们的成员中。
所以我们可以用C++给我们提供的文件流的读和写来帮助我们进行上述操作。
#include#includeusing namespace std;
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()
{// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
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;
}
struct ServerInfo
{string _address;
int _port;
Date date;
};
struct ConfigureManager
{public:
ConfigureManager(const char* filename="./server.config")
:_filename(filename)
{}
void WriteText(const ServerInfo& info){ofstream ofs(_filename,ios_base::out);
//这里我们流插入一个成员,我们最好加一个换行或者空格
//这样可以防止我们的流提取的时候,读取到的数据粘连在一起
ofs<ifstream ifs(_filename,ios_base::in|ios_base::binary);
ifs>>info._address>>info._port>>info.date;
}
private:
string _filename;
};
int main()
{//二进制写出去
// ServerInfo info={"127.0.0.1",888};
ServerInfo info={"127.0.0.1",888,{2022,11,30}};
ConfigureManager cm;
cm.WriteText(info);
//二进制的读取
ServerInfo rinfo;
// ConfigureManager cm;
cm.ReadText(rinfo);
cout<
文本方式读写的文件是可以直接进行查看的
四、istringstream 序列化和反序列化 ostringstream和istringstream然后我们也是可以正常读取到的
#includeusing namespace std;
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()
{// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
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;
}
struct ChatInfo
{string _name; // 名字
int _id; // id
Date _date; // 时间
string _msg; // 聊天信息
};
int main()
{//序列化
ChatInfo winfo={"催逝员",114514,{2022,11,30},"啊哈哈哈哈,鸡汤来喽,哈哈哈"};
//对应的是sprintf
ostringstream oss;
oss<>rInfo._name;
iss>>rInfo._id;
iss>>rInfo._date;
iss>>rInfo._msg;
cout<<"------------------------------------------"<
stringstream在库中stringstream继承了istringstream和ostringstream的功能,所以直接调用这个stringstream就可以了
我们可以将我们上面的代码修改如下
#includeusing namespace std;
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()
{// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
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;
}
struct ChatInfo
{string _name; // 名字
int _id; // id
Date _date; // 时间
string _msg; // 聊天信息
};
int main()
{//序列化
ChatInfo winfo={"催逝员",114514,{2022,11,30},"啊哈哈哈哈,鸡汤来喽,哈哈哈"};
//对应的是sprintf
stringstream oss;
oss<>rInfo._name;
iss>>rInfo._id;
iss>>rInfo._date;
iss>>rInfo._msg;
cout<<"------------------------------------------"<
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
售后响应及时
7×24小时客服热线数据备份
更安全、更高效、更稳定价格公道精准
项目经理精准报价不弄虚作假合作无风险
重合同讲信誉,无效全额退款