网站建设seo虾哥网络wordpress菜单跳转

当前位置: 首页 > news >正文

网站建设seo虾哥网络,wordpress菜单跳转,欧美化妆品网站模板,网站集约化建设 统一出口文章目录 继承的概念与定义继承的定义定义格式不同继承方式与继承的基类中访问限定符间的影响C中的继承和访问控制总结父类的private成员在子类中的访问限制protected成员的使用场景成员访问方式总结继承方式的默认值实际应用中的继承方式 示例代码 OOP中类之间的关系“is a” … 文章目录 继承的概念与定义继承的定义定义格式不同继承方式与继承的基类中访问限定符间的影响C中的继承和访问控制总结父类的private成员在子类中的访问限制protected成员的使用场景成员访问方式总结继承方式的默认值实际应用中的继承方式 示例代码 OOP中类之间的关系“is a” 关系“has a” 关系 类模板的继承类模板继承的基本语法访问控制和作用域解析名称查找和依赖名称名称查找和作用域解析示例 父类和子类对象赋值兼容转换子类对象可以赋值给父类对象、父类指针或父类引用父类对象不能赋值给子类对象父类的指针或引用可以通过强制类型转换赋值给子类的指针或引用安全的类型转换强制类型转换 总结 继承中的作⽤域隐藏规则作⽤域相关知识考察 ⼦类的默认成员函数子类的构造函数子类的拷贝构造函数子类的赋值运算符子类的析构函数子类的赋值运算符重载不能被继承的类 继承友元静态成员继承与友元继承与静态成员 多继承与菱形继承继承模型单继承多继承多继承中指针偏移问题 菱形继承 虚继承虚继承的原理虚继承的内存分布注意事项 继承和组合继承Inheritance组合Composition继承与组合的比较继承与组合的使用原则实例分析示例 1组合has-a 关系示例 2继承is-a 关系 组合与继承的实际应用综合示例 继承的概念与定义 面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类这使得创建和维护一个应用程序变得更容易。这样做也达到了重用代码功能和提高执行效率的效果。 当创建一个类时您不需要重新编写新的数据成员和成员函数只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类新建的类称为派生类。
继承的定义 定义格式 继承格式class derived-class: access-specifier base-class Person是⽗类也称作基类。Student是⼦类也称作派⽣类
不同继承方式与继承的基类中访问限定符间的影响 类的继承有三种类型公有继承public、保护继承protected和私有继承private。C中的访问限定符有public、protected和private它们分别控制成员的可访问性。 具体的继承后访问权限如下: 类成员/继承方式public继承protected继承private继承父类的public成员子类的public成员子类的protected成员子类的private成员父类的protected成员子类的protected成员子类的protected成员子类的private成员父类的private成员在子类中不可见在子类中不可见在子类中不可见 C中的继承和访问控制总结 父类的private成员在子类中的访问限制 父类的private成员在子类中是不可见的。这意味着虽然子类对象中仍然包含父类的private成员但语法上子类无法访问这些成员无论是在子类的内部还是外部。 protected成员的使用场景 父类的private成员在子类中不能被访问。如果需要父类成员在类外不能直接访问但在子类中能够访问那么应该将这些成员定义为protected。protected成员限定符主要是为了解决继承中的访问控制问题而出现的。 成员访问方式总结 通过继承方式和父类成员的访问限定符可以总结出父类的其他成员在子类中的访问方式 public protected private 子类对父类成员的访问权限是取父类成员的访问限定符与继承方式的最小值。 继承方式的默认值 在使用关键字class时默认的继承方式是private。而使用关键字struct时默认的继承方式是public。尽管如此最好显式地写出继承方式以提高代码的可读性。 class Base { private:int privateMember; protected:int protectedMember; public:int publicMember; };class Derived : public Base {// 继承方式为public访问权限如下// privateMember不可见// protectedMemberprotected// publicMemberpublic };实际应用中的继承方式 在实际应用中通常使用public继承很少使用protected或private继承。原因在于protected或private继承的成员只能在子类内部使用限制了代码的扩展性和可维护性。 示例代码 class Base { private:int privateMember; protected:int protectedMember; public:int publicMember; };class PublicDerived : public Base {// privateMember不可见// protectedMemberprotected// publicMemberpublic };class ProtectedDerived : protected Base {// privateMember不可见// protectedMemberprotected// publicMemberprotected };class PrivateDerived : private Base {// privateMember不可见// protectedMemberprivate// publicMemberprivate };OOP中类之间的关系 “is a” 关系 “is a”关系通过继承Inheritance来表示表示类之间的层次关系。 “is a”关系通常表示继承Inheritance关系也就是一个类是另一个类的特殊类型。比如狗Dog是动物Animal的一种我们可以通过继承来表示这种关系 class Animal { public:void makeSound() {std::cout Animal sound std::endl;} };class Dog : public Animal { // Dog is an Animal public:void makeSound() {std::cout Bark std::endl;} };在这个例子中Dog类继承自Animal类这表明“狗是一种动物”Dog is an Animal。Dog类可以访问Animal类中的公共成员函数和变量。 “has a” 关系 “has a”关系通过组合Composition或聚合Aggregation来表示表示一个类拥有另一个类的实例。 “has a”关系通常表示组合Composition或聚合Aggregation关系即一个类包含另一个类作为其成员。这种关系强调一个类拥有另一个类的实例。比如汽车Car有一个引擎Engine可以用组合来表示这种关系 class Engine { public:void start() {std::cout Engine starts std::endl;} };class Car { // Car has an Engine private:Engine engine;public:void startCar() {engine.start();std::cout Car starts std::endl;} };在这个例子中Car类包含一个Engine类的实例这表明“汽车有一个引擎”Car has an Engine。Car类可以使用Engine类中的方法来实现其功能。 类模板的继承 类模板继承的基本语法 templateclass T class Base {// 基类内容 };templateclass T class Derived : public BaseT {// 派生类内容 };访问控制和作用域解析 访问控制继承时基类的成员的访问权限在派生类中依旧遵循C的访问控制规则即public、protected和private。作用域解析在派生类中访问基类的成员时需要使用作用域解析符来明确调用基类的成员 templateclass T class Derived : public BaseT { public:void foo() {BaseT::bar(); // 调用基类的bar函数} };名称查找和依赖名称 名称查找与依赖名称的问题主要源于模板的按需实例化机制和两阶段名称查找机制 两阶段名称查找 C编译器对模板代码进行两次名称查找 第一次名称查找在模板定义时进行。编译器解析所有与模板参数无关的非依赖名称。第二次名称查找在模板实例化时进行。编译器解析依赖于模板参数的名称即依赖名称。 依赖名称Dependent Names是指那些依赖于模板参数的名称。在第一次名称查找时编译器无法确定这些名称的具体含义只有在模板实例化时才能解析。 名称查找和作用域解析示例 template typename T class Base { public:void foo() {std::cout Base foo std::endl;} };template typename T class Derived : public BaseT { public:void bar() {// 问题点编译器在第一次名称查找时不知道foo()是从BaseT继承的// 因为foo()是依赖于模板参数T的名称// foo(); // 这会导致编译错误// 解决方法1使用this指针this-foo();// 解决方法2使用作用域解析符BaseT::foo();} };int main() {Derivedint d;d.bar(); // 输出 Base fooreturn 0; }编译器会在第一次名称查找时尝试解析foo()。但是由于foo()是依赖于模板参数T的成员函数编译器无法确定foo()是从基类继承的。这是因为模板是按需实例化的编译器在第一次查找时并不知道派生类实例化时会包含哪些基类成员。 在使用Derivedint d;初始化的时候会对构造函数进行实例化并调用构造函数但是当使用d.bar();时如果在bar()中为foo();即会编译错误原因就如上述无法确定从基类继承。 所以解决如下 使用this指针 void bar() {this-foo(); // 正确 }编译器会在第二阶段名称查找时解析foo()并正确地找到基类中的foo()成员函数。这是因为this指针在类定义中总是已知的并且它指向当前对象包括从基类继承的部分。 使用作用域解析符 void bar() {BaseT::foo(); // 正确 }BaseT::foo()明确指出了foo()来自基类BaseT消除了编译器的名称查找歧义。 父类和子类对象赋值兼容转换 子类对象可以赋值给父类对象、父类指针或父类引用 在公有继承中子类对象可以赋值给父类对象、父类指针或父类引用把⼦类中⽗类那部分切来赋值过去。这种转换称为向上转换upcasting。 class Base { public:void baseMethod() {std::cout Base method std::endl;} };class Derived : public Base { public:void derivedMethod() {std::cout Derived method std::endl;} };int main() {Derived derivedObj;Base baseObj derivedObj; // 子类对象赋值给父类对象Base* basePtr derivedObj; // 子类对象的地址赋值给父类指针Base baseRef derivedObj; // 子类对象赋值给父类引用baseObj.baseMethod(); // 可以调用父类的方法basePtr-baseMethod(); // 可以通过父类指针调用父类的方法baseRef.baseMethod(); // 可以通过父类引用调用父类的方法// 以下调用都会导致编译错误因为父类对象/指针/引用不能访问子类特有的方法// baseObj.derivedMethod();// basePtr-derivedMethod();// baseRef.derivedMethod();return 0; }父类对象不能赋值给子类对象 父类对象不能赋值给子类对象因为父类对象可能不包含子类对象所需的所有信息。这种转换会导致子类特有的数据丢失或变得不确定。 Base baseObj; Derived derivedObj;// 以下赋值会导致编译错误 // derivedObj baseObj;父类的指针或引用可以通过强制类型转换赋值给子类的指针或引用 父类的指针或引用可以通过强制类型转换赋值给子类的指针或引用但必须确保父类的指针实际上指向一个子类对象。这种转换称为向下转换downcasting 安全的类型转换 如果父类是多态类型可以使用RTTI运行时类型信息中的dynamic_cast来进行安全转换。 Base* basePtr new Derived;Derived* derivedPtr dynamic_castDerived(basePtr); if (derivedPtr) {derivedPtr-derivedMethod(); // 安全转换后可以调用子类方法 } else {// 转换失败basePtr并不指向Derived对象 }强制类型转换 虽然可以使用static_cast进行强制转换但这种转换在父类指针不指向子类对象时是危险的。 Base basePtr new Derived;Derived* derivedPtr static_castDerived(basePtr); derivedPtr-derivedMethod(); // 需要确保basePtr实际指向Derived对象总结 子类对象可以赋值给父类对象、父类指针或父类引用称为向上转换upcasting但会发生对象切片slicing。父类对象不能赋值给子类对象因为父类对象缺乏子类特有的信息。父类指针或引用可以赋值给子类指针或引用但必须确保指向实际的子类对象。可以使用dynamic_cast进行安全转换。 继承中的作⽤域 隐藏规则 在继承体系中⽗类和⼦类都有独⽴的作⽤域。⼦类和⽗类中有同名成员⼦类成员将屏蔽⽗类对同名成员的直接访问这种情况叫隐藏。在⼦ 类成员函数中可以使⽤⽗类::⽗类成员显式访问需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。注意在实际中在继承体系⾥⾯最好不要定义同名的成员。 作⽤域相关知识考察 #include iostream // 不要忘记包含 iostream 头文件以使用 coutclass A { public:void fun() {std::cout func() std::endl;} };class B : public A { public:void fun(int i) {std::cout func(int i) i std::endl;} };int main() {B b;b.fun(10); // 调用 B 类的 fun(int i)b.fun(); // 尝试调用 A 类的 fun()但由于重载实际上调用的是 B 类的 fun(int i) return 0; }A和B类中的两个func构成什么关系 此时的A和B类构成的是隐藏的关系。 编译运⾏结果是什么 编译报错。b.fun(); ⼦类的默认成员函数 子类的构造函数 子类的构造函数必须调用父类的构造函数来初始化父类的那部分成员。如果父类没有默认构造函数则必须在子类构造函数的初始化列表中显式调用父类的构造函数。 Student(const char name, int num): Person(name), _num(num) {cout Student() endl; }在初始化列表中可以注意初始化顺序先声明的先初始化所以先声明的父类会先定义。 子类的拷贝构造函数 子类的拷贝构造函数必须调用父类的拷贝构造函数来完成父类部分的拷贝初始化。 Student(const Student s): Person(s), _num(s._num) {cout Student(const Student s) endl; }子类的赋值运算符 子类的赋值运算符必须调用父类的赋值运算符来完成父类部分的复制。需要注意的是子类的赋值运算符会隐藏父类的赋值运算符所以需要显式调用父类的赋值运算符。 Student operator(const Student s) {cout Student operator(const Student s) endl;if (this ! s) {// 构成隐藏所以需要显式调用Person::operator(s);_num s._num;}return *this; }子类的析构函数 不用再子类析构函数中显式调用父类的析构函数子类的析构函数在被调用完成后会自动调用父类的析构函数来清理父类成员。这样可以保证子类对象先清理子类成员再清理父类成员的顺序。 ~Student() {cout ~Student() endl; }析构会按照后定义的先析先调用子类析构再调用父类析构。 多态中⼀些场景析构函数需要构成重写重写的条件之⼀是函数名相同。那么编译器会对析构函数名进⾏特殊处理处理成destructor()所以⽗类析构函数不加virtual的情况下⼦类析构函数和⽗类析构函数构成隐藏关系。 子类的赋值运算符重载 ⼦类的operator必须要调⽤⽗类的operator完成⽗类的复制。需要注意的是⼦类的operator隐 藏了⽗类的operator所以显⽰调⽤⽗类的operator需要指定⽗类作⽤域 student operator(const student s) {if (this ! s){person::operator(s);} }不能被继承的类 有两种方法可以使类不可以被继承 ⽗类的构造函数私有⼦类的构成必须调⽤⽗类的构造函数但是⽗类的构成函数私有化以后⼦类看不⻅就不能调⽤了那么⼦类就⽆法实例化出对象。C11新增了⼀个final关键字final修改⽗类⼦类就不能继承了。 class Base final { public: void func5() { cout Base::func5 endl; }protected: int a 1;private: // C98的⽅法 /Base(){}/ }继承友元静态成员 继承与友元 友元关系不继承 在C中友元关系是特定于某个类的。一个函数或类如果是父类的友元它不会自动成为子类的友元。因此父类的友元函数不能访问子类的私有成员和保护成员。同样地如果你希望某个函数既是父类的友元又是子类的友元也可以在子类中声明该友元函数。 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; // 尝试访问子类的保护成员编译错误 }int main() {Person p;Student s;Display(p, s); // 编译报错error C2248: “Student::_stuNum”: 无法访问 protected 成员return 0; }Display函数是Person类的友元因此它可以访问Person类的保护成员 _name。但是当它尝试访问Student类的保护成员_stuNum时会产生编译错误。原因是友元关系不继承Display函数虽然是Person的友元但它不是Student的友元所以不能访问Student的保护成员。 将Display在子类中声明即可解决该问题 class Student : public Person { public:friend void Display(const Person p, const Student s); // 友元函数也要声明在子类中 protected:int _stuNum; // 学号 };这样Display函数就能同时访问Person和Student的保护成员了。 继承与静态成员 在C中静态成员是属于类而不是某个特定对象的。⽗类定义了static静态成员则整个继承体系⾥⾯只有⼀个这样的成员这意味着即使类派生出了多个子类它们都共享同一个静态成员实例。 class Person { public:string _name;static int _count; };int Person::_count 0; // 静态成员初始化class Student : public Person { protected:int _stuNum; };int main() {Person p;Student s;// 非静态成员_name地址不同说明子类继承后父子类对象各有一份cout p._name endl;cout s._name endl;// 静态成员_count地址相同说明子类和父类共用同一个静态成员cout p._count endl;cout s._count endl;// 公有的情况下父子类都可以访问静态成员cout Person::_count endl;cout Student::_count endl;return 0; }运行结果 0133FDE4 0133FDBC 0014E478 0014E478 0 0_name是一个非静态成员在Person和Student对象中分别有独立的实例所以它们的地址不同。_count是一个静态成员Person和Student共享同一个静态成员实例因此它们的地址相同。无论是通过父类还是子类都可以访问静态成员。 多继承与菱形继承 继承模型 单继承 单继承是指一个子类只有一个直接父类。在这种情况下子类继承父类的所有非私有成员继承结构简单明了访问成员变量也不存在歧义问题。 多继承 多继承是指一个子类有多个直接父类。C支持多继承这意味着一个子类可以从多个父类继承成员。在多继承中C规定在内存布局上先继承的父类放在前面后继承的父类放在后面子类自己的成员放在最后。 class Person { public:string _name; // 姓名 };class Student : public Person { protected:int _num; // 学号 };class Teacher : public Person { protected:int _id; // 职工编号 };class Assistant : public Student, public Teacher { protected:string _majorCourse; // 主修课程 };多继承中指针偏移问题 问题下⾯说法正确的是() Ap1p2p3 Bp1p2p3 Cp1p3!p2 Dp1!p2!p3 class Base2 { public: int _b2; }; class Base1 { public: int _b1; }; class Derive : public Base1, public Base2 { public: int _d; };int main() {Derive d;Base1* p1 d;Base2* p2 d;Derive* p3 d;return 0; }继承的时候会按照生命顺序来进行分配空间也就是继承顺序。上述例子中先继承的是Base1后继承的是Base2所以按照规则栈会先为继承的Base1的信息进行开辟空间栈向下开辟空间然后再为Base2开辟空间所以空间图如上图所示。 Base1* p1 d; Base2* p2 d; Derive* p3 d;以上是用了继承中基类对于派生类的向上转换会进行类似切片操作详见上文所以此时的指向为下图 此时的p1和p3指向的是同一块地址p2指向的之后分配的继承了Base2的空间。 正确答案为p1 p3 ! p2 菱形继承 菱形继承是多继承中的一种特殊情况发生在一个子类通过两个不同的路径继承自同一个基类时形成菱形结构。 这种继承方式会带来数据冗余和访问二义性的问题。 class Person { public:string _name; // 姓名 };class Student : public Person { protected:int _num; // 学号 };class Teacher : public Person { protected:int _id; // 职工编号 };class Assistant : public Student, public Teacher { protected:string _majorCourse; // 主修课程 };int main() {Assistant a;a._name peter; // 编译报错error C2385: 对“_name”的访问不明确a.Student::_name xxx; // 需要显式指定访问哪个父类的成员a.Teacher::_name yyy; // 但是数据冗余问题无法解决return 0; }数据冗余在Assistant类中由于Student和Teacher都继承了Person所以Assistant中会有两份Person的拷贝。换句话说Assistant类中有两份_name成员这样会导致内存上的浪费。访问二义性当你在Assistant类中访问_name时编译器无法确定你想访问的是从Student继承过来的_name还是从Teacher继承过来的_name因此会报错。 a.Student::_name xxx; a.Teacher::_name yyy;可以通过显式的指定访问的是哪个父类的成员或者使用虚继承即可解决当前问题。 不推荐使用菱形继承 虚继承 虚继承virtual inheritance是C中的一种特殊继承机制用来解决多继承中的菱形继承问题特别是避免数据冗余和访问二义性。 在多继承中如果一个子类通过不同的路径从同一个基类继承那么就会形成菱形继承。菱形继承会导致子类中存在多个基类实例从而产生数据冗余和访问二义性的问题。虚继承通过修改基类在继承链中的存储方式使得即使存在多重继承所有子类中只会存在一个基类的实例从而避免数据冗余和访问二义性。 class Person { public:string _name; // 姓名 };// 使用虚继承Person类 class Student : virtual public Person { protected:int _num; // 学号 };// 使用虚继承Person类 class Teacher : virtual public Person { protected:int _id; // 职工编号 };class Assistant : public Student, public Teacher { protected:string _majorCourse; // 主修课程 };int main() {// 使用虚继承可以解决数据冗余和二义性Assistant a;a._name peter;return 0; }虚继承的原理 当一个类通过virtual关键字虚继承一个基类时编译器确保在多继承链中该基类只会有一个实例。在上述示例中Student和Teacher都虚继承自Person因此在Assistant类中Person的实例只会有一个。 虚继承的内存分布 在普通继承中每个子类都会在其对象中包含父类的成员。但在虚继承中编译器通过在子类中存储一个指向基类的指针来避免冗余。这个指针指向了唯一的基类实例确保整个继承体系中只存在一个基类实例。 注意事项 构造函数调用顺序因为虚继承之后只存在一个实例所以当使用虚继承时基类的构造函数在最派生类如Assistant的构造函数中被调用而不是在虚继承的直接派生类如Student或Teacher中。派生类的构造函数负责初始化基类的那部分。 class Assistant : public Student, public Teacher { public:Assistant(const string name) : Person(name), Student(), Teacher() {} };在这个例子中由于Person是通过虚继承的所以必须在Assistant的构造函数中显式地调用Person的构造函数来初始化_name。 虚继承的时候注意进行虚继承的是那个产生数据冗余和二义性的公共基类的子类。 继承和组合 继承Inheritance和组合Composition是面向对象编程中两种重要的代码复用手段。它们在实际开发中各有优势和适用场景。 继承Inheritance 继承是一种is-a关系表示子类是父类的一种特殊类型。通过继承子类可以复用父类的属性和方法。 特点 代码复用子类自动继承父类的所有成员变量和成员函数。多态性子类可以重写父类的虚函数提供不同的实现。强耦合子类与父类之间有很强的依赖关系父类的修改可能影响到所有子类。 class Car { public:void Start() {cout Car starts. endl;} };class BMW : public Car { public:void Drive() {cout BMW drives fast. endl;} };在上面的代码中BMW类继承了Car类所以BMW类可以直接使用Car类中的Start方法。 组合Composition 组合是一种has-a关系表示一个类拥有另一个类的实例。这种方式通过将一个对象作为另一个对象的成员变量来实现代码复用。 组合的特点 松耦合组合关系中的类是独立的一个类的修改不会影响到其他类。黑箱复用组合对象的内部实现对外部不可见只暴露必要的接口。灵活性通过组合可以动态地创建更复杂的对象结构。 class Engine { public:void Start() {cout Engine starts. endl;} };class Car { private:Engine engine; // Car has an Engine public:void Start() {engine.Start();cout Car starts. endl;} };在上面的代码中Car类包含了一个Engine类的实例Car类通过组合来复用Engine类的功能。 继承与组合的比较 复用性继承可以直接复用父类的实现组合则通过使用已有类的实例来复用功能。耦合度继承会导致子类与父类的紧密耦合组合则保持类之间的独立性。可维护性由于继承的强耦合性父类的修改可能影响子类从而降低了代码的可维护性。组合则更容易维护因为它遵循单一职责原则每个类只负责自己的部分。扩展性组合更容易扩展因为可以通过组合不同的类来创建新的功能而继承则在层次结构上有更多的限制。 继承与组合的使用原则 优先使用组合在设计类结构时优先考虑使用组合因为它可以减少耦合提高代码的灵活性和可维护性。适当使用继承当子类确实是父类的一种类型即符合is-a关系时可以考虑使用继承。继承的优势在于实现多态性但过度使用继承可能导致复杂的继承层次结构和高耦合。 实例分析 示例 1组合has-a 关系 class Tire { protected:string _brand Michelin; // 品牌size_t _size 17; // 尺寸 };class Car { protected:string _colour 白色; // 颜色string _num 陕ABIT00; // 车牌号Tire _t1, _t2, _t3, _t4; // 轮胎组合 };在这里Car类通过组合了四个Tire类的实例来实现车轮的功能这就是一个典型的has-a关系。 示例 2继承is-a 关系 class Car { protected:string _colour 白色; // 颜色string _num 陕ABIT00; // 车牌号 };class BMW : public Car { public:void Drive() { cout 好开-操控 endl; } };class Benz : public Car { public:void Drive() { cout 好坐-舒适 endl; } };组合与继承的实际应用 继承主要用于需要复用父类的代码或实现多态性的时候。组合主要用于需要动态组合功能、减少类之间的耦合以及增强代码的灵活性时。 综合示例 在一些场景中组合和继承可能会混合使用例如在一个stack类中既可以使用组合来包含一个vector对象也可以通过继承来扩展vector类的功能。 **继承方式 ** templateclass T class stack : public vectorT {// stack继承自vector };**组合方式 ** templateclass T class stack { public:vectorT _v; // 通过组合方式来包含一个vector对象 };在实际设计时建议优先考虑组合这样可以保持类的封装性和独立性从而提高代码的可维护性。