一些首先需要了解的小知识点:
c++和java都是面向对象的语言,具有封装、继承、多态三大特性
- 对象的静态类型(声明类型):对象在声明时采用的类型,在编译期就已经确定,不可以更改
- 对象的动态类型(实际类型):目前所指对象的类型,在运行期决定的,可以更改
- 绑定:一个方法的调用与方法所在的类(方法主体)关联起来
- 静态绑定:程序在编译期就进行的绑定
- 动态绑定:程序在运行时根据具体对象的类型进行绑定
- c++函数调用默认的绑定方式是静态绑定,只有虚函数进行的是动态绑定
- java方法的调用只有final,static,private和构造方法是静态绑定的,其他方法默认动态绑定
c++和java的多态
多态就是不同继承关系的类对象,去调用同一函数,产生了不同的行为
c++(单继承):
- 派生类添加了与基类的某个成员函数同名(同函数名)的函数,派生类中的函数就会遮蔽从基类继承下来的同类函数,需要在函数名前添加作用域限定符::去显示调用基类中的成员函数
- 通过基类对象、基类指针或基类引用,只能访问到派生类中从基类继承下来的成员,不能访问派生类中新增的成员。也就是说在静态绑定下,调用了声明类型对应对象的函数。
- 子类不能重载父类函数。只要子类中出现父类的同名函数,子类就会重定义该函数,若该函数在父类被声明为虚函数(virtual),则称为重写。
class Base {
public:
void print(int a) const{
cout << "Class Base" << endl;
}
};
class Base1:public Base {
public:
void print(int a,int b) const {
cout << "Class Base1" << endl;
}
void addFunction() const {
cout << "派生类的新增函数" << endl;
}
};
int main() {
Base base_x; //基类对象
Base1 base1_x; //派生类对象
base1_x.print(1,2); //输出“Class Base1”
base1_x.print(1); //报错,函数不接受一个参数。说明父类void print(int a)函数被隐藏
base1_x.Base::print(); //输出“Class Base”
base_x.print(); //输出“Class Base”
base_x = base1_x; //将派生类对象赋值给基类对象
base_x.print(1); //输出“Class Base”
//因为c++默认函数调用是静态绑定,最后调用基类对象时看的是声明类型Base,绑定调用基类中的函数。
Base *base_p;
base_p = &base1_x; //将派生类对象地址赋值给基类指针
base_p->print(1); //输出“Class Base”
Base &base_r=base1_x; //派生类对象初始化基类引用
base_r.print(1); //输出“Class Base”
//basex.addFunction(); 报错,派生类新增成员不能访问
}
- 想用基类指针访问到在派生类中的同名函数,应该将基类中的函数声明为虚函数virtual进行重写,然后通过指针或引用来使用动态绑定。
- 当一个引用类型变量是一个对声明类型实例的引用时(或一个引用类型指针指向一个声明类型实例),变量的实际类型是被引用或被指向变量的类型。而函数调用哪个类的方法由对象的实际类型决定,这就是动态绑定。虚函数virtual则是动态绑定的基础。
- c++的多态对应静动态绑定方式分为:编译时多态和运行时多态
- 运行时多态必须满足:1、具有继承关系;2、必须在基类中声明虚函数;3、通过基类指针或基类引用来调用虚函数
#include<iostream>
using namespace std;
class Base {
public:
virtual void print(int a) const{
cout << "Class Base" << endl;
}
};
class Base1:public Base {
public:
//下面两个print都对基类print函数进行了重写,且这两个print互为重载
virtual void print(int a) const { //virtual可省略,继承于Base
cout << "Class Base1" << endl;
}
virtual void print(int a,int b) const { //virtual可省略,继承于Base
cout << "Class Base1 two" << endl;
}
void addFunction() const {
cout << "派生类的新增函数" << endl;
}
};
int main() {
Base basex;
Base1 base1x;
base1x.print(1); //输出“Class Base1”
base1x.Base::print(1); //输出“Class Base”
basex.print(1); //输出“Class Base”
basex = base1x; //将派生类对象赋值给基类对象
basex.print(1); //输出“Class Base”
basex.print(1,2); //报错,基类对象仍然是静态绑定
//basex.addFunction();
Base* basep;
basep = &base1x; //将派生类对象地址赋值给基类指针
basep->print(1); //输出“Class Base1”
basep->addFunction(); //报错,addFunction()不是从基类中继承下来的虚函数,所以仍然不能调用
Base& baser = base1x; //派生类对象初始化基类引用
baser.print(1,2); //输出“Class Base1 Two”
}
Java(单继承):
- java派生类中的方法都是抽象方法(后文介绍,与c++的virtual相似),除非特意用final或static修饰了方法成为静态绑定,都是默认为动态绑定。
- Java中不存在指针,传值都是引用传递,所以也不用像c++那样区分基类对象、基类指针和基类引用,直接是向上转换动态绑定,调用实际类型决定的方法
- 如果java基类中的方法被修饰成静态方法,则不能被重写。父类中定义的静态方法在子类中被重定义,那只能通过“父类名.静态方法名”去访问。
- java中可以在子类中重载父类同名方法,当子类方法与父类方法同名时但签名不同发生重载,而签名一样,返回类型一样或兼容则是重写
- 一个被final修饰的类和方法都不能被继承,被final修饰的数据域是一个常数。这样可以防止类被继承或方法被重写
public class Test1 {
public static void main(String[] args) {
Base basex = new Base(); //基类对象
Base1 base1x = new Base1(); //派生类对象
base1x.print(); //输出“Class Base1”
base1x.print(1); //输出“Class Base1但是重载”
base1x.fun(); //输出“fun Base1”
base1x.ifBaseChanged(); //输出“Class Base”,说明方法重写只是在子类中进行方法重写,不会改变父类中的方法
basex.print(); //输出“Class Base”
basex.fun(); //输出“fun Base”;
basex = base1x; //引用传递
basex.print(); //输出“Class Base1” base1为basex的实际类型,调用base1的print方法
basex.fun(); //输出“fun Base” fun是静态方法无法进行动态绑定,所以仍然输出声明类型的方法
//basex.ifBaseChanged(); 报错,不可以访问
}
}
class Base{
public void print() {
System.out.println("Class Base");
}
static void fun() {
System.out.println("fun Base");
}
}
class Base1 extends Base{
@Override
public void print() {
System.out.println("Class Base1");
}
public void print(int a) {
System.out.println("Class Base1但是重载");
}
public void ifBaseChanged() {
super.print();
}
static void fun() {
System.out.println("fun Base1");
}
}
C++(多继承):
c++允许多继承
- 多继承中派生类从所有基类中继承了数据成员和成员函数,继承和访问规则和单继承一样。但是多继承增加了命名冲突的可能性
- 如果多个基类中存在同名函数,全部继承给了派生类,派生类对象调用这些函数时需要标清作用域;如果派生类中声明了与它们同名的函数,则会将它们都遮蔽。
#include<iostream>
using namespace std;
class Base {
public:
void setX(int x) {
this->x = x;
}
int getX() const{
return x;
}
private:
int x;
};
class Base1 :public Base {
public:
void setY(int y) {
this->y = y;
}
int getY() const{
return y;
}
private:
int y;
};
class Base2 :public Base {
public:
void setZ(int z){
this->z = z;
}
int getZ() const {
return z;
}
private:
int z;
};
class Derived :public Base1, public Base2 {
};
int main() {
Derived d;
d.Base1::setX(111);
cout << d.Base1::getX() << endl;
d.Base2::setX(222);
cout << d.Base2::getX() << endl;
return 0;
}
- 派生类Derived含有通过Base1,Base2继承下来的基类Base中的同名函数。当通过派生类对象来访问基类Base的成员时,不能用间接基类Base来限定,因为无法表明成员是从Base1还是Base2继承的,必须使用直接基类Base1或Base2来限定
- 对于同一个间接基类的多继承,在派生类中通过不同派生路径继承下来的间接基类成员有多份相同的副本。可以将同一个间接基类声明为虚基类,这样在派生类中从不同派生路径继承下来的间接基类成员只有一份副本。
class Base {
public:
Base(int x) {
this->x = x;
}
void setX(int x) {
this->x = x;
}
int getX() const{
return x;
}
private:
int x;
};
class Base1 :virtual public Base {
public:
Base1(int y) :Base(y) {
this->y = y;
}
void setY(int y) {
this->y = y;
}
int getY() const{
return y;
}
private:
int y;
};
class Base2 :virtual public Base {
public:
Base2(int z) :Base(z) {
this->z = z;
}
void setZ(int z){
this->z = z;
}
int getZ() const {
return z;
}
private:
int z;
};
class Derived :public Base1, public Base2 {
public:
Derived(int value) :Base(value), Base1(value), Base2(value){}
};
int main() {
Derived d(111);
d.setX(111);
cout << d.getX() << endl;
return 0;
}
-
如果虚基类中没有无参构造函数,只有有参构造函数,则在类层次结构中,直接或间接继承虚基类的所有派生类都必须在构造函数初始化列表中显示调用虚基类的有参构造函数。
-
纯虚函数是在基类中声明的没有函数体的虚函数。含有纯虚函数的类称为抽象类,抽象类只能作为其他类的基类,所以又称抽象基类
-
不能创建抽象类对象,但可以声明抽象类指针或抽象类引用,指向或引用其派生类对象。
-
建立抽象类的目的就是为了多态地使用其中的纯虚函数
class Shape{
public:
virtual double getArea() = 0;
virtual double getPerimeter() = 0;
}
java的抽象类和接口
java抽象类
与c++抽象类定义类似,在基类中声明但没有函数体的方法,称为抽象方法,用abstract修饰,而含有抽象方法的基类称为抽象类,类的头部也要用abstract修饰。抽象类的构造方法定义为protected,因为它只被子类使用。创建抽象类的目的同样是为了多态地调用抽象方法。
子类可以重写父类的方法并将它定义为抽象的,这很少见,但是当父类的方法实现在子类中变得无效时,子类必须定义为抽象的。
java接口
java不能多继承,所以java通过接口来实现类似于多继承的功能。抽象类可以包括它子类的共同特征,而子类中部分子类有自己的共同特征,此时可以用到接口。接口是一种与类相似的结构,用于指明相关或不相关类的对象的共同行为。
为了区分接口和类,java采取下面的语法来定义接口:
public interface Edible{
public abstract String howToEat();
}
用关键词implement让对象所属的类来实现这个接口。类和接口之间的关系称为:接口继承。
class Chicken extends Animal implements Edible{
@Override
public String hoeToEat(){
return "Chicken: Fry it";
}
}
class Tiger extends Animal{
}
abstract class Fruit implements Edible{
}
class Apple extends Fruit{
@Override
public String hoeToEat(){
return "Apple:Make apple cider";
}
接口中所有方法都是public abstract,所有的数据域都是public static final。
接口与抽象类的区别:
一个类可以实现多个接口,但只能继承一个父类
同时接口也可以继承其他接口,这样的接口称为子接口,但是接口不能继承类
所有的类共享一个根类Object,但是接口没有共同的根。
一般来说,清晰描述父子关系的“是…的一种”关系,使用类,而用来描述行为动作,表明对象拥有某种属性的,使用接口。
通常推荐使用接口而非抽象类,因为接口可以为不相关类定义共同的父类型。接口比类更加灵活。