网站图片链接到视频怎么做,办公室装修专业网站,wordpress.shop,ps个人主页网页设计模板继承#xff08;上#xff09;#xff1a;【C进阶学习】第一弹——继承#xff08;上#xff09;——探索代码复用的乐趣-CSDN博客
前言#xff1a; 在前面我们已经讲了继承的基础知识#xff0c;让大家了解了一下继承是什么#xff0c;但那些都不是重点#xff0c;今…继承上【C进阶学习】第一弹——继承上——探索代码复用的乐趣-CSDN博客
前言 在前面我们已经讲了继承的基础知识让大家了解了一下继承是什么但那些都不是重点今天我们一起来挖掘一下继承底层的一些知识和一些极容易出错的点 目录 一、隐藏
1.1 隐藏的概念
1.2 隐藏的两种类型
二、派生类的默认成员函数
三、继承与友元
四、继承与静态成员
五、总结 一、隐藏
1.1 隐藏的概念 在 C 中继承是一种机制使得子类可以继承父类的成员变量和成员函数。然而当子类中出现和父类同名的成员变量或成员函数时会发生一种特殊的现象即隐藏。 隐藏是指如果子类中出现了与父类同名的成员变量或成员函数则子类中的这个成员会“隐藏”父类中的同名成员使得父类中的同名成员在子类中不可见。 1.2 隐藏的两种类型
具体来说有以下两种情况
成员变量隐藏 如果子类中出现了和父类同名的成员变量则子类中的这个成员变量会隐藏父类中的同名成员变量。例如
class Parent {
public:int a;
};class Child : public Parent {
public:int a; // 此处的 a 会隐藏 Parent 中的 a
};int main() {Child c;c.a 10; // 此处修改的是 Child 中的 a而不是 Parent 中的 areturn 0;
}
成员函数隐藏 如果子类中出现了和父类同名的成员函数则子类中的这个成员函数会隐藏父类中的同名成员函数。例如
class Parent {
public:void func() {cout Parent::func() endl;}
};class Child : public Parent {
public:void func() {cout Child::func() endl;}
};int main() {Child c;c.func(); // 此处调用的是 Child 中的 func()而不是 Parent 中的 func()return 0;
} 需要注意的是虽然子类中的成员会隐藏父类中的同名成员但是父类中的成员仍然存在只是在子类中不可见。如果想在子类中访问父类中被隐藏的成员可以使用作用域运算符::来显式地指明要访问的成员所在的类。例如 class Parent {
public:int a;
};class Child : public Parent {
public:int a;
};int main() {Child c;c.a 10; // 此处修改的是 Child 中的 ac.Parent::a 20; // 此处修改的是 Parent 中的 areturn 0;
}
总之在 C 中的继承中隐藏是一种特殊的机制需要注意避免误用。
二、派生类的默认成员函数 在 C 中当我们定义一个类时可以省略掉其中的成员函数的实现而直接在类定义的外部提供其实现。这种情况下如果我们不提供任何实现那么 C 编译器会自动为我们提供一个默认的构造函数、析构函数和拷贝构造函数和拷贝赋值运算符。 对于派生类来说情况也是类似的。当我们定义一个派生类时如果我们不提供任何构造函数那么 C 编译器会自动为我们提供一个默认的构造函数其构造函数的参数列表和父类的构造函数一致。例如 class Parent {
public:Parent(int a) : m_a(a) {}int m_a;
};class Child : public Parent {
public:Child() : Parent(0) {} // 此处的构造函数会自动调用 Parent 的构造函数并传入 0 作为参数
};
同时如果我们没有提供任何析构函数那么 C 编译器也会自动为我们提供一个默认的析构函数其析构函数的函数体为空。例如
class Parent {
public:~Parent() {}
};class Child : public Parent {
public:~Child() {} // 此处的析构函数会自动调用 Parent 的析构函数
};
需要注意的是如果我们提供了任何一个构造函数或析构函数那么 C 编译器就不会再为我们提供默认的构造函数或析构函数了。这时如果我们需要使用默认的构造函数或析构函数需要我们自己显式地提供。析构顺序为先派生类再基类
另外对于拷贝构造函数和拷贝赋值运算符来说如果我们没有提供任何拷贝构造函数和拷贝赋值运算符那么 C 编译器会自动为我们提供一个默认的拷贝构造函数和拷贝赋值运算符其行为是浅拷贝Shallow Copy即直接拷贝成员变量的值。例如
class Parent {
public:Parent(int a) : m_a(a) {}int m_a;
};class Child : public Parent {
public:Child(int a, int b) : Parent(a), m_b(b) {}int m_b;
};int main() {Child c1(1, 2);Child c2(c1); // 此处调用的是 Child 的默认拷贝构造函数直接拷贝 m_a 和 m_b 的值return 0;
} 但是如果我们的类中有指针类型的成员变量那么默认的拷贝构造函数和拷贝赋值运算符就会出现问题因为它们只会拷贝指针的值而不会拷贝指针所指向的内存。这时我们需要自己提供一个拷贝构造函数和拷贝赋值运算符实现深拷贝Deep Copy。例如 class Parent
{
public:Parent(int a):_a(a){}int _a;
};
class Child :public Parent
{
public:int* _b;Child(int a, int b):Parent(a),_b(new int(b)) //深度拷贝{}~Child(){delete _b;_b nullptr;}Child(const Child c):Parent(c){_b new int(*c._b);}Child operator(const Child c){if (this ! c){_a c._a;delete _b;_b new int(*(c._b)); }return *this;}void Print(){cout _a: _a _b: *_b endl;}
};
int main()
{Child c1(1, 2); Child c2(c1); //此处调用的是c2的深度拷贝c2.Print();Child c3 c1;c3.Print();return 0;
}
运行结果 我们通过两张图来总结一下 三、继承与友元
在C中友元关系不能被继承因为友元关系是独立于类定义的并不是类的成员。因此如果在父类中声明了一个友元函数或友元类子类无法继承这种关系。
下面是一些相关知识点 1、友元函数不能是成员函数友元函数不是类的成员函数因此不能使用this指针也不能直接访问类的私有成员。需要通过类的对象或引用来访问私有成员。2、友元关系不具有传递性如果A类声明了B类为友元则B类不会自动成为A类的友元。 友元函数可以是模板函数模板函数可以被声明为类的友元这样模板函数可以访问类的私有成员。3、友元类如果一个类声明另一个类为友元则该友元类的所有成员函数都可以访问原类的私有成员。4、友元不能继承由于友元关系不是类的成员因此不能被继承。如果在父类中声明了一个友元函数或友元类子类无法继承这种关系。但子类可以在自己的范围内重新声明该友元关系。 示例 #includeiostream
#includestring
using namespace std;
class Base {
public: friend void friendFunction(Base);
protected:int value;
};class Derived : public Base {
public:// friendFunction 在 Derived 中不是友元函数需要重新声明friend void friendFunction(Derived);
};void friendFunction(Base base) {// 可以访问 Base 的私有成员base.value 10;
}void friendFunction(Derived derived) {// 可以访问 Derived 的私有成员derived.value 20;
}int main() {Derived derived;friendFunction(derived);return 0;
} 在上面的示例中由于友元关系不能被继承因此在Derived类中需要重新声明friendFunction函数为友元函数以便在Derived类的实例上调用该函数。 四、继承与静态成员
在 C 中类可以包含静态成员变量和静态成员函数其中静态成员变量属于类本身而不是类的某个对象因此它们可以在不创建类对象的情况下被访问。 当一个类继承另一个类时子类可以继承其父类的静态成员并且可以在子类中重新定义这些静态成员。在这种情况下子类和父类各自拥有自己的静态成员变量它们之间没有任何关系。 下面是一个简单的例子
#includeiostream
using namespace std;class Parent {
public:static int a;
};int Parent::a 10; //静态成员的定义只能在类外进行class Child : public Parent {
public:static int a; //类中只能声明静态成员
};int Child::a 20; //静态成员的定义只能在类外进行int main() {cout Parent::a endl; //输出10cout Child::a endl; //输出20return 0;
}
运行结果 在上面的例子中Parent 类和 Child 类都有一个静态成员变量 a它们各自拥有自己的实现。在 main 函数中我们可以直接通过类名来访问这些静态成员变量。 需要注意的是如果子类中没有重新定义父类的静态成员变量那么子类可以直接访问父类的静态成员变量例如 Parent::a。如果子类重新定义了父类的静态成员变量那么子类只能访问自己的静态成员变量例如 Child::a。 此外静态成员函数也可以继承并且可以在子类中重新定义。在子类中重新定义父类的静态成员函数时子类的静态成员函数会隐藏父类的静态成员函数因此如果在子类中需要调用父类的静态成员函数需要使用作用域运算符 :: 来显式地调用。 还有一个需要注意的点就是类中只能声明静态成员静态成员的定义只能在类外进行。 总之在 C 中静态成员在继承中的行为与普通成员有所不同需要注意其使用方法。
五、总结 以上就是C继承中需要额外注意的点此外还有一个很重要的知识点我们还没讲到——多继承、菱形继承、虚拟继承这几个知识点是有很大关联性的且我们在平时使用继承时也很容易出错鉴于篇幅问题这几个问题会在下一篇单拎出来来讲今天的内容就到此为止。 感谢各位大佬观看创作不易还请各位大佬一键三连