Coursera课程笔记----C++程序设计----Week7

文件操作与模版(Week 7)

文件操作

数据的层次

  • 位 bit

  • 字节 byte

  • 域/记录

    • //举例,学生记录
      int ID;
      char name[10];
      int age;
      int rank[10];
      
  • 将所有记录顺序的写入一个文件,就是顺序文件

文件和流

  • 顺序文件的本质是一个有限字符构成的顺序字符流
  • C++标准库中,有ifstreamofstreamfstream共3个类,用于文件操作,统称为文件流类
  • image-20200524012631444

文件操作

  • 使用/创建文件的基本流程
    • 打开文件
      • 目的:通过指定文件名,建立文件和文件流对象的关联;指明文件的使用方式
    • 读/写文件
      • 利用读/写指针进行相应位置的操作
    • 关闭文件

建立顺序文件

#include <fstream> //包含头文件
ofstream outFile("client.dat",ios::out|ios::binary);//打开文件
  • ofstream是fstream中定义的类
  • outFile是自定义的ofstream类的对象
  • 参数1:将要建立的文件的文件名
  • 参数2:打开并建立文件的选项
    • ios::out 输出到文件,删除原有内容
    • ios:app 输出到文件,保留原有内容,总是在尾部添加
    • ios::binary 以二进制文件格式打开文件

  • 也可以先创建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));
//二进制文件读写,直接写进二进制数据,记事本看未必正确
//下面的程序从键盘输入几个学生的姓名和成绩
//并以二进制文件形式存起来
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
class CStudent{
  public:
  char szName[20];
  int nScore;
};
int main()
{
  CStudent s;
  ofstream OutFile("c:\\tmp\\students.dat",ios::out|ios::binary);
  while(cin>>s.szName>>s.nScore)
  {
    if(stricmp(s.szName,"exit") == 0) //名字为exit则结束
      break;
    OutFile.write((char*)&s,sizeof(s));
  }
  OutFile.close();
  return 0;
}
  • 文本文件/二进制文件打开文件的区别
    • 在Unix/Linux下,二者一致,没有区别
    • 在Windows下,文本文件是以"\r\n"作为换行符
      • 读出时,系统会将0x0d0a只读入0x0a
      • 写入时,对于0x0a系统会自动写入0x0d
//下面程序将student.dat文件的内容读出并显示
#include <iostream>
#include <fstream>
using namespace std;
class CStudent
{
  public:
  char szName[20];
  int nScore;
};

int main()
{
  CStudent s;
  ifstream inFile("students.dat",ios::in|ios::binary)
    if(!inFile)
    {
      cout<<"error"<<endl;
      return 0;
    }
  while(inFile.read((char*)&s,sizeof(s)))
  {
    int nReadedBytes = inFile.gcount();//看刚才读了多少字节
    cout<<s.szName<<" "<<s.score<<endl;
  }
  inFile.close();
  return 0;
}
//下面程序将student.dat文件的Jane的名字改成Mike
#include <iostream>
#include <fstream>
using namespace std;
class CStudent
{
  public:
  char szName[20];
  int nScore;
};
int main()
{
  CStudent s;
  fstream iofile("c:\\tmp\\students.dat",ios::in|ios::out|ios::binary);
  if(!iofile)
  {
    cout<<"error";
    return 0;
  }
  iofile.seekp(2*sizeof(s),ios::beg);//定位写指针到第三个记录
  iofile.write("Mike",strlen("Mike")+1);
  iofile.seekg(0,ios::beg);//定位读指针到开头
    while(iofile.read((char*)&s,sizeof(s)))
  {
    cout<<s.szName<<" "<<s.score<<endl;
  }
  iofile.close();
  return 0;
}

显式关闭文件

  • if stream fin("test.dat",ios::in);

    fin.close();

  • ofstream fout("test.dat",ios::out);

    fout.close();

示例:mycopy程序,文件拷贝

//用法示例:
//mycopy src.dat dest.dat
//将src.dat拷贝到dest.dat
//如果dest.dat原来就有,则原来的文件会被覆盖
#include <iostream>
#include <fstream>
using namespace std;

int main(int argc, char* argv[])
{
  if(argc != 3)
  {
    cout<<"File name missing!"<<endl;
    return 0;
  }
  ifstream inFile(argv[1],ios::binary|ios::in);//打开文件用于读
  if(!inFile)
  {
    cout<<"Source file open error."<<endl;
    return 0;
  }
  ofstream outFile(argv[2],ios::binary|ios::out);//打开文件用于写
  if(!outFile)
  {
    cout<<"New file open error."<<endl;
    inFile.close();//打开的文件一定要关闭
    return 0;
  }
  char c;
  while(inFile.get(c)) //每次读取一个字符
    outFile.put(c); //每次写入一个字符
  outFile.close();
  inFile.close();
  return 0;
}

函数模版

泛型程序设计

  • Generic Programming
  • 算法实现时不指定具体要操作的数据的类型
  • 泛型——算法实现一遍,即可适用于多种数据结构
  • 优势:减少重复代码的编写
  • 大量编写模版,使用模版的程序设计
    • 函数模版
    • 类模版

函数模版

  • 为了交换两个int变量的值,需要编写如下Swap函数:
void Swap(int &x, int &y)
{
  int tmp = x;
  x = y;
  y = tmp;
}
  • 为了交换两个double型变量的值,还需要编写一个类似的Swap函数(虽然存在重载,可以使两个Swap函数同名,但是因为参数的类型不同,函数需要重写)

  • 能否只写一个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;
}
  • 函数模版可以有多个类型参数
  • 求数组最大元素的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;
}
  • 函数模版可以重载,只要它们的形参表不同即可
  • C++编译器遵循以下优先顺序
    1. 先找参数完全匹配普通函数(非由模版实例化而得的函数)
    2. 再找参数完全匹配模版函数
    3. 再找实参经过自动类型转换后能够匹配的普通函数
    4. 都找不到则报错
  • 避免赋值兼容原则引起函数模版中类型参数的二义性(T是int还是double),可以在函数模版中使用多个类型参数,可以避免二义性(T1时int,T2是double)

类模版

问题引入

  • 想要定义一批相似的类
  • 通过定义类模版,能够方便的生成不同的类
  • 举例:
    • 数组
      • 一种常见的数据类型
      • 元素可以是整数、学生、字符串……
    • 考虑一个数组类
      • 需要提供一些基本操作:查看数组长度(len),获取其中的一个元素(get),赋值其中的一个元素(set)
      • 对于这些数组类,除了元素的类型不同之外,其他的完全相同
    • 类模版
      • 在定义类的时候给他一个/多个参数
      • 这个/些参数表示不同的数据类型

类模版的定义

  • C++的类模版的写法如下:
template<类型参数表>
class 类模版名
{
  成员函数和成员变量
};
  • 类模版里的成员函数,如在类模版外面定义时
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
  {return key<p.key;}
  • Pair类模版的使用
int main()
{
  Pair<string,int> student("Tom",19);
  //实例化出一个类Pair<string,int>
  cout<<student.key<<" "<<student.value;
  return 0;
}

使用类模版声明对象

  • 编译器由类模版生成类的过程叫类模版的实例化
    • 编译器自动用具体的数据类型,替换类模版中的类型参数,生成模版类的代码
  • 由类模版实例化得到的类叫模版类
    • 为类型参数指定的数据类型不同,得到的模版类也会不同
  • 同一个类模版生成的两个模版类是不兼容

函数模版作为类模版成员

#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;
}

类模版与非类型参数

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

    template <class T, int elementsNumber>

    • 非类型参数:用来说明类模版中的属性
    • 类型参数:用来说明类模版中的属性类型、成员操作的参数类型返回值类型

类模版与继承

  1. 类模版派生出类模版
template<class T1, class T2>
  class A{
    T1 v1;T2 v2;
  };
template<class T1, class T2>
  class B:public A<T2,T1>{ //对于B的模版类来说,v1应该是T2类型,v2应该是T1类型
    T1 v3;T2 v4;
  };
template <class T>
  class C:public B<T,T>{
    T v5;
  };
int main(){
  B<int,double> obj1;
  C<int> obj2;
  return 0;
}
  1. 模版类(即类模版中类型/非类型参数实例化后的类)派生出类模版
template <class T1, class T2>
  class A{T1 v1; T2 v2;};
template <class T>
  class B:public A<int,double>{T v;}
int main()
{
  B<char> obj1;
  return 0;
}
//自动生成两个模版类:A<int, double>和B<char>
  1. 普通类派生出类模版
class A {int v1;};

template <class T>
  class B: public A{ T v;};

int main(){
  B<char> obj1;
  return 0;
}
  1. 模版类派生出普通类
template <class T>
  class A{ T v1; int n;};

class B:public A<int> {double v;};
int main(){
  B obj1;
  return 0;
}

image-20200524231418594

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 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对象中的字符
    • 成员函数at会做范围检查,如果超出范围,会抛出out_of_range异常,而下标运算符不做范围检查
string s1("Hello");
for(int i=0; i<s1.length();i++)
  cout<<s1.at(i)<<endl;
  • 用+运算符连接字符串
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;
b1 = (s1 > s3);
cout << b << endl;

子串

  • 成员函数 substr()
string s1("hello world"),s2;
s2 = s1.substr(4,5);//下标4开始,5个字符
cout << s2 << endl;

寻找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",1) << endl;
cout << s1.find("ll",1) << endl;
//分别从下标1,2,3开始查找"ll"
  • 成员函数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个字符
//输出:heah world

在string中插入字符

  • 成员函数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'结尾

输入与输出

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

image-20200525175437731

  • istream是用于输入的流类,cin就是该类的对象
  • ostream是用于输出的流类,cout就是该类的对象
  • ifstream是用于从文件读取数据的类
  • ofstream是用于向文件写入数据的类
  • iostream是既能用于输入,又能用于输出的类
  • fstream是既能从文件读取数据,又能向文件写入数据的类

标准流对象

  • 输入流对象:

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

    • cout:与标准输出设备相连
    • cerr:与标准错误输出设备相连
    • clog:与标准错误输出设备相连
  • cin对应于标准输入流,用于从键盘读取数据,也可以被重定向为从文件中读取数据

  • cout对应于标准输出流,用于向屏幕输出数据,也可以被重定向为向文件写入数据

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

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

  • cerr和clog的区别在于cerr不使用缓冲区,直接向显示器输出信息;而输出到clog中的信息先会被存放在缓冲区,缓冲区满或者刷新时才输出到屏幕

输出重定向

#include <iostream>
using namespace std;
int main(){
  int x,y;
  cin >> x >> y;
  freopen("test.txt","w",stdout);//将标准输出重定向到test.txt文件,把stdout重定向到test.txt,w代表写
  if(y == 0) //若除数为0则在屏幕上输出错误信息
    cerr << "error." << endl;
  else
    cout<<x/y;//输出结果到test.txt
  return 0;
}

输入重定向

#include <iostream>
using namespace std;
int main(){
  double f;
  int n;
  freopen("t.txt","r",stdin);//cin被改为从t.txt中读取数据,r代表读
  cin >> f >> n;
  cout << f << "," << n << endl;
  return 0;
}

判断输入流结束

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

int x;
while(cin >> x){ //存在强制类型转换符的重载
  ...
}
return 0;
  • 如果是从文件输入,比如前面有freopen("some.txt","r",stdin); 那么读到文件尾部,输入流就算结束
  • 如果从键盘输入,则在单独一行输入Ctrl+Z代表输入流结束

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.getliine(...))判断输入是否结束

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

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

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

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

#include <iostream>
using namespace std;
int main(){
  int x;
  char buf[100];
  cin >> x;
  cin.getline(buf,90);
  cout<<buf<<endl;
  return 0;
}

image-20200525210434671

练习题

Quiz 1

#include <iostream>
using namespace std;
// 在此处补充你的代码
template <class T>
class CArray3D{
private:
    T *** _array;
    int _r,_c,_l;
public:
    CArray3D(int r,int c,int l)
    {
        _array = new T **[r];
        for (int i = 0; i < r; i++) {
            _array[i] = new T* [c];
        }
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                _array[i][j] = new T[l];
            }
        }
        _r = r; _c = c; _l = l;
    }

    T ** operator[] (int index)
    {
        return _array[index];
    }
};
int main()
{
    CArray3D<int> a(3,4,5);
    int No = 0;
    for( int i = 0; i < 3; ++ i )
        for( int j = 0; j < 4; ++j )
            for( int k = 0; k < 5; ++k )
                a[i][j][k] = No ++;
    for( int i = 0; i < 3; ++ i )
        for( int j = 0; j < 4; ++j )
            for( int k = 0; k < 5; ++k )
                cout << a[i][j][k] << ",";
    return 0;
}

Quiz 2

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

int main()
{
    double a;
    cin >> a;
    cout << setiosflags(ios::fixed) << setprecision(5) << a << endl;
    cout << scientific << setprecision(7) << a << endl;
}

Quiz 3

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

int main()
{
    int a;
    cin >> a;
    cout << hex << a << endl;
    cout << dec << setw(10) << setfill('0')<< a << endl;
}

Quiz 4

/**********************
author KINGRAIN@EECS_PKU
time June 10,2010
dorm
poj3430
给定n个字符串(从1开始编号),每个字符串中的字符位置从0开始编号,长度为1-500,现有如下若干操作:
copy N X L:取出第N个字符串第X个字符开始的长度为L的字符串。
add S1 S2:判断S1,S2是否为0-99999之间的整数,若是则将其转化为整数做加法,若不是,则作字符串加法,返回的值为一字符串。
find S N:在第N个字符串中从左开始找寻S字符串,返回其第一次出现的位置,若没有找到,返回字符串的长度。
rfind S N:在第N个字符串中从右开始找寻S字符串,返回其第一次出现的位置,若没有找到,返回字符串的长度。
insert S N X:在第N个字符串的第X个字符位置中插入S字符串。
reset S N:将第N个字符串变为S。
print N:打印输出第N个字符串。
printall:打印输出所有字符串。
over:结束操作。
其中N,X,L可由find与rfind操作表达式构成,S,S1,S2可由copy与add操作表达式构成。
***********************/
/**********************
解题心得
S1 S2可能大于99999
N X L 可能小于0
使用构造函数主要是受到大牛的影响 本来直接调用函数就行了…… 看起来比较高级,但其实有点麻烦
**********************/
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

string str[22];
string commend;
int N;
inline string MyCopy(); // copy N X L:取出第N个字符串第X个字符开始的长度为L的字符串。
inline string MyAdd(); // add S1 S2:判断S1,S2是否为0-99999之间的整数,若是则将其转化为整数做加法,若不是,则作字符串加法,返回的值为一字符串。
inline int MyFind(); // find S N:在第N个字符串中从左开始找寻S字符串,返回其第一次出现的位置,若没有找到,返回字符串的长度。
inline int MyRfind(); // rfind S N:在第N个字符串中从右开始找寻S字符串,返回其第一次出现的位置,若没有找到,返回字符串的长度。
inline void MyInsert(); // insert S N X:在第N个字符串的第X个字符位置中插入S字符串。
inline void MyReset(); // reset S N:将第N个字符串变为S。
struct GETS
{
    GETS(string &s) // 递归获得真正的s串
    {//因为S可以是字符串、copy表达式、add表达式,所以此处需要分情况处理
        cin >> s;
        if (s=="copy")
            s = MyCopy();
        else if (s=="add")
            s = MyAdd();
    }
};

struct GETINT
{
    string Commend;
    GETINT(int &n) // 递归获得真正的int n
    {//因为int型变量N,X,L可由整数、find与rfind操作表达式构成,所以此处要分情况处理
        cin >> Commend;
        if (Commend=="find")
            n = MyFind();
        else if (Commend=="rfind")
            n = MyRfind();
        else
            n = atoi(Commend.c_str());
    }
};

struct GETSTRING
{
    GETSTRING(int &m, string &s) // 递归获得真正的s串 并判断其是否为整数
    {
        GETS Gets(s);
        int i = 0;
        for (m=0; i<s.length(); i++)
            if ((s.at(i)>='0')&&(s.at(i)<='9'))
                m = m * 10 + s.at(i)-'0';
            else
                break;
        if ((i!=s.length())||(m>99999))
            m = -1;
    }
};

int main()
{
    cin >> N;
    for (int i=0; i<N; i++)
        cin >> str[i+1];
    while (cin >> commend)
    {
        if (commend=="over")
            break;
        switch(commend.at(1))
        {
            case 'n': MyInsert(); break;
            case 'e': MyReset(); break;
            case 'r': if (commend=="print")
                {
                    int n;
                    cin >> n;
                    cout << str[n] << endl;
                }
                else
                {
                    for (int j=1; j<=N; j++)
                        cout << str[j] << endl;
                }
                break;
        }
    }
    return 0;
}

inline string MyCopy()
{
    int n, x, l;
    GETINT getintn(n);
    GETINT getintx(x);
    GETINT getintl(l);
    return (str[n].substr(x,l));
}

inline string MyAdd() // add S1 S2:判断S1,S2是否为0-99999之间的整数,若是则将其转化为整数做加法,若不是,则作字符串加法,返回的值为一字符串。
{
    string s1,s2;
    int m=-1, n=-1;
    GETSTRING getstringms1(m,s1);
    GETSTRING getstringns2(n,s2);
    if ((m==-1)||(n==-1))
        return (s1+s2);
    else
    {
        m += n;
        char chars[8];
        sprintf(chars,"%d",m);
        return chars;
    }
}


inline int MyFind() // find S N:在第N个字符串中从左开始找寻S字符串,返回其第一次出现的位置,若没有找到,返回 ?? 哪个 ?? 字符串的长度。
{
    string s;
    int n,value;
    cin >> s;
    GETINT getintn(n);
    value = str[n].find(s);
    if (value==string::npos)
        value = str[n].length();
    return value;
}

inline int MyRfind() // rfind S N:在第N个字符串中从右开始找寻S字符串,返回其第一次出现的位置,若没有找到,返回字符串的长度。
{
    string s;
    int n,value;
    cin >> s;
    GETINT getintn(n);
    value = str[n].rfind(s);
    if (value==string::npos)
        value = str[n].length();
    return value;
}

inline void MyInsert() // insert S N X:在第N个字符串的第X个字符位置中插入S字符串。
{
    string s;
    int n,x;
    GETS Gets(s);
    GETINT getintn(n);
    GETINT getintx(x);
    str[n].insert(x,s);
}

inline void MyReset() // reset S N:将第N个字符串变为S。
{
    string s;
    int n;
    GETS Gets(s);
    GETINT getintn(n);
    str[n].assign(s);
}
//还是一道没思路的题,查的答案
//sprintf 最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf 在大多数场合可以替代itoa
//如:把整数123 打印成一个字符串保存在s 中。
//sprintf(s, "%d", 123); 产生"123"
posted @ 2020-05-31 13:17  maimai_d  阅读(201)  评论(0编辑  收藏  举报