C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()
scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中
printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)
注意宽度输出和精度输出控制
C语言借助了相应的缓冲区来进行输入与输出,如下图所示:
1.可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序
2.可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这部分,就可以定义“行”的概念,然后解析缓冲区的内容,返回一个“行”
使用scanf和printf可能导致错误:
如果真的要使用scanf就在使用前,先开好足够的空间 s._name.resize(100)
建议:
1.C++尽量取用cin和cout
2.如果要精准输出某个值的前几位,或者以什么方式打印,用cout和cin不方便的时候,再去用scanf和printf
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其
单位 可以是bit,byte,packet
)的抽象描述
C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程 这种输入输出的过程被形象的比喻为“流”
特性:有序连续、具有方向性
为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能
Student s;
cin>> s._name>>s._age;//数据从设备(终端)流向对象中(内存)
cout<<"名字":<<s._name<<"年龄"<<s._age<<endl;//数据对象(内存)流向设备(终端)
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类
cin 和 cout都是全局对象
C++标准库提供了4个全局流对象cin、cout、cerr、clog,
使用cout进行标准输出,即数据从内存流向控制台(显示器)
使用cin进行标准输入即数据通过键盘输入到程序中
同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同
注意:在使用时候必须要包含文件并引入std标准命名空间
//使用cin和cout需要包含头文件iostream 以及引入命名空间std
#include
using namespace std;
int main()
{
int a = 0;
cin >> a;
cout << a;
}
//当然也可以在使用的时候指定cout和cin所属的命名空间
int main()
{
int a = 0;
std::cin >> a;
std::cout << a;
}
//如果一次输入过多,则多余的数据会留在缓冲区以供后序提取
int main()
{
int a = 0;
int b = 0;
cin >> a;//输入:20 30
cout << a << endl;//20
cin >> b;//直接从缓冲区提取,不需要我们输入
cout << b << endl;//30
}
输入的数据类型必须与要提取的数据类型一致,否则出错
空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入
int main()
{
string s;
getline(cin,s);//获取字符串以换行符\n结束
cout<<s<<endl;
}
cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了:
所以内置类型我们可以直接使用cout<< 和cin >>进行输入和输出
int main()
{
int i = 0;
double d = 3.14;
cout << i;//cout.operator<<(i)
cout << d;//cout.operator<<(d)
return 0;
}
例如:日期类
class Date
{
public:
//声明为友元函数
friend istream& operator>>(istream& in, Date& d);
friend ostream& operator<<(ostream& out, Date& d);
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, Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
int main()
{
Date d1;
cin >> d1;//2022 1 1
cout << d1;//2022/1/1
return 0;
}
问:scanf/printf 和cin/cout的优势
scanf/printf 的缺点是只支持内置类型. cin/cout真正优势是,自定义类型重载了operator>> 和operator<<之后,自定义类型对象也可也直接输出
6.在线OJ中的输入和输出:
C语言的方式: 按ctrl+c终止程序
while(scanf("%d",&a) != EOF) //EOF->-1
{}
//或者写成:
while(~scanf("%d",&a)) //~(-1) ->0
{}
C++的方式 按ctrl+z + enter结束程序
// 单个元素循环输入
while(cin>>a)
{}
// 多个元素循环输入
while(cin>>a>>b>>c)
{}
// 整行接收
while(cin>>str) //operator>>(cin,str)
//istream& operator>>(istream& is,string& str) 返回流is对象
//流对象is调用operator bool函数,该函数没有返回值
{}
小例子:
class A
{
public:
operator bool()
{
return _a != 0;
}
int _a;
};
int main()
{
A a;
while (a)//相当于a.operator bool()
{
cin >> a._a;
}
return 0;
}
此时也支持连续输入, 当输入0时停止, 因为operator bool的条件时 _a != 0
struct ServerInfo
{
char _ip[20];//保存ip地址
int _port;//端口号
};
//打开文件/关闭文件:fopen fclose
//文本读写:fprintf fscanf 二进制读写:fwrite fread
以二进制形式写:
fwrite函数原型:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
//ptr:指向写入的内容 size:每一个写入的内容是多少字节 count:写入多少个 stream:写到哪个流中
//以二进制形式写入
void TestC_Write_Bin()
{
ServerInfo info = { "127.1.1.1",10 };
FILE* fout = fopen("test.bin", "wb");
//检查文件是否打开失败
//assert(fout);
if (NULL == fout)
{
perror("fopen");
exit(-1);
}
//写文件
fwrite(&info, sizeof(info), 1, fout);
fclose(fout);
}
以二进制形式读
fread函数原型:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//ptr:从文件中读取放到ptr中 size:每一个读取的内容是多少字节 count:读取多少个 stream:从哪个流中读取
//以二进制形式读取
void TestC_Read_Bin()
{
FILE* fin = fopen("test.bin", "rb");
//检查文件是否打开失败
//assert(fin);
if (NULL == fin)
{
perror("fopen");
exit(-1);
}
//读文件
ServerInfo info;
fread(&info, sizeof(info), 1, fin);
fclose(fin);
printf("%s:%d\n", info._ip, info._port);
}
测试:
int main()
{
TestC_Write_Bin();
TestC_Read_Bin(); //输出:127.1.1.1:10
return 0;
}
以文本形式写入
fprintf形式:
int fprintf ( FILE * stream, const char * format, ... );
写入的时候注意事项:要用空格/换行符区分每一个项
//以文本形式写
void TestC_Write_Text()
{
FILE* fout = fopen("test.txt", "w");
//检查文件是否打开失败
//assert(fout);
if (NULL == fout)
{
perror("fopen");
exit(-1);
}
ServerInfo info = { "127.1.1.1",10 };
fprintf(fout, "%s %d", info._ip, info._port);//写入的时候,必须要有区分(空格/换行符)
//fprintf(fout, "%s\n%d", info._ip, info._port);
fclose(fout);
}
以文本形式读取:
fscanf形式:
int fscanf ( FILE * stream, const char * format, ... );
//以文本形式读
void TestC_Read_Text()
{
FILE* fin = fopen("test.txt", "r");
//检查文件是否打开失败
//assert(fin);
if (NULL == fin)
{
perror("fopen");
exit(-1);
}
ServerInfo info;
fscanf(fin,"%s%d", info._ip, &info._port);//读取,注意:_ip是数组名,所以不用加&,但是_port是变量,要加&
fclose(fin);
printf("%s:%d\n", info._ip, info._port);
}
测试:
int main()
{
TestC_Write_Text();
TestC_Read_Text();//127.1.1.1:10
return 0;
}
C++根据文件内容的数据格式分为二进制文件和文本文件,采用文件流对象操作文件的一般步骤:
流对象(类) | 作用: |
---|---|
ofstream | 只写 |
ifstream | 只读 |
fstream | 读+写 |
打开方式 | 功能 |
---|---|
in | 以读的方式打开文件 |
out | 以写的方式打开文件 |
binary | 以二进制方式对文件进行操作 |
ate | 输出位置从文件的末尾开始 |
app | 以追加的方式对文件进行写入 |
trunc | 先将文件内容清空再打开文件 |
3.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写
成员函数 | 功能 |
---|---|
put | 写入一个字符到文件中 |
write | 写入一段字符到文件 |
get | 从文件中读取字符 |
read | 从文件中读取多个字符 |
tellg | 获取当前字符在文件中的位置 |
seekg | 设置对文件进行操作的位置 |
>>运算符重载 | 将数据能像“流”一样的形式进行输入 |
<<运算符重载 | 将数据能像“流”一样的形式进行输出 |
4.关闭文件
class ConfigManager
{
public:
ConfigManager(const char* filename)
:_filename(filename)
{}
private:
string _filename;
};
下面的函数都可以写为类的成员函数
使用或的方式进行多种打开
例如:
以二进制形式写入数据模式为:ios_base::out | ios_base::binary
以二进制形式读取数据模式为:ios_base::in | ios_base::binary
以二进制的形式对文件进行写入:
void WriteBin(ServerInfo& info)
{
ofstream ofs(_filename.c_str(), ios_base::out | ios_base::binary);//构造流对象
ofs.write((const char*)&info, sizeof(ServerInfo));//把info的内容写入到ofs流对象
}
以二进制的形式对文件进行读取:
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename.c_str(), ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(ServerInfo));//将内容读取到info
cout << info._ip << " " << info._port << endl;//验证是否读取了
}
测试 :
int main()
{
ConfigManager con("hello");
ServerInfo info = { "1.1.1",10 };
con.WriteBin(info);
con.ReadBin(info);//1.1.1 10
return 0;
}
不需要检查文件是否打开成功,因为如果失败了会抛异常
可以使用>> 和<< 对文件进行操作
以文本的形式对文件进行写入:
void WriteTest()
{
ofstream ofs("test.txt");//定义文件流对象,打开文件
ofs << "helloMango\n";//将字符串(流)到文件中
ofs.close();
}
以文本的形式对文件进行读取:
void ReadTest()
{
ifstream ifs("test.txt");//定义文件流对象,打开文件
string tmp;
ifs >> tmp;//把文件流得到数据读取到tmp,以空格/换行作为结束
ifs.close();
cout << tmp << endl;
}
在C语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
int a = 10;
char arr[10];
itoa(a,arr,10);//将整形a的转化为10进制字符数字存储在arr数组中
2.使用sprintf()函数
int a = 10;
char arr[10];
sprintf(arr,%d,a);//将整形a的转化为十进制基数格式存储在arr数组中
但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃
在C++中
可以使用stringstream类对象来避开此问题, 在程序中如果想要使用stringstream,必须要包含头文件sstream
在该头文件下,有标准库三个类:
类 | 作用 |
---|---|
istringstream | 流的输入操作 |
ostringstream | 流的输出操作 |
stringstream | 输入操作+输出操作 |
stringstream主要可以用来: (可以进行输入操作也可以进行输出操作)
#include
int main()
{
int a = 12345678;
string sa;
// 将一个整形变量转化为字符串,存储到string类对象中
stringstream s;
s << a;//将int类型的a变量的值放入输入流s中 (输入操作)
s >> sa;//从流s中提取字符串赋给sa (输出操作)
cout << sa<<endl;//输出sa的值:12345678
// s.clear():注意多次转换时,必须使用clear将上次转换状态清空掉
// stringstreams在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit
// 因此下一次转换是必须调用clear()将状态重置为goodbit才可以转换.但是clear()不会将stringstreams底层字符串清空掉
// s.str(""):将stringstream底层管理string对象设置成"",否则多次转换时,会将结果全部累积在底层string对象中
s.str("");//清空s,
s.clear(); // 重置状态.不重置会转化失败
//进行下一次转化
double d = 12.34;
s << d;
s >> sa;
string sValue;
sValue = s.str(); // str()方法:返回stringsteam中管理的string类型
cout << sValue << endl;//12.34
return 0;
}
int main()
{
stringstream sstream;
sstream << "first" << " " << "string,"; // 将多个字符串放入 sstream 中
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;//输出sstream流对象的内容:strResult is: first string, second string
// 清空sstream流对象的内容
sstream.str("");
s.clear()//将上次转换状态清空
sstream << "third string";
cout << "strResult is: " << sstream.str() << endl;//strResult is: third string
return 0;
}
注意: