网站目录怎么做301跳转,域名注册过后怎么使用,郑州中原影视城,公司网站建设的费用会计处理目录
一、继承的概念
1.继承的基本概念
2.继承的定义和语法
3.继承基类成员访问方式的变化
编辑 4.总结
二、基类和派生类对象赋值转换
三、继承中的作用域
四、派生类的默认成员函数
1.派生类中的默认构造函数
2.派生类中的拷贝构造函数
3.派生类中的移动构造函数…目录
一、继承的概念
1.继承的基本概念
2.继承的定义和语法
3.继承基类成员访问方式的变化
编辑 4.总结
二、基类和派生类对象赋值转换
三、继承中的作用域
四、派生类的默认成员函数
1.派生类中的默认构造函数
2.派生类中的拷贝构造函数
3.派生类中的移动构造函数
4.派生类的拷贝赋值运算符 5.派生类的移动赋值运算符
6.派生类的析构函数
为什么基类析构函数需要virtual关键字修饰
理由多态性和正确的析构顺序
问题非虚析构函数导致的资源泄漏
总结
五、继承和友元
六、继承与静态成员 七、复杂的菱形继承和菱形虚拟继承
1.单继承
2.多继承
3.菱形继承 菱形继承的问题
4.菱形虚拟继承
5.虚拟继承解决数据冗余和二义性的原理
虚基表的工作机制 一、继承的概念
在C中继承是一种面向对象编程的重要特性它允许一个类称为派生类或子类从另一个类称为基类或父类继承属性和行为成员变量和成员函数。通过继承派生类不仅可以拥有基类的所有成员还可以扩展或修改这些成员以提供更具体或特殊的功能。
1.继承的基本概念 基类Base Class提供基础属性和行为的类。派生类Derived Class从基类继承并扩展或修改其功能的类。访问控制Access Control Public 继承基类的public和protected成员在派生类中保持其访问级别不变public成员依然是publicprotected成员依然是protected。Protected 继承基类的public和protected成员在派生类中都变为protected成员。Private 继承基类的public和protected成员在派生类中都变为private成员。构造函数和析构函数派生类的构造函数在执行前会先调用基类的构造函数析构函数的调用顺序则相反先调用派生类的析构函数再调用基类的析构函数。多重继承Multiple InheritanceC允许一个派生类从多个基类继承。 2.继承的定义和语法
class Base {
public:int baseValue;void baseFunction() {// 基类成员函数}
};class Derived : public Base {
public:int derivedValue;void derivedFunction() {// 派生类成员函数}
};3.继承基类成员访问方式的变化 4.总结 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管是在类内还是在类外都不能去访问它。基类private成员在派生类中是不能被访问的如果基类成员不想在类外直接被访问但需要在派生类中能访问就定义为protected。可以看出保护成员限定符是继承才出现的。实际上面的表格我们进行一下总结就能发现基类的私有成员在子类中都是不可见的。基类的其他成员在子类的访问方式 Min(成员在基类的访问限定符继承方式),public protected private。使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public不过最好显示的写出继承方式。在实际运用中一般都是使用public继承几乎很少使用protected/private继承也不提倡使用protected/private继承因为 protected/private继承下来的成员都只能在派生类的类里使用实际中扩展维护性不强。 二、基类和派生类对象赋值转换 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。 基类对象不能赋值给派生类对象。 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。 class Base {
public:int baseValue;virtual void display() {std::cout Base class std::endl;}
};class Derived : public Base {
public:int derivedValue;void display() override {std::cout Derived class std::endl;}
};Base baseObj;
Derived derivedObj;baseObj derivedObj; // 对象切割发生
baseObj.display(); // 输出 Base class在上面的例子中尽管derivedObj赋值给了baseObj但baseObj只保留了Base类的部分派生类的derivedValue被切割掉了调用display函数时也只会调用基类的版本。
此外C允许使用基类的指针或引用来指向派生类对象这可以实现多态性。多态性允许你通过基类接口调用派生类的重载函数。
Base* basePtr derivedObj;
basePtr-display(); // 输出 Derived class多态性Base baseRef derivedObj;
baseRef.display(); // 输出 Derived class多态性在上面代码中basePtr和baseRef都指向Derived对象并且调用display方法时会调用派生类Derived中的版本这是因为display函数被声明为virtual。virtual关键字我们下面会讲
另外还有类型转换:static_cast和dynamic_cast感兴趣的可以去了解下。
三、继承中的作用域 1. 在继承体系中 基类 和 派生类 都有 独立的作用域 。 2. 子类和父类中有同名成员 子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏 也叫重定义。 在子类成员函数中可以 使用 基类 :: 基类成员 显示访问 3. 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 4. 注意在实际中在 继承体系里 面最好 不要定义同名的成员 。 // Student的_num和Person的_num构成隐藏关系可以看出这样代码虽然能跑但是非常容易混淆
class Person
{
protected :string _name 小李子; // 姓名int _num 111; // 身份证号
};
class Student : public Person
{
public:void Print(){cout 姓名:_name endl;cout 身份证号:Person::_num endl;cout 学号:_numendl;}
protected:int _num 999; // 学号
};
void Test()
{Student s1;s1.Print();
}; // B中的fun和A中的fun不是构成重载因为不是在同一作用域
// B中的fun和A中的fun构成隐藏成员函数满足函数名相同就构成隐藏。
class A
{
public:void fun(){cout func() endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout func(int i)- iendl;}
};
void Test()
{B b;b.fun(10);
}; 四、派生类的默认成员函数 在之前的学习中 我们知道类可以自动生成一些默认的成员函数这些成员函数包括默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数。而对于派生类这些默认成员函数的生成和行为有一些特殊的规则和注意事项下面我讲详细介绍。 1.派生类中的默认构造函数 默认构造函数在没有用户定义的构造函数时自动生成。对于派生类的默认构造函数它会调用基类的默认构造函数如果存在然后初始化派生类的成员。而如果基类没有默认构造函数则必须在派生类构造函数的初始化列表阶段显示调用。 class Base {
public:Base() {std::cout Base default constructor std::endl;}
};class Derived : public Base {
public:Derived() {std::cout Derived default constructor std::endl;}
};int main() {Derived d; // 输出Base default constructor// Derived default constructorreturn 0;
}2.派生类中的拷贝构造函数 拷贝构造函数在没有用户定义的情况下自动生成用于创建类的对象副本。派生类的拷贝构造函数会首先调用基类的拷贝构造函数然后复制派生类的成员。 class Base {
public:Base(const Base) {std::cout Base copy constructor std::endl;}
};class Derived : public Base {
public:Derived(const Derived other) : Base(other) {std::cout Derived copy constructor std::endl;}
};int main() {Derived d1;Derived d2 d1; // 输出Base copy constructor// Derived copy constructorreturn 0;
}3.派生类中的移动构造函数 移动构造函数在没有用户定义的情况下自动生成用于移动资源所有权。派生类的移动构造函数会首先调用基类的移动构造函数然后移动派生类的成员。 class Base {
public:Base(Base) noexcept {std::cout Base move constructor std::endl;}
};class Derived : public Base {
public:Derived(Derived other) noexcept : Base(std::move(other)) {std::cout Derived move constructor std::endl;}
};int main() {Derived d1;Derived d2 std::move(d1); // 输出Base move constructor// Derived move constructorreturn 0;
}4.派生类的拷贝赋值运算符 拷贝赋值运算符在没有用户定义的情况下自动生成用于将一个对象的内容赋值给另一个对象。派生类的拷贝赋值运算符会首先调用基类的拷贝赋值运算符然后赋值派生类的成员。 class Base {
public:Base operator(const Base) {std::cout Base copy assignment operator std::endl;return *this;}
};class Derived : public Base {
public:Derived operator(const Derived other) {Base::operator(other);std::cout Derived copy assignment operator std::endl;return *this;}
};int main() {Derived d1, d2;d1 d2; // 输出Base copy assignment operator// Derived copy assignment operatorreturn 0;
}5.派生类的移动赋值运算符 移动赋值运算符在没有用户定义的情况下自动生成用于将一个对象的内容移动到另一个对象。派生类的移动赋值运算符会首先调用基类的移动赋值运算符然后移动派生类的成员。 class Base {
public:Base operator(Base) noexcept {std::cout Base move assignment operator std::endl;return *this;}
};class Derived : public Base {
public:Derived operator(Derived other) noexcept {Base::operator(std::move(other));std::cout Derived move assignment operator std::endl;return *this;}
};int main() {Derived d1, d2;d1 std::move(d2); // 输出Base move assignment operator// Derived move assignment operatorreturn 0;
}6.派生类的析构函数 析构函数在没有用户定义的情况下自动生成用于清理对象。派生类的析构函数会首先调用派生类的析构函数然后调用基类的析构函数。 class Base {
public:virtual ~Base() {std::cout Base destructor std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout Derived destructor std::endl;}
};int main() {Base* b new Derived();delete b; // 输出Derived destructor// Base destructorreturn 0;
}为什么基类析构函数需要virtual关键字修饰
基类的析构函数需要加virtual关键字是为了确保在删除派生类对象时能够正确调用析构函数。这是一个非常重要的概念尤其是在使用多态性和通过基类指针或引用操作派生类对象时。
理由多态性和正确的析构顺序
class Base {
public:virtual ~Base() {std::cout Base destructor std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout Derived destructor std::endl;}
};当基类的析构函数是虚函数时通过基类指针删除派生类对象时,C会首先调用派生类的析构函数然后再调用基类的析构函数。这确保了派生类中分配的资源可以先被正确释放再释放基类中分配的资源。
int main() {Base* b new Derived();delete b; // 输出顺序Derived destructor// Base destructorreturn 0;
}问题非虚析构函数导致的资源泄漏
如果基类的析构函数不是虚函数则通过基类指针删除派生类对象时只会调用基类的析构函数而不会调用派生类的析构函数。这会导致派生类中的资源没有被正确释放造成资源泄漏。
class Base {
public:~Base() {std::cout Base destructor std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout Derived destructor std::endl;}
};int main() {Base* b new Derived();delete b; // 只输出Base destructorreturn 0;
}在上述代码中由于 Base 类的析构函数不是虚函数删除 b 时只调用了 Base 的析构函数Derived 类的析构函数没有被调用这会导致 Derived 类中的资源没有被正确释放。
总结
为了确保在使用多态性时派生类对象可以被正确地销毁避免资源泄漏基类的析构函数应该声明为虚函数。这一做法可以保证删除派生类对象时派生类和基类的析构函数都能被正确调用。以下是总结的要点 多态性支持使用基类指针或引用操作派生类对象时确保正确调用派生类的析构函数。正确的析构顺序先调用派生类的析构函数再调用基类的析构函数确保资源正确释放。避免资源泄漏防止派生类中的资源没有被释放导致内存泄漏或其他资源泄漏。 五、继承和友元
友元关系是单向的和局部的友元关系不能继承也就是说基类友元不能访问子类私有和保护成员。
#include iostream// 基类
class Base {
private:int basePrivateVar;
protected:int baseProtectedVar;
public:int basePublicVar;Base() : basePrivateVar(1), baseProtectedVar(2), basePublicVar(3) {}friend void baseFriendFunction(Base obj);
};// 基类的友元函数
void baseFriendFunction(Base obj) {std::cout Base Private Var: obj.basePrivateVar std::endl;std::cout Base Protected Var: obj.baseProtectedVar std::endl;
}// 派生类
class Derived : public Base {
private:int derivedPrivateVar;
protected:int derivedProtectedVar;
public:int derivedPublicVar;Derived() : derivedPrivateVar(4), derivedProtectedVar(5), derivedPublicVar(6) {}friend void derivedFriendFunction(Derived obj);
};// 派生类的友元函数
void derivedFriendFunction(Derived obj) {// 基类的友元不能访问派生类的私有或保护成员// std::cout Derived Private Var: obj.derivedPrivateVar std::endl; // 错误// std::cout Derived Protected Var: obj.derivedProtectedVar std::endl; // 错误std::cout Derived Public Var: obj.derivedPublicVar std::endl;
}int main() {Base baseObj;Derived derivedObj;baseFriendFunction(baseObj); // 可以访问Base类的私有和保护成员derivedFriendFunction(derivedObj); // 可以访问Derived类的公共成员// 基类的友元函数不能访问派生类的私有和保护成员// baseFriendFunction(derivedObj); // 错误return 0;
}六、继承与静态成员
基类定义了static成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子类都只有一个static成员实例。
静态成员变量需要在类外进行定义和初始化。静态成员函数则不需要在类外定义。
#include iostream// 基类
class Base {
public:static int staticVar; // 声明静态成员变量static void staticFunction() { // 声明并定义静态成员函数std::cout Static Function in Base std::endl;}
};// 定义静态成员变量
int Base::staticVar 10;// 派生类
class Derived : public Base {
public:void display() {std::cout Base staticVar: staticVar std::endl; // 访问基类的静态成员变量staticFunction(); // 调用基类的静态成员函数}
};int main() {Derived obj;obj.display();// 静态成员可以通过类名直接访问Base::staticVar 20;Derived::staticVar 30;std::cout Base staticVar after modification: Base::staticVar std::endl;std::cout Derived staticVar after modification: Derived::staticVar std::endl;return 0;
}静态成员的特点 类共享性所有类的对象共享同一个静态成员变量。类作用域静态成员变量和静态成员函数在类作用域内但可以通过类名直接访问。内存管理静态成员变量在程序启动时分配内存程序结束时释放内存。 注意
静态成员函数静态成员函数不能访问非静态成员变量和非静态成员函数因为它们属于类本身而不是类的某个对象。但是静态成员函数可以访问静态成员变量和其他静态成员函数。 七、复杂的菱形继承和菱形虚拟继承
1.单继承
一个子类只有一个直接父类时称这个继承关系为单继承。 2.多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承 3.菱形继承
菱形继承也称钻石继承是指一种特殊的多继承情况其中一个类从两个基类继承而这两个基类又继承自同一个祖先类。这种继承关系形成了一个菱形结构。 菱形继承的问题 从上面的对象成员模型构造可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
#include iostream// 祖先类
class A {
public:int value;A() : value(0) {}
};// 两个派生类继承自 A
class B : public A {};
class C : public A {};// 派生类 D 同时继承自 B 和 C
class D : public B, public C {};int main() {D obj;// obj.value; // 错误二义性问题不知道是从 B 继承的 A 还是从 C 继承的 A// 解决方法之一是明确指定路径obj.B::value 1;obj.C::value 2;std::cout obj.B::value: obj.B::value std::endl;std::cout obj.C::value: obj.C::value std::endl;return 0;
}4.菱形虚拟继承
为了解决上面菱形继承所带来的问题我们可以使用虚拟继承。虚拟继承确保在菱形继承结构中只存在一个基类的实例。
如在上面的代码中我们可以在B和C继承A的时候使用虚拟继承即
class B : virtual public A {};
class C : virtual public A {}; 需要注意的是虚拟继承不要在其他地方去使用。 5.虚拟继承解决数据冗余和二义性的原理
class A
{
public:int _a;
};
// 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.B::_a 1;d.C::_a 2;d._b 3;d._c 4;d._d 5;return 0;
}
下面是菱形继承的内存对象成员模型这里可以看到数据冗余 下面是菱形虚拟继承的内存对象成员模型 这里可以分析出D对象中将A放到了D对象组成的最下面这个A同时属于B和C那么B和C如何去找到公共的A呢
这里是通过了B和C的两个指针指向的一张表。这两个指针叫虚基表指针这两个表叫虚基表。虚基表中存储的是偏移量通过偏移量就能找到A 。
总结
虚拟继承通过确保每个虚拟基类在派生类中只有一个共享实例从而避免了重复实例化和二义性问题。为了实现这一点编译器会使用虚基表来跟踪和管理虚拟基类的实例。
虚基表的工作机制 虚基表的引入 每个使用虚拟继承的类会包含一个虚基表指针。这个指针指向一个虚基表该表包含虚拟基类的指针。 共享基类实例 在派生类如 D的对象中虚基表指针确保所有虚拟基类实例都指向同一个实际基类实例。这意味着 D 中只有一个 A 类的实例。 成员访问的重定向 在访问基类成员时编译器使用虚基表来正确地定位基类成员确保访问的是唯一的基类实例。 所以当一个类虚拟继承另一个类时编译器在对象布局中插入一个虚基表指针vbptr。这个指针指向一个虚基表vbtbl而虚基表中包含指向虚拟基类的偏移量或地址。通过这种方式每个派生类能够正确地定位并访问唯一的虚拟基类实例。 上面就是我们对C继承的全部理解了~