hugo/content/posts/bj_homework.md
2022-11-12 20:20:58 +08:00

50 KiB
Raw Blame History

+++ title = "Bj Homework" date = 2022-10-12 draft = false author = "Logic" +++

第一部分

  1. str大小为指针的大小即8字节p的大小也为指针的大小即8字节。因为C++传数组给一个函数,数组类型自动转换为指针类型,因而传的实际是地址。创建一个数组,数组名实际为一指针变量,指向该数组的起始地址。
  2. 运行测试的结果为段错误。这是因为p是形参p相当于str指针的复制在函数内部new申请新内存后将内存地址赋给p并不会改变str指针的值。故str指针仍为NULL则在将数据复制到str指针指向的地址时会段错误。
  3. 运行结果为一串未知字符这是因为函数内部申请的变量为局部变量其作用域仅限于函数执行过程中在函数执行结束后局部变量即这里的p指向的栈空间中的内存区域就会被释放返回值只是p指针的拷贝指向原来p指向的地址。此时返回的指针指向的地址中的数据是不确定的故可能会打印出一串未知字符或出现错误。 4. 会正常打印出“你好世界”。
  4. 会打印出“世界”因为使用new在堆上分配了内存后str指向分配的这片内存即str保存了这片内存的起始地址而使用delete清理这片内存只是回收了这片内存空间并没有将str指针重置。str仍然指向这片内存空间则str并非空指针strcpy可以正常的复制字符串printf也可以正常打印输出。

第三次作业

  1. 使用I/O流以文本方式建立一个文件test1.txt写入字符"已成功写入文件!"用其他字处理程序例如Windows记事本程序Notepad打开看看是否正确写入。 本题比较简单只需掌握C++中基本的I/O输入输出方式即可这里重要的是掌握C++中流的概念。这一部分可以参考 C++ Prime Plus(第6版)中文版 第17章相关内容。简单理解流相当于管道在文件硬件也可视为文件和程序之间传递字节数据。因此文件流相关的类fstream对应的重载如<<和标准输入输出流iostream中的用法类似。则代码如下

    #include <fstream>
    #include <iostream>
    using namespace std;
    
    int main() {
      fstream iofile("test1.txt", ios::out);
      iofile << "已成功写入文件!";
      cout << "写入成功";
      iofile.close();
    }
    

编译运行如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1004jRF7OH.png" >}}

查看文件内容如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1004TxWH43.png" >}}

  1. 声明一个dog类包含体重合年龄两个成员变量及相应的成员两数声明一个实例dog1.体重为5年龄为10使用I/O流把dog1的状态写入磁盘文件再声明另一个实例dog2通过读文件把dog1的状态赋给dog2。分别使用文本方式和二进制方式操作文件看看有何不同再看看磁盘文件的ASCI码有何不同。

这个题只是在第一个题的基础上结合面向对象编程,简单的声明一个类并构造两个成员函数,一个用于修改对象的信息,一个用于打印对象的信息。在打开文件流的时候用两种不同的方式打开文件流即可。

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
using namespace std;

class dog {
public:
  int weight;
  int age;

  void change_attr(int x1, int x2) {
    weight = x1;
    age = x2;
  }

  // 打印dog信息
  void print() { cout << "weight: " << weight << "  age: " << age << endl; }

  dog() {}
};

int main() {
  string line;
  dog dog1;
  dog1.change_attr(5, 10);

  // 文本模式
  fstream doginfo("./test2.txt", ios::out);
  // cout << doginfo.is_open();
  // 写入文件
  doginfo << dog1.weight << " " << dog1.age;
  dog dog2;
  doginfo.close();
  doginfo.open("./test2.txt", ios::in);
  getline(doginfo, line);
  stringstream ss;
  // 读取文件内容转换成字符串流并读入
  string wei;
  string ag;
  ss << line;
  ss >> wei >> ag;
  int x1 = stoi(wei), x2 = stoi(ag);
  dog2.change_attr(x1, x2);
  dog2.print();
  doginfo.close();

  // 二进制模式
  doginfo.open("test3.txt", ios::out | ios::binary);
  // 写入文件
  doginfo << dog1.weight << " " << dog1.age;
  doginfo.close();
  doginfo.open("test3.txt", ios::in | ios::binary);
  getline(doginfo, line);
  ss << line;
  ss >> wei >> ag;
  x1 = stoi(wei);
  x2 = stoi(ag);
  dog2.change_attr(stoi(wei), stoi(ag));
  dog2.print();
  doginfo.close();
}

运行结果为

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1007fXhkIJ.png" >}}

用文本编辑器打开test2.txt和test3.txt得

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1007LKkmym.png" >}}

这里并没有显示出什么差异主要是因为对于可见字符来说使用二进制方式和文本方式差异不大但是对于一些不可见字符如文件的文件头等二进制读取方式会将数据原封不动的读取出来而文本则会处理为文本后读取因为txt文件除了编码类型外无文件头所以二者没什么区别但对于其他有格式文件来说可能就会存在区别另外以文本方式打开时遇到结束符CTRLZ(0x1A)就认为文件已经结束。所以,若使用文本方式打开二进制文件,就很容易出现文件读不完整,或內容不对的错误。即使是用文本方式打开文本文件,也要谨慎使用,比如复制文件,就不应该使用文本方式。

  1. 编写程序提示用户输入一个十进制整数.分别用十进制、八进制、和十六进制形式输出。

对进制进行转换可以使用短除反取余的方式除对应进制数然后反向取余数即可。但是如果用int 或者long int类型存储输入的十进制数并进行转换能转换的数字大小是有限的在考虑可能有大整数的情况下使用字符串存储并进行大整数的模数运算从而实现了大整数的进制转换。该程序可以输入任意大的整数都可以完成进制转换。

#include <algorithm>
#include <atomic>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

// 字符转数字
int ctoi(char num) { return num - '0'; }

// 8进制大整数转换
pair<string, int> big_mod_eight(string now) {
  string quo;
  int remain;
  int before = ctoi(now[0]);
  if (now.length() == 1) {
    remain = before % 8;
    int result = before / 8;
    quo = to_string(result);
    return make_pair(quo, remain);
  }
  before = 0;
  for (int i = 0; i < now.length(); i++) {
    int number = before * 10 + ctoi(now[i]);
    before = number % 8;
    remain = number / 8;
    quo = quo + to_string(remain);
  }
  if (quo[0] == '0')
    quo.erase(quo.begin());
  return make_pair(quo, before);
}

// 16进制大整数转换
pair<string, int> big_mod_hex(string now) {
  string quo;
  int remain;
  // 只有一位直接返回
  if (now.length() == 1) {
    remain = ctoi(now[0]);
    quo = "0";
    return make_pair(quo, remain);
  }
  // 两位及以上进行处理
  int before = ctoi(now[0]);
  for (int i = 1; i < now.length(); i++) {
    int number = before * 10 + ctoi(now[i]);
    if (number < 16) {
      quo += "0";
      i++;
      if (i < now.length())
        number = number * 10 + ctoi(now[i]);
    }
    before = number % 16;
    remain = number / 16;
    quo = quo + to_string(remain);
  }
  if (quo[0] == '0')
    quo.erase(quo.begin());
  return make_pair(quo, before);
}

int main(int argc, char *argv[]) {

  string interger;
  vector<int> convert;

  while (true) {
    cout << "Please enter a decimal, and q for quit: ";
    cin >> interger;
    // interger = "23";
    string result;
    int remain;
    pair<string, int> temp;
    result = interger;
    if (interger == "q") {
      break;
    } else {
      cout << "Dec = " << interger;
      while (result != "0") {
        temp = big_mod_eight(result);
        result = temp.first;
        remain = temp.second;
        convert.push_back(remain);
      }
      cout << " Oct = ";
      // 将结果逆序
      reverse(begin(convert), end(convert));
      for (auto n : convert) {
        cout << n;
      }
      convert.clear();

      result = interger;
      while (result != "0") {
        temp = big_mod_hex(result);
        result = temp.first;
        remain = temp.second;
        convert.push_back(remain);
      }
      cout << " Hex = ";
      // 将结果逆序
      reverse(begin(convert), end(convert));
      for (auto n : convert) {
        cout << hex << n;
      }
      cout << endl;
      convert.clear();
    }
  }
  return 0;
}

运行结果图如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1011Jy1fbS.png" >}}

  1. 编写程序实现如下功能:打开指定的一个文木文件,在每一个行前加行号。

本题考察C++基本的文件IO和文件指针比较方便的做法是用一个中间文件首先读取原文件的内容每读取一行加一个行号并写入到中间文件中最后将加了行号的中间文件内容再写入原文件并删除中间文件。

#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

int main() {
  cout << "Please input the filename you want to open: ";
  string filename;
  cin >> filename;
  ifstream fin(filename);
  string backup = filename + ".back_up";
  ofstream fout(backup);
  int number = 1;
  string sline;
  while (true) {
    getline(fin, sline);
    if (fin.eof())
      break;
    fout << number << " " << sline << endl;
    number++;
  }
  fin.close();
  fout.close();
  fout.open(filename);
  fin.open(backup);
  while (true) {
    getline(fin, sline);
    if (fin.eof())
      break;
    fout << sline << endl;
  }
  fin.close();
  fout.close();
  // 删除临时文件
  const char *filerename = backup.c_str();
  if (remove(filerename) == 0) {
    cout << "Add line number success!";
  } else {
    cout << "Remove backup file error!";
  }
  return 0;
}

效果如下,原文件内容为

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1011AbNMl5.png" >}}

运行程序后,文件内容为

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1011d5ZAut.png" >}}

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1011FgrC32.png" >}}

  1. 定义一个保存学生信息的结构体STUDENT包含的学生信息有学号、姓名、专业和平均分。其中学号和平均分使用整型姓名和专业使用宁符数组。使用动态数组存储学生信息并编写菜单实现学生信息的录入、删除和显示功能。由手录入学生的数量未知因此要使用new运算符实现动态内存的分配、使用delete运算符实现动态内存的回收。另外使用标准流对象cin和cout完𢦓数据的输入输出使用函数重载(例如添加学生到数组时可以采用不同的参数列表、显示学生信息时可以指定成绩区间等)、默认形参、引用变量;以上功能实现在自定义的名字空间中(建议使用学号做名字空间)。

定义命名空间为zqy19281235定义STUDENT结构体定义添加学生函数使用默认形参和引用变量。考虑到学号的唯一性使用map模板来存储学生信息。并将map通过引用传入函数通过学号删除学生信息并释放申请的内存重载显示学生信息的函数当不输入指定的成绩区间时默认全部显示当输入指定成绩区间时可以仅指定最大值或者最小值另一个使用默认形参进行处理。代码如下

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

namespace zqy19281235 {
struct STUDENT {
  int sno;
  string name;
  string specialty;
  int average;
};

// 有默认形参,使用引用变量
void add_stu(map<int, struct STUDENT *> &students, int sno, string name,
             string specialty, int average = 60) {
  struct STUDENT *new_stu = new (struct STUDENT);
  new_stu->sno = sno;
  new_stu->name = name;
  new_stu->specialty = specialty;
  new_stu->average = average;
  students.insert(make_pair(sno, new_stu));
}

void del_stu(map<int, struct STUDENT *> &students, int sno) {
  // 释放内存
  delete (students.find(sno)->second);
  students.erase(sno);
  cout << "Delete success!";
}

// 默认全部显示
void look_up_stu(map<int, struct STUDENT *> &students) {
  for (auto stu : students) {
    cout << "Sno = " << stu.second->sno << " Name = " << stu.second->name
         << " Specialty = " << stu.second->specialty
         << " Average = " << stu.second->average << endl;
  }
}

// 重载的显示函数
void look_up_stu(map<int, struct STUDENT *> &students, int flag, int min = 0,
                 int max = 100) {
  for (auto stu : students) {
    if (stu.second->average >= min && stu.second->average <= max) {
      cout << "Sno = " << stu.second->sno << " Name = " << stu.second->name
           << " Specialty = " << stu.second->specialty
           << " Average = " << stu.second->average << endl;
    }
  }
}

} // namespace zqy19281235

int main() {
  char select;
  cout << "Welcome to my student manage system!!!!!" << endl;
  map<int, zqy19281235::STUDENT *> students;
  while (true) {
    cout << "Please select the operation you want:" << endl
         << "\t1.add a student.\n"
         << "\t2.delete a student\n"
         << "\t3.display students infomation\n"
         << "\tq for quit\n"
         << "your select:";
    cin >> select;
    switch (select) {
    case '1': {
      int number, average;
      string aver;
      string name, specialty;
      cout << "The student number:";
      cin >> number;
      cout << "The student name:";
      cin >> name;
      cout << "The student specialty:";
      cin >> specialty;
      cin.get();
      cout << "The student average(can be nothing):";
      getline(cin, aver);
      if (aver.length() == 0) {
        zqy19281235::add_stu(students, number, name, specialty);
      } else {
        average = stoi(aver);
        zqy19281235::add_stu(students, number, name, specialty, average);
      }
      break;
    }
    case '2': {
      int number;
      cout << "The student number:";
      cin >> number;
      zqy19281235::del_stu(students, number);
      break;
    }
    case '3': {
      string min, max;
      cout << "Enter the min average of the students:";
      cin.get();
      getline(cin, min);
      cout << "Enter the max average of the students:";
      getline(cin, max);
      if (min.length() == 0 && max.length() == 0) {
        zqy19281235::look_up_stu(students);
        break;
      } else if (max.length() == 0) {
        zqy19281235::look_up_stu(students, 1, stoi(min));
      } else if (min.length() == 0) {
        zqy19281235::look_up_stu(students, 1, 0, stoi(max));
      } else {
        zqy19281235::look_up_stu(students, 1, stoi(min), stoi(max));
      }
      break;
    }
    case 'q': {
      return 0;
    }
    default:
      break;
    }
  }
}

功能展示如下:

  • 添加学生

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1012YB8d7l.png" >}}

  • 显示学生信息(无限制)

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1012NSCVin.png" >}}

  • 显示学生信息(有限制)

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1012JkQKxs.png" >}}

  • 删除学生信息

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1012uB9SkP.png" >}}

第四次作业

1

设计并测试一个名为Rectangle的矩形类其属性为矩形的左下角与右上角两个点的坐标提供能计算矩形的面积的成员函数。

设计一个结构体表示点坐标设计矩形类的属性为两个坐标成员函数为计算面积。并在main函数中简单测试代码如下

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

typedef struct point {
  int x;
  int y;
} Point;

// 方形类
class Rectangle {
public:
  Point left_bottom;
  Point right_top;
  Rectangle(Point left, Point right) {
    left_bottom.x = left.x;
    left_bottom.y = left.y;
    right_top.x = right.x;
    right_top.y = right.y;
  };
  int area() {
    return abs(right_top.x - left_bottom.x) * abs(right_top.y - left_bottom.y);
  }
};

int main(int argc, char *argv[]) {
  Point point1 = {1, 1};
  Point point2 = {2, 3};
  Rectangle *rec1 = new Rectangle(point1, point2);
  printf("%d", rec1->area());
  return 0;
}

测试结果如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1026iUdZ0b.png" >}}

2

设计一个用于人事管理的“人员”类。由于考虑到通用性,这里只抽象出所有类型人员都具有的属性:编号、性别、出生日期、身份证号等。其中“出生日期”声明为一个“日期”类内嵌对象。用成员函数实现对人员信息的录入和显示。要求包括:构造函数和析构函数、拷贝构造函数、内联成员函数、带默认形参值的成员函数、类的组合。

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

class date {
public:
  string birthday;
};

class Person {
public:
  string pno;
  string sex;
  string idno;
  Person();
  Person(const Person &obj);
  void add(string x, string y, string z, string xx);
  inline void display();
  ~Person();

private:
  date date_;
};

Person::Person() {}

Person::~Person() {}

Person::Person(const Person &obj) {
  cout << "call copy function";
  pno = obj.pno;
  sex = obj.sex;
  idno = obj.idno;
  date_.birthday = obj.date_.birthday;
}

void Person::add(string x, string y, string z, string xx = "00000000") {
  pno = x;
  sex = y;
  idno = xx;
  date_.birthday = z;
}

inline void Person::display() {
  cout << "Info of this Person is: " << endl
       << "No: " << pno << "\tSex: " << sex << "\tBirthday:" << date_.birthday
       << "\tIdno" << idno << endl;
}

3

定义一个Cat类拥有静态数据成员HowManyCats记录Cat的对象个体数目静态成员函数GetHowMany()存取HowManyCats。非静态数据成员CatID记录当前对象的编号成员函数GetCatID()存取CatID。设计程序测试这个类生成若干个Cat对象输出每个对象的数据成员值体会静态成员和非静态成员的用法。

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

class Cat {
public:
  static int HowManyCats;
  static void GetHowMany() { cout << HowManyCats << endl; }
  string CatID;
  void GetCatID() { cout << CatID << endl; }
  Cat(string);
  ~Cat();

private:
};

int Cat::HowManyCats = 0;

Cat::Cat(string id) {
  HowManyCats++;
  CatID = id;
}

Cat::~Cat() { HowManyCats--; }

int main(int argc, char *argv[]) {
  string IDs[5] = {"aaaaaaaa", "bbbbbbbbb", "cccccc", "dddddddd", "eeeeeeeee"};
  vector<Cat *> cats;
  // 实例化Cat
  for (int i = 0; i < 5; i++) {
    Cat *newcat = new Cat(IDs[i]);
    cats.push_back(newcat);
  }

  // 打印所有对象
  for (int i = 0; i < 5; i++) {
    cats[i]->GetCatID();
    cats[i]->GetHowMany();
    delete cats[i];
  }

  return 0;
}

定义Cat类后在main中实例化五个Cat并输出。输出时输出CatID和HowManyCats。随后删除该实例。在构造函数中每实例化一个Cat就将HowManyCats加一。删除时则减一。测试结果如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1027RWGklD.png" >}}

4

设计一个字符串类MyString要求如下

(1) 正确封装数据,要求使用动态内存分配的方式管理封装的数据;

(2) 正确实现构造函数和析构函数,正确初始化数据,防止内存泄露;构造函数要有多个重载版本;

(3) 实现存取字符串的功能其中取字符串的函数为get_string存字符串的函数为set_string

(4) 实现追加字符串的功能、取得字符串长度的功能;

(5) 要求类的声明写在MyString.h文件中类的实现写在MyString.cpp文件中。

编写函数测试你所设计的类。

使用char指针指示字符串在构造函数和set_string中为指针动态分配内存并将字符串拷贝过去连接字符串则重新申请空间并释放之前申请的空间然后进行字符串的拷贝。代码如下

// MyString.h

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

class MyString {
private:
  char *str;
  int length;

public:
  MyString();
  MyString(const char *s);
  char *get_string();
  void set_string(char *s);
  int get_length();
  void append(char *s);
  ~MyString();
};
// MyString.cpp

#include "MyString.h"
#include <cstring>
using namespace std;

MyString::MyString(const char *s) {
  length = strlen(s);
  str = new char[length + 1];
  str[length] = '\0';
  //安全拷贝
  strncpy(str, s, length + 1);
}
MyString::~MyString(){};

char *MyString::get_string() { return str; }
void MyString::set_string(char *s) {
  length = strlen(s);
  str = new char[length + 1];
  str[length] = '\0';
  //安全拷贝
  strncpy(str, s, length + 1);
}
int MyString::get_length() { return length; }
void MyString::append(char *s) {
  length = length + strlen(s);
  char *str_temp = new char[length + 1];
  str_temp[length] = '\0';
  strncpy(str_temp, str, length + 1);
  strncat(str_temp, s, length + 1);
  delete str;
  str = str_temp;
}

int main(int argc, char *argv[]) {
  char *first = "This is first string";
  char *second = "This is second";
  MyString mystring(first);
  cout << "Length: " << mystring.get_length() << endl;
  cout << "String is : " << mystring.get_string() << endl;
  // 添加第二字符串
  mystring.append(second);
  cout << "Length: " << mystring.get_length() << endl;
  cout << "String is : " << mystring.get_string() << endl;
  return 0;
}


在main中定义两个字符串并测试得

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1027hTiJxJ.png" >}}

5

尝试封装学生列表类。对于学生信息包含姓名、学号、成绩等封装一个学生列表类CStudentList在这个类中实现增加、按学号删除、修改学生信息、显示学生信息的功能其中保存学生的信息应该使用动态内存的方法以实现保存"无限"的学生信息。其他要求如下:

(1) 至少显示学生信息的函数要有重载版本,比如可选择根据学生学号显示学生信息、根据学生成绩显示学生信息、根据给定的成绩区间显示学生的信息(此时要练习默认形参值)。

(2) 练习引用,比如可设计根据姓名取得学生信息并以引用的形式返回该学生信息的函数;对于按引用返回的学生信息,可实现修改其成绩的功能。

(3) 学生信息可以仍然使用结构体来表示。

(4) 重点是懂得封装的思想,理解构造函数和析构函数的设计和使用。

注意:由于还没有介绍复制构造函数和赋值运算符函数,所以该类会有不少问题,但对于简单的增加、按学号删除和显示学生信息的功能,还是要能够正常运行的。

这个学生类中的部分函数在之前作业中已经实现过,这里就直接拿来使用。主要是要将函数和数据进行封装。代码如下

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

struct STUDENT {
  int sno;
  string name;
  string specialty;
  int average;
};

class Stu_info {
public:
  Stu_info(){};
  ~Stu_info(){};
  // 有默认形参,使用引用变量
  void add_stu(map<int, struct STUDENT *> &students, int sno, string name,
               string specialty, int average = 60) {
    struct STUDENT *new_stu = new (struct STUDENT);
    new_stu->sno = sno;
    new_stu->name = name;
    new_stu->specialty = specialty;
    new_stu->average = average;
    students.insert(make_pair(sno, new_stu));
  }

  void del_stu(map<int, struct STUDENT *> &students, int sno) {
    // 释放内存
    delete (students.find(sno)->second);
    students.erase(sno);
    cout << "Delete success!";
  }
  // 默认全部显示
  void look_up_stu(map<int, struct STUDENT *> &students) {
    for (auto stu : students) {
      cout << "Sno = " << stu.second->sno << " Name = " << stu.second->name
           << " Specialty = " << stu.second->specialty
           << " Score = " << stu.second->average << endl;
    }
  }

  // 重载的显示函数
  void look_up_stu(map<int, struct STUDENT *> &students, int flag, int min = 0,
                   int max = 100) {
    for (auto stu : students) {
      if (stu.second->average >= min && stu.second->average <= max) {
        cout << "Sno = " << stu.second->sno << " Name = " << stu.second->name
             << " Specialty = " << stu.second->specialty
             << " Score = " << stu.second->average << endl;
      }
    }
  }
};

int main() {
  char select;
  cout << "Welcome to my student manage system!!!!!" << endl;
  Stu_info *stu_info = new Stu_info();
  map<int, STUDENT *> students;
  while (true) {
    cout << "\nPlease select the operation you want:" << endl
         << "\t1.add a student.\n"
         << "\t2.delete a student\n"
         << "\t3.display students infomation\n"
         << "\tq for quit\n"
         << "your select:";
    cin >> select;
    switch (select) {
    case '1': {
      int number, average;
      string aver;
      string name, specialty;
      cout << "The student number:";
      cin >> number;
      cout << "The student name:";
      cin >> name;
      cout << "The student specialty:";
      cin >> specialty;
      cin.get();
      cout << "The student score(can be nothing):";
      getline(cin, aver);
      if (aver.length() == 0) {
        stu_info->add_stu(students, number, name, specialty);
      } else {
        average = stoi(aver);
        stu_info->add_stu(students, number, name, specialty, average);
      }
      break;
    }
    case '2': {
      int number;
      cout << "The student number:";
      cin >> number;
      stu_info->del_stu(students, number);
      break;
    }
    case '3': {
      string min, max;
      cout << "Enter the min score of the students:";
      cin.get();
      getline(cin, min);
      cout << "Enter the max score of the students:";
      getline(cin, max);
      if (min.length() == 0 && max.length() == 0) {
        stu_info->look_up_stu(students);
        break;
      } else if (max.length() == 0) {
        stu_info->look_up_stu(students, 1, stoi(min));
      } else if (min.length() == 0) {
        stu_info->look_up_stu(students, 1, 0, stoi(max));
      } else {
        stu_info->look_up_stu(students, 1, stoi(min), stoi(max));
      }
      break;
    }
    case 'q': {
      return 0;
    }
    default:
      break;
    }
  }
}

测试结果如下:

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1028t4AFaW.png" >}}

编程题1

编写并测试一个单件类:该类能够控制系统中最多只存在一个该类的对象。

该题其实背后涉及的原理很复杂在查阅资料后可以找到一种最简洁的实现方式该方式也被称为Meyers' Singleton.其实现代码如下

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

class Singleton {
private:
  Singleton() { test = 0; };
  ~Singleton(){};
  Singleton(const Singleton &);
  Singleton &operator=(const Singleton &);

public:
  int test;
  static Singleton *getInstance() {
    static Singleton instance;
    return &instance;
  }
};

int main(int argc, char *argv[]) {
  Singleton *object1 = Singleton::getInstance();
  object1->test = 1;
  Singleton *object2 = Singleton::getInstance();
  printf("%d", object2->test);
  return 0;
}

这里使用了静态局部变量该变量在第一次调用getInstance函数时会被初始化在之后即便再次调用getInstance函数该变量的地址也不会变化即在该函数内该变量唯一。这一点可以通过main中的测试来说明首先调用公有的静态方法获取一个实例。然后修改实例中的test。因为构造函数中test的值为0若再次获取到的为新实例则object2的test值应为0但实际运行结果如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1028sPiBf8.png" >}}

这说明object2获取到的仍然为同一实例打印指针可更直观的了解object1、2为同一实例。

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/1028wdh3Oj.png" >}}

编程题2

魔兽世界之一:备战描述魔兽世界的西面是红魔军的司令部,东面是蓝魔军的司令部。两个司令部之间是依次排列的若干城市。

红司令部City 1City 2........City n蓝司令部

两军的司令部都会制造武士。武士一共有 dragon 、ninja、iceman、lion、wolf 五种。每种武士都有编号、生命值、攻击力这三种属性。双方的武士编号都是从1开始计算。红方制造出来的第n个武士编号就是n。同样蓝方制造出来的第n个武士编号也是n。

武士在刚降生的时候有一个生命值。在每个整点双方的司令部中各有一个武士降生。红方司令部按照iceman、lion、wolf、ninja、dragon的顺序循环制造武士。蓝方司令部按照lion、dragon、ninja、iceman、wolf的顺序循环制造武士。制造武士需要生命元。制造一个初始生命值为m的武士司令部中的生命元就要减少m个。如果司令部中的生命元不足以制造某个按顺序应该制造的武士那么司令部就试图制造下一个。如果所有武士都不能制造了则司令部停止制造武士。给定一个时间和双方司令部的初始生命元数目要求你将从0点0分开始到双方司令部停止制造武士为止的所有事件按顺序输出。一共有两种事件其对应的输出样例如下

  1. 武士降生输出样例: 004 blue lion 5 born with strength 5,2 lion in red headquarter

    表示在4点整编号为5的蓝魔lion武士降生它降生时生命值为5降生后蓝魔司令部里共有2个lion武士。为简单起见不考虑单词的复数形式注意每制造出一个新的武士都要输出此时司令部里共有多少个该种武士。

  2. 司令部停止制造武士输出样例: 010 red headquarter stops making warriors

    表示在10点整红方司令部停止制造武士

输出事件时首先按时间顺序输出同一时间发生的事件先输出红司令部的再输出蓝司令部的。输入第一行是一个整数代表测试数据组数。每组测试数据共两行。第一行一个整数M。其含义为 每个司令部一开始都有M个生命元( 1 <= M <= 10000)。第二行:五个整数,依次是 dragon 、ninja、iceman、lion、wolf 的初始生命值。它们都大于0小于等于10000。输出对每组测试数据要求输出从0时0分开始到双方司令部都停止制造武士为止的所有事件。对每组测试数据首先输出"Case:n" n是测试数据的编号从1开始 。接下来按恰当的顺序和格式输出所有事件。每个事件都以事件发生的时间开头,时间以小时为单位,有三位。样例输入

1

20

3 4 5 6 7

样例输出

Case:1

000 red iceman 1 born with strength 5,1 iceman in red headquarter

000 blue lion 1 born with strength 6,1 lion in blue headquarter

001 red lion 2 born with strength 6,1 lion in red headquarter

001 blue dragon 2 born with strength 3,1 dragon in blue headquarter

002 red wolf 3 born with strength 7,1 wolf in red headquarter

002 blue ninja 3 born with strength 4,1 ninja in blue headquarter

003 red headquarter stops making warriors

003 blue iceman 4 born with strength 5,1 iceman in blue headquarter

004 blue headquarter stops making warriors

这道题题面比较复杂。但是仔细分析后可知指挥部只有制造武士这一种行为实际上比较简单。只需读取输入为每个武士赋予相应的属性即可。这里因为武士本身没有行为可以使用结构体存储。司令部为一个类其中包含了生命元数量制造武士顺序现在制造的武士的下标司令部的颜色武士的标号和所有武士的信息这几个成员变量。使用武士名作为key构造哈希表存储武士信息可以快速获得某种武士的数量。构造函数中初始化成员变量。成员函数为制作武士和判断两个函数。制造武士用于根据指定下标制造对应顺序的武士判断则用于判断当前应制造的武士能否制造不能则寻找下一个能制造的武士如果已经不能制造则终止制造武士并将over置为true; 代码如下

#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

typedef struct own_warrior {
  int number;
  int life_value;
  int attacker;
} own_warrior;

struct warrior {
  string name;
  int life_value;
};

// 构造战士信息哈希表
map<int, struct warrior *> warriors;
vector<string> warrior_name = {"dragon", "ninja", "iceman", "lion", "wolf"};

class headquarter {
public:
  int meta_life;
  vector<int> order;
  int warrior_now;
  bool over;
  string color;
  int warrior_num;
  map<string, vector<struct own_warrior *> *> warrior_info;
  headquarter(int, vector<int>, string);
  headquarter(headquarter &&) = default;
  headquarter(const headquarter &) = default;
  headquarter &operator=(headquarter &&) = default;
  headquarter &operator=(const headquarter &) = default;
  ~headquarter();
  void make_warrior(int);
  void judge_make();

private:
};

// 构造函数,初始化生命元和战士信息
headquarter::headquarter(int life, vector<int> my_order, string my_color) {
  meta_life = life;
  order = my_order;
  color = my_color;
  warrior_num = 1;
  over = false;
  warrior_now = 0;
  for (int i = 0; i < warrior_name.size(); i++) {
    vector<struct own_warrior *> *temp_vector =
        new vector<struct own_warrior *>;
    warrior_info.insert(make_pair(warrior_name[i], temp_vector));
  }
}

headquarter::~headquarter() {}

// 制造战士
void headquarter::make_warrior(int warr_now) {
  int life_value = warriors[order[warr_now]]->life_value;
  string name = warriors[order[warr_now]]->name;
  // 为新战士分配内存空间放入对应的map中
  own_warrior *new_warrior = new own_warrior;
  new_warrior->number = warrior_num;
  new_warrior->life_value = life_value;
  new_warrior->attacker = 0;
  warrior_info[name]->push_back(new_warrior);
  cout << color << " " << warriors[order[warr_now]]->name << " " << warrior_num
       << " born with strength " << warriors[order[warr_now]]->life_value << ","
       << warrior_info[name]->size() << " " << name << " in " << color
       << " headquarter" << endl;
  warrior_num++;
  warrior_now = (warr_now + 1) % (warriors.size());
  // cout << warriors.size() << " " << warrior_now << endl;
  meta_life -= life_value;
  return;
}

// 判能否制造下一个战士,不能则继续制造下一个,直到都不能则结束.
void headquarter::judge_make() {
  if (warriors[order[warrior_now]]->life_value <= meta_life) {
    make_warrior(warrior_now);
    return;
  } else {
    for (int i = 1; i < warriors.size(); i++) {
      if (warriors[order[(warrior_now + i) % warriors.size()]]->life_value <=
          meta_life) {
        warrior_now = (warrior_now + i) % warriors.size();
        make_warrior(warrior_now);
        return;
      }
    }
    over = true;
    cout << color << " headquarter stops making warriors" << endl;
    return;
  }
}

int main(int argc, char *argv[]) {
  int case_no = 0;
  int length = 5;
  vector<int> red_order = {2, 3, 4, 1, 0};
  vector<int> blue_order = {3, 0, 1, 2, 4};
  //处理每组数据
  while (cin >> case_no) {
    int time = 0;
    int case_life = 20;
    cin >> case_life;
    warriors.clear();
    for (int i = 0; i < length; i++) {
      int wa_life = 0;
      cin >> wa_life;
      struct warrior *new_warrior = new struct warrior;
      new_warrior->life_value = wa_life;
      new_warrior->name = warrior_name[i];
      warriors.insert(make_pair(i, new_warrior));
    }
    headquarter *red_headquarter = new headquarter(case_life, red_order, "red");
    headquarter *blue_headquarter =
        new headquarter(case_life, blue_order, "blue");
    cout << "Case:" << case_no << endl;
    while (!red_headquarter->over || !blue_headquarter->over) {
      if (!red_headquarter->over) {
        printf("%03d ", time);
        red_headquarter->judge_make();
      }
      if (!blue_headquarter->over) {
        printf("%03d ", time);
        blue_headquarter->judge_make();
      }
      time++;
    }
  }
  return 0;
}

运行结果如下

{{< figure src="https://gcore.jsdelivr.net/gh/game-loader/picbase@master/uPic/10290QUmPy.png" >}}

第五次作业

完善字符串类

编写封装完善的MyString类并测试要求有多个构造函数、完成深拷贝的拷贝构造函数和赋值运算符函数以及一些其他功能函数练习掌握内联函数的使用。

该题为完善之前的字符串类,需要为之前的字符串类添加深拷贝的拷贝构造函数,即在拷贝字符串的过程中为指针申请新的内存空间。同时还要重载赋值运算符‘=在重载运算符的过程中若想直接修改赋值运算符的左值则需使用this指针指向调用运算符的对象本身。重载的运算符可以看做对象的成员函数object.operator=()。返回对象本身即返回*this,对this指针解引用即可得到对象本身代码如下

// MyString.h 
#include <cstdio>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

class MyString {
private:
  char *str;
  int length;

public:
  MyString();
  MyString(char *s);
  MyString(const MyString &obj);
  MyString &operator=(const MyString &obj);
  char *get_string();
  void set_string(char *s);
  int get_length();
  void append(char *s);
  void append(const MyString &obj);
  ~MyString();
};

// MyString.cpp
#include "MyString.h"
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

using namespace std;
MyString::MyString() { cout << "MyString类的默认构造函数被调用" << endl; }

MyString::MyString(char *s) {
  length = strlen(s);
  str = new char[length + 1];
  str[length] = '\0';
  //安全拷贝
  strncpy(str, s, length + 1);
  cout << "MyString类的有参构造函数被调用当前字符串为" << str << endl;
}
MyString::~MyString() {
  cout << "MyString类的析构函数被调用当前字符串为" << str << endl;
  delete str;
};

MyString::MyString(const MyString &obj) {
  set_string(obj.str);
  cout << "MyString类的复制构造函数被调用当前字符串为";
  cout << obj.str << endl;
}

MyString &MyString::operator=(const MyString &obj) {
  set_string(obj.str);
  cout << "MyString类的赋值运算符被调用当前字符串为";
  cout << obj.str << endl;
  return *this;
}

char *MyString::get_string() { return str; }
void MyString::set_string(char *s) {
  length = strlen(s);
  str = new char[length + 1];
  str[length] = '\0';
  //安全拷贝
  strncpy(str, s, length + 1);
}
int MyString::get_length() { return length; }

void MyString::append(char *s) {
  length = length + strlen(s);
  char *str_temp = new char[length + 1];
  str_temp[length] = '\0';
  strncpy(str_temp, str, length + 1);
  strncat(str_temp, s, length + 1);
  delete str;
  str = str_temp;
}

void MyString::append(const MyString &obj) { this->append(obj.str); }

int main(int argc, char *argv[]) {
  MyString str;
  str.set_string("I love C++, ");
  cout << "字符串长度:" << str.get_length() << "\t" << str.get_string()
       << endl;
  str.append("yeah!");
  cout << "字符串长度:" << str.get_length() << "\t" << str.get_string()
       << endl;
  {
    MyString str("I like C++ programming!");
    MyString str2(str), str3 = str;
  }
  MyString str2;
  cout << str.get_string() << endl;
  str2 = str;
  str2.append(str2);
  cout << str2.get_string() << endl;
  return 0;
}

得到的运行结果为: 结果

组合类

设计一个学生类该类与MyString类构成组合类重新设计学生列表类。

对于学生包含姓名、学号、成绩等请完善封装学生类CStudent将之前使用的结构体STUDENT封装为该类其中学生的姓名、专业要使用上题的MyString类的对象存储完善封装学生列表类CStudentList保存数量不定的学生并在这个类中实现增加学生、按学号删除、修改学生信息、显示学生信息的功能显示学生信息的函数要有重载版本为CStudentList类提供复制构造函数、赋值运算符函数注意初始化列表的使用。

该题是将字符串类和之前构造的学生类进行组合。这道题的关键在于CStudentList类中的拷贝构造函数和赋值运算符的重载这两部分的代码如下

  // CStudentList的拷贝构造函数
  CStudentList(const CStudentList &obj) {
    for (auto stu : obj.students) {
      CStudent *stu_temp = new CStudent();
      stu_temp->sno = stu.second->sno;
      // 使用了MyString的拷贝构造函数
      stu_temp->name = stu.second->name;
      stu_temp->score = stu.second->score;
      stu_temp->specialty = stu.second->specialty;
      students.insert(make_pair(stu.second->sno, stu_temp));
    }
  }
  // 赋值运算符函数
  CStudentList &operator=(const CStudentList &obj) {
    for (auto stu : obj.students) {
      CStudent *stu_temp = new CStudent();
      stu_temp->sno = stu.second->sno;
      // 使用了MyString的拷贝构造函数
      stu_temp->name = stu.second->name;
      stu_temp->score = stu.second->score;
      stu_temp->specialty = stu.second->specialty;
      students.insert(make_pair(stu.second->sno, stu_temp));
      this->students.insert(make_pair(stu.second->sno, stu_temp));
    }
    return *this;
  }

剩余的部分主要在于用MyString类替代之前CStudent的char*此时我们的MyString类已经可以通过'='进行直接赋值非常方便不再需要操作char*时的strcpy等函数。再用CStudentList来操作CStudent即可这里在修改学生信息时如果不存在该学生的信息则会报错不能修改。代码如下

#include "MyString.h"
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

MyString::MyString() { cout << "MyString类的默认构造函数被调用" << endl; }

MyString::MyString(char *s) {
  length = strlen(s);
  str = new char[length + 1];
  str[length] = '\0';
  //安全拷贝
  strncpy(str, s, length + 1);
  cout << "MyString类的有参构造函数被调用当前字符串为" << str << endl;
}
MyString::~MyString() {
  cout << "MyString类的析构函数被调用当前字符串为" << str << endl;
  delete str;
};

MyString::MyString(const MyString &obj) {
  set_string(obj.str);
  cout << "MyString类的复制构造函数被调用当前字符串为";
  cout << obj.str << endl;
}

MyString &MyString::operator=(const MyString &obj) {
  set_string(obj.str);
  cout << "MyString类的赋值运算符被调用当前字符串为";
  cout << obj.str << endl;
  return *this;
}

char *MyString::get_string() { return str; }
void MyString::set_string(char *s) {
  length = strlen(s);
  str = new char[length + 1];
  str[length] = '\0';
  //安全拷贝
  strncpy(str, s, length + 1);
}
int MyString::get_length() { return length; }

void MyString::append(char *s) {
  length = length + strlen(s);
  char *str_temp = new char[length + 1];
  str_temp[length] = '\0';
  strncpy(str_temp, str, length + 1);
  strncat(str_temp, s, length + 1);
  delete str;
  str = str_temp;
}

void MyString::append(const MyString &obj) { this->append(obj.str); }

// CStudent类
class CStudent {
public:
  int sno;
  MyString name;
  MyString specialty;
  int score;
  CStudent();
  CStudent(CStudent &&) = default;
  CStudent(const CStudent &) = default;
  CStudent &operator=(CStudent &&) = default;
  CStudent &operator=(const CStudent &) = default;
  ~CStudent();

private:
};

class CStudentList {
public:
  CStudentList(){};
  // CStudentList的拷贝构造函数
  CStudentList(const CStudentList &obj) {
    for (auto stu : obj.students) {
      CStudent *stu_temp = new CStudent();
      stu_temp->sno = stu.second->sno;
      // 使用了MyString的拷贝构造函数
      stu_temp->name = stu.second->name;
      stu_temp->score = stu.second->score;
      stu_temp->specialty = stu.second->specialty;
      students.insert(make_pair(stu.second->sno, stu_temp));
    }
  }
  // 赋值运算符函数
  CStudentList &operator=(const CStudentList &obj) {
    for (auto stu : obj.students) {
      CStudent *stu_temp = new CStudent();
      stu_temp->sno = stu.second->sno;
      // 使用了MyString的拷贝构造函数
      stu_temp->name = stu.second->name;
      stu_temp->score = stu.second->score;
      stu_temp->specialty = stu.second->specialty;
      students.insert(make_pair(stu.second->sno, stu_temp));
      this->students.insert(make_pair(stu.second->sno, stu_temp));
    }
    return *this;
  }
  ~CStudentList(){};
  map<int, CStudent *> students;
  // 有默认形参,使用引用变量
  void add_stu(int sno, char *name, char *specialty, int score = 60) {
    CStudent *student = new CStudent();
    student->sno = sno;
    student->score = score;
    student->name.set_string(name);
    student->specialty.set_string(specialty);
    students.insert(make_pair(sno, student));
  }

  void del_stu(int sno) {
    // 释放内存
    delete (students.find(sno)->second);
    students.erase(sno);
    cout << "Delete success!";
  }
  // 默认全部显示
  void look_up_stu() {
    for (auto stu : students) {
      cout << "Sno = " << stu.second->sno
           << " Name = " << stu.second->name.get_string()
           << " Specialty = " << stu.second->specialty.get_string()
           << " Score = " << stu.second->score << endl;
    }
  }

  // 重载的显示函数
  void look_up_stu(int flag, int min = 0, int max = 100) {
    for (auto stu : students) {
      if ((stu.second->score) >= min && stu.second->score <= max) {
        cout << "Sno = " << stu.second->sno
             << " Name = " << stu.second->name.get_string()
             << " Specialty = " << stu.second->specialty.get_string()
             << " Score = " << stu.second->score << endl;
      }
    }
  }

  // 根据学号修改学生信息
  void change_stu(int sno, char *name, char *specialty, int score = 60) {
    int keycount = students.count(sno);
    if (keycount != 1) {
      cout << "Don't have this student, please add it to system!!" << endl;
      return;
    } else {
      students.find(sno)->second->sno = sno;
      students.find(sno)->second->name.set_string(name);
      students.find(sno)->second->specialty.set_string(specialty);
      students.find(sno)->second->score = score;
      cout << "Update success!" << endl;
      return;
    }
  }
};

int main() {
  char select;
  cout << "Welcome to my student manage system!!!!!" << endl;
  CStudentList *stu_info = new CStudentList();
  while (true) {
    cout << "\nPlease select the operation you want:" << endl
         << "\t1.add a student.\n"
         << "\t2.delete a student\n"
         << "\t3.display students information\n"
         << "\t4.change student information\n"
         << "\tq for quit\n"
         << "your select:";
    cin >> select;
    switch (select) {
    case '1': {
      int number, average;
      string aver;
      string name, specialty;
      cout << "The student number:";
      cin >> number;
      cout << "The student name:";
      cin >> name;
      cout << "The student specialty:";
      cin >> specialty;
      cin.get();
      cout << "The student score(can be nothing):";
      getline(cin, aver);
      if (aver.length() == 0) {
        stu_info->add_stu(number, (char *)name.data(),
                          (char *)specialty.data());
      } else {
        average = stoi(aver);
        stu_info->add_stu(number, (char *)name.data(), (char *)specialty.data(),
                          average);
      }
      break;
    }
    case '2': {
      int number;
      cout << "The student number:";
      cin >> number;
      stu_info->del_stu(number);
      break;
    }
    case '3': {
      string min, max;
      cout << "Enter the min score of the :";
      cin.get();
      getline(cin, min);
      cout << "Enter the max score of the :";
      getline(cin, max);
      if (min.length() == 0 && max.length() == 0) {
        stu_info->look_up_stu();
        break;
      } else if (max.length() == 0) {
        stu_info->look_up_stu(1, stoi(min));
      } else if (min.length() == 0) {
        stu_info->look_up_stu(1, 0, stoi(max));
      } else {
        stu_info->look_up_stu(1, stoi(min), stoi(max));
      }
      break;
    }
    case '4': {
      int number, average;
      string aver;
      string name, specialty;
      cout << "The student number:";
      cin >> number;
      cout << "The student name:";
      cin >> name;
      cout << "The student specialty:";
      cin >> specialty;
      cin.get();
      cout << "The student score(can be nothing):";
      getline(cin, aver);
      if (aver.length() == 0) {
        stu_info->change_stu(number, (char *)name.data(),
                             (char *)specialty.data());
      } else {
        average = stoi(aver);
        stu_info->change_stu(number, (char *)name.data(),
                             (char *)specialty.data(), average);
      }
      break;
    }
    case 'q': {
      return 0;
    }
    default:
      break;
    }
  }
}

运行的效果为

img