【北京大学】程序设计实习 (C++版)第7周 文件操作和模板

目录

第7周 文件操作和模板

7-1 文件操作

7-2 函数模板

7-3 类模板   

7-4  string类     

7-5 输入输出类


第7周 文件操作和模板

7-1 文件操作

  • 数据的层次

位 bit

字节 byte

域/记录:

例如: 学生记录

         int ID;

         char name[10];

         int age;

         int rank[10];

将所有记录顺序地写入一个文件  顺序文件

  • 文件和流

顺序文件 —一个有限字符构成的顺序字符流

C++标准库中: ifstream, ofstream和fstream共3个类,用于文件操作 — 统称为文件流类

 

  • 文件操作

使用/创建文件的基本流程

 

具体步骤:

建立顺序文件:

 

也可以先创建 ofstream对象, 再用 open函数 打开

ofstream fout;

fout.open( “test.out”, ios::out|ios::binary );

判断打开是否成功:

if(!fout) { cerr << “File open error!”<<endl; }

文件名可以给出绝对路径, 也可以给相对路径,没有交代路径信息, 就是在当前文件夹下找文件

文件的读写指针:

对于输入文件,有一个读指针

对于输出文件, 有一个写指针

对于输入输出文件, 有一个读写指针

标识文件操作的当前位置,

该指针在哪里  读写操作就在哪里进行

ofstream fout(“a1.out”, ios::app);

long location = fout.tellp(); //取得写指针的位置

location = 10L;

fout.seekp(location); // 将写指针移动到第10个字节处

fout.seekp(location, ios::beg); //从头数location

fout.seekp(location, ios::cur); //从当前位置数location

fout.seekp(location, ios::end); //从尾部数location

location 可以为负值

 

ifstream fin(“a1.in”,ios::in);

long location = fin.tellg(); //取得读指针的位置

location = 10L;

fin.seekg(location); //将读指针移动到第10个字节处

fin.seekg(location, ios::beg); //从头数location

fin.seekg(location, ios::cur); //从当前位置数location

fin.seekg(location, ios::end); //从尾部数location

location 可以为负值

 

int x=10;

fout.seekp(20, ios::beg);

fout.write( (const char *)(&x), sizeof(int) );

fin.seekg(0, ios::beg);

fin.read( (char *)(&x), sizeof(int) );

二进制文件读写, 直接写二进制数据, 记事本看未必正确

 

显式关闭文件

ifstream fin(“test.dat”, ios::in);

fin.close();

ofstream fout(“test.dat”, ios::out);

fout.close();

 

程序示例1:写二进制文件

#include <iostream>

#include <fstream>

using namespace std;

class CStudent

{

public:

    char szName[20];

    int nScore;

};

int main()

{

    CStudent s1;

    ofstream outFile("D:\student.dat", ios::in|ios::binary);

    if (!outFile){

        cout << "errror" << endl;

        return 0;

    }

 

    while (cin >> s1.szName >> s1.nScore){

        if(stricmp(s1.szName,"exit")==0){

            break;

        }

        outFile.write((char*)&s1,sizeof(s1));

    }

    outFile.close();

    return 0;

}

程序示例2:读二进制文件

#include <iostream>

#include <fstream>

using namespace std;

class CStudent

{

public:

    char szName[20];

    int nScore;

};

int main()

{

    CStudent s1;

    ifstream iutFile("D:\student.dat", ios::in | ios::binary);

    if (!iutFile) {

        cout << "errror" << endl;

        return 0;

    }

    while (iutFile.read((char*)&s1, sizeof(s1))) {

        cout << s1.szName << s1.nScore<<endl;

    }

    iutFile.close();

    return 0;

}

7-2 函数模板

泛型程序设计Generic Programming

算法实现时不指定具体要操作的数据的类型

泛型 — 算法实现一遍  适用于多种数据结构

优势: 减少重复代码的编写

大量编写模板, 使用模板的程序设计

• 函数模板

• 类模板

函数模板:

为了交换两个int变量的值, 需要编写如下Swap函数:

void Swap(int & x, int & y)

{

         int tmp = x;

         x = y;

         y = tmp;

}

为了交换两个double型变量的值, 还需要编写如下Swap

函数:

void Swap(double & x, double & y)

{

         double tmp = x;

         x = y;

         y = tmp;

}

函数模板

能否只写一个Swap, 就能交换各种类型的变量

用函数模板解决:

template<class 类型参数1, class 类型参数2, … >

返回值类型 模板名 (形参表)

{

         函数体

}

程序示例:

交换两个变量值的函数模板

template <class T>

void Swap(T & x, T & y)

{

         T tmp = x;

         x = y;

         y = tmp;

}

int main(){

         int n = 1, m = 2;

         Swap(n, m); //编译器自动生成 void Swap(int &, int &)函数

         double f = 1.2, g = 2.3;

         Swap(f, g); //编译器自动生成 void Swap(double &, double &)函数

         return 0;

}

函数模板中可以有不止一个类型参数

template<class T1, class T2>

T2 print(T1 arg1, T2 arg2)

{

         cout<< arg1 << " "<< arg2<<endl;

         return arg2;

}

求数组最大元素的MaxElement函数模板

template <class T>

T MaxElement(T a[], int size) //size是数组元素个数

{

         T tmpMax = a[0];

         for( int i = 1; i < size; ++i )

                   if( tmpMax < a[i] )

                            tmpMax = a[i];

         return tmpMax;

}

函数模板可以重载, 只要它们的形参表不同即可

例, 下面两个模板可以同时存在:

template<class T1, class T2>

void print(T1 arg1, T2 arg2)

{

         cout<< arg1 << " "<< arg2<<endl;

}

template<class T>

void print(T arg1, T arg2)

{

         cout<< arg1 << " "<< arg2<<endl;

}

C++编译器遵循以下优先顺序:

Step 1: 先找参数完全匹配的普通函数(非由模板实例化

而得的函数)

Step 2: 再找参数完全匹配的模板函数

Step 3: 再找实参经过自动类型转换后能够匹配的普通

函数

Step 4: 上面的都找不到, 则报错

 

程序示例:

#include <iostream>

using namespace std;

template <class T>

T Max(T a, T b) {

    cout << "Template Max 1" << endl;

    return 0;

}

template <class T, class T2>

T Max(T a, T2 b) {

    cout << "Template Max 2" << endl;

    return 0;

}

double Max(double a, double b) {

    cout << "MyMax" << endl;

    return 0;

}

int main()

{

    int i = 4, j = 5;

    Max(1.2, 3.4); //调用Max(double, double)函数

    Max(i, j); //调用第一个T Max(T a, T b)模板生成的函数

    Max(1.2, 3); //调用第二个T Max(T a, T2 b)模板生成的函数

    return 0;

}

运行结果为:

 

赋值兼容原则引起函数模板中类型参数的二义性

template<class T>

T myFunction(T arg1, T arg2)

{

         cout<<arg1<<“ ”<<arg2<<“\n”;

         return arg1;

}

myFunction(5, 7); //ok: replace T with int

myFunction(5.8, 8.4); //ok: replace T with double

myFunction(5, 8.4); //error: replace T with int or double? 二义性

 

可以在函数模板中使用多个类型参数, 可以避免二义性

template<class T1, class T2>

T1 myFunction( T1 arg1, T2 arg2)

{

         cout<<arg1<<“ ”<<arg2<<“\n”;

         return arg1;

} …

myFunction(5, 7); //ok:replace T1 and T2 with int

myFunction(5.8, 8.4); //ok: replace T1 and T2 with double

myFunction(5, 8.4); //ok: replace T1 with int, T2 with double

程序示例:

#include <iostream>

using namespace std;

template <class T>

void Swap(T & a, T& b)

{

    T   temp = a;

    a = b;

    b = temp;

}

int main()

{

    int n = 1, m = 2;

    Swap(n, m); //编译器自动生成 void Swap(int &, int &)函数

    cout << n << " " << m<< endl;

 

    double f = 1.2, g = 2.3;

    Swap(f, g); //编译器自动生成 void Swap(double &, double &)函数

    cout << f << " "<<g << endl;

    return 0;

}

运行结果为:

 

7-3 类模板   

问题的提出:

 

对于这些数组类

• 除了元素的类型不同之外, 其他的完全相同

类模板

• 在定义类的时候给它一个/多个参数

• 这个/些参数表示不同的数据类型

在调用类模板时, 指定参数, 由编译系统根据参数提供的数据类型自动产生相应的模板类

类模板的定义

C++的类模板的写法如下:

template <类型参数表>

class 类模板名

{

         成员函数和成员变量

};

类型参数表的写法就是:

class 类型参数1, class 类型参数2, …

 

类模板里的成员函数, 如在类模板外面定义时,

template <型参数表>

返回值类型 类模板名<类型参数名列表>::成员函数名(参数表)

{

……

}

用类模板定义对象的写法如下:

类模板名 <真实类型参数表> 对象名(构造函数实际参数表);

如果类模板有无参构造函数, 那么也可以只写:

类模板名 <真实类型参数表> 对象名;

程序示例:

Pair类模板:

template <class T1, class T2>

class Pair{

public:

         T1 key; //关键字

         T2 value; //值

         Pair(T1 k,T2 v):key(k),value(v) { };

         bool operator < (const Pair<T1,T2> & p) const;

};

template<class T1,class T2>

bool Pair<T1,T2>::operator<( const Pair<T1, T2> & p) const

//Pair的成员函数 operator <

{ return key < p.key; } 7类模板的定义

Pair类模板的使用:

int main()

{

         Pair<string, int> student("Tom",19);

         //实例化出一个类 Pair<string, int>

         cout << student.key << " " << student.value;

         return 0;

}

输出结果:

Tom 19

使用类模板声明对象

编译器由类模板生成类的过程叫类模板的实例化

         编译器自动用具体的数据类型

         替换类模板中的类型参数, 生成模板类的代码

由类模板实例化得到的类叫模板类,为类型参数指定的数据类型不同, 得到的模板类不同。

同一个类模板的两个模板类是不兼容的

Pair<string, int> * p;

Pair<string, double> a;

p = & a; //wrong;

函数模版作为类模板成员:

#include <iostream>

using namespace std;

template <class T>

class A{

public:

         template<class T2>

         void Func(T2 t) { cout << t; } //成员函数模板

};

int main(){

         A<int> a;

         a.Func('K'); //成员函数模板 Func被实例化

         return 0;

}

函数模版作为类模板成员

程序输出:

K

类模板与非类型参数

类模板的参数声明中可以包括非类型参数

template <class T, int elementsNumber>

• 非类型参数: 用来说明类模板中的属性

• 类型参数: 用来说明类模板中的属性类型, 成员操作的参数类型和返回值类型

类模板的 “<类型参数表>” 中可以出现非类型参数:

template <class T, int size>

class CArray{

         T array[size];

public:

         void Print( )

         {

                  for(int i = 0; i < size; ++i)

                  cout << array[i] << endl;

         }

};

注意事项:

类模板与非类型参数

CArray<double, 40> a2;

CArray<int, 50> a3;

注意:

CArray<int,40>和CArray<int,50>完全是两个类,这两个类的对象之间不能互相赋值

类模板与继承:

 

程序示例:

#include <iostream>

#include <string>

using namespace std;

template <class T1,class T2>

class Pair

{

public:

    T1  key;

    T2  value;

    Pair(T1 k, T2 v) :key(k), value(v) {

    }

 

    bool operator<(const Pair<T1, T2> &pconst;

};

template <class T1, class T2>

bool Pair<T1, T2>::operator<(const Pair<T1, T2> &pconst {

    return key < p.key;

}

int main()

{

    Pair<string, int> student("Tom", 19);

    //实例化出一个类 Pair<string, int>

    cout<< student.key << " " << student.value<<endl;

    return 0;

}

运行结果:

 

7-4  string类     

string类:

  • string 类 是一个模板类, 它的定义如下:

         typedef basic_string<char> string;

  • 使用string类要包含头文件 <string>

  • string对象的初始化:

         string s1("Hello"); // 一个参数的构造函数

         string s2(8, ‘x’); // 两个参数的构造函数

         string month = “March”;

  • 不提供以字符和整数为参数的构造函数

错误的初始化方法:

         string error1 = ‘c’; // 错

         string error2(‘u’); // 错

         string error3 = 22; // 错

         string error4(8); // 错

  • 可以将字符赋值给string对象

         string s;   s = ‘n’;

  • 构造的string太长而无法表达  会抛出length_error异常

         string 对象的长度用成员函数length()读取;

         string s("hello");

         cout << s.length() << endl;

  • string 支持流读取运算符

         string stringObject;

         cin >> stringObject;

  • string 支持getline函数

         string s;

         getline(cin, s);

 

string的赋值和连接

用 ‘=’ 赋值

         string s1("cat"), s2;

         s2 = s1;

用assign成员函数复制

         string s1("cat"), s3;

         s3.assign(s1);

用assign成员函数部分复制

         string s1("catpig"), s3;

         s3.assign(s1, 1, 3);

//从s1 中下标为1的字符开始复制3个字符给s3

单个字符复制

                    s2[5] = s1[3] = ‘a’;

逐个访问string对象中的字符

string s1("Hello");

for(int i=0; i<s1.length(); i++)

                    cout << s1.at(i) << endl;

成员函数at会做范围检查, 如果超出范围, 会抛出out_of_range异常, 而下标运算符不做范围检查

 

用 + 运算符连接字符串

                    string s1("good "), s2("morning! ");

                    s1 += s2;

                    cout << s1;

用成员函数 append 连接字符串

                    string s1("good "), s2("morning! ");

                    s1.append(s2);

                    cout << s1;

                    s2.append(s1, 3, s1.size()); //s1.size(), s1字符数

                    cout << s2;

                    //下标为3开始, s1.size()个字符

                    //如果字符串内没有足够字符, 则复制到字符串最后一个字符

比较string

用关系运算符比较string的大小

• == , >, >=, <, <=, !=

• 返回值都是bool类型, 成立返回true, 否则返回false

• 例如:

string s1("hello"), s2("hello"), s3("hell");

bool b = (s1 == s2);

cout << b << endl;

b = (s1 == s3);

cout << b << endl;

b = (s1 > s3);

cout << b << endl;

 

用成员函数compare比较string的大小

string s1("hello"), s2("hello"), s3("hell");

int f1 = s1.compare(s2);

int f2 = s1.compare(s3);

int f3 = s3.compare(s1);

int f4 = s1.compare(1, 2, s3, 0, 3); //s1 1-2; s3 0-3

int f5 = s1.compare(0, s1.size(), s3); //s1 0-end

cout << f1 << endl << f2 << endl << f3 << endl;

cout << f4 << endl << f5 << endl;

 

输出

0 // hello == hello

1 // hello > hell

-1 // hell < hello

-1 // el < hell

1 // hello > hell

 

子串

成员函数 substr()

string s1("hello world"), s2;

s2 = s1.substr(4,5); //下标4开始5个字符

cout << s2 << endl;

 

交换string

string s1("hello world"), s2("really");

s1.swap(s2);

cout << s1 << endl;

cout << s2 << endl;

 

string的特性:

成员函数 capacity()

返回无需增加内存即可存放的字符数

成员函数maximum_size()

返回string对象可存放的最大字符数

成员函数length()和size()相同

返回字符串的大小/长度

成员函数empty()

返回string对象是否为空

成员函数resize()

改变string对象的长度

 

程序示例:

#include <iostream>

#include <string>

 

using namespace std;

 

int main()

{

    string s1("hello world");

    cout << s1.capacity() << endl;

    cout << s1.max_size() << endl;

    cout << s1.size() << endl;

    cout << s1.length() << endl;

    cout << s1.empty() << endl;

    cout << s1 << "aaa" << endl;

    s1.resize(s1.length() + 10);

    cout << s1.capacity() << endl;

    cout << s1.max_size() << endl;

    cout << s1.size() << endl;

    cout << s1.length() << endl;

    cout << s1 << "aaa" << endl;

    s1.resize(0);

    cout << s1.empty() << endl;

    return 0;

}

运行结果为:

 

寻找string中的字符:

成员函数 find()

string s1("hello world");

s1.find("lo");

//s1中从前向后查找 lo 第一次出现的地方

//如果找到, 返回 lo”开始的位置, l 所在的位置下标

//如果找不到, 返回 string::npos (string中定义的静态常量)

成员函数 rfind()

string s1("hello world");

s1.rfind("lo");

//s1中从后向前查找 lo 第一次出现的地方

//如果找到, 返回 lo”开始的位置, l 所在的位置下标

//如果找不到, 返回 string::npos

寻找string中的字符

 

成员函数 find_first_of()

string s1("hello world");

s1.find_first_of(“abcd");

//s1中从前向后查找 abcd 中任何一个字符第一次出现的地方

//如果找到, 返回找到字母的位置; 如果找不到, 返回 string::npos

成员函数 find_last_of()

string s1("hello world");

s1.find_last_of(“abcd");

//s1中查找 abcd 中任何一个字符最后一次出现的地方

//如果找到, 返回找到字母的位置; 如果找不到, 返回 string::npos

 

成员函数 find_first_not_of()

string s1("hello world");

s1.find_first_not_of(“abcd");

//s1中从前向后查找不在 abcd 中的字母第一次出现的地方

//如果找到, 返回找到字母的位置; 如果找不到, 返回 string::npos

成员函数 find_last_not_of()

string s1("hello world");

s1.find_last_not_of(“abcd");

//s1中从后向前查找不在 abcd 中的字母第一次出现的地方

//如果找到, 返回找到字母的位置; 如果找不到, 返回 string::npos

替换string中的字符

 

成员函数erase()

string s1("hello worlld");

s1.erase(5);

cout << s1;

cout << s1.length();

cout << s1.size();

// 去掉下标 5 及之后的字符

 

成员函数 find()

string s1("hello worlld");

cout << s1.find("ll", 1) << endl;

cout << s1.find("ll", 2) << endl;

cout << s1.find("ll", 3) << endl;

//分别从下标1, 2, 3开始查找 ll

替换string中的字符

输出:

2 2 9

 

成员函数 replace()

string s1("hello world");

s1.replace(2,3, “haha");

cout << s1;

//s1中下标2 开始的3个字符换成 haha

输出:

hehaha world

 

string s1("hello world");

s1.replace(2,3, "haha", 1,2);

cout << s1;

//s1中下标2 开始的3个字符

//换成 haha 中下标1开始的2个字符

替换string中的字符

输出:

heah world

 

成员函数 insert()

string s1(“hello world”);

string s2(“show insert”);

s1.insert(5, s2); // s2插入s1下标5的位置

cout << s1 << endl;

s1.insert(2, s2, 5, 3);

//s2中下标5开始的3个字符插入s1下标2的位置

cout << s1 << endl;

                                               

转换成C语言式char *字符串

成员函数 c_str()

string s1("hello world");

printf("%s\n", s1.c_str());

// s1.c_str() 返回传统的const char * 类型字符串

//且该字符串以 \0 结尾

 

成员函数data()

string s1("hello world");

const char * p1=s1.data();

for(int i=0; i<s1.length(); i++)

printf("%c",*(p1+i));

//s1.data() 返回一个char * 类型的字符串

//s1 的修改可能会使p1出错。

转换成C语言式char *字符串

输出:

hello world

 

成员函数copy()

string s1("hello world");

int len = s1.length();

char * p2 = new char[len+1];

s1.copy(p2, 5, 0);

p2[5]=0;

cout << p2 << endl;

// s1.copy(p2, 5, 0) s1的下标0的字符开始,

//制作一个最长5个字符长度的字符串副本并将其赋值给p2

//返回值表明实际复制字符串的长

7-5 输入输出类

与输入输出流操作相关的类

 

istream是用于输入的流类, cin就是该类的对象。

ostream是用于输出的流类, cout就是该类的对象。

ifstream是用于从文件读取数据的类。

ofstream是用于向文件写入数据的类。

iostream是既能用于输入,又能用于输出的类。

fstream 是既能从文件读取数据,又能向文件写入数据的类。

 

标准流对象

输入流对象: cin 与标准输入设备相连

输出流对象:   out与标准输出设备相连

                           cerr 与标准错误输出设备相连

                           clog 与标准错误输出设备相连

缺省情况下

cerr << "Hello,world" << endl;

clog << "Hello,world" << endl;

cout << “Hello,world” << endl; 一样

cin对应于标准输入流,用于从键盘读取数据,也可以被重定向

为从文件中读取数据。

cout对应于标准输出流,用于向屏幕输出数据,也可以被重定

向为向文件写入数据。

cerr对应于标准错误输出流,用于向屏幕输出出错信息,

clog对应于标准错误输出流,用于向屏幕输出出错信息,

cerr和clog的区别在于cerr不使用缓冲区,直接向显示器输出信

息;而输出到clog中的信息先会被存放在缓冲区,缓冲区满或者

刷新时才输出到屏幕。

 

判断输入流结束

可以用如下方法判输入流结束:

int x;

while(cin>>x){

…..

}

return 0;

如果是从文件输入,比如前面有freopen(“some.txt”,”r”,stdin);那么,读到文件尾部,输入流就算结束

如果从键盘输入,则在单独一行输入Ctrl+Z代表输入流结束

程序示例:

#define _CRT_SECURE_NO_WARNINGS  //该语句必须在#include<stdio.h>之前,否则还会报错

#include <iostream >

using namespace std;

int main() {

    double f; int n;

    freopen("D:\\1.txt", "r", stdin); //cin被改为从 t.txt中读取数据

   

    while (cin >> f >> n) {

        cout << f << "," << n << endl;

    }

    return 0;

}

运行结果:

 

istream类的成员函数

istream & getline(char * buf, int bufSize);

从输入流中读取bufSize-1个字符到缓冲区buf,或读到碰到‘\n’

为止(哪个先到算哪个) 。

istream & getline(char * buf, int bufSize,char delim);

从输入流中读取bufSize-1个字符到缓冲区buf,或读到碰到delim字

符为止(哪个先到算哪个) 。

两个函数都会自动在buf中读入数据的结尾添加\0’ 。 ,‘\n’ 或delim都不会被读入buf但会被从输入流中取走。 如果输入流中‘\n’或delim之前的字符个数达到或超过了bufSize个,就导致读入出错,其结果就是:虽然本次读入已经完成,但是之后的读入就

都会失败了。可以用 if(!cin.getline(…)) 判断输入是否结束

 

bool eof(); 判断输入流是否结束

int peek(); 返回下一个字符,但不从流中去掉.

istream & putback(char c); 将字符ch放回输入流

istream & ignore( int nCount = 1, int delim = EOF );从流中删掉最多nCount个字符,遇到EOF时结束

程序示例2

#include <iostream >

using namespace std;

int main() {

    int x;

    char buf[100];

    cin >> x;

    cin.getline(buf, 90);

    cout << buf << endl;

    return 0;

}

 

运行结果:

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值