赞
踩
1.1
1.1.1 生活中的继承
龙生龙,凤生凤,老鼠儿子会打洞。
生活中的继承是一种长相和行为的继承。
1.1.2 类之间的关系
思想很重要
)思想很重要
)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 继承的意义
1.4.3
1.5 继承的中的访问级别
1.5.1 思考
根据面向对象理论
直接访问
父类的私有成员不能直接访问
父类的私有成员实验证明:
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
访问级别1.5.3 思考
#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 ,应该改成 string1.6 继承中的构造和析构
1.6.1子类构造函数
1.6.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; }
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 函数重载再论
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; }
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; }
void how_to_print(Parent * p)
{
p->print();
}
在编译这个函数的时候,编译器不可能知道指针p究竟指向了什么。但是编译器没有理由报错,于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。