继承
继承,在已有类的基础上创建新类的过程
1,基类和派生类
-
类继承关系的语法形式
class 派生类名 : 基类名表
{
数据成员和成员函数声明
}; -
基类名表 构成
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n -
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
派生类都不能直接使用基类的私有成员 -
派生类的生成过程
●吸收基类成员(全部吸收(构造、析构除外),但不一定可见)
●改造基类成员
●添加派生类新成员 -
派生类中重名函数
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员
在派生类中使用基类的同名成员,显式地使用类名限定符:类名 :: 成员 -
派生类中使用静态成员
基类定义的静态成员,将被所有派生类共享
根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
派生类中访问静态成员,用以下形式显式说明:类名 :: 成员或通过对象访问 对象名 . 成员
使用:
//公有继承
#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 ;
}
#include<bits/stdc++.h>
using namespace std;
class Person
{
private:
string name;
string sex;
int age;
public:
void getInfo()
{
cout<<name<<" "<<sex<<" "<<age<<endl;
}
void setInfo(string n,string s,int a)
{
name=n;
sex=s;
age=a;
}
};
class Student:public Person
{
private:
string ID;
string banji;
string zhuanye;
double score;
public:
void setInfo(string n,string s,int a,string id,string bj,string zy,double score)
{
Person::setInfo(n,s,a);//屏蔽(hide)了基类的同名成员
//调用继承于基类的成员函数访问继承于基类的数据成员
ID=id;
banji=bj;
zhuanye=zy;
this->score=score;
}
void getInfo()
{
Person::getInfo();
cout<<ID<<" "<<zhuanye<<" "<<banji<<" "<<score<<endl;
}
};
int main()
{
Student s;
s.setInfo("小白","女",18,"001","5班","计算机",90);
s.getInfo();
return 0;
}
#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
{ public:
void f()
{ i=5;
Add();
B::i++;
B::Add();
}
};
int main()
{ B x;
D y;
x.Add();
x.out();
y.f();
cout<<"static i="<<B::i<<endl;
cout<<"static i="<<x.i<<endl;
}
//运行结果
static i=1
static i=8
static i=8
2, 基类的初始化
在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
-
派生类构造函数声明为
派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )… 对象成员n ( 变元表 ) ; 使用初始化列表 -
构造函数执行顺序:基类 -> 对象成员-> 派生类
-
派生类构造函数和析构函数的定义规则
(1)基类的构造函数和析构函数不能被继承
(2)如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
(3)如果基类无无参的构造函数,派生类必须定义构造函数
(4)如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
(5)派生类是否定义析构函数与所属的基类无关 -
派生类的构造函数的定义
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用 -
派生类析构函数
(1)当派生类中不含对象成员时
● 创建派生类对象,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
● 撤消派生类对象,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
● 定义派生类对象,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
● 撤消派生类对象,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
#include<iostream>
using namespace std ;
class parent_class
{
int data1,data2 ;
public :
parent_class ( int p1 , int p2 ) { data1 = p1; data2 = p2; }
int inc1() { return ++ data1; }
int inc2() { return ++ data2 ; }
void display()
{cout << "data1=" << data1 << " , data2=" << data2 << endl ; }
};
class derived_class : private parent_class
{
int data3 ;
parent_class data4 ;//基类对象做数据成员
public:
derived_class(int p1,int p2,int p3,int p4,int p5): parent_class ( p1 , p2 ) , data4 ( p3 , p4 )
{data3 = p5 ;}
int inc1 ( ) { return parent_class :: inc1 ( );}
int inc3 ( ) { return ++ data3 ; }
void display ( )
{ parent_class :: display ( ) ;
data4.display ( ) ;
cout << "data3=" << data3 << endl ;
}
} ;
int main ( )
{ derived_class d1 ( 17 , 18 , 1 , 2 , -5 ) ;
d1 . inc1 ( ) ;
d1 . display ( ) ;
}
运行结果:
data1=18 , data2=18
data1=1 , data2=2
data3=-5
3,继承的实例使用
考察一个点、圆、圆柱体的层次结构
#include<bits/stdc++.h>
using namespace std;
class Point
{
protected:
int x,y;
public:
Point(int x,int y){x=0;y=0;}//默认带参数的构造函数
void set(int x,int y){this->x=x;this->y=y;}
int getX()const{return x;}
int getY()const{return y;}
friend ostream & operator <<(ostream & out,Point &p)
{
out<<"("<<p.getX()<<","<<p.getY()<<")"<<endl;
}
};
class Circle:public Point
{
protected:
double r;
public:
Circle(int x,int y,double r):Point(x,y){r=0;}
// 带初始化式构造函数,首先调用基类构造函数
double setR(double r){this->r=r;}
double getR()const{return r;}
double getArea(){return r*r*3.14;}
friend ostream & operator <<(ostream & out,Circle &c)
{
out<<"("<<c.getX()<<","<<c.getY()<<")"<<" "<<c.r<<endl;
}
};
class Cylinder:public Circle
{
protected:
double h;
public:
Cylinder(int x,int y,double r,double h):Circle(x,y,r){h=0;}
void setH(double h){this->h=h;}
double getH()const{return h;}
double getArea(){return Circle::getArea()*2+2*Circle::getR()*3.14*h;};
double getVolume(){return Circle::getArea()*h;}
friend ostream & operator <<(ostream & out,Cylinder &cy)
{
out<<"("<<cy.x<<","<<cy.y<<")"<<" "<<cy.r<<" "<<cy.h<<endl;
}
};
int main()
{
Point p(1,1);//定义点对象并初始化
p.set(2,2);//置点的新数据值
cout<<p;
Circle c(2,2,1);
c.setR(2);
c.set(3,3);
cout<<c.getArea();
cout<<c;
Cylinder cy(1,2,3,4);
cy.set(1,1);
cy.setR(2);
cy.setH(1.0);
cout<<cy.getArea();
cout<<cy.getVolume();
cout<<cy;
return 0;
}
4,多继承
一个类有多个直接基类的继承关系称为多继承
- 多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
}; - 多继承的派生类构造和访问
(1)多继承的构造函数:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)//初始化列表
{
// 派生类新增成员的初始化语句
}
(2)多继承方式下构造函数的执行顺序:
● 先执行所有基类的构造函数
● 再执行对象成员的构造函数
● 最后执行派生类的构造函数
(3)多继承的析构函数
多继承方式下析构函数的执行顺序,与构造函数的执行顺序相反,功能是在派生类中对新增的有关成员进行必要的清理工作。 - 虚基类
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。要使派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。虚继承声明使用关键字 virtual。 - 赋值兼容规则
赋值兼容规则,指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代:
派生类的对象可以赋给基类对象,可初始化基类的引用,可以赋给基类类型的指针
//派生类对象赋值给基类对象
Base b;
Derived d;
b=d;
//初始化基类的引用
Derived d;
Base &br=d;
//派生类对象的地址赋给基类类型的指针
Derived d;
Base *bptr=&d;
多态性
多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
多态性的实现和联编有关。所谓联编(Binding,绑定)就是把函数名与函数体的程序代码连接(联系)在一起的过程。
为了实现多态性,利用虚函数机制,可部分地采用动态联编。
1,静态联编
普通成员函数重载可表达为两种形式:
- 在一个类说明中重载
void Show ( int , char ) ;
void Show ( char * , float ) ;//字符数组和float类型为参数
- 基类的成员函数在派生类重载。有 3 种编译区分方法:
(1)根据参数的特征加以区分
void Show ( int , char );
void Show ( char * , float ); 不是同一函数,编译能够区分
(2)使用“ :: ”加以区分
A :: Show ( );
B :: Show ( );
(3)根据类对象加以区分
Aobj . Show ( ) 调用 A :: Show ( )
Bobj . Show ( ) 调用 B :: Show ( )
2,类指针的关系
基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
(1)直接用基类指针引用基类对象;
(2)直接用派生类指针引用派生类对象;
(3)用基类指针引用一个派生类对象;//赋值兼用规则
(4)用派生类指针引用一个基类对象。 //不好用,空间不一致
- 2.1 基类指针引用派生类对象
A * p ; // 指向类型 A 的对象的指针
A A_obj ; // 类型 A 的对象
B B_obj ; // 类型 B 的对象
p = & A_obj ; // p 指向类型 A 的对象
p = & B_obj ; // p 指向类型 B 的对象,它是 A 的派生类
利用 p,可以通过 B_obj 访问所有从 A 类继承的元素 , 但不能用 p访问 B 类自定义的元素 (除非用了显式类型转换)
- 2.2 派生类指针引用基类对象
派生类指针只有经过强制类型转换后,才能引用基类对象
使用:
#include<bits/stdc++.h>
using namespace std ;
class A
{
char name[20];
public:
void setName(char *n){strcpy(name,n);}
void showName(){cout<<name<<endl;}
};
class B:public A
{
char phoneNum[20];
public:
void setPhoneNum(char *num){strcpy(phoneNum,num);}
void showPhoneNum(){cout<<phoneNum<<endl;}
};
int main()
{
A aobj;
A *p;
B bobj;
p=&aobj;
p->setName("lfy");
p->showName();
p=&bobj;
p->setName("lfy2");
p->showName();
bobj.setPhoneNum("0987654321");
bobj.showPhoneNum();
((B *)p)->setPhoneNum("1234567890");
//A类型强制类型转换为B类型,基类不能直接操作派生类新增成员
((B *)p)->showPhoneNum();
return 0;
}
class Date
{
private:int year,month,day;
public:
Date(int y,int m,int d){setDate(y,m,d);}
void setDate(int y,int m,int d){year=y;month=m;day=d;}
void showDate(){cout<<year<<"/"<<month<<"/"<<day<<endl;}
};
class DateTime:Date
{
private:int hour,minute,second;
public:
DateTime(int y,int m,int d,int h,int mi,int s):Date(y,m,d){setDateTime(h,mi,s);}
void setDateTime(int h,int mi,int s){hour=h;second=s;minute=mi;}
void showDateTime()
{ ((Date *)this)->showDate();//对 this 指针作类型转换调用基类成员函数
cout<<hour<<":"<<minute<<":"<<second<<endl;
}
};
int main()
{
DateTime d(2020,5,16,11,11,30);
d.showDateTime();
return 0;
}
虚函数:
根据赋值兼容,用基类类型的指针指向派生类,就可以通过这个指针来使用类(基类或派生类)的成员函数。
如果这个函数是普通的成员函数,通过基类类型的指针访问到的只能是基类的同名成员。
而如果将它设置为虚函数,则可以使用基类类型的指针访问到指针正在指向的派生类的同名函数。从而实现运行过程的多态。
3,虚函数与动态联编
实现动态联编方式的前提:
●先要声明虚函数
●类之间满足赋值兼容规则
●通过指针与引用来调用虚函数。
- 3.1 虚函数和基类指针
基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员
注意:
-
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
-
虚函数必须是类的成员函数
不能将友元说明为虚函数,但虚函数可以是另一个类的友元 -
析构函数可以是虚函数,但构造函数不能是虚函数
使用:
#include<iostream>
using namespace std ;
class Base
{ public :
Base(char xx) { x = xx; }
void who()
{ cout << "Base class: " << x << "\n" ; }
protected: char x;
} ;
class First_d : public Base
{ public :
First_d(char xx, char yy):Base(xx) { y = yy; }
void who()
{ cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected: char y;
} ;
class Second_d : public First_d
{ public :
Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
void who()
{ cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected: char z;
} ;
int main()
{
Base B_obj( 'A' ) ;
First_d F_obj( 'T', 'O' ) ;
Second_d S_obj( 'E', 'N', 'D' ) ;
Base * p ;
p = & B_obj ; p -> who() ;
p = &F_obj ; p -> who() ;
p = &S_obj ; p -> who() ;
F_obj.who() ;
( ( Second_d * ) p ) -> who() ;
}
- 3.2 虚函数的重载特性
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
class base
{ public :
virtual void vf1 ( ) ;
virtual void vf2 ( ) ;
virtual void vf3 ( ) ;
void f ( ) ;
} ;
class derived : public base
{ public :
void vf1 ( ) ; // 虚函数
void vf2 ( int ) ; //重载,参数不同,虚特性丢失
char vf3 ( ) ; // error,仅返回类型不同
void f ( ) ; // 非虚函数重载,覆盖
} ;
- 3.3 虚析构函数
虚析构函数用于指引 delete 运算符正确析构动态对象
析构函数未使用虚函数时
class A
{ public:
~A(){ cout << "A::~A() is called.\n" ; }
} ;
class B : public A
{ public:
~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main()
{
A *Ap = new B ;
B *Bp = new B ;
cout << "删除第一个对象:\n" ;
delete Ap;
cout << "删除第二个对象:\n" ;
delete Bp ;
}
析构函数使用虚函数后:
class A
{ public:
virtual ~A(){ cout << "A::~A() is called.\n" ; }
} ;
class B : public A
{ public:
~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main()
{
A *Ap = new B ;
B *Bp = new B ;
cout << "删除第一个对象:\n" ;
delete Ap;
cout << "删除第二个对象:\n" ;
delete Bp ;
}
注意:
1.派生类应该从它的基类公有派生。
2.必须首先在基类中定义虚函数。
3.派生类对基类中声明虚函数重新定义时,关键字virtual可以不写。
4.一般通过基类指针访问虚函数时才能体现多态性。
5.一个虚函数无论被继承多少次,保持其虚函数特性。
6.虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态函数。
7.构造函数、内联成员函数、静态成员函数不能是虚函数。
(虚函数不能以内联的方式进行处理)
8.析构函数可以是虚函数,通常声明为虚函数。
4,纯虚函数与抽象类
纯虚函数是一个在基类中只有说明的虚函数,没有实现,它的实现留给该基类的派生类去做。
纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
一个具有纯虚函数的基类称为抽象类。
派生类一定要重写虚函数。
抽象类不能直接生成对象,可以声明抽象类的指针和引用
感想
先总结下最近的学习情况,有以下几方面的问题:
1,这个学期除了我正常进度的网课外,还有几门需要补修的必修课,其中有些课因为课程时间冲突,无法按老师的正常进度上课。对于这种情况,我的原计划本是抽空自己看网课的回放,自己补上,但是可悲的是,原计划付之东流,并没有在计划范围内完成自己该做的事,有些课欠了不少的网课。反思了下,自己心态比较浮躁,学习上欠扎实,效率不高。
2,在家里,对自己比较“宽容”。特别是前段时间,小区里春检水电,有时候停电,楼上楼下改水管道,就会给自己找借口,“停电耽误了我学习;楼上电钻太吵”类似的借口。家里时常有些其他事情,也会比较“热闹”,而我就特别容易受外界影响。但其实客观的讲,“只要想做会找方法,不想做就是找借口”,我自己心里确实多装了一些安逸和懒惰。
其实上网课能看回放,时间上很灵活,相对自由,不用像我上学期两个校区间来回折腾着补课,方便了不少。我应该节制自己的惰性和贪玩,不要失去网课原有的价值和意义。
3,前期学习的内容遗忘,特别是这学期的数据库,组成原理,还需温故知新,加强巩固。
再说一下最近的收获,对于图书管理系统,相比最开始有了更多的理解,整体上通透了一些。对文件读写,运算符重载,STL相关等知识点的使用,有了更直接的认识。在写程序的过程中,特别是调试的时候,有时候错误和问题较多,让人很头痛,可能现在遇到的问题和解决的问题多一点,以后才能使用的更熟练。
这次作业已经交了,但我又注意到,我的存在问题,读写的数据文件因该是一致的,如记录数据文件读操作用“inRecord.txt”;写操作用“outRecord.txt”,导致新的记录不会在下一次执行时读出。此外,老师也说了,还要考虑多客户端的时候,要有登录和不同客户的数据文件。关于登录的问题,我大题上思考了下,写了个简单的登陆。
#include<iostream>
using namespace std;
string password="123456";
int main()
{
int option;
void login(string a,string b); //登陆
void reset_password(string n);//重置密码
string id="2018217371";
string input;
while(1)
{
cout<<"1. 学生登陆"<<endl;
cout<<"2. 重置密码"<<endl;
cout<<"3. 退出"<<endl;
cin>>option;
switch (option)
{
case 1:
login(id,input);
break;
case 2:
reset_password(id);
break;
case 3:
break;
default:
cout<<"输入错误,请正确输入!"<<endl;
break;
}
if(option==3)
{
cout<<"退出成功!"<<endl;
break;
}
}
}
void login(string a,string b)
{
cout<<"学生登陆"<<endl;
cout<<"学号:";
cin>>a;
cout<<"密码:";
for(int i=3;i>=0;i--)
{
cin>>b;
if (a=="2018217371"&&b==password)
{
cout<<"登陆成功!"<<endl;
break;
}
else
{
cout<<"密码错误,请重新输入"<<endl;
}
}
}
void reset_password(string n)
{
string id;
string old_password;
cout<<" 学号:";
cin>>id;
cout<<"旧密码:";
cin>>old_password;
if(old_password==password &&id==n)
{
cout<<"请输入新密码:";
cin>>password;
cout<<"密码重置成功"<<endl;
}
else
cout<<"重置密码失败!"<<endl;
}
还有一点学习以外的收获,现在慢慢意识到身体素质真的重要,所以坚持了一段时间,每天会做有氧运动,提高免疫力,延年益寿。
前段时间在b站上,看罗翔老师说刑法里提到:“学习和读书是为了培养智慧”,就像是吃饭,我们或许不记得三天前吃了什么,但是吃过的东西就成了身体的养料,读书也是同样的,学习或读书的内容也会成为自己的养分。