【C++面向对象】--- 继承 的奥秘(下篇)

这篇具有很好参考价值的文章主要介绍了【C++面向对象】--- 继承 的奥秘(下篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

个人主页:平行线也会相交💪
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创
收录于专栏【C++之路】💌
本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍
希望我们一起努力、成长,共同进步。🍓
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象

一、作用域

接下来对C++继承体系中的作用域展开分析。

在C++继承体系中,子类和父类有各自的作用域,所以子类和父类可以定义同名的成员

请看针对不同作用域的举例:

局部域和当前类域【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
这里有个小概念:
隐藏/重定义子类和父类有同名成员时,子类的成员隐藏了父类的成员。(如上左图所示)

指定当前的父域:
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象

作用域当然也对成员函数起作用,请看:
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象

出个小题

类B和类A中的fun()函数有什么关系。

class A
{
public:
	void fun()
	{
		cout << "fun()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "fun(int i)" << endl;
	}
};

B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。并不会构成函数重载(因为函数重载针对的是不同的作用域)
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象

小总结

  • 在继承体系中基类和派生类都有独立的作用域
  • 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
    也叫重定义。(在子类成员函数中,可以使用基类::基类成员进行显示访问,举个例子就比如说:B b; b.A::fun();
  • 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 但是其实在实际中在继承体系里面最好不要定义同名的成员(省的给自己添麻烦)。

二、派生类的默认成员函数

再来回顾一下C++中的6个默认成员函数:构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址及const取地址运算符重载。

构造函数

class Person
{
public:
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};
class Student : public Person
{
public:
    Student(const char* name = "李四",int id = 0)
        :_id(0)
    {}
protected:
    int _id;
};
int main()
{
    Student s;
    return 0;
}

运行结果如下:
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
上述代码中我们并没有定义Person类对象,但是却调用了Person类中的默认构造函数,为什么呢?

因为C++规定了派生类必须调用父类的成员函数来初始化父类的成员变量。
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
这里是在初始化列表来调用父类中的默认成员函数的。

在来看下面的情况,请看:
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
解释在创建Student对象时,先调用Person类的构造函数来初始化Person类的成员变量_name,然后再调用Student类的构造函数来初始化Student类的成员变量_id。
所以这里是Person类中的成员函数先进行初始化,然后再对Student中的成员进行初始化。即派生类的构造函数在执行之前,基类的构造函数必须首先完成。

重点:通过使用初始化列表,并在其中调用基类的构造函数来初始化基类的成员变量,可以确保在派生类的构造函数中正确初始化基类的数据成员这是由于派生类的构造函数在执行之前,基类的构造函数必须首先完成。

拷贝构造函数

class Person
{
public:
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }
    Person(const Person& p)
        : _name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};
class Student : public Person
{
public:
    //构造函数
    Student(const char* name = "李四",int id = 0)
        :_id(0)
        ,Person(name)
    {}

    //拷贝构造函数
    Student(const Student& s)
        :Person(s)
        , _id(s._id)
    {}
protected:
    int _id;
};
int main()
{
    Student s1;
    Student s2(s1);
    return 0;
}

运行结果如下:【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象

如果我们去掉基类拷贝构造函数中的Person(s)会怎样呢(即没有显式调用基类中的拷贝构造函数)?

解析:去掉Person(s)将导致基类Person的成员变量_name不会被复制,而是会调用基类中的默认构造函数,而倘若此时基类也没有提供默认构造函数的话就会直接报错。
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
所以,我们应该显式调用拷贝构造函数。如下:

//拷贝构造函数
Student(const Student& s)
    :Person(s)//这里要显式调用拷贝构造函数,否则会调用基类中的默认构造函数
    , _id(s._id)
{}

一句话总结派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

赋值运算符重载

//父类赋值运算符重载
Person& operator=(const Person& p)
{
    cout << "Person operator=(const Person& p)" << endl;
    if (this != &p)
        _name = p._name;

    return *this;
}
//子类赋值运算符重载
Student& operator=(const Student& s)
{
    cout << "Student& operator= (const Student& s)" << endl;
    if (this != &s)
    {
        Person::operator=(s);
        _id = s._id;
    }
    return *this;
}

运行结果如下:
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
这里有的小伙伴看到Student s2 = s1;可能会产生疑惑,为什么这里不调用赋值运算符重载函数。
解答

因为在语句Student s2 = s1;中,发生的是对象的初始化,而不是赋值操作
当使用Student s2 = s1;来初始化一个已存在的对象s2时,会调用拷贝构造函数而不是赋值运算符重载函数。拷贝构造函数用来创建一个新对象,并将其内容初始化为另一个同类型对象的副本。
如果要调用赋值运算符重载函数,需要使用赋值操作符=来对已存在的对象进行赋值,例如s2 = s1;。这样才会调用赋值运算符重载函数,将s1的值赋给s2。

析构函数

//父类析构函数
~Person()
{
    cout << "~Person()" << endl;
}
//子类析构函数
~Student()
{
    cout << "~Student()" << endl;
}

在C++中,无法显式调用父类的析构函数。当一个派生类对象被销毁时,首先会自动调用派生类的析构函数,然后再自动调用基类的析构函数(即按照先父后子的顺序来完成对对象的析构)
如果要显式调用是没有办法保证先子后父进行析构的。

小总结

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
    的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。即按照先清理派生类对象,再清理基类对象的顺序
  • 派生类对象初始化先调用基类构造再调派生类构造;同时派生类对象初始化先调用基类构造再调派生类构造。

三、继承与友元

友元关系不能继承,即基类友元不能访问子类私有和保护成员,基类的友元只能访问基类的成员而不能访问派生类的成员。

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
	friend void Display(const Person& p, const Student& s);
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);
	return 0;
}

【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象

解释:Person类和Student类互相引用对方作为友元函数,因此需要先进行一次前向声明(即开头的class Student;。这样可以确保在实际定义这两个类的成员函数之前,编译器已经知道这两个类的存在。

四、继承和静态成员

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()
{
	Person p;
	Student s;
	cout << &p._name << endl;
	cout << &s._name << endl;
	
	cout << &p._count << endl;
	cout << &s._count << endl;
	cout << &Person::_count << endl;
	cout << &Student::_count << endl;
}

运行结果如下:
【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象
静态成员变量是一种属于类而不是类的实例的变量。它在所有类的实例之间共享,并且在整个程序的生命周期中只存在一个副本。静态成员变量是在类定义外部进行初始化的

静态成员变量适用于在类的多个实例之间共享数据,并且可以通过类名直接访问,而无需实例化类对象。它们在数据共享和数据统计方面非常有用。需要注意的是,静态成员变量仅属于类,而不属于类的任何特定实例。

静态成员变量的访问方式:静态成员变量可以使用类名::成员变量名的方式进行访问(即类名::成员变量名),例如Person::_count

下面请看下面代码,要统计Person类及其Person派生类对象总共创建了多少个

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()
{
	Person p;
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << Person::_count << endl;
}

运行结果:Person类及其派生类对象总共创建了4个对象

解释:在代码中,将_count定义为静态成员变量是为了在整个类层级中共享同一个计数变量。当创建派生类对象时,构造函数会依次调用每个类的构造函数,包括父类的构造函数。所以在父类的构造函数中进行++_count操作,可以确保每个派生类对象的创建都能正确地增加计数。

好了,本文到这里就结束了,希望对大家学习C++继承体系有所帮助。
再见啦,友友们!!!

【C++面向对象】--- 继承 的奥秘(下篇),C++之路,c++,开发语言,面向对象文章来源地址https://www.toymoban.com/news/detail-650659.html

到了这里,关于【C++面向对象】--- 继承 的奥秘(下篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++ 面向对象三大特性——继承

    ✅1主页:我的代码爱吃辣 📃2知识讲解:C++ 继承 ☂️3开发环境:Visual Studio 2022 💬4前言:面向对象三大特性的,封装,继承,多态,今天我们研究研究 C++的继承 。 目录 一.继承的概念及定义 1.继承的概念  2.继承的定义 二. 继承关系和访问限定符  三.基类和派生类对象赋

    2024年02月12日
    浏览(69)
  • C++:面向对象大坑:菱形继承

    单继承:一个子类只有 一个直接父类 时称这个继承关系为单继承。 图示: 多继承:一个子类有 两个或以上直接父类 时称这个继承关系为多继承。 图示: 1.概念 菱形继承:菱形继承是多继承的一种特殊情况。即:一个类是另外几个类的子类,而这几个子类又是另外一个类

    2024年04月27日
    浏览(37)
  • 【面向对象语言三大特性之 “继承”】

    目录 1.继承的概念及定义 1.1继承的概念 1.2 继承定义 1.2.1定义格式  1.2.2继承关系和访问限定符  1.2.3继承基类成员访问方式的变化 2.基类和派生类对象赋值转换 3.继承中的作用域 4.派生类的默认成员函数 5.继承与友元 6. 继承与静态成员 7.复杂的菱形继承及菱形虚拟继承 8.继

    2023年04月08日
    浏览(44)
  • go语言(十一)----面向对象继承

    一、面向对象继承 写一个父类 子类继承 父类 子类的新方法 定义子类 两种方法: 第一种: 第二种: 子类继承父类的使用

    2024年01月22日
    浏览(39)
  • 【C++历险记】面向对象|菱形继承及菱形虚拟继承

    个人主页:兜里有颗棉花糖💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 单继承:一个子类 只有一个直接父类 时称这个继承

    2024年02月10日
    浏览(43)
  • c++面向对象之封装、继承、和多态

    把客观事物封装成类,而且可以把自己的数据和方法设置为只能让可信的类或者对象操作,对不可信的信息进行隐藏(利用public,private,protected,friend)实现 has-a :描述一个类由多个部件类构成,一个类的成员属性是另一个已经定义好的类。 use-a:一个类使用另一个类,通过类之间

    2024年02月02日
    浏览(53)
  • 第八站:C++面向对象(继承和派生)

     派生:由父类派生出子类 继承:子类继承父类(继承不会继承 析构函数和构造函数 : 父类的所有成员函数,以及数据成员,都会被子类继承! ) \\\"子类派生出的类\\\"会指向\\\"父类被继承的类\\\", 父类就是基类 实例1: 先创建一个父类,有私有成员数据(name,和age),成员函数,描述信息,有参的

    2024年01月19日
    浏览(44)
  • 面向对象的三大特性之继承(C++)

    概念   继承机制是面向对象编编程使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。 定

    2024年02月06日
    浏览(51)
  • C++ 面向对象核心(继承、权限、多态、抽象类)

    继承(Inheritance)是面向对象编程中的一个重要概念,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和方法。继承是实现类之间的关系,通过继承,子类可以重用父类的代码,并且可以在此基础上添加新的功能或修改已有的功能。 在C++中,继承

    2024年02月08日
    浏览(49)
  • 【JAVA】面向对象的编程语言(继承篇)

    个人主页:【😊个人主页】 系列专栏:【❤️初识JAVA】 在之前的文章中,我们介绍过面向对象的编程语言,今天我们就来就进入到JAVA面对对象的编程世界,今天我们主要来介绍面向对象的编程范式中一个重要的概念——继承。 继承是java面向对象编程技术的一块基石,因为

    2024年02月09日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包