C语言学习笔记—C中的继承(方法1)

0、简述

        在此之前,我们一起学习了如何用C语言进行OOP,并了解了组合和聚合的概念。在这里我们继续讨论对象及其对应类之间的关系,涵盖继承和多态。

        我们已经知道类之间可能存在的关系,C中继承和多态很大程度上依赖于前两章解释的理论基础。我们已经解释了组合和聚合关系,该文将继续讨论继承关系,以及一些其他主题。

        以下是我们接下来将解释的主题:

        第一个主题是继承关系:本章将介绍在C语言中实现继承关系的各种方法,并对它们进行比较。

        第二个主题是多态:在各类之间具有继承关系的情况下,多态允许我们在子类中拥有关于相同行为函数的不同版本。我们将一起学习在C语言中拥有多态函数的方法,这将是我们理解C++如何提供多态的第一步。

        让我们从继承关系开始讨论。

一、继承

        在上一篇笔记中,我们讨论了“拥有”关系,最终得到了组合和聚合关系。在该笔记中,我们将讨论“是”或“是一个”关系

        继承关系是“是”的关系。

        继承关系也可以称为扩展关系,因为它只向已存在的对象或类中添加额外的属性和行为

        在某些情况下,一个对象需要具有与另一个对象相同的属性。换句话说,新对象是对老对象的扩展。

        例如,一个学生具有一个人的所有属性,但也可能有额外的属性,如下:

        上述例子清楚地说明了,student_t 扩展了 person_t,有了新的属性:student_number 和 student_GPA,这是学生的特有属性。

        前面指出,继承(或扩展)关系与组合、聚合那么“拥有”关系不同,它是一种“是”的关系。因此,对于上述的例子,我们可以说“一个学生是一个人”,这在教育软件领域似乎是正确的。只要在一个领域中存在“是”的关系时,他就可能是继承关系。在前面的例子中,person_t通常称为基类型或父类型。student_t通常称为子类型或继承的子类型

、继承的本质

        如果要深入挖掘并了解继承关系到底是什么,就会发现它本质上确实是一个组合关系。我们可以说一个学生具有人的特性,我们可以假设在Student类的属性结构中存在一个私有的person对象。也就是说,继承关系可以等价于一对一的组合关系。

        因此,还可以这样写:

        这样的语法在C语言中完全有效,实际上,使用结构变量(而不是指针)进行结构嵌套的设置方法功能很强大。它允许在新的结构中加入一个结构变量,这个新结构实际是对原有结构的扩展。

        在前面结构类型的设置中,需要将person_t类型的字段作为第一个字段,这样指向student_t类型的指针可以很容易地转换为指向person_t类型的指针,它们都可以指向内存中的相同地址。这种情况叫做向上转换注意:结构变量不具有此性能

        看如下演示:

        可以看到,我们期望 s_ptr 和 p_ptr 指针都指向内存中相同的地址。注意,每次运行时显示的地址可能不同,但关键是两个指针指向的是相同的地址。这意味着 student_t 类型的结构变量在内存分布上确实继承了 person_t 结构。这也意味着,我们可以使用指向 student 对象的指针来使用Person 类的行为函数。

        换句话说:Person类的行为函数可以被 Student 对象重用!

        请注意,下面的这段代码是错误的,不能编译

        

        声明 person 字段时产生了错误,因为不能由不完整的类型创建变量。记住,结构的前向声明会得到一个不完整的类型声明。只能有不完整类型的指针,而不能有不完整的变量。之前已经看到,对不完整类型,甚至无法为其分配堆内存。

        这是什么意思呢?这意味着,如果打算使用嵌套结构变量来实现继承,则 student_t 结构应该看到 person_t 结构的实际定义,根据我们对封装的了解,person_t 应该是私有的,对任何其他类都不可见。

        因此,有两种方法来实现继承关系:

                1、使子类能够访问基类的私有实现。

                2、使子类只能访问基类的公共接口。

2.1 C语言中实现继承的第一种方法 

        我们将在下例中举例演示第一种方法,它们都用一些行为函数表示了相同的类:Student 和 Person。在 main 函数中构造了这些类的对象,并在一个简单的场景中运行。

        其中Student类需要能访问实际的Person类属性结构的私有定义。

        Person类的头文件:

        详看上述构造函数,它接受创建person对象所需的所有值:人名、性别、年龄。可以看到,属性结构person_t是不完整的,因此 Student 类不能使用该头文件来建立继承关系,因为person_t只有声明没有定义。

        另一方面,该头文件不能包含 person_t 属性结构的实际定义,因为该头文件将被代码的其他部分使用,这部分代码不应该知道Person内部的任何情形。那么,这该怎么办呢?我们希望逻辑的某个部分知道一个结构定义,而其他部分不能知道。这就是私有头文件的切入点。

        私有头文件是一个普通的头文件,它应该被实际需要它的特定代码部分或特点类所包含和使用。对于上述代码,person_t 的实际定义应该就是私有头文件中的一部分。看如下私有头文件的示例:

        

        可以看到,它只包含了person_t结构的定义,仅此而已。这是Person类中应该保持私有的一部分,但它需要对Student类公开。

        这时,需要这个定义来定义 student_t 属性结构。下面的代码展示了Person类属性的私有实现源文件:

         Person类的源文件:

#include<stdlib.h>
#include<string.h>

#include"person_private.h"  //person_t的定义 在这个私有头文件中

person_t* person_new(void)
{
    return (person_t*)malloc(sizeof(person_t));
}


void person_ctor(person_t *person, const char *name, const char *gender, unsigned int age)
{
    strcpy(person->name, name);
    strcpy(person->gender, gender);
    person->age = age;
}


void person_dtor(person_t* person)
{
    //什么也不用做
}


void person_get_name(person_t* person, char *buffer)
{
    strcpy(buffer, person->name);
}


void person_get_gender(person_t* person, char *buffer)
{
    strcpy(buffer, person->gender);
}


unsigned int person_get_age(person_t* person)
{
    return person->age;
}

        与之前的所有例子一样,这里对Person类定义除了加了私有头文件的这一概念,其他没有什么特别的。

        Student类的头文件:

        可以看到,Student类的构造函数接受与Person类的构造函数相似的参数。这是因为student对象实际上包含了person对象,它需要这些值来填充其组合的person对象。这意味着student构造函数需要为student对象中的person部分设置属性。

        注意:我们只为Student类增加了两个额外拓展的行为函数,因为我们可以对student对象使用Person类的行为函数。

        Student类的源文件:

#include<stdlib.h>
#include<string.h>

#include"person.h"
#include"person_private.h" 


typedef struct
{
    person_t person;    //这里继承person类的所有属性,并且由于采用结构嵌套,还可以使用它的所有行为函数。

    char *student_number;
    unsigned int GPA;
}student_t;


student_t* student_new(void)
{
    return (student_t*)malloc(sizeof(student_t));
}


void student_ctor(student_t *student, const char *name, 
                 const char *gender, unsigned int age, 
                 const char *student_number, float GPA)
{
    //调用基类的构造函数
    person_ctor( (struct person_t*)student, name, gender, age );

    student->student_number = (char*)malloc(sizeof(char)*16);
    strcpy(student->student_number, student_number);
    student->GPA = GPA;
}


void student_dtor(student_t* student)
{
    free(student->student_number);
    person_dtor((struct person*)student);
}


void student_get_Number(student_t *student, char *buffer)
{
    strcpy(buffer, student->student_number);
}


unsigned int student_get_GPA(student_t *student)
{
    return student->GPA;
}

        上述代码包含有关继承关系的最重要的代码。首先,我们需要包含Person类的私有头文件,因为在定义student_t结构类型时,需要使用person_t类型来定义第一个字段。而且,因为这个字段是一个实际的变量而不是一个指针,它要求 person_t 是预先定义好的。

        注意,这个变量必须是结构中的第一个字段。否则,就不可能使用Person类的行为函数。

        同样,在Student类的构造函数中,调用了基类的构造函数来初始化基类对象的属性。看看在将指向 student_t 类型的指针传递给 person_ctor 函数时,是如何将 student_t 指针转换为 person_t 指针的。之所以可以这样做,是因为 person 字段是 student_t 的第一个成员

        同样,在Student类的析构函数中,也调用了基类的析构函数。这种析构应该首先发生在子级,然后是基级,和构造顺序相反。

        下面这个主逻辑源文件中,它将使用Student类并创建一个 Student类型的对象:

        在这个主要场景中,我们已经包含了Person和Student两个类的公共接口(不是私有头文件),但是只创建了一个student对象。可以看到,student对象继承了其内部person对象的所有属性,可以通过person类的行为函数读取这些属性。

        main函数运行结果:

    

  • 32
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值