• 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下,文本文件是以" "作为换行符
        • 读出时,系统会将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
    ",s1.c_str());
    //s1.c_str()返回传统的const char *类型字符串
    //且该字符串以''结尾
    

    输入与输出

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

    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,或读到碰到' '为止(哪个先到算哪个)
    istream & getline(char * buf, int bufSize, char delim);
    
    • 从输入流中读取bufSize-1个字符到缓冲区buf,或读到碰到delim字符为止(哪个先到算哪个)

    • 两个函数都会自动在buf中读取数据的结尾添加。 ' '或delim都不会被读入buf,但会被从输入流中取走。如果输入流中' '或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"
    
  • 相关阅读:
    大白话解说,半分钟就懂 --- 分布式与集群是什么 ? 区别是什么?
    intellij idea中去除@Autowired注入对象的红色波浪线提示
    用JQuery获取事件源怎么写
    springBoot 配置url 访问图片
    地图服务 纬度、经度对应坐标轴x,y
    5个问题带你了解export和import的使用以及export和export defalut 的区别
    5个你可能不知道的html5语义化标签
    CSS选择器[attribute | = value] 和 [attribute ^ = value]的区别
    前端ps实用小技巧
    7步教你使用git命令上传本地代码至github仓库(小白向)
  • 原文地址:https://www.cnblogs.com/maimai-d/p/12997289.html
Copyright © 2020-2023  润新知