
目录
一.封装(无固定阐述)
二.继承
1.继承定义(就用共有继承就行)
2.基类和派生类对象赋值转换(public继承条件下)
(1)示例1:子类对象 可以赋值给 父类的对象 / 基类的指针 / 基类的引用。
(2)示例2:基类对象不能赋值给派生类对象。
3.继承中的作用域
(1)成员变量相同构成隐藏
(2)成员函数的函数名相同就构成隐藏
(3)不同域中函数名相同就是隐藏,函数所在作用域相同才能构成函数重载
4.派生类的默认成员函数
(1)子类构造函数原则:
(2)子类中的赋值运算符重载
(3)析构函数
(4)小问题:如何设计一个不能被继承的类?
5.继承与友元
6.继承与静态成员
7.复杂的菱形继承及菱形虚拟继承
防止冗余二义性,用virtual虚继承
(1)示例1
(2)示例2:菱形继承中公共A的存储
(3)示例3:菱形虚拟继承中公共A的存储
(4)问题:能否把菱形继承中的公共A的成员写成static,这样BC中的公共A就都能访问了?
(5)示例
8.继承和组合
面向对象三大特性:封装、继承、多态
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;
}
继承的性质
private成员不能用 protected成员类里面可以用
总结: 1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的 不可见是指基类的私 有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它 。 2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在 派生类中能访问,就定义为 protected 。 可以看出保护成员限定符是因继承才出现的 。 3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他 成员在子类的访问方式 == Min( 成员在基类的访问限定符,继承方式 ) , public > protected > private 。 4. 使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public , 不过 最好显示的写出继承方式 。 5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承 ,也不提倡 使用 protetced/private 继承,因为 protetced/private 继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强子类对象 赋值给 父类的对象时无类型转换,只是切片,把子类对象中继承父类的那一部分赋给父类对象(父类对象无法给子类对象)
公有条件下,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 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;
}
了解菱形继承的问题,解决起来也复杂,如何解决要了解一下。我们自己设计尽量不要用菱形继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承:菱形继承是多继承的一种特殊情况。 菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。 在 Assistant 的对象中 Person成员会有两份。在菱形继承的腰部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关系建议组合
都可以->建议组合