文章目录
一、学习内容
(一)类之间的关系
- 继承:在已有类的基础上创建新类的过程
- 一个 B 类继承A类,或称从类 A 派生类 B
类 A 称为基类(父类),类 B 称为派生类(子类)
(二)基类和派生类
- 类继承关系的语法形式
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
-
基类名表 :
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n例:
class A{
public:
int a;
int b;
private:
int c;
protected:
int d;
};
class B: public A
{ int c;
};
- 访问控制:
表示派生类对基类的继承方式
使用关键字:
public 公有继承
private 私有继承
protected 保护继承
1、访问控制
不论哪种方式继承基类,派生类都不能直接使用基类的私有成员
派生类的生成过程
● 吸收基类成员
(派生类吸收基类中除构造函数和析构函数之外的全部成员,但不一定可见)
● 改造基类成员
通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
● 添加派生类新成员
仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。
例:
class Father
{
int a,b;
public:
// 成员函数
};
class Son:public Father
{
int c;
public:
// 成员函数
};
图示:
公有继承
例:公有继承的测试
#include<iostream>
using namespace std ;
class A
{ public :
void get_XY() { cout << "Enter two numbers of x, y : " ; cin >> x >> y ; }
void put_XY() { cout << "x = "<< x << ", y = " << y << '\n' ; }
protected: int x, y ;
};
class B : public A
{ public :
int get_S() { return s ; };
void make_S() { s = x * y ; }; // 使用基类数据成员x,y
protected: int s;
};
class C : public B
{ public :
void get_H() { cout << "Enter a number of h : " ; cin >> h ; }
int get_V() { return v ; }
void make_V() { make_S(); v = get_S() * h ; } // 使用基类成员函数
protected: int h, v;
};
int main()
{ A objA ;
B objB ;
C objC ;
cout << "It is object_A :\n" ;
objA.get_XY() ;
objA.put_XY() ;
cout << "It is object_B :\n" ;
objB.get_XY() ;
objB.make_S() ;
cout << "S = " << objB.get_S() << endl ;
cout << "It is object_C :\n" ;
objC.get_XY() ;
objC.get_H();
objC.make_V() ;
cout << "V = " << objC.get_V() << endl ;
}
例:
定义一个基类person(不定义构造函数)
- 姓名、性别、年龄(访问权限设置为私有)
- 定义公有的成员函数
set_p()
- 定义公有的成员函数
display_p()
,显示person的信息
再由基类派生出学生类(不定义构造函数,采用公有继承的方式)
- 增加学号、班级、专业和入学成绩
- 定义公有成员函数
set_t()
- 定义成员函定义公有的成员函数
display_s()
,显示所有的信息
#include<iostream>
#include <string>
using namespace std;
class Person
{
string name;
int age;
string sex;
public:
void set_p() {
cout<<"name\tage\tsex"<<endl;
cin>>name>>age>>sex;
}
void show_p() {
cout<<name<<" "<<age<<" "<<sex<<endl;
}
};
派生类的写法有两种:
方法一:
class student :public Person
{
string no;
string zhuanye;
string t_class;
float score;
public:
void set_t(){
set_p(); //调用继承于基类的成员函数访问继承于基类的私有数据成员
cout<<"zhuanye\tt_class\tscore"<<endl;
//cin>>zhuanye>>t_class>>score;//错误,没有输入基类继承过来的成员
cin>>name>>age>>sex>>zhuanye>>t_class>>score;
}
void show_t() {
show_p();
//cout<<zhuanye<<" "<<t_class<<" "<<score<<endl;//没有显示学号
cout<<name<<" "<<age<<" "<<sex<<zhuanye<<" "<<t_class<<" "<<score<<endl;
}
};
方法二:
class student :public Person
{
string no;
string zhuanye;
string t_class;
float score; //设计自己成员
public:
//改造基类成员 成员函数的覆盖
void set(){ //隐藏了基类中的同名成员
Person::set(); //调用继承于基类的成员函数访问继承于基类的数据成员(首先调用基类函数)
cout<<"zhuanye\tt_class\tscore"<<endl;
cin>>zhuanye>>t_class>>score;
}
void show() {
Person::show();//不但显示自己成员,还显示基类成员 基类成员的使用
cout<<zhuanye<<" "<<t_class<<" "<<score<<endl;
}
};
2、重名成员
- 重名数据成员
#include<iostream>
using namespace std ;
class A
{ public:
int a1, a2 ;
A( int i1=0, int i2=0 ) { a1 = i1; a2 = i2; }
void print()
{ cout << "a1=" << a1 << '\t' << "a2=" << a2 << endl ; }
};
class B : public A
{ public:
int b1, b2 ;
B( int j1=1, int j2=1 ) { b1 = j1; b2 = j2; }
void print() //定义同名函数
{ cout << "b1=" << b1 << '\t' << "b2=" << b2 << endl ; }
void printAB()
{ A::print() ; //派生类对象调用基类版本同名成员函数
print() ; //派生类对象调用自身的成员函数
}
};
int main()
{ B b ; //没有任何参数,使用默认参数
b.A::print();
b.printAB();
}
3、派生类中访问静态成员
例:
#include<iostream>
using namespace std ;
class B
{ public:
static void Add() { i++ ; }
static int i;
void out() { cout<<"static i="<<i<<endl; }
};
int B::i=0;
class D : private B//不建议使用private
{ public:
void f()
{ i=5;//操作基类成员
Add();//直接使用i=6
B::i++;//点明使用基类成员i=7
B::Add();//i=8
}
};
int main()
{ B x; D y;
x.Add();
x.out();
y.f();
//类成员在一个地方改动在其他地方都会改动 所有类共同拥有:
cout<<"static i="<<B::i<<endl;
cout<<"static i="<<x.i<<endl;
//cout<<"static i="<<y.i<<endl;
}//x和y拥有一个副本 类成员复制过来
(三)基类的初始化
例:
调用构造函数顺序测试,构造函数无参数
#include<iostream>
using namespace std ;
class Base
{
public :
Base ( ) { cout << "Base created.\n" ; }
} ;
class D_class : public Base
{
//派生类构造函数
public :
D_class ( ) { cout << "D_class created.\n" ; }
} ;
int main ( )
{
D_class d1 ;//先调用基类构造函数再本身
}
派生类构造函数和析构函数的定义规则
- 派生类的构造函数的定义
派生类的数据成员包括:
基类的数据成员
派生类新增数据成员
(参数要足够多)
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
例:
class base {
int n;
public:
base(int a)
{
cout<<"constructing base class"<<endl;
n=a;
cout<<"n="<<n<<endl;
}
//~base(){cout<<"destructing base class"<<endl;}
};
class subs:public base {
base bobj;
int m;
public:
subs(int a,int b,int c):base(a),bobj(c)
{
cout<<"constructing sub cass"<<endl;
m=b;
cout<<"m="<<m<<endl;
}
//~subs(){cout<<"destructing sub class"<<endl;}
};
void main()
{
subs s(1,2,3);
}
执行结果:
constructing base class
n=1
constructing base class
n=3
constructing sub cass
m=2
destructing sub class
destructing base class
destructing base class
- 派生类析构函数
例:
class B
{
public:
B() { cout<<"B()"<<endl; }
~B() { cout<<"~B()"<<endl; }
};
class D : public B
{
public:
D(){ cout<<"D()"<<endl; }
~D() { cout<<"~D()"<<endl; }
};
int main()
{
D d;
return 0;//自动调用析构函数,先自身析构再基类
}
执行结果:
B()
D()
~D()
~B()
(四)多继承
多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
例:
类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加自己的成员
多继承的派生类构造和访问
例:
- 多继承的构造函数
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序与派生类构造函数中所定义的成员初始化列表顺序没有关系。
- 多继承的析构函数
虚基类
举例说明:
class B { public : int b ;} ;
class B1 : public B { private : int b1 ; } ;
class B2 : public B { private : int b2 ; } ;
class C : public B1 , public B2
{ public : int f ( ) ; private : int d ; } ;
有:
C c ;
c . B ; // error
c . B :: b ; // error,从哪里继承的?
c . B1 :: b ; // ok,从B1继承的
c . B2 :: b ; // ok ,从B2继承的
解:
int main ()
{ C c ;
c . B1 :: b = 5 ; c . B2 :: b = 10 ;
cout << "path B1==> " << c . B1 :: b << endl ;
cout << "path B2==> " << c . B2 :: b << endl ;
}
虚继承声明使用关键字:virtual
例:
例如:
class B { public : int b ;} ;
class B1 : virtual public B { private : int b1 ; } ;
class B2 : virtual public B { private : int b2 ; } ;
class C : public B1 , public B2
{ private : float d ; } ;
有:
C cc ;
cc . b // ok
注:
由于类 C 的对象中只有一个 B 类子对象,名字 b 被约束到该子对象上,所以,当以不同路径使用名字 b 访问 B 类的子对象时,所访问的都是那个唯一的基类子对象。
即 cc . B1 :: b 和 cc . B2 :: b
引用是同一个基类 B 的子对象
赋值兼容规则
在程序中需要使用基类对象的任何地方,可用公有派生类的对象替代。
赋值兼容规则中所指的替代包括以下的情况:
- 派生类的对象可以赋给基类对象
- 派生类的对象可以初始化基类的引用
- 派生类的对象的地址可以赋给基类类型的指针
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员
二、作业总结
作业要求:
图书管理系统(客户端)
数据类:
基础类:读者最大借书量、最长借书时间(按天计算)。
这两个都是类成员
日前类:包括年月日;
重载输入运算符时,要进行数据的合法性检验;
重载输出运算符时,按照“年/月/日”形式输出;
重载+运算符;
借阅记录类:包括日期、书号、读者学号、
类型(借出/还回/续借)、
图书类型(基础/文学休闲/专业);
读者类:学号(常成员)、姓名、专业、班级、已借图书数量、
借阅记录向量;
图书类:书号(常成员)、书名、作者、出版社、出版日期、
图书类型(基础/文学休闲/专业)、馆藏总量、在馆数量、
借阅记录向量;
操作类:
数据成员:图书/借阅记录向量、学生对象,当前日期
成员函数:带学生对象和当前日期的构造函数;
按书名、书号、出版社、出版日期查询图书;
按书名模糊查询图书;
按书名+作者、出版社+作者查询图书;
输入起止出版日期,查询指定时间段出版的图书;
按书号查询图书借阅情况;
图书类型查询本人的借阅情况;
借书(输入书号;
如果书库有这本书且本人借阅量不超限,
办理借书手续(本人借书量加一、
图书在库数量减一,
对本人和书生成借阅记录);
续借(内容基本同上);
还书(内容类似);
改写成继承版
class operate
{
protected:
vector<Book> b1;
vector<Record> record;
Reader r1;
Date d1;
multimap<string,int> sname;
multimap<string,int> author;
multimap<string,int> press;
multimap<Date,int> pressdate;
multimap<int,int> bookdate;
multimap<string,int> booktype;
multimap<string,int> booknumber;
multimap<string, int> recordnum;
multimap<string, int> recordself; //自己的学号和在借阅记录中的位置
public :
operate(Reader reader,Date date)
{
r1=reader;
d1=date;
RecordRead();
BookRead();
}
operate()
{
RecordRead();
BookRead();
}
~operate()
{
RecordWrite();
BookWrite();
}
Reader getReader(){return r1;}
Date getD(){return d1;}
void RecordRead();
void RecordWrite();
void BookRead();
void BookWrite();
vector<Book>getB(){return b1;}
vector<Record>getR(){return record;}
void BookSelect(string bn);
void BookSelect1(string name);
void BookSelect2(string bpress);
void BookSelect3(Date &pd);
void searchMoHu(string s);
void searchQuJian(int d1,int d2);
void SearchWithAB(string au,string name);
void SearchWithCD(string pr,string au);
void selectRecord(string booknumber);//按书号查询图书借阅情况;
void selectReader(string booktype);//图书类型查询本人的借阅情况
};
void operate::RecordRead()
{
Record p;
ifstream ins("Record.txt",ios::in);
if(!ins)
{
return ;
}else{
if (ins.peek() == EOF)//每次读一个元素进来读回的不是EOF,则存在读缓冲器里
return;
while(ins>>p)
{
record.push_back(p);
recordnum.insert(make_pair(p.getBookNumber(), record.size() - 1));
if (p.getId() == r1.getId())
recordself.insert(make_pair(p.getId(), record.size() - 1));
//cout<<p<<endl;
}
ins.close();
}
}
void operate::RecordWrite()
{
fstream outs("Record.txt",ios::out);
if(!outs)
{
return ;
}
for (vector<Record>::iterator it= record.begin(); it< record.end();it++)
{
outs << *it << endl;
}
//outs<<"-1";
outs.close();
}
void operate::BookRead()
{
Book t;
ifstream ins("Librarian.txt",ios::in);
if(!ins)
{
return;
}else{
if (ins.peek()==EOF)
return;
while (ins>>t)
{
b1.push_back(t);
int x = b1.size() - 1;
sname.insert(make_pair(t.getSname(), x));
booknumber.insert(make_pair(t.getBookNumber(), x));
press.insert(make_pair(t.getPress(), x));
pressdate.insert(make_pair(t.getPressDate(), x));
author.insert(make_pair(t.getAuthor(), x));
//cout<<t<<endl;
}
ins.close();
}
}
void operate::BookWrite()
{
fstream outs("Librarian.txt",ios::out);
if(!outs)
{
return ;
}
for (vector<Book>::iterator it = b1.begin(); it!= b1.end(); it++)
{
outs << *it << endl;
}
//outs<<"-1";
outs.close();
}
void operate::BookSelect(string bn)
{
multimap<string, int>::iterator it;
it= booknumber.find(bn);
if (it!= booknumber.end())
{
int x = it->second;
cout << b1[x] << endl;
}
}
void operate::BookSelect1(string name)
{
multimap<string, int>::iterator it;
it= sname.find(name);
if (it!= sname.end())
{
int x = it->second;
cout << b1[x] << endl;
}
}
void operate::BookSelect2(string bpress)
{
multimap<string, int>::iterator it;
it= press.find(bpress);
if (it!=press.end())
{
int x = it->second;
cout << b1[x] << endl;
}
}
void operate::BookSelect3(Date &pd)
{
multimap<Date, int>::iterator it,a,b,c;
it= pressdate.find(pd);
if (it==pressdate.end())
{
return;
}
else
{
a = pressdate.lower_bound(pd);
b = pressdate.upper_bound(pd);
for (c=a; c!=b;c++)
cout << b1[c->second] << endl;
}
}
void operate::selectRecord(string booknumber)
{
multimap<string, int>::iterator it, a, b, c;
it= recordnum.find(booknumber);
if (it== recordnum.end())
{
return;
}
else
{
a = recordnum.lower_bound(booknumber);
b = recordnum.upper_bound(booknumber);
for (c=a;c!=b;c++)
{
cout << record[c->second] << endl;
}
}
}
void operate::selectReader(string booktype)
{
multimap<string, int>::iterator it;
it= recordself.find(r1.getId());
if (it!= recordself.end())
{
for (it= recordself.begin(); it!= recordself.end(); it++)
{
if(record[it->second].getBookType()==booktype)
cout << record[it->second] << endl;
}
}
}
class searchs{
public:
searchs(const string &cmp_string):m_s_cmp_string(cmp_string){}
bool operator ()(const multimap<string,int>::value_type &pair)
{
int x1=pair.first.find(m_s_cmp_string,0);
int x2=m_s_cmp_string.size();
if(x1!=-1){
return pair.first.substr(x1,x2)==m_s_cmp_string;
}else{
return false;
}
}
private:
const string &m_s_cmp_string;
};
void operate::searchMoHu(string name){
multimap<string,int>::iterator it;
multimap<string,int>::iterator it_e;
it=sname.begin();
it_e=sname.end();
while(true){
while(it ==sname.begin()){
it = find_if(it, sname.end(), searchs(name));
if(it!=it_e)
cout<<b1[it->second];
break;
}
it = find_if(++it, sname.end(), searchs(name));
if (it!= it_e)
cout<<b1[it->second];
else
break;
}
}
void operate::searchQuJian(int d1,int d2){
multimap<int, int>::iterator it;
multimap<int, int>::iterator it1;
multimap<int, int>::iterator it2;
it1=bookdate.lower_bound(d1);
it2=bookdate.upper_bound(d2);
for(it=it1;it!=it2;it++){
cout<<b1[it->second];
}
}
void operate::SearchWithAB(string au,string name){
multiset<int> m1,m2,m;
multiset<int>::iterator i;
multimap<string,int>::iterator iter,iter1;
multimap<string,int>::iterator it_e,it_e1;
iter =sname.begin();
it_e =sname.end();
iter1 =author.begin();
it_e1 =author.end();
while(true){
while(iter ==sname.begin()){
iter = find_if(iter, sname.end(), searchs(name));
if(iter!=it_e)
m1.insert(iter->second);
cout<<b1[iter->second];
break;
}
iter = find_if(++iter, sname.end(), searchs(name));
if (iter != it_e ){
m1.insert(iter->second);
cout<<b1[iter->second];}
else
break;
}
while(true){
while(iter1 ==author.begin()){
iter1 = find_if(iter1, author.end(), searchs(au));
if(iter1!=it_e1)
m2.insert(iter1->second);
break;
}
iter1 = find_if(++iter1,author.end(), searchs(au));
if (iter1 != it_e1 )
m2.insert(iter1->second);
else
break;
}
set_intersection(m1.begin(),m1.end(),m2.begin(),m2.end(),inserter(m,m.begin()));
for(i=m.begin(); i!=m.end(); i++)
{
cout<<b1[*i];
}
}
void operate::SearchWithCD(string pr,string au){
multiset<int> m1,m2,m;
multiset<int>::iterator i;
multimap<string,int>::iterator iter,iter1;
multimap<string,int>::iterator it_e,it_e1;
iter =press.begin();
it_e =press.end();
iter1 =author.begin();
it_e1 =author.end();
while(true){
while(iter ==press.begin()){
iter = find_if(iter, press.end(), searchs(pr));
if(iter!=it_e)
m1.insert(iter->second);
cout<<b1[iter->second];
break;
}
iter = find_if(++iter, press.end(), searchs(pr));
if (iter != it_e ){
m1.insert(iter->second);
cout<<b1[iter->second];}
else
break;
}
while(true){
while(iter1 ==author.begin()){
iter1 = find_if(iter1, author.end(), searchs(au));
if(iter1!=it_e1)
m2.insert(iter1->second);
break;
}
iter1 = find_if(++iter1,author.end(), searchs(au));
if (iter1 != it_e1 )
m2.insert(iter1->second);
else
break;
}
set_intersection(m1.begin(),m1.end(),m2.begin(),m2.end(),inserter(m,m.begin()));
for(i=m.begin(); i!=m.end(); i++)
{
cout<<b1[*i];
}
}
class Operate1:public operate
{
protected:
Reader reader;
Date date;
public:
Operate1(Reader &reader1,Date date1)
{
reader=reader1;
date=date1;
RecordWrite();
BookWrite();
}
void setDate(Date &d){date=d;}
Date getDate(){return date;}
void JieShu(string bn);
void XuJie(string bn);
void HuanShu(string bn);
};
void Operate1::JieShu(string bn)
{
multimap<string, int>::iterator it;
it= booknumber.find(bn);
if (it== booknumber.end())
{
return;
}else
{
if (b1[it->second].getBookIn()==0||r1.getBorrowNumber()==jichu::getmaxnum())
{
return;
}else
{
int i= r1.getBorrowNumber();
r1.setBorrowNumber(++i);
int j= b1[it->second].getBookIn();
b1[it->second].setBookIn(--j);
Record jy;
jy.setD1(d1);
jy.setBookNumber(bn);
jy.setId(r1.getId());
jy.setType("借出");
jy.setBookType(b1[it->second].getBookType());
Date d2= d1+jichu::getmaxDate();
jy.setD2(d2);
record.push_back(jy);
}
}
}
void Operate1::XuJie(string bn)
{
multimap<string, int>::iterator it;
it= booknumber.find(bn);
if (it== booknumber.end())
{
return;
}else
{
Record jy;
jy.setD1(d1);
jy.setBookNumber(bn);
jy.setId(r1.getId());
jy.setType("续借");
jy.setBookType(b1[it->second].getBookType());
Date d2= d1+jichu::getmaxDate();
jy.setD2(d2);
record.push_back(jy);
}
}
void Operate1::HuanShu(string bn)
{
multimap<string, int>::iterator it;
it= booknumber.find(bn);
if (it== booknumber.end())
{
return;
}else
{
multimap<string, int>::iterator it1, p1;
p1 =recordself.upper_bound(bn);
p1--;
int a = r1.getBorrowNumber();
r1.setBorrowNumber(--a);
int j= b1[it->second].getBookIn();
b1[it->second].setBookIn(--j);
Record jy;
jy.setD1(record[p1->second].getD1());
jy.setD2(d1);
jy.setBookNumber(bn);
jy.setId(r1.getId());
jy.setType("还书");
jy.setBookType(b1[it->second].getBookType());
record.push_back(jy);
}
}
int main()
{
Date t1(2020,5,11);
Reader rd1("2018212444","史瑶瑶","计算机科学与技术","18-4",2);
Operate1 op1(rd1, t1);
op1.operate::BookSelect("001");
op1.operate::BookSelect1("TP311.561/S799.2");
op1.operate::BookSelect2("人民教育出版社");
Date t2(2019,1,2);
op1.operate::BookSelect3(t2);
op1.operate::selectReader("专业");
op1.operate::selectRecord("001");
op1.operate::SearchWithCD("北京:清华大学出版社","尚展垒");
op1.operate::searchMoHu("程序");
op1.operate::searchQuJian(2000,2020);
op1.selectRecord("O141.4/L473.3");
op1.JieShu("001");
op1.JieShu("O141.4/L473.3");
op1.XuJie("001");
op1.HuanShu("O141.4/L473.3");
}
主要问题:
1.文件的读写:注意格式
- 读文件时,文件的内容必须符合形式。
例:读取各文件的内容参数之间以空格隔开 - 编码格式要一致,否则会乱码
- 读文件方法:
while(ins>>p)
{
record.push_back(p);
recordnum.insert(make_pair(p.getBookNumber(), record.size() - 1));
if (p.getId() == r1.getId())
recordself.insert(make_pair(p.getId(), record.size() - 1));
//cout<<p<<endl;
}
ins.close();
}
2、map的熟练使用
3、各种查询的用法
三、学习感想
继承就是在已有类上创建新类。要特别注意吸收、改造、添加这三个过程,知道函数覆盖与重载区别(函数名称、返回值、形式参数类型个数均相同),记住定义派生类对象时,构造函数的执行顺序;撤销派生类时,析构函数的执行顺序,注意类成员的调用以及共享性等等。继承这方面的知识在之前有接触,学起来较之间的内容容易一些,但也需要多加练习和巩固。