第九天2017/04/18(3、重载/覆盖 PK 重写/重定义、父类子类混搭风、抽象类)
2017-04-20 11:04
225 查看
1、重载/覆盖 PK 重写/重定义 【预备知识】 函数重载 必须在同一个类中发生 子类无法重载父类的函数,父类同名的函数将会被名称覆盖 重载是在编译期间根据参数类型和个数决定函数调用 重载只放在同一个类之中,在编译期间就确定 函数重写 必须发生在父类与子类之间 父类与子类中的函数必须有完全相同的函数原型 使用virtual关键字声明后能够产生多态(如果没有virtual,那叫重定义) 多态是运行期间根据具体对象的类型决定函数调用 重写发生在父子类之间, 覆盖 父类和子类中有“同名”的函数,子类的函数会把父类的函数隐藏起来 重定义(是一种特殊的覆盖) 父类和子类中有“相同函数原型”的函数,子类的函数会把父类的函数隐藏起来 1、重载/覆盖 PK 重写/重定义 重载:在“同一个类”中,函数名相同、函数原型不同,此时发生重载 覆盖:无virtual关键字,在父类、子类中,函数名相同、函数原型不同,此时在子类中的函数隐藏了父类中的函数。 --------------------------------------------------------------------------------------------- 重写:有virtual关键字,在父类、子类中,函数名、函数原型都相同,此时发生重写 重定义:无virtual关键字,在父类、子类中,函数名、函数原型都相同,此时在子类中的函数隐藏了父类中的函数,类似于“覆盖”。 #include <iostream> using namespace std; class B { public: void f() { } virtual void f(int i) { } void f(int i,int j) { } }; class D:public B { public: //子类中没有void f()函数 void f(int i) { } //重写 void f(int i,int j) { } //发生名称覆盖 void f(int i,int j,int k) { } //发生名称覆盖 }; void g(B& b) { b.f(1); } void main() { D d; /***************************************************************************/ //【重点】 //疑问:为什么子类对象不能调用父类中的f()函数? //答:因为子类中有函数名为f的函数,有由于子类的函数不会重载父类的函数,所以 //子类中的f函数会把父类中的无参的f()函数给覆盖,因此直接d.f()会发生编译错误! //d.f();//error: 没有重载函数接受0个参数的f() //疑问:如果我就是想用子类对象去调用父类中的f()函数,应该怎么做? //答:加上作用域符B::,此时d对象就会调用父类B中的f()函数。 d.B::f(); // //【结论】子类中的f()函数不会重载父类中的f()函数,父类同名的函数将被覆盖!重载只发生在同一个类中! /***************************************************************************/ D dd; //覆盖 //dd.f(); //编译失败,因为无virtual发生同名覆盖 dd.B::f(); //作用域B::,调用子类中的void f() //覆盖、重定义 dd.B::f(1,2);//作用域B::,调用子类中的void f(int i,int j) dd.f(1,2); //覆盖:调用子类中的void f(int i,int j) //多态 B &b = d; b.f(1); //多态:调用子类中的void f(int i) b.B::f(1); //作用域B::,调用父类中的void f(int i) //重载 } ---------------------------------------------------------------------- 2、父类对象、子类对象混搭风【该模块中,隐藏了一个天大的Bug:“P++步长”】 本质:由于步长的影响 【结论】 不要用父类指针p指向子类对象的数组,通过p++,去 遍历这个子类对象的数组。 同理:也不要用子类指针p指向父类对象的数组,通过p++,去 遍历这个父类对象的数组 【为什么?】因为父类指针p指向了子类对象的数组,在进行p++的时候, p增加的步长是sizeof(父类),但是由于sizeof(父类)不一定等于sizeof(子类), 因此p在进行加1后,指向的位置不一定是下一个子类对象的首地址。当p指向的位置 不是下一个子类对象的首地址的时候,如果进行访问子类对象中的成员,程序必然会发生 崩溃。 //程序案例 #include <iostream> using namespace std; class A { public: virtual void f() { cout<<"A:f()"<<endl; } }; class B:public A { public: int i; //为什么在子类中加上一个属性i,程序就会运行崩溃:因为加了一个变量,对步长有影响 public: B(int i=0,int j=0){} virtual void f() { cout<<"B:f()"<<endl; } }; void howToF(A* pBase) { pBase->f(); } int main() { A *p = NULL; B *q = NULL; B c[3] = {B(1,1),B(1,1),B(1,1)}; p = c; //父类指针p指向由子类对象构成的数组 q = c; //子类指针q指向由子类对象构成的数组 for(int i=0;i<3;i++) { //p->f(); //因为步长的原因,访问成员时,会发生程序崩溃 q->f(); p++; //p++增加的步长是sizeof(父类),而不是sizeof(子类) q++; } for(int i=0;i<3;i++) { howToF(&c[i]); //形参:子类对象的地址,实参:父类指针 //解释:此处运行正确,为什么? //此处没有用p++形式,而是用了下标操作c[i],避开了p++的步长不一致导致的程序崩溃。 } return 0; } ---------------------------------------------------------------------- 3、抽象类 #include <iostream> using namespace std; class A { public: virtual void ff() = 0; }; //A g1(); //不允许使用返回抽象类 "A" 的函数 A& g21(); //允许使用返回抽象类引用类型 "A&" 的函数 A& g22(A&); A& g23(A*); A* g31(); //允许使用返回抽象类指针类型 "A*" 的函数 A* g32(A*); A* g32(A&); //void f1(A a);//不允许使用抽象类类型 "A" 的参数 void f2(A &a);//允许使用抽象类引用类型 "A&" 的参数 void f2(A *a);//允许使用抽象类指针类型 "A*" 的参数 class B:public A { public: void ff() { } }; int main() { //A a1;//不能实例化抽象类 //A a2 = new A;//不能实例化抽象类 A *a4 = new B; //可以用抽象类的指针指向抽象类的子类对象 B b1; A &a3 = b1;//可以用抽象类的引用指向抽象类的子类对象 } 4、多重继承与抽象类--->实现多继承接口 【知识复习】 #include <iostream> using namespace std; class A1 { public: virtual void ff() = 0; //纯虚函数 void gg() { cout<<"普通函数"<<endl; } //普通函数在A1中实现 }; class A2 { public: virtual void ff() = 0; //纯虚函数 void gg() { cout<<"普通函数"<<endl; }//普通函数在A2中实现 }; class B:public A1,public A2//B多重继承A1、A2时 { public: virtual void ff() //纯虚函数ff在B中实现 { cout<<"纯虚函数"<<endl; } }; int main() { B b; //b访问纯虚函数ff,不会发生二义性 b.ff(); //b访问普通函数gg,会发生二义性 //b.gg(); //编译失败 //防止二义性,应该加上作用域标识符A1::、A2:: b.A1::gg(); b.A2::gg(); } 【知识引出】 疑问:多重继承会发生二义性,但是多重继承在C++中有什么作用呢? 答:在项目开发过程中,多重继承的作用主要是:多重继承多个抽象类(接口),这样会有效的避免二义性。 多继承接口案例 #include <iostream> using namespace std; class A { public: virtual void ff(){ cout<<"ff:A"<<endl; } }; class interface1 //抽象类接口1 { public: virtual void gg1 b99c () = 0; }; class interface2 //抽象类接口2 { public: virtual void gg2() = 0; }; class B:public A,public interface1,public interface2 { //B类继承了类A、接口interface1、接口interface2 public: void gg1() { cout<<"gg1"<<endl; } void gg2() { cout<<"gg2"<<endl; } void kk(){ cout<<"KK"<<endl; } }; int main() { B b; b.ff(); b.gg1(); b.gg2(); b.kk(); }
相关文章推荐
- C++父类子类间函数重定义,虚函数重写(覆盖)以及重载
- C++父类子类间函数重定义,虚函数重写(覆盖)以及重载
- 重写(覆盖)子类的权限不能低于父类的权限
- C++和Java在 子类继承父类时,两者成员函数重写和重载的特性
- 重写(重定义)父类里多个重载中的一个时,父类的其他重载都会被隐藏
- java学习,子类覆盖父类及重写的相关学习
- 创建一个抽象类A,该类中包含一个求两个数之和抽象方法。创建一个子类B,在B中重写求和方法,且使用方法重载使得
- C++中重定义、重写、重载的区别以及隐藏与覆盖的访问
- C#第2次试验(1).创建一个抽象类A,该类中包含一个求两个数之和抽象方法。创建一个子类B,在B中重写求和方法,且使用方法重载使得方法可以分别计算整数、双精度、字符串。
- 面向对象 子类中 < 重载 重写 >父类的方法
- C++中重定义、重写、重载的区别以及隐藏与覆盖的访问
- 子类对父类中虚方法的处理有重写(override)和覆盖(new)的区别
- 2.创建一个抽象类A,该类中包含一个求两个数之和抽象方法。创建一个子类B,在B中重写求和方法,且使用方法重载使得方法可以分别计算整数、双精度、字符串
- 子类与父类之间的方法重载、隐藏、重写与虚方法调用
- A.4-继承,方法重写(覆盖)(override),虚函数(virtaul)和多态,抽象方法和抽象类abstract,密封类(sealed)和密封方法,常量的定义(const)
- C#--第2周实验--任务13--创建一个抽象类A,创建一个子类B,在B中重写求和方法,且使用方法重载使得方法可以分别计算整数、双精度、字符串。
- 2018/01/07JAVA 基础 / 接口与继承:重写【子类继承并覆盖父类的对象方法】、隐藏【子类继承并覆盖父类的类方法】与实现类实现接口方法の区别
- 类成员函数中重载/重写(覆盖)/重定义(隐藏)的区别
- 子类中的方法和父类同名,但是参数不同,是重写(overload)不是覆盖(override)
- C++ overload(重载) overrriding(重写 or 覆盖) redefine(重定义)浅析