知识点
STL
1、STL是c++封装的一组容器。因为使用的泛型进行的封装,所以数据可以是任意类型的。并且只能存放值或者指针,不能存引用
2、STL主要有但部分组成:容器,迭代器,算法
3、容器有vector(向量,又称为动态数组) 、map(存储的是键值对,当需要进行大量的查找操作时,使用map)、set
4、迭代器是严格区分类型的,使用时要明确是哪一个类型的容器的迭代器。
例如:图书类型的向量的迭代器:vector :: iterator 迭代器变量名;
5、
6、一些常用的通用操作
7、vector
- 初始化方法:
vector<t> v; //初始化成一个新向量
vector<t> v(a,a+size)//使用数组初始化向量
- push_back():在末尾添加一个元素
- pop_back():删除最后一个元素
- 迭代器遍历:
vector <t> v;vector <t> :: iterator::i;for(i=v.begin();i<v.end();i++){}
8、map
- map的迭代器
map <t> m;map<t>::iterator i;
- 查找函数 find():如果有这个元素,返回他的迭代器指针;如果没有,返回m.end()
- 对于其中的每一个元素,都分为key,value。获取key,使用 i->first,获取value,使用i->second
- 添加方法:insert(key,value)
- 删除
9、算法(针对所有容器)
说明:
1、以上算法在的first,last是一个查找区间相当于[first,last)。是迭代器指针
2、count_if():定义在区间上的查找条件,通常是一个函数。
3、使用到<,>的,如果是对象,要重载运算符
查找:一下查找方法适用于任何容器
- 精确查找
- 模糊查找:find_if()使用时如果是类,需要==重载运算符
- 查找返回的是迭代器指针
排序
作业分析
作业要求
图书管理系统(后台管理员)
数据类:
基础类:读者最大借书量、最长借书时间(按天计算)。这两个都是类成员
日期类:包括年月日,重载输入运算符时,要进行数据的合法性检验;重载输出运算符时,按照“年/月/日”形式输出;重载+运算符;
借阅记录类:包括日期、书号、读者学号、类型(借出/还回/续借)、图书类型(基础/文学休闲/专业);
读者类:学号(常成员)、姓名、专业、班级、已借图书数量、借阅记录向量;
图书类:书号(常成员)、书名、作者、出版社、出版日期、图书类型(基础/文学休闲/专业)、馆藏总量、在馆数量、借阅记录向量;
操作类:
数据成员:图书/学生/借阅记录向量
成员函数:对图书进行文件读写、在图书向量内完成对图书基本信息的增删查改;
对学生进行文件读写、在学生向量内完成对学生基本信息的增删查改;
借阅记录的管理和统计功能后续添加;
对于每个类的分析
1、日期类
- 日期类中数据成员和成员函数。
数据成员首先想到年,月,日。
成员函数分为构造函数和普通函数。 - 对于构造函数,必须要有默认的无参构造函数和带三个参数的构造函数,最好写上赋值构造函数,便可以用已知的时间初始化一个时间。
- 对于普通函数,首先要有的是各个参数的get,set函数,用于获取具体的年月日
- 其次,要重载属于输入/出运算符。再次复习一下内容
1、首先,使用友元函数。具体的函数结构:
friend ostream & operator>> (ostream& in, Date &d) {in>>对象的参数;...return out;}
friend istream & operator<< (istream& out,Date &d){out<<对象的参数;...return out;}
2、其次,重载后的输入运算符,既可以用于键盘输入,也可以用于读文件。
⭐注意:读文件时,文件的内容必须符合形式。比如说,读取的日期的内容应该参数之间以空格隔开,并且是整数的形式。如果我在文件中存储的是2020/4/26,那么便无法使用<<读取 - 然后,重载赋值运算符。因为后面的类中,少不了赋值。Date类是作为成员变量存在,所以必须重载赋值运算符。
- 然后,重载加法运算符。
1、需求来源:借书时,需要明确想要借书的最大时间,通常是天数,因此要计算最晚的还书日期。
2、方法分析:
有三种情况:
1、还书日期还在本月,那么用借书的day+借书天数即可
2、还书日期不在本月,但是在本年。那么先计算出本月的剩余天数。用借书天数-本月剩余天数,然后循环。直到某一个月的天数>此月所需要的天数。那么月份便是此月份,天数即使剩余的天数。
3、对于2中的情况,如果出现加一个月后月份为13,那么月份变成1,年数+1,使用2中的方法继续计算。
⭐因为在此过程中需要判断剩余天数是否大于当月所有的天数,所以可以在成员变量中添加一个成员变量:天数数组。 int d[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};。
3、写代码过程中出现的错误:
a、判断闰年的函数马虎写成了y&100!=0
b、如果函数返回值为Date,不能直接输出cout<<d+10<<endl;cout<<d+93<<endl;
要写成Date d1=d+93;cout<<d1<<endl;
但是如果是对象的引用,可以直接写cout<<d+10<<endl;cout<<d+93<<endl;
4、代码:
bool isRunNian(int y)
{
if (y % 400 == 0 || (y % 4 == 0 && y %100 != 0))
{
return true;
}
else
return false;
}
Date operator+(int day1)
{
if (isRunNian(year))
{
d[1] = 29;
}
else
{
d[1] = 28;
}
int temp;
temp = this->day + day1;
if (temp <= d[month - 1])
{
year = this->year;
month = this->month;
day = temp;
}
else
{
//11/20 65
day1 = day1 - (d[month - 1] - day);
month++;
while (d[month - 1] < day1)
{
month++;
if (month > 12)
{
month = 1;
year++;
}
day1 = day1 - d[month - 1];
}
day = day1;
}
return *this;
}
2、读者类和图书类
- 读者类和图书类相似,因为他们的成员变量中不仅有变量,还都含有常量。
- 对于常量的处理:
1、在构造函数中,使用初始化列表赋值
2、在赋值构造函数和重载赋值运算符和重载输入运算符中,使用const_cast<指针>(常量的地址)
string *temp = const_cast<string *>(&xh); *temp = tu.xh;
对于const_cast的说明:const_cast使得一个未用const修饰的指针int *temp
可以指向const修饰的变量int xh
。因此,这个可以对*temp
进行赋值。比较特殊的是,对*temp
进行赋值后,temp所指向的地址的确是xh
变量所在的地址,*temp
的值也的确是修改后的值,但是常量xh
的值并没有改变。
例如:
const int a=10;
const int*d=&a;
int *f=const_cast<int *>(d) ;
cout << a << endl;
cout << *d << endl;
cout << *f << endl;
*f = 20;
cout << a << endl;
cout << *d << endl;
cout << *f << endl;
cout << &a << endl;
cout << d << endl;
cout << f << endl;
3、代码:
duzhe &operator=(const duzhe &tu)
{
string *temp = const_cast<string *>(&xh);
*temp = tu.xh;
name = tu.name;
zy = tu.zy;
bj = tu.bj;
count = tu.count;
return *this;
}
- 然后成员函数就是get,set函数,重载运算符函数。
3、操作类
- 操作类是最重要的类。
- 在操作类中,最重要的是对类的理解。在全局上的理解,有两个文件,分别存储图书馆所有的图书信息和读者信息。通过这个系统,我们可以对图书和读者进行增删改查的工作。文件是存储在硬盘上的,而我们的增删改查是在内存上进行的,所以需要读取文件内容存放在内存,使用后为了长期保存将其存储在文件里。提到一个类,除了成员变量外,就是函数。构造函数的作用是初始化,这里的工作就是读取文件中的内容。析构函数的作用就是将修改后的内存中的信息放到文件中永久保存。。
- 操作类的成员变量是 三个向量。
- 第一次写的时候,没考虑到的问题:没有弄清楚读文件和写文件的作用。并且由于没考虑到图书馆中的读者和图书的数量肯定非常多,所以在查询时和查重时使用遍历的方法,效率低。为了提高查询效率,添加map数据成员。
实现方法
创建两个map类型的数据成员,一个用来存储读者的学号和读者在向量中的下标。一个用来储存书号和在向量中的下标。那么在查询时使用find()方法,可以快速找到。 - 具体功能的实现
增:增加时,需要看原来是否已经存在。对于读者,如果已经存在,则不用再添加。对于图书,如果已经存在,则在图书向量中不要再添加一个图书对象,而是将原来的图书的在馆数量和馆藏数量+1。
改:通过map快速找到对象,更新数据。但是更新时不要修改学号和书号。如果没有,不用更新
删:删除图书和读者,都是通过map找到对象所在的下标。如果存在,通过erase(迭代器)方法删除。
注意:
删除后,要重建map。因为向量中的每个对象的下标改变了。重建就是删除原来的,重新添加数据
void deleteDz(string xh)
{
multimap<string, int>::iterator i;
vector<duzhe>::iterator it = dz.begin();
i = duzheXh.find(xh);
if (i != duzheXh.end())
{
int x = i->second;
dz.erase(it + x);
//map重建
duzheXh.clear();
duzheName.clear();
vector<duzhe>::iterator it1 = dz.begin();
int a = 0;
for (it1; it1 < dz.end(); it1++)
{
duzheName.insert(make_pair((*it1).getName(), a));
duzheXh.insert(make_pair((*it1).getXh(), a));
a++;
}
}
else
return;
}
查:按照学号、书号查和按照名字查。还是先在map中的value中查询是否有对应的内容。没有就算了,如果有的话,如果是结果只有一个,直接输出;如果结果有多个,需要使用lower_bound和upper_bound.
例如:
void searchByTsName(string name)
{
multimap<string, int>::iterator i, a, b, c;
i = tushuName.find(name);
if (i == tushuSh.end())
{
return;
}
else
{
a = tushuName.lower_bound(name);
b = tushuName.upper_bound(name);
for (c = a; c != b; c++)
cout << ts[c->second] << endl;
}
}
- 困扰我许久的问题
1、总是不能在控制台输入数据
当我写完操作类进行测试时,当文件为空时,可以输入数据和写进文件。但是再次使用时便不可输入。后来我才知道,原来是我的文件的写入格式不符合读文件的格式。比如说日期写成’2020/4/27‘,但是读的时候应该是 ‘2020 4 20’
2、每次读出来的内容,最后一条的数据的最后一个数据总是输出两遍
在读文件过程中,使用eof()放法作为判断是否读完的标志。对于此方法,如果文件为空,不进行读操作。如果文件不为空,eof()方法确实存在这个问题。
解决方法:直接使用读取过程作为判断标准while (ifs >> t) { dz.push_back(t); duzheName.insert(make_pair(t.getName(), dz.size() - 1)); duzheXh.insert(make_pair(t.getXh(), dz.size() - 1)); // cout<<ifs.eof()<<endl; }
3、每次使用迭代器输出图书信息,总是没有书名
因为我后来写了一个复制构造函数,但是我写错了。但是我改正后答案还是不对。后来我删掉这个函数结果就正确了😔但是后来我添加了这个函数,结果又正确了,不知道当时咋回事。
- 小问题
1、引用的问题:方法里定义的局部对象不能返回引用
2、数据结果中出现了随机数,可能是因为变量进行了重声明,或者某一个指针变量的含义理解错误
学习总结
通过最近的这次作业,我感觉做系统最重要的是理解,然后是学以致用。
掌握每个知识的的使用场景非常重要。最开始的时候,我就没有理解题意,导致内容做的不对,通过老师的讲解,自己在一些地方才理解什么意思。再有就是一些知识的的理解不够深刻。以后,我写的每一个程序,我要力求明白它存在是否有意义,没有意义的功能不写,写了的功能理解其中的每一部分的作用。
并且,这次作业我写的非常乱,因为我遇到错误了,想要按保存原来的,然后再修改,最后弄了很多文件,自己都乱套了。我记得我把那个注释的代码删掉了,难道是没保存?之后我还是要有条理一些。