C++进阶剖析(十 九)之继承

    xiaoxiao2024-11-22  68

    1.1

    继承是非常重要的,在面向对象编程中有着举足轻重的地位。尤其是在设计模式中。

    1.1.1 生活中的继承 龙生龙,凤生凤,老鼠儿子会打洞。 生活中的继承是一种长相和行为的继承。

    1.1.2 类之间的关系

    组合关系(思想很重要) 一台电脑有CPU,主板,显示器等组成继承关系(思想很重要) 电脑分为惠普,联想等(is a ) 1.1.3

    1.2 类的组合关系 1.2.1代码实例 电脑类的实现

    #include <iostream> using namespace std; class Disk { public: Disk() { printf("Disk()\n"); } ~Disk() { printf("~Disk()\n"); } }; class CPU { public: CPU() { printf("CPU()\n"); } ~CPU() { printf("~CPU()\n"); } }; class MainBoard { public: MainBoard() { printf("MainBoard()\n"); } ~MainBoard() { printf("~MainBoard()\n"); } }; class Memory { public: Memory() { printf("Memory()\n"); } ~Memory() { printf("~Memory()\n"); } }; class Computer { public: Computer() { printf("Computer()\n"); } ~Computer() { printf("~Computer()\n"); } private: Disk disk; Memory memory; MainBoard mainBoard; CPU cpu; }; int main() { Computer c1; return 0; } 先父母 再客人,后自己

    运行结果

    1.2.2 组合关系的特点

    将其他类的对象作为当前类的成员来使用当前类的对象于成员对象的生命期相同成员对象在用法上于普通对象完全一致组合关系是同生死,共存亡

    注意: 实际中我们如果能用组合尽量不要用继承,因为继承的复杂度远大于组合的复杂度。

    1.3 继承关系 1.3.1 继承

    子类对象拥有父类的所有属性和行为子类就是一种特殊的父类子类对象可以当做父类对象使用,(子类对象当做父类对象使用会退化成父类对象,也就是子类中属性和方法不能使用了)子类中可以添加父类中没有的方法和属性继承可以进行代码复用

    1.3.2 重要规则

    子类是一个特殊的父类子类对象可以直接初始化父类对象子类对象可以直接赋值给父类对象

    1.3.3 示例代码

    class Parent { private: int mv; public: Parent() { cout<<"Parent()"<<endl; mv = 10; } void method() { cout<< "mv= "<<mv <<endl; } }; class Child :public Parent // { public: void print() { cout<<"i'm child class"<<endl; } }; int main() { Child c ; c.method(); //继承父类的方法 Parent p1 = c; // 调用父类copy 构造 Parent p2 ; // p2 =c; //子类对象给父类对象赋值 return 0; }

    1.4 继承中的情况分析 1.4.1对类的属性在继承中如何初始化

    类的属性在继承中初始化的时候分工是很明确的。 (1)父类的成员变量的初始化在父类中进行 (2)子类的成员变量在子类中进行 (3)子类中的类对象的初始化在自己的类中进行初始化,仅仅是在子类中调用初始化列表就行

    实例程序代码包含了: (1)二阶构造(父类中二阶构造,子类中二阶构造),注意父类和子类中二级构造的方式, 父类中二级构造和子类中二阶构造是分工进行完成的,只是简单的函数调用而已。 (2)子类中包含类对象(组合的关系), (3)子类中包含类对象的指针(利用二阶构造进行初始化,), (4)成员函数是类对象的指针,类对象如何利用组合类的成员函数对类对象进行初始化(这里只能利用成员函数,因为这在类的外部 ) (5)父类和子类中包含同名的成员函数 print函数,成员函数如何被调用(这是后面多态的必要条件) (6)子类对象初始化父类对象以后,父类对象不能使用子类新增加的成员变量和成员函数。 (7)使用了二阶构造以后,copy构造函数还有用吗? Child * p2 =child; //true ,因为仅仅是将一个指针指向了一个地址 Child c2 =*child;//error //浅copy ,发生多次释放内存情况,导致程序错误。 具体情况看下面的代码。

    #include <iostream> using namespace std; class Test { protected: int a; public: Test(int a =0) { this->a =a; } int getA()const { return a; } void setA(int a) { this->a =a; } void print() { cout <<"Test() a =" << a <<endl; } ~Test() { cout<<"~Test()"<<endl; } }; class Parent { protected: int mi; char * name1; Parent(int i =0) { this->mi =i; } bool TwoConstructor(const char* name1) { bool ret =true; this->name1 =new char[strlen(name1)+1]; //importance if(this->name1 != NULL) { strcpy(this->name1,name1); } else { ret =false; } return ret; } public: static Parent * NewInstance(int i,const char* name1) { Parent * ret = new Parent(i); if( !(ret && ret->TwoConstructor(name1))) { delete ret; ret = NULL; cout<< "Parent::NewInstance(int i,const char* name1) failure "<<endl; return ret; } cout<< "Parent::NewInstance(int i,const char* name1) success "<<endl; return ret; } void print() { cout << "mi = "<< mi<< ", name1 = "<<name1<<endl; } ~Parent() { delete[] name1; name1 = NULL; cout <<"~Parent()"<<endl; } }; class Child :public Parent { protected: int mj; char *name2; Test t1; Test *p1; Child(int j,int a):t1(a) { this->mj =j; } bool TwoConstructor(const char* name1,const char* name2,const Test& obj) { bool ret =true; ret =Parent::TwoConstructor(name1); if(!ret) return ret; this->name2 = new char[strlen(name2)+1]; if(name2 != NULL) { strcpy(this->name2,name2); } else { return false; } p1= new Test; p1->setA(obj.getA()); return ret; } public: static Child * NewInstance(int j,int a,const char* name1,const char* name2,const Test& obj) { Child * ret = new Child(j,a); if(!(ret && ret->TwoConstructor(name1,name2,obj))) { delete ret; ret = NULL; cout<< "Child::NewInstance(int i,const char* name1) failure "<<endl; } cout<< "Child::NewInstance(int i,const char* name1) success "<<endl; return ret; } void print() { Parent::print(); cout<< "mj = "<< mj <<" , name2 = "<<name2 <<endl; t1.print(); p1->print(); } void test1() { printf("test子类对象初始化父类对象以后,子类对象新添加的成员变量的成员函数是否还能用\n "); } ~Child() { delete[] name2; delete p1; cout <<"~Child()"<<endl; } }; int main() { /* Parent * parent = Parent::NewInstance(4,"zhangsan"); if(parent != NULL) { parent->print(); delete parent; } */ Test t1(2); Child * child = Child::NewInstance(10,20,"zhangsan","lisi",t1); if(child != NULL) { child->print(); delete child; } Parent * parent1 =child; //parent1->test();//error 子类对象初始化父类对象以后,子类对象新添加的成员变量和成员函数不能使用 return 0; }

    1.4.2 继承的意义

    继承是C++中代码复用的重要手段。通过继承,可以活动父类的所有功能,并且可以再子类中重写已有功能,或者添加新功能。

    1.4.3

    1.5 继承的中的访问级别 1.5.1 思考

    子类是否可以直接访问父类的私有成员?

    根据面向对象理论

    子类拥有父类的一切属性和行为 ===========》子类能够直接访问父类的私有成员 根据C++语法:外界不能直接访问类的private成员=======》子类不能直接访问父类的私有成员

    实验证明:

    class Parent { private: int mv; public: Parent() { mv = 10; } int value() { return mv; } }; class Child :public Parent // { public: int addValue(int v) { mv = mv +v; } }; int main() { return 0; }

    如何解决呢?父类中使用protected关键字

    1.5.2 protected

    面向对象中的访问级别不只是 public 和 private可以定义protected访问级别关键字protected的意义 修饰的成员不能被外界直接访问修饰的成员可以被子类直接访问

    1.5.3 思考

    为什么需要protected关键字 1.5.4 组合和继承的综合实例object 类(被继承),Point(点类) ,Line(类) #include <iostream> #include <string> #include <sstream> using namespace std; class Object { protected: string mName; string mInfo; public: Object() { mName = "Object"; mInfo = ""; } string name() { return mName; } string info() { return mInfo; } }; class Point :public Object { private: int mX; int mY; public: Point(int mx = 0,int my =0) { ostringstream s; mName = "Point"; mX = mx; mY = my; s <<"p("<<mX<<","<<mY<<")"; mInfo = s.str(); } }; class Line :public Object { private: Point mP1; Point mP2; public: Line(Point mp1,Point mp2) { ostringstream s; mP1 = mp1; mP2 = mp2; mName ="Line"; s <<"Line from "<<mP1.info() <<" to "<<mP2.info(); mInfo = s.str(); } }; int main() { Object o; cout<<o.name()<<endl; cout<<o.info()<<endl; cout<< endl; Point p1(10,20); cout<<p1.info()<<endl;; cout<<p1.name()<<endl; Point p2(1,2); cout<<endl; Line line(p1,p2); cout<< line.info()<<endl; cout<< line.name()<<endl; return 0; }

    注意在写代码过程中,上面代码中下面一行报错了 s <<"Line from "<<mP1.info() <<" to "<<mP2.info();报错的原因不在于这一行,而是在Object类中的info()函数的时候返回值是void ,应该改成 string

    1.6 继承中的构造和析构 1.6.1子类构造函数

    子类构造函数必须对继承来的成员进行初始化 (1)直接通过初始化列表或者赋值的方式进程初始化 (2)调用父类构造函数进行初始化

    1.6.2 父类构造函数在子类中的调用方式

    默认调用 (1)适用于无参数构造函数和使用默认参数的构造函数显示调用 (1)通过初始化列表进行调用 (2)适用于所有父类构造函数

    1.6.3 对象创建时构造函数调用顺序 1.调用父类构造函数 2. 调用成员变量的构造函数 3. 调用类自身的构造函数

    总结1:先父母,再客人,后自己

    总结2:析构与构造顺序相反

    1.7 父子间的冲突 1.7.1 思考

    子类中是否可以定义父类中的同名成员如果可以,如何区分?如果不可以,为什么?写代码说明 class Parent { public: int mi; }; class Child : public Parent { public: int mi; }; int main() { Child c; c.mi = 100; // mi 究竟是子类自定义的,还是从父类继承得到的? return 0; } mi 究竟是子类自定义的,还是从父类继承得到的?

    1.7.2 理论

    子类可以定义父类中的同名成员

    子类中的成员将隐藏父类中的同名成员

    父类中的同名成员依然存在于子类中

    通过 作用域分辨符(::)访问父类中的同名成员

    代码

    namespace A { int g_i = 0; } namespace B { int g_i = 1; } class Parent { public: int mi; Parent() { cout << "Parent() : " << "&mi = " << &mi << endl; } }; class Child : public Parent { public: int mi; Child() { cout << "Child() : " << "&mi = " << &mi << endl; } }; int main() { Child c; c.mi = 10; c.Parent::mi = 100; cout << "&c.mi = " << &c.mi << endl; cout << "c.mi = " << c.mi << endl; cout << "&c.Parent::mi = " << &c.Parent::mi << endl; cout << "c.Parent::mi = " << c.Parent::mi << endl; return 0; }

    1.7.3 函数重载再论

    子类中定义的函数是否能重载父类中的同名函数?示例1 class Parent { public: int mi; void add(int v) { mi += v; } void add(int i ,int j) { mi += (i + j); } }; class Child :public Parent { public: int mi; }; int main() { Child c1 ; c1.mi = 100; c1.Parent::mi =1000; c1.add(1); //why 为什么都累加到父类中的mi了? 正常逻辑来看,当父类定义mi,以及add函数的时候,还没有子类中的mi,add作用于父类的mi是合情合理的。 c1.add(2,3); cout<< c1.mi<<endl; // 100 cout<< c1.Parent::mi <<endl; //1006 return 0; } 示例代码2: class Parent { public: int mi; void add(int v) { mi += v; } void add(int i ,int j) { mi += (i + j); } }; class Child :public Parent { public: int mi; void add(int a,int b, int c) { mi += (a + b + c); } }; int main() { Child c1 ; c1.mi = 100; c1.Parent::mi =1000; c1.add(1); c1.add(2,3); cout<< c1.mi<<endl; cout<< c1.Parent::mi <<endl; return 0; }

    为啥会报错

    原因: 在子类中定义和父类同名的函数也会发生同名覆盖,父类中的add函数被隐藏 了。父类中的add 和子类中的add 不可能是重载,因为父类中的add和子类中的add在不同的作用域中。

    那么如何解决上面的问题呢? 加作用域符 c1.Parent::add(1);

    实验结论: 同样会发生同名覆盖。

    子类中的函数将隐藏父类的同名函数

    子类无法重载父类中的成员函数

    使用作用域分辨符访问父类中的同名函数

    子类可以定义父类中完全相同的成员函数(多态的必备)

    1.8 同名覆盖引发的问题 1.8.1 父子兼容性

    子类对象可以直接赋值给父类对象

    子类对象可以直接初始化父类对象

    父类指针可以直接指向子类对象

    父类引用可以直接引用子类对象

    代码示例

    class Parent { public: int mi; Parent(int mi =0) { this->mi =mi; } void add(int v) { mi += v; } void add(int i ,int j) { mi += (i + j); } }; class Child :public Parent { public: int mv; Child(int mv = 0) { this->mv = mv; } void add(int a,int b, int c) { mv += (a + b + c); } }; int main() { Parent p; Child c; p = c; // 子类对象可以直接赋值给父类对象 Parent p1(c); // 子类对象可以直接初始化父类对象 Parent &p3 =c; //父类引用可以直接引用子类对象 Parent * p4 = &c; // 父类指针可以直接指向子类对象 p3.mi = 100; p3.add(1); // // 没有发生同名覆盖 p3.add(2,3); // 没有发生同名覆盖 // 为什么编译不过? // p4->mv = 0; // p4->add(1,2,3); return 0; }

    1.8.2 父类指针(引用)指向子类对象时

    子类对象退化成父类对象只能访问父类中定义的成员可以直接访问被子类覆盖的同名成员

    1.8.3函数重写

    子类中可以重定义父类中已经存在的成员函数

    这种重定义发生在继承中,叫函数重写

    函数重写是同名覆盖的一种特殊情况

    当函数重新遇上赋值兼容会发生什么? 1.8.4 举例

    class Parent { public: int mi; Parent(int mi =0) { this->mi =mi; } void print() { cout<<"this is parent"<<endl; } }; class Child :public Parent { public: int mv; Child(int mv = 0) { this->mv = mv; } void print() { cout<<"this is child"<<endl; } }; void how_to_print(Parent * p) { p->print(); } int main() { Parent p; Child c; p.print(); c.print(); how_to_print(&p); // this is parent ? how_to_print(&c); // this is parent? return 0; } 结果 1.8.5 问题分析 编译期间 ,编译器只能根据指针的类型判断所指向的对象根据赋值兼容,编译器认为父类指针指向的是父类对象因此,编译结果只可能是调用父类中定义的同名函数 void how_to_print(Parent * p) { p->print(); }

    在编译这个函数的时候,编译器不可能知道指针p究竟指向了什么。但是编译器没有理由报错,于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。

    问题引出,编译器的处理方法是合理的吗? 是期望的吗?下节介绍 参考一 :狄泰软件学院C++进阶剖析 参考二 : C++ primer中文版
    最新回复(0)