目录
(1)示例1:子类对象 可以赋值给 父类的对象 / 基类的指针 / 基类的引用。
(3)不同域中函数名相同就是隐藏,函数所在作用域相同才能构成函数重载
(1)子类构造函数原则:构造顺序-先父后子。析构顺序-先子后父。
(4)问题:能否把菱形继承中的公共A的成员写成static,这样BC中的公共A就都能访问了?
(5)static 静态成员函数/变量 可以被继承,但不被对象包含
面向对象三大特性:封装、继承、多态
1、C+ + Stack类设计和C设计Stack对比。 封装更好、访问限定符+类 (狭义)
2、迭代器设计。没有迭代器,容器的访问只能暴露底层结构。-> 使用复杂、使用成本很高,对使用者要求极高。
封装了容器底层结构,不暴露底层结构情况,提供统-的访问容器的方式,降低使用成本, 简化使用。
3、stack/queue/priority. _queue的设计-- 适配器模式
定义:类设计层次的复用
图书管理系统
角色类:学生、老师、保安、保洁、后勤:
有些数据和方法每个角色都有的——设计重复了
有些数据和方法每个角色独有的
继承:
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。调用Print可以看到成员函数的复用。
- class Person
- {
- public:
- void Print()
- {
- cout << "name:" << _name << endl;
- cout << "age:" << _age << endl;
- }
- // protected/private成员对于基类 -- 一样的,类外面不能访问,类里面可以访问
- // protected/private成员对于派生类 -- private成员不能用 protected成员类里面可以用
-
- //protected:
- private:
- string _name = "peter"; // 姓名
- int _age = 18; // 年龄
- // ...
- };
-
- // 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。调用Print可以看到成员函数的复用。
- class Student : public Person
- {
- public:
- void func()
- {
- Print();
- //_age = 0; // 不可见
- }
- protected:
- int _stuid; // 学号
- };
-
- class Teacher : public Person
- {
- protected:
- int _jobid; // 工号
- };
-
- int main()
- {
- Student s;
- Teacher t;
- s.Print();
- t.Print();
- return 0;
- }
子类继承父类后会把父类的内容拷贝一份放在子类中
- class A
- {
- public:
- void print()
- {
- cout << "1" << endl;
- }
- int a = 0;
- };
- class B:public A
- {
- public:
- void print()
- {
- cout << "1" << endl;
- }
- int b = 1;
- };
- class C :public B
- {
- public:
- void print()
- {
- cout << "1" << endl;
- }
- int b = 1;
- };
-
- int main()
- {
- A a;
- B b;
- C c;
- cout << sizeof(a) << endl;
- cout << sizeof(b) << endl;
- cout << sizeof(c) << endl;
- return 0;
- }
-
- class Base1{ public: int _b1; };
- class Base2 { public: int _b2; };
- class Derive : public Base1, public Base2
- {
- public: int _d;
- };
- int main()
- {
- Derive d;
- Base1* p1 = &d;
- Base2* p2 = &d;
- Derive* p3 = &d;
- return 0;
- }
A.p1 == p2 == p3
B.p1 < p2 < p3
C.p1 == p3 != p2
D.p1 != p2 != p3
分析:p1和p2虽然都是其父类,但在子类内存模型中,其位置不同,所以p1和p2所指子类的位置也不相同,因此p1!=p2,
由于p1对象是第一个被继承的父类类型,所有其地址与子类对象的地址p3所指位置都为子类对象的起始位置,因此p1==p3,所以C正确
class B {public: int b;};
class C1: public B {public: int c1;};
class C2: public B {public: int c2;};
class D : public C1, public C2 {public: int d;};
A.D总共占了20个字节 (只看A选项)
B.B中的内容总共在D对象中存储了两份
C.D对象可以直接访问从基类继承的b成员
D.菱形继承存在二义性问题,尽量避免设计菱形继承
A.C1中b和c1共8个字节,C2中c2和b共8个字节,D自身成员d 4个字节,一共20字节
B.由于菱形继承,最终的父类B在D中有两份
C.子类对象不能直接访问最顶层基类B中继承下来的b成员,因为在D对象中,b有两份,一份是从
C1中继承的,一份是从C2中继承的,直接通过D的对象访问b会存在二义性问题,在访问时候,可
以加类名::b,来告诉编译器想要访问C1还是C2中继承下来的b。
D.菱形继承存在二义性问题,尽量避免设计菱形继承,如果真有需要,一般采用虚拟继承减少数据冗余,选C。
private成员不能用 protected成员类里面可以用
子类对象 赋值给 父类的对象时无类型转换,只是切片,把子类对象中继承父类的那一部分赋给父类对象(父类对象无法给子类对象)
公有条件下,rp和ptrp都是指向s父类的那一部分
- class Person
- {
- protected :
- string _name; // 姓名
- string _sex; // 性别
- int _age; // 年龄
- };
- class Student : public Person
- {
- public :
- int _No ; // 学号
- };
//s = (Student)p; 这样就是错的
子类成员将屏蔽父类对同名成员的直接访问(局部优先)
可以使用 基类::基类成员 显示访问父类的 _num
- //Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
- class Person
- {
- protected:
- string _name = "小李子"; // 姓名
- int _num = 111; // 身份证号
- };
-
- class Student : public Person
- {
- public:
- void Print()
- {
- cout << " 姓名:" << _name << endl;
- cout << " 学号:" << _num << endl;
- cout << " 身份证号:" << Person::_num << endl;
- }
- protected:
- int _num = 999; // 学号
- };
- int main()
- {
- Student s;
- s.Print();
- return 0;
- }
(自己设计继承,避开隐藏)
- // 两个fun关系是隐藏
- class A
- {
- public:
- void fun()
- {
- cout << "A::func()" << endl;
- }
- };
- class B : public A
- {
- public:
- void fun()
- {
- cout << "B::func()"<< endl;
- }
- };
-
- int main()
- {
- B b;
- b.fun(); //优先调用B子类中的
- b.A::fun(); //指定才调用A子类中的
-
- return 0;
- };
(自己设计继承,避开隐藏)
因为系统的函数名区分中会加上作用域
这里 b.fun(); 不是调用A中的fun,而是构成隐藏,调用B中的fun,因为B中的fun需要传参数,所以编译报错
-
- // A::fun 和 B::fun 的关系 -> 隐藏
- // 函数重载要求在同一作用域
- class A
- {
- public:
- void fun()
- {
- cout << "A::func()" << endl;
- }
- };
- class B : public A
- {
- public:
- void fun(int i)
- {
- cout << "B::func()" << endl;
- }
- };
-
- int main()
- {
- Student s1;
- s1.Print();
-
- B b;
- //b.fun(); // 构成隐藏,编译报错
- b.A::fun();
- b.fun(1);
-
- return 0;
- };
-
- int main()
- {
- return 0;
- }
打印结果:
A::func()
B::func()
- class A
- {
- public: void test(float a) { cout << a; }
- int a = 0;
- };
- class B :public A
- {
- public: void test(int b) { cout << b; cout << " sss"; }
- int b = 1;
- };
- void main()
- {
- A* a = new A;
- B* b = new B;
- a = b;
- a->test(1.1);
- }
a=b是把b的父类那部分赋给a,相当于没有任何改变,a->test(1.1); 调用的是父类中的test,B中虽然隐藏了A的test函数,但调用的是父类对象a,隐藏是通过子类对象调用test才能勾成隐藏,所以打印结果是1.1
- 假设父类都写全了
- class Person
- {
- public:
- Person(const char* name)
- : _name(name)
- {
- cout << "Person()" << endl;
- }
-
- Person(const Person& p)
- : _name(p._name)
- {
- cout << "Person(const Person& p)" << endl;
- }
-
- Person& operator=(const Person& p)
- {
- cout << "Person operator=(const Person& p)" << endl;
- if (this != &p)
- _name = p._name;
-
- return *this;
- }
-
- ~Person()
- {
- cout << "~Person()" << endl;
- }
- protected:
- string _name; // 姓名
- };
a、先 调用父类构造函数初始化继承自父类成员(父类那一堆当成一个整体的自定义类型成员变量即可,下面的Person(name)就是显示传参调用父类构造函数)
b、后 子类的构造函数再初始化子类的成员 -- 规则参考普通类
总结:构造函数初始化顺序:先父后子。析构函数析构顺序:先子后父。
初始化列表顺序不能代表初始化顺序,初始化顺序看声明顺序,声明中父类在子类的所有成员变量之前,下面仍然是先调用父类Person的构造函数(假设父类所有函数都写齐全了)
Person::operator=(s); 调用父类的赋值运算符重载切片后赋给this的继承父类部分,s3传给s时切片出继承父类的部分,this传给父类的赋值运算符重载中的this时切片成继承父类的部分,共切片了2次
- // s1 = s3
- Student& operator=(const Student& s)
- {
- if (this != &s)
- {
- Person::operator=(s);
- _num = s._num;
- }
-
- cout << "Student& operator=(const Student& s)" << endl;
-
- return *this;
- }
父子类的析构函数构成隐藏关系
原因:多态的需要,析构函数名统一会被处理成destructor(),解释详见这篇文章大标题二 -> 7(124条消息) C++:多态 详解_beyond.myself的博客-CSDN博客
为了保证析构顺序,先子后父,
子类析构函数完成后会自动调用父类析构函数,所以不需要我们显示调用
-
- // 父子类的析构函数构成隐藏关系
- // 原因:下一节多态的需要,析构函数名统一会被处理成destructor()
-
- // 为了保证析构顺序,先子后父,
- // 子类析构函数完成后会自动调用父类析构函数,所以不需要我们显示调用
- ~Student()
- {
- //Person::~Person();
-
- cout << "~Student()" << endl;
- }// -》自动调用父类析构函数
使父类构造函数私有,CreateObj函数返回构造函数,并设置成静态,使CreateObj可以在main函数中不用对象就能调用
- class A
- {
- //friend class B;
- public:
- static A CreateObj()
- {
- return A();
- }
-
- private:
- A()
- {}
- };
-
- // 父类A的构造函数私有化以后,B就无法构造对象
- class B : public A
- {
-
- };
-
- int main()
- {
- B b;
-
- A a = A::CreateObj();
-
- return 0;
- }
- class Student;
- class Person
- {
- public:
- friend void Display(const Person& p, const Student& s);
- protected:
- string _name; // 姓名
- };
- class Student : public Person
- {
- protected:
- int _stuNum; // 学号
- };
- void Display(const Person& p, const Student& s)
- {
- cout << p._name << endl; //是父类友元,可以访问父类保护
- cout << s._stuNum << endl; //但不是子类友元,不可以访问子类保护
- }
- void main()
- {
- Person p;
- Student s;
- Display(p, s);
- }
- class Person
- {
- public:
- Person() { ++_count; }
- protected:
- string _name; // 姓名
- public:
- static int _count; // 统计人的个数。
- };
-
- int Person::_count = 0;
-
- class Student : public Person
- {
- protected:
- int _stuNum; // 学号
- };
- class Graduate : public Student
- {
- protected:
- string _seminarCourse; // 研究科目
- };
-
- int main()
- {
- Student s1;
- Student s2;
- Student s3;
- Graduate s4;
- Person s;
-
- cout << " 人数 :" << Person::_count << endl;
- cout << " 人数 :" << Student::_count << endl;
- cout << " 人数 :" << s4._count << endl;
-
- cout << " 人数 :" << &Person::_count << endl;
- cout << " 人数 :" << &Student::_count << endl;
- cout << " 人数 :" << &s4._count << endl;
-
- return 0;
- }
了解菱形继承的问题,解决起来也复杂,如何解决要了解一下。我们自己设计尽量不要用菱形继承
在菱形继承的腰部class Student : virtual public Person 加上virtual虚继承,防止冗余二义性,使 Person不重复,virtual作用是:把_name放在公共区域,a.Student::_name 或 a.Teacher::_name都访问这个公共的_name
- class Person
- {
- public:
- string _name; // 姓名
- int _a[10000];
- };
- class Student : virtual public Person //virtual虚继承,防止冗余,使_name不重复
- {
- protected:
- int _num; //学号
- };
- class Teacher : virtual public Person
- {
- protected:
- int _id; // 职工编号
- };
- class Assistant : public Student, public Teacher
- {
- protected:
- string _majorCourse; // 主修课程
- };
-
- int main()
- {
- // 这样会有二义性无法明确知道访问的是哪一个
- Assistant a;
- a._name = "peter";
-
- // 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
- a.Student::_name = "xxx";
- a.Teacher::_name = "yyy";
-
- cout << sizeof(a) << endl;
-
- return 0;
- }
- // 对象模型
- class A
- {
- public:
- int _a;
- };
-
- //int A::_a = 0;
-
- class B : public A
- //class B : virtual public A
- {
- public:
- int _b;
- };
-
- class C : public A
- //class C : virtual public A
- {
- public:
- int _c;
- };
-
- class D : public B, public C
- {
- public:
- int _d;
- };
-
- int main()
- {
- D d;
- //d._a = 0;
- d.B::_a = 1;
- d.C::_a = 2;
- d._b = 3;
- d._c = 4;
- d._d = 5;
-
- //D d1;
- //D d2;
- //cout << &d1._a << endl;
- //cout << &d2._a << endl;
-
- return 0;
- }
菱形继承中,对象B和C中的A分别存在自己的对象中存着一份,这样就会造成冗余和二义性,冗余是多存储了一次A,多消耗了4字节;二义性是d._a不知道访问的是 B中的A 还是 C中的A。
- // 对象模型
- class A
- {
- public:
- int _a;
- };
-
- //int A::_a = 0;
-
- //class B : public A
- class B : virtual public A
- {
- public:
- int _b;
- };
-
- //class C : public A
- class C : virtual public A
- {
- public:
- int _c;
- };
-
- class D : public B, public C
- {
- public:
- int _d;
- };
-
- int main()
- {
- D d;
- //d._a = 0;
- d.B::_a = 1;
- d.C::_a = 2;
- d._b = 3;
- d._c = 4;
- d._d = 5;
-
- return 0;
- }
虚拟菱形继承,对象B和C中的A放在了最底下一个公共的地方,并都存储了一个指针,这个指针指向的位置存储着 距离A存储位置的偏移量 ,这个指针叫虚基表指针,A叫虚基类。
答:不能,静态成员变量是属于所有对象的,你这样定义d1和d2里面的_a地址是一样的
- // 对象模型
- class A
- {
- public:
- int _a;
- //static int _a;
- };
-
- //int A::_a = 0;
-
- class B : public A
- //class B : virtual public A
- {
- public:
- int _b;
- };
-
- class C : public A
- //class C : virtual public A
- {
- public:
- int _c;
- };
-
- class D : public B, public C
- {
- public:
- int _d;
- };
-
- int main()
- {
- D d1;
- D d2;
- cout << &d1._a << endl;
- cout << &d2._a << endl;
-
- return 0;
- }
静态成员一定是不被包含在对象中的,基类对象中不包含基类的静态成员变量,子类对象不包含基类的静态成员变量
静态成员属于整个类,不属于任何对象,所以在整体体系中只有一份
静态成员函数可以被继承,成员变量所有的都会被继承,无论公有私有
- // 对象模型
- class A
- {
- public:
- int _a;
- //static int _a;
- };
-
- //int A::_a = 0;
-
- //class B : public A
- class B : virtual public A
- {
- public:
- int _b;
- };
-
- //class C : public A
- class C : virtual public A
- {
- public:
- int _c;
- };
-
- class D : public B, public C
- {
- public:
- int _d;
- };
-
- int main()
- {
- D d;
- d._a = 0;
- d.B::_a = 1;
- d.C::_a = 2;
- d._b = 3;
- d._c = 4;
- d._d = 5;
-
- //D d1;
- //D d2;
- //cout << &d1._a << endl;
- //cout << &d2._a << endl;
-
- B b;
- b._a = 10;
- b._b = 20;
-
- B* ptr1 = &d;
- B* ptr2 = &b;
-
- cout << ptr1->_a << endl;
- cout << ptr2->_a << endl;
- cout << ptr1->_b << endl;
- cout << ptr2->_b << endl;
-
- return 0;
- }
顺序容器(vector/list/deque)
stack queue priority_ queue 既可以继承,也可以组合,这里用的就是组合
模块间关系:高内聚,低耦合(UML)
适合is-a关系建议继承
适合has-a关系建议组合
都可以->建议组合