技术文章翻译(八) -- 理解C++文件处理

本人声明

1.本栏仅为归档自己看到的优秀文章;
2.文章版权归原作者所有;
3.因为个人水平有限,翻译难免有错误,请多多包涵。

原文地址

https://www.codeguru.com/cpp/cpp/algorithms/understanding-file-processing-in-c.html

文章正文

理解C++文件处理

作者: Manoj Debnath 发表于:2017.03.29
文件主要用于在外部存储器(例如硬盘、光盘、磁带等)中存储大量的数据。我们也可以将数据存储在变量和数组中,但它们都是临时的或非持久的。为了存储持久数据,我们通常使用两种类型的文件结构:平面文件(无序)或数据库(有序)。
由于在数据库的存储和检索过程使用了复杂的逻辑,所以存储在数据库的数据本身就是有序的。在大多数情况下,数据库管理员会密切关注这些数据。就其复杂性而言,这就使得数据库变得昂贵。从另一方面来说,平面文件是简单而便宜的。与数据库不同,除非程序的逻辑确实需要应用到文件上,否则不会将有管理员将相关的规则应用于文件的存储和检索过程中。在本文中,我们将说明文件处理过程以及我们如何通过C ++程序逻辑有效地使用普通文件。

数据项概述

从根本上来说,数据项被简化为由零和1表示的位组合。最小的数据单元是由数据类型char指定的,每个char占用一个字节(1字节= 8位,1位可以表示0或1)的存储器。 C++还支持一种名为wchar_t的数据类型,它不止一个字节,可以支持更宽泛的字符集,例如Unicode字符集。因此,当我们存储’A’时,我们实际上存储的是65(二进制形式为01000001,值是根据ASCII字符集得到的)。

#include <iostream>
#include <iomanip>
#include <bitset>
using namespace std;

int main() {
   int c=65;
   cout<<"A = "<<static_cast<char>(c)<<" cast to char"<<endl;
   cout<<"A = "<<bitset<8>(c)<<" in 8 bit binary"<<endl;
   cout<<"A = "<<bitset<16>(c)<<" in 16 bit binary"<<endl;
   cout<<"A = "<<oct<<c<<" in octal"<<endl;
   cout<<"A = "<<hex<<c<<" in hexadecimal"<<endl;
   cout<<"A = "<<dec<<c<<" in decimal"<<endl;
   // Or simply, cout<<"A = "<<c<<" in decimal"<<endl;
   return 0;
}
  • 输出结果
A = A cast to char
A = 01000001 in 8 bit binary
A = 0000000001000001 in 16 bit binary
A = 101 in octal
A = 41 in hexadecimal
A = 65 in decimal
C++文件处理

在C ++文件处理过程中,文件只是一个没有任何结构信息的字节序列。文件要么以底层平台管理数据结构所维护的特定字节数结束,要么使用名为EOF(文件结尾)的标记结束。

当我们打开文件时,会创建一个对象。该对象与流相关联。该流提供用于我们程序和文件之间的通信通道。在C++中,我们常用的cin(标准输入)和cout(标准输出)对象只不过是打开通道的对象,用于分别从键盘输入流和输出流到屏幕。

从这方面来说,值得注意的是,计算中的所有设备也被视为文件(设备文件)。因此,从数据项流入流出方式来说,流入流出到平面文件或设备文件(如打印机,屏幕或键盘)并没有太大差别。但是,它们的访问模式存在限制,例如只读,只写或读,写。

类似地,有cerr和clog这样的标准错误对象,用来打印程序的错误消息。头文件包括这些标准I / O对象。头文件用于文件处理。该头文件中定义了模板流类,例如用于文件输入的basic_ifstream,用于文件输出的basic_ofstream和用于文件输入和输出的basic_fstream。除此之外,还有几个创建了别名的定义,例如basic_ifstream可以从文件中输入char。同时,basic_ofstream用于将char输出到文件。

因此,对于文件IO操作,头文件提供了三种类型的支持:
ifstream从给定文件中读取
ofstream写入指定文件
fstream同时支持读写操作

下来让我们尝试用一个例子,来说明如何用这些对象来进行文件的写入和读取操作。

  • 使用fstream
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main(void){
   fstream file;
   // Opening file for writing
   file.open ("testfile.dat", ios::out);
   if(file.is_open()){
      file << "this line will be written
         into testfile.dat."<<endl;
      file << "this line also will be written
         into testfile.dat."<<endl;
      file.close();
   }else{
      cerr<<"Error opening file!!"<<endl;
   }
   string buf;
   // Opening file for reading
   file.open("testfile.dat",ios::in);
   if(file.is_open()){
      while(getline(file,buf))
      cout<<buf;
      file.close();
   }else{
      cerr<<"Error opening file!!"<<endl;
   }
   return 0;
}
  • 使用ifstream和ofstream
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main(void){
   ofstream ofile;
   // Opening file for writing
   ofile.open ("testfile.dat");
   if(ofile.is_open()){
      ofile << "this line will be written
         into testfile.dat."<<endl;
      ofile << "this line also will be written
         into testfile.dat."<<endl;
      ofile.close();
   }else{
      cerr<<"Error opening file!!"<<endl;
   }
   string buf;
   // Opening file for reading
   ifstream ifile;
   ifile.open("testfile.dat");
   if(ifile.is_open()){
      while(getline(ifile,buf))
      cout<<buf;
      ifile.close();
   }else{
      cerr<<"Error opening file!!"<<endl;
   }
   return 0;
}

注意:模式参数ios :: in对指定的文件进行读取操作,它要求在读取之前必须存在该文件;另外,ios :: out会在未找到文件的情况下创建文件。使用fstream对象时,可以在同时进行读写模式下打开文件。

fstream file;
file.open ("testfile.dat", ios::in |
   ios::out | ios::binary);

还有其他一些模式参数,例如,用于以附加模式打开文件的ios :: app,ios :: ate等。更多详细信息,请参阅C ++标准API文档。

参数ios :: binary表示我们以二进制模式打开文件。如果以二进制模式打开文件,那么使用提取(<<)和写入(>>)运算符或使用getline函数,进行读取和写入数据的效率就会很低。在这种情况下,正确的方法是使用ostream(ofstream)和istream(ifstream)对象的写入和读取功能。

read(buffer, size);
write(buffer, size);
流定位

I / O对象内部保持了两个位置;一个叫做get位置(tellg,seekg),由ifstream对象来维护。该位置指向下一个输入操作中读取的元素。类似地,存在一个put(tellp,seekp),它是由ofstream对象来维护,这个位置指向下一个输出操作中写入的元素。

我们可以借助以下函数操纵这些位置:
tellg(),tellp():返回成员类型streampos的值,分别表示当前的读写位置。
seekg(position),seekp(position):将位置更改为文件中读写操作的绝对位置。
seekg((offset, direction),seekg(offset, direction):streamoff类型的偏移值,表示由枚举类型方向确定的某个特定点的相对位置,例如:
ios :: beg:从流的开头计算偏移量
ios :: end:Offset从流的末尾开始计算
ios :: cur:从当前位置开始计算的偏移量

  • 一个简单的例子
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main(void){
   string buf="ABCD EFGH HIJK";
   fstream file;
   // Uncheck this if file doesn't already exist
   // file.open ("testfile.dat", ios::out | ios::binary);
   // file.close();
   file.open ("testfile.dat", ios::out |
      ios::in | ios::binary);
   if(!file.is_open()){
      cerr<<"Error opening file!!"<<endl;
      exit(1);
   }
   file.write(reinterpret_cast<char *>(&buf),buf.size());
   file.seekg(0,ios::end);
   cout<<"size = "<<file.tellg()<<endl;
   file.seekg(0,ios::beg);
   file.read (reinterpret_cast<char *>(&buf),buf.size());
   cout<<buf<<endl;
   file.close();
   return 0;
}
访问结构化数据

如前所述,平面文件没有按照逻辑格式进行数据项存储的机制。此外,C++不会强加任何这样的结构。我们能做的是应用某些程序逻辑,使得我们能够按照结构方式来进行数据的检索和存储操作。我们以一个实例来说明。

  • employee.h文件
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include <string>
using namespace std;
class Employee
{
private:
   int m_empno;
   char m_fname[20];
   char m_lname[20];
   char m_email[20];
   double m_salary;
public:
   Employee(int=0,string="",string="",string="",
      double=0.0);
   int empno() const;
   void setEmpno(int empno);
   string fname() const;
   void setFname(const string &fname);
   string lname() const;
   void setLname(const string &lname);
   string email() const;
   void setEmail(const string &email);
   double salary() const;
   void setSalary(double salary);
};
#endif   // EMPLOYEE_H
  • employee.cpp文件
#include "employee.h"
Employee::empno() const{ return m_empno; }
Employee::setEmpno(int empno){ m_empno = empno; }
Employee::fname() const { return m_fname; }
Employee::setFname(const string &fname)
{
   int sz=fname.size();
   sz=(sz<20? sz: 19);
   fname.copy(m_fname,sz);
   m_fname[sz]='\0';
}
string Employee::lname() const { return m_lname;}
void Employee::setLname(const string &lname)
{
   int sz=lname.size();
   sz=(sz<20? sz: 19);
   lname.copy(m_lname,sz);
   m_lname[sz]='\0';
}
string Employee::email() const { return m_email; }
void Employee::setEmail(const string &email)
{
   int sz=email.size();
   sz=(sz<20? sz: 19);
   email.copy(m_email,sz);
   m_email[sz]='\0';
}
Employee::salary() const { return m_salary;}
void Employee::setSalary(double salary)
   { m_salary = salary;}
Employee::Employee(int eno, string fn, string ln,
   string mail, double sal)
{
   setEmpno(eno);
   setFname(fn);
   setLname(ln);
   setEmail(mail);
   setSalary(sal);
}
  • main.cpp文件
#include <iostream>
#include <iomanip>
#include <fstream>
#include "employee.h"
using namespace std;
const string FILENAME="emp.dat";
void print_table();
bool isExists(int);
void create(Employee);
void create(Employee emp)
{
   if(isExists(emp.empno())==true){
      cout<<"Cannot create! Record with Employee No #"
         <<emp.empno()<<" already exists."<<endl;
      return;
   }
   ofstream outfile(FILENAME, ios::app|ios::binary);
   if(!outfile){
      cout<<"Error opening file!";
      exit(1);
   }
   outfile.write(reinterpret_cast<const char *>
      (&emp),sizeof(Employee));
   outfile.close();
}
bool isExists(int eno)
{
   bool exists=false;
   ifstream infile(FILENAME, ios::in|ios::binary);
   while(!infile.eof()){
      Employee ee;
      infile.read(reinterpret_cast<char *>
      (&ee),sizeof(Employee));
      if(ee.empno()==eno) {exists=true; break;}
   }
   infile.close();
   return exists;
}
void print_table(){
   cout << left
      << setw(10) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << '+' << '+'
      << endl;
   cout << setfill(' ') << '|' << left
      << setw(9) << "Emp No." << setfill(' ')
      << '|' << setw(20) << "First Name" << setfill(' ')
      << '|' << setw(20) << "Last Name" << setfill(' ')
      << '|' << setw(20) << "Email" << setfill(' ')
      << '|' << right<< setw(20) << "Balance" << '|'
      << endl;
   cout << left << setw(10) << setfill('-') << left <<
      << setw(21) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << left << '+'
      << setw(21) << setfill('-') << '+' << '+' << endl;
   Employee record;
   ifstream infile(FILENAME, ios::in|ios::binary);
   infile.read(reinterpret_cast<char *>
      (&record),sizeof(Employee));
   while(!infile.eof()){
      cout << setfill(' ') << '|' << left
         << setw(9) << record.empno()
         << setfill(' ') << '|' << setw(20) << record.fname()
         << setfill(' ') << '|' << setw(20) << record.lname()
         << setfill(' ') << '|' << setw(20) << record.email()
         << setfill(' ') << '|' << right << setw(20)
         << record.salary() << '|' << endl;
      infile.read(reinterpret_cast<char *>
         (&record),sizeof(Employee));
   }
   infile.close();
   cout << left << setw(10) << setfill('-') << left << '+'
         << setw(21) << setfill('-') << left << '+'
         << setw(21) << setfill('-') << left << '+'
         << setw(21) << setfill('-') << left << '+'
         << setw(21) << setfill('-') << '+' << '+' << endl;
}
int main(void)
{
   ofstream outfile(FILENAME, ios::out|ios::binary);
   if(!outfile){
      cout<<"Error opening file!";
      exit(1);
   }
   outfile.close();
   int empno;
   string fname, lname, email;
   double sal;
   while(true){
      cout<<"\nEnter Employee no.(0 to exit)#";
      cin>>empno;
      if(empno==0) break;
      if(isExists(empno)) {
         cout<<"Employee number exists.
         Please enter different number."<<endl;
         continue;
      }
      cout<<"\nEnter first name, last name, email, salary\n# ";
      cin>>setw(19)>>fname;
      cin>>setw(19)>>lname;
      cin>>setw(19)>>email;
      cin>>sal;
      Employee emp(empno,fname,lname,email,sal);
      create(emp);
      print_table();
   }
   return 0;
}
  • 输出结果
    这里写图片描述
    图1:程序运行结果
结论

使用标准C ++ API进行文件处理的关键是通过ifstream和ofstream对象,它们继承自istream和ostream类。 fstream对象更灵活,能够同时以读写模式打开文件。 put操作(tellp,seekp)用于写入,get操作(tellg,seekg)用于从文件中读取数据项。

关于作者

Manoj Debnath
manosolireap@outlook.com

相关文章

Last.fm Open Sources C++ Moost Library Now Available
CppDepend Pro License for C\C++ open source project contributors

栏目导航
上一篇:技术文章翻译(七) – 深入研究C++字符串流处理过程
下一篇:技术文章翻译(九) – 理解C++指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值