C++文件及综合练习

使用文件流操作文本文件

任务描述

本关任务:编写一个统计本月服装的销售情况的函数。

相关知识

我们在编写程序的时候,最密不可分的就是对文件进行相应的操作,我们可以从文件中读取数据,可以将数据保存到文件,可以……

总而言之,言而总之,一言以蔽之,对文件的操作是非常重要的,下面我们就来介绍一下 C++ 中是如何对文件进行操作的。

文件流操作文件
在 C++ 中,对文件的操作是通过 stream 的子类 fstream( file stream )来实现的,所以,要用这种方式操作文件,就必须加入头文件,代码如下:

#include 

fstream 提供了三个类,用来实现 C++ 对文件的操作:

  • ofstream: 写操作(输出)的文件类(由 ostream 引申而来)

    ifstream: 读操作(输入)的文件类(由 istream 引申而来)

    fstream: 可同时读写操作的文件类(由 iostream 引申而来)

使用文件流操作文件可以分为三个步骤:打开文件、读写文件、关闭文件

打开文件
打开文件用于读时可以使用类 fstream 或者 ifstream 函数。

ifstream 函数

ifstream inFile("test.txt", ios::in);
  1. 1.inFile 是声明的 ifstream
    的一个对象(也可以叫变量,只是这个变量里面包含的东西较多,类似于结构变量),声明该对象时会自动执行一个特殊的函数(构造函数,学习面向对象部分的时候会了解);

  2. test.txt和 ios::in 是传递给该函数的参数。test.txt是要打开的路径和文件名,ios::in
    是文件打开的方式,表示打开文件用于输入;

  3. 执行该函数将会以读的方式打开当前目录下的文件test.txt。之后通过 inFile 调用一些函数就可以操作文件test.txt了。

stream 函数

由于类 fstream 也可以打开文件用于输入,上面的语句也可以这样写:

fstream inFile("test.txt", ios::in);

类 fstream 的文件打开方式有:

打开方式 描述
ios::in 打开一个供读取的文件
ios::out 打开一个供写入的文件
ios::app 写入的所有数据将被追加到文件的末尾,此方式需要使用 ios::out
ios::ate 写入的数据将追加到文件的末尾,但也可写到其他地方,此方式不需要用 ios::out
ios::trunc 废弃当前文件内容
ios::nocreate 如果要打开的文件并不存在,那么以此参数调用 open 函数将无法进行
ios::noreplace 如果要打开的文件已存在,试图用 open 函数打开时将返回一个错误
ios::binary 以二进制的形式打开一个文件

其中适合于文件读的打开方式也可以用于类 ifstream ,适合于文件写的打开方式也可以用于 ofstream,ios::binary 两者都可以用。

读写文件

1.文件的读取

如果以文本的方式操作文件(没有属性 ios::binary ),则读文件的语法和用 cin 从键盘输入的语法很像。例如下面的语句可以从文件test.txt中读取一个整数和一个浮点数。

int n;
float f;
inFile >> n >> f;

2.文件的写入

文件的写入也和输出到屏幕的语法很像。

例如下面的程序将整数100和浮点数3.14写入文件a.txt。

// 声明对象ofile,以读的方式打开文件a.txt
ofstream ofile("a.txt", ios::out);
// 将100、空格、3.14、换行符写入文件a.txt
ofile << 100 << " " << 3.14 << endl;
// 关闭文件
ofile.close();

3.文件关闭

文件的关闭不管是 ifstream、ofstream 还是 fstream 的对象,都可以使用相同的语法关闭文件,即xx.close();。

编程要求

在右侧编辑器中的Begin-End之间补充代码,完成void count(ifstream & fin, ofstream & fout)函数,实现使用文件流操作文本文件的功能,即统计本月服装的销售情况,具体要求如下:

  • 参数 fin
    打开的输入文件,包含每种服装的销售情况,每种服装信息占一行,分别为服装编号,销售件数,每件的销售价格(整型),数据之间用空格隔开。

例如:a001 4 120 125 150 110,表示编号为 a001 的服装销售了4件,每件的销售价格分别为120、125、150、110。

  • 参数 fout 是统计信息的输出文件。函数对文件 fin 中的销售信息进行统计后,将服装编号和销售总额写入文件 fout
    中,每种服装统计信息占一行,分别为服装编号,销售总额(整型),中间用一个空格隔开。

如上述服装的统计信息为:a001 505

提示:文件 fin 中包含多少种服装信息不确定。

streamTxt.cpp

#include 
#include 
using namespace std;

/*
  函数count:统计文件fin中每种服装的销售总额,并写入文件fout中
  参数fin:文件每种服装的销售情况,fout:每种服装销售总额的写入文件
  返回值:无
  说明:文件fin中,每种服装信息占一行,分别为服装编号,销售件数,每件的销售价格(整型)。
  文件fout:每种服装统计信息占一行,分别为服装编号,销售总额(整型),中间用一个空格隔开。
*/
void count(ifstream & fin, ofstream & fout)
{
    // 请在此添加代码,补全函数count
    /********** Begin *********/
    char s[100];
    fin>>s;
    while(!fin.eof())
    {
        int i,n,c=0,t;
        fin>>n;
        for(i=0;i<n;i++)
        {
            fin>>t;
            c+=t;
        }
        fout<<s<<" "<<c<<endl;
        fin>>s;
    }
   
    
    /********** End **********/
}

main.cpp

#include 
#include 
using namespace std;

//ÉùÃ÷Íⲿº¯Êý£¬º¯ÊýcountÔÚÆäËüÔ´ÎļþÖÐʵÏÖ
extern void count(ifstream & fin, ofstream & fout);

int main()
{
	char s[100];
	int n, i, num, p[100],k;
	//×¼±¸Îļþ
	ofstream cloth("cloth.txt");
	cin>>n; //ÊäÈë·þ×°ÖÖÀàÊýÁ¿
	for(i=0;i<n;i++)
	{
		cin>>s; //ÊäÈë·þ×°±àºÅ
		cin>>num; //ÊäÈë¸Ã·þ×°ÏúÊÛÊýÁ¿
		for(k=0;k<num;k++)
			cin>>p[k]; //ÊäÈëÿ¼þ·þ×°µÄÏúÊÛ¼Û¸ñ
		//дÈëÎļþ
		cloth<<s<<" "<<num;
		for(k=0;k<num;k++)
			cloth<<" "<<p[k];
	}
	//¹Ø±ÕÎļþ
	cloth.close();
	//´ò¿ªÏúÊÛÎļþ
	ifstream fin("cloth.txt");
	//´ò¿ªÍ³¼ÆÎļþ
	ofstream fout("count.txt");
	//µ÷Óú¯Êýcount
	count(fin,fout);
	//¹Ø±ÕÎļþ
	fin.close();
	fout.close();
	//¶Á³öͳ¼ÆÐÅÏ¢²¢Êä³ö
	ifstream f("count.txt");
	f>>s;
	while(!f.eof())
	{
		f>>n;
		cout<<s<<" "<<n<<endl;
		f>>s;
	}
	f.close();
	return 0;
}

使用文件流操作二进制文件

任务描述

本关任务:编写一个在文件中查找某种服装的数量并返回的函数。

相关知识

C 语言在对文件进行操作时,将文件分为文本文件和二进制文件。上一关中我们学习了对文本文件的处理方式,下面我们来学习对二进制文件的处理函数。

文件流操作二进制文件的读写

读二进制文件
要以二进制的方式操作文件,需要首先以二进制的方式打开文件。

例如下面的程序可以将写到文件c.dat中的整数读出:

ifstream fl("c.dat", ios::binary);
int n;
fl.read((char*)&n,sizeof(n));

第一行程序申明 ifstream 的对象 fl ,并以二进制方式打开文件c.dat用于读。

第三行从文件中读出一个整数。read 函数的第一个参数是读出的数据要放到内存中的位置,类型为char*。读出的整数要赋值给 n,所以该实参为&n,并进行了类型转换。第二个参数是读出的字节数,一个整数的字节数可以用sizeof(n)求得。

写二进制文件
以文件流的方式操作文件一样可以支持二进制方式的块读写。

例如:

ofstream cl("c.dat", ios::binary);
int n = 10;
cl.write((char*)&t,sizeof(t));

第一行程序申明了 ofstream 的对象 cl,并以二进制方式( ios::binary )打开文件c.dat(如果文件c.dat不存在,会先创建)用于输出( fstream 的对象的对象都是用于文件输出)。

第三行则将整数 t 以块写入的方式写入文件c.dat。函数 write 的第一个参数是要写入文件的数据首地址,必须是char*类型,要写入的数据是 t,所以该实参为&t,并进行了类型转换。第二个参数是要写入文件的字节数,t 整型变量,所占字节数可以用sizeof(t)求得。

编程要求

在右侧编辑器中的Begin-End之间补充代码,完成int getNumber(ifstream &ifile, char *label)函数,以实现使用文件流操作二进制文件的功能。具体要求如下:

  • 参数 ifile 为以二进制方式打开的存储服装信息的文件。服装信息用结构 clothing 定义,结构 clothing 的定义为:
struct clothing {
    char label[12];     // 编号
    int numberRemaining;     // 剩余件数
};

参数 label 为要查找的服装编号。函数要求从文件中读出服装信息,并查找编号为 label 的服装,找到则返回其剩余件数,找不到则返回 0。文件中包含的服装信息的数量不确定。

streamBin.cpp

#include 
#include 
#include 
using namespace std;

// 结构clothing
struct clothing {
    char label[12];  // 编号
    int numberRemaining;  // 剩余件数
};

/*
  函数getNumber:在文件ifile中查找标签为lable的服装数量
  参数ifile:存放服装信息的文件,label:要查找的服装标签
  返回值:标签为label的服装数量
  说明:文件中ifile中存放着服装信息,服装信息为以二进制写入的一个个clothing结构变量
*/
int getNumber(ifstream &ifile, char *label)
{
    // 请在此添加代码,补全函数getNumber
    /********** Begin *********/
    clothing  t;
    // 读出种服装信息到t中
    ifile.read((char*)&t,sizeof(clothing));
    while(!ifile.eof())
    {
        if(strcmp(label, t.label)==0)
        {
            return t.numberRemaining;
  	}
        ifile.read((char*)&t,sizeof(clothing));
    }
    return 0;   
    
    /********** End **********/
}

main.cpp

#include 
#include 
using namespace std;

//½á¹¹clothing
struct clothing {
	char label[12]; //񅧏
	int numberRemaining; //Ê£Óà¼þÊý
};

//ÉùÃ÷Íⲿº¯Êý£¬º¯ÊýgetNumberÔÚÆäËüÔ´ÎļþÖÐʵÏÖ
extern int getNumber(ifstream &ifile, char *label);

int main()
{
	int n,i;
	char le[100];
	clothing t;
	//×¼±¸Îļþ
	//´ò¿ªÎļþ£¬¶þ½øÖÆ·½Ê½
	ofstream cloth("cloth.dat", ios::binary);
	cin>>n; //ÊäÈë·þ×°ÖÖÀàÊýÁ¿
	for(i=0;i<n;i++)
	{
		cin>>t.label; //ÊäÈë·þ×°±àºÅ
		cin>>t.numberRemaining; //ÊäÈë·þ×°Ê£ÓàÊýÁ¿
		//дÈëÎļþ
		cloth.write((char*)&t,sizeof(t));
	}
	//¹Ø±ÕÎļþ
	cloth.close();
	//´ò¿ªÎļþÓÃÓÚ¶Á£¬¶þ½øÖÆ·½Ê½
	ifstream fin("cloth.dat",ios::binary);
	//ÊäÈëÒª²éÕҵķþ×°±êÇ©
	cin>>le;
	//µ÷Óú¯ÊýgetNumber
	n = getNumber(fin,le);
	//Êä³ö·þ×°¼þÊý
	cout<<n<<endl;
	//¹Ø±ÕÎļþ
	fin.close();
	return 0;
}

使用链表进行学生信息管理

任务描述

本关任务:编写一个以链表为基础的学生信息管理小程序。

相关知识

为了完成本关任务,你需要掌握:

  1. 结构体的使用;
  2. 单向链表的插入、删除和遍历;
  3. 头结点。

结构体
在 C++ 中,结构体与接下来要介绍的类的概念差别不大,所以这里主要是介绍它在 C 语言中的经典用法。

结构体的声明分为3部分,一般表现形式如下:

struct <结构体名>{<成员变量>};

例如:

struct Test     // 声明一个名为 Test 的结构体,它有两个成员变量 A,B
{
    int A;
    char B;
};     //不要忘了这个分号

声明好结构体之后,就可以声明这个结构体类型的变量,语法与声明 int ,char 这些类型的变量一样。对于结构体变量,可以使用.成员运算符访问它的成员变量。

例如:

/* Test类的声明同上 */
int main()
{
    Test t1;     // 声明一个结构体变量
    t1.A = 10;      // 访问 A 成员变量
    t1.B = 'A';     // 访问 B 成员变量
    Test t2 = t1;     // 将其赋值给另一结构体变量
}

同样也可以声明结构体的指针,这个时候要访问它所指的对象的成员,可以用面向指针的-> 成员运算符。

例如:

/* Test类的声明同上 */
Test t;
Test *ptr = &t;
ptr->A = 10;     // 通过指针访问 A 成员
ptr->B = 'A';     // 通过指针访问 B 成员

单向链表
链表是一种常用的数据结构,特点是删除、插入操作效率高。链表的实现有很多种,这里简单介绍一下带头结点的单向链表。

链表的关键是链,它用来连接一个一个的节点,而这个链则是用指针来实现的。

当一个结构体 T 中有一个T*成员变量时,就能用这个成员变量链接到下一个 T 类型的节点,对于下一个节点,也是一样的看法,这样就形成了一条长链,就像这张图:

用代码来说就是:

struct Linked
{
int Data; // 存放数据
Linked *next; // 指向下一个节点的指针
};
单向链表的插入
由链表的结构可以发现,要向某个节点后面插入一个新节点,只需要将原有节点后的链“断开”,然后让新节点的 next 指向断开后的部分,而原有节点的 next 则指向这个新节点,如图:

在这里插入图片描述

代码实现如下:

void insertAfter(Linked *link,Linked *newNode)
{
    newNode->next = link->next;     // 新节点的指针指向旧节点之后的内容
    link->next = newNode;     // 然后再更新旧节点所指的后一个节点
}

单向链表的删除
要删除一个节点的后一个节点,那就只需将这个节点的 next 指针指向后一个节点的 next 指针所指的内容,也就是跨过要删除的节点,如图:

C++文件及综合练习_第1张图片

代码实现如下:

void deleteAfter(Linked *link)
{
    link->next = link->next->next;     // 当前节点的指针指向下一个节点的下一个节点
}

头结点的作用
如果仔细思考上面的插入和删除节点的代码,就会发现对于增加节点,当向一个空链表插入节点时,insertAfter 函数的 link 参数值应该是0。这样插入函数就需要判断传递进来的链表是不是空链,根据判断结果选择不同的做法,而且还要更新外部那个存放了 link 参数值的变量,就像这样:

Linked newNode;
Linked *lk = 0;
lk = insertAfter(lk,&newNode)// 需要接收 insertAfter 的返回值来更新 lk

删除也是一样,也需要考虑只有一个节点的情况下删除该怎么做。

但是,当我们引入一个头结点时情况就不一样了,由于链表始终会有一个节点,那么插入、删除操作就不用考虑容量从0到1,1到0的变化,这样所有的情况下的这两种操作都统一了起来,简化了代码的设计。

单向链表的遍历
要遍历一个单向链表,只需使用一个指针,从头结点之后的一个节点开始,不断地将其 next 值赋给这个指针,直到 next 为0即可,比如:

void iter(Linked *head)
{
    Linked *ptr = head->next;
    while(ptr)      // 判断是否经过了最后一个节点,因为最后一个节点的 next 是 0
    {
        ptr->Data = 0;     // 对节点进行操作
        ptr = ptr->next;     // 进入到下一个节点
    }
}

编程要求

在右侧编辑器中的Begin-End之间补充代码,设计一个以链表为基础的学生信息管理,系统中包含五个函数的实现,具体功能如下:

  • Linked* Create():创建并返回一个空链表;
  • void InsertAfter(Linked *node,int num,float sc):在 node
    所指节点之后插入一个新节点,并用参数 num ,sc 的值分别初始化新节点的成员变量学号和分数;
  • void DeleteAfter(Linked *node):删除 node 节点之后的节点;
  • Linked* GetByIndex(Linked *head,int index):返回 head 所指链表中索引为 index
    的节点,比如当 index 为 0 时,返回的应该是头结点之后的第一个节点;
  • void PrintAll(Linked *head):按照<学号> <分数>的格式打印链表中所有节点的成员变量的值,每个一行。

其中链表结构体 Linked,除了实现链表所必要的成员变量外,还有两个成员变量(变量名自拟):

  • 学号,int 类型;
  • 分数,float 类型。

注意:测评代码保证上述操作涉及到的节点都是存在的。且新节点可以使用 new 运算符来动态创建,那么删除节点时就对应使用 delete运算符。

.h

#include 
using namespace std;


struct Linked
{
    /********* Begin *********/
    //结构体的成员变量
    int num;
    float sc;
    Linked *next;
    

    /********* End *********/
};

Linked* Create()
{
    /********* Begin *********/
    //创建并返回一个新链表
    struct Linked *head = (struct Linked *) malloc(sizeof(struct Linked));//创建头结点,并分配内存,需要的内存大小就是结构体的大小。别忘了在malloc前进行强制类型转换。(struct LinkList*)
    head->next = NULL;
    //head ->num = NULL;
    //head ->sc = NULL;
    return head;
  /********* End *********/
}

void InsertAfter(Linked *node,int num,float sc)
{
    /********* Begin *********/
    //在指定节点后插入一个新节点,内容由 num,sc 参数指定

    // 
    Linked *link = (struct Linked *) malloc(sizeof(struct Linked));
    link -> next = NULL;
   //link->key = key;
   //Linked *link;
if(node -> num == 0)
{
    node -> num = num;
    node -> sc = sc;
}
else{
   link->num = num;
   link -> sc = sc;
   //point it to old first node
   link->next = node -> next;
   node -> next = link;
   //point first to new first node
}
    /********* End *********/
}

void DeleteAfter(Linked *node)
{
    /********* Begin *********/
    //删除此节点之后的一个节点
    node -> next = node -> next -> next;
    

    /********* End *********/
}

Linked* GetByIndex(Linked *head,int index)
{
    /********* Begin *********/
    //返回指定索引处的节点
    int n = index+1;
    // while (n > 1)
    // {
    //     head = head -> next;
    // }
    if (n>1)
    {
        head = head -> next;
        n--;
    }
   // cout << "head" << "index" < num<< head -> sc<< endl;
    return head;
    
    /********* End *********/
}

void PrintAll(Linked *head)
{
    /********* Begin *********/
    //按格式打印此链表中所有节点的成员变量
    while (head != NULL)
    {
        cout << head -> num <<" "<< head -> sc <<endl;
        head = head -> next;
    }
    
    
    /********* End *********/
}

.cpp

#include "usr.h"

int main()
{
	int num;
	float score;
	cin >> num >> score ;
    Linked *lk = Create();
    InsertAfter(lk,num,score);
	cin >> num >> score ;
    InsertAfter(GetByIndex(lk,0),num,score);
	cin >> num >> score ;
    InsertAfter(GetByIndex(lk,1),num,score);
    DeleteAfter(GetByIndex(lk,0));
    PrintAll(lk);
}

你可能感兴趣的:(C++程序设计题)