• C语言实现面向对象编程 | 干货


    前言

            GOF的《设计模式》一书的副标题叫做“可复用面向对象软件的基础”,从标题就能看出面向对象是设计模式基本思想。

    由于C语言并不是面向对象的语言,C语言没有直接提供封装、继承、组合、多态等面向对象的功能,但C语言有struct和函数指针。我们可以用struct中的数据和函数指针,以此来模拟对象和类的行为。

    所以在正式开始设计模式前,先看看如何用C语言实现面向对象编程

    本章针对面向对象的封装、继承、组合、多态给出C语言的实现方法。

    封装

            封装是指对象仅暴露必要的对外接口(这里指public方法)来和其它对象进行交互,其它的属性和行为都无需暴露,这使得对象的内部实现可以自由修改。

    这也要求对象包含它能进行操作所需要的所有信息,不必依赖其它对象来完成自己的操作。

    以下以电力公司的例子演示封装。

    电力公司生产并提供电力。为了汇聚各种发电厂的电力并让用户获得电力,电力公司提供了两个统一接口:

    1、电力公司汇聚各种发电厂的电力,无论是火力发电厂、水力发电厂、原子能发电厂等都使用一个接口。如果什么时候一家火力发电厂改造成了风力发电厂,发电厂的实现完全不一样了,但接口不变,所以电力公司感觉不到发电厂变了,不需要为了发电厂实现升级而改造电力公司的系统。

    2、电力公司向用户提供电力,无论用户用电的设备是烤面包机还是洗衣机,电力公司和用户之间都使用一个接口(电源插座)。用户的用电设备可以千变万化,但接口(电源插座)不变。所以电力公司不用关系用户的什么设备在用电。

    代码:

    1. #include
    2. struct PowerCompany {
    3. int powerReserve;
    4. void (*PowerPlant)(struct PowerCompany *this, int power);
    5. void (*PowerUser)(struct PowerCompany *this, int power);
    6. };
    7. void PowerPlant(struct PowerCompany *this, int power)
    8. {
    9. this->powerReserve += power;
    10. printf("默认发电厂,发电%d瓦\n", power);
    11. return;
    12. }
    13. void PowerUser(struct PowerCompany *this, int power)
    14. {
    15. if (this->powerReserve >= power) {
    16. printf("用电%d瓦\n", power);
    17. this->powerReserve -= power;
    18. } else {
    19. printf("电力不足,用电失败\n");
    20. }
    21. return;
    22. }
    23. /* struct PowerCompany 的构造函数 */
    24. void PowerCompany(struct PowerCompany *this)
    25. {
    26. this->powerReserve = 0;
    27. this->PowerPlant = PowerPlant;
    28. this->PowerUser = PowerUser;
    29. return;
    30. }
    31. /* struct PowerCompany 的析构函数 */
    32. void _PowerCompany(struct PowerCompany *this)
    33. {
    34. }
    35. int main(void)
    36. {
    37. struct PowerCompany myPowerCompany;
    38. PowerCompany(&myPowerCompany);
    39. /* 发电 */
    40. myPowerCompany.PowerPlant(&myPowerCompany, 1000);
    41. /* 用电 */
    42. myPowerCompany.PowerUser(&myPowerCompany, 800);
    43. myPowerCompany.PowerUser(&myPowerCompany, 800);
    44. _PowerCompany(&myPowerCompany);
    45. return 0;
    46. }

    从电力公司的例子中可以看出,良好的封装可以有效减少耦合性,封装内部实现可以自由修改,对系统的其它部分没有影响。

    继承

            面向对象编程最强大的功能之一就是代码重用,而继承就是实现代码重用的主要手段之一。继承允许一个类继承另一个类的属性和方法。

    我们可以通过识别事物之间的共性,通过抽象公共属性和行为来构造父类,而通过继承父类来构造各子类。父类,即公共属性和行为,就得到了复用。

    以下哺乳动物的例子演示继承。

    猫和狗都是哺乳动物,它们具有公共的属性和行为。比如,猫和狗都有眼睛,且它们都会叫。

    我们把眼睛的颜色、会叫抽象出来,作为哺乳动物父类的属性,让猫类、狗类都继承哺乳动物父类,可实现对”眼睛的颜色“、”会叫“实现的复用。

    UML:

    代码:

    1. #include
    2. struct Mammal {
    3. int eyeColor;
    4. void (*ShowEyeColor)(struct Mammal *this);
    5. int callNum;
    6. void (*Call)(struct Mammal *this);
    7. };
    8. void ShowEyeColor(struct Mammal *this)
    9. {
    10. if (this->eyeColor == 1) {
    11. printf("眼睛是绿色\n");
    12. } else {
    13. printf("眼睛是蓝色\n");
    14. }
    15. return;
    16. }
    17. void Call(struct Mammal *this)
    18. {
    19. printf("叫%d声\n", this->callNum);
    20. return;
    21. }
    22. // struct Mammal 的构造函数
    23. void Mammal(struct Mammal *this, int eyeColor, int callNum)
    24. {
    25. this->eyeColor = eyeColor;
    26. this->ShowEyeColor = ShowEyeColor;
    27. this->callNum = callNum;
    28. this->Call = Call;
    29. return;
    30. }
    31. struct Dog {
    32. struct Mammal mammal;
    33. };
    34. // struct Dog 的构造函数
    35. void Dog(struct Dog *this, int eyeColor, int callNum)
    36. {
    37. Mammal(this, eyeColor, callNum);
    38. // 狗类的其它属性,略
    39. return;
    40. }
    41. // struct Dog 的析构函数
    42. void _Dog(struct Dog *this)
    43. {
    44. }
    45. struct Cat {
    46. struct Mammal mammal;
    47. // 猫类的其它属性,略
    48. };
    49. // struct Cat 的构造函数
    50. void Cat(struct Cat *this, int eyeColor, int callNum)
    51. {
    52. Mammal(this, eyeColor, callNum);
    53. return;
    54. }
    55. // struct Cat 的析构函数
    56. void _Cat(struct Cat *this)
    57. {
    58. }
    59. int main(void)
    60. {
    61. struct Dog myDog;
    62. Dog(&myDog, 1, 3);
    63. myDog.mammal.ShowEyeColor(&myDog);
    64. myDog.mammal.Call(&myDog);
    65. _Dog(&myDog);
    66. struct Cat myCat;
    67. Cat(&myCat, 2, 5);
    68. myCat.mammal.ShowEyeColor(&myCat);
    69. myCat.mammal.Call(&myCat);
    70. _Cat(&myCat);
    71. return 0;
    72. }

    多态

            多态与继承是紧耦合的关系,但它通常作为面向对象技术中最强大的优点之一。

    子类从继承父类的接口,每个子类是单独的实体,每个子类需要对同一消息有单独的应答。

    每个子类对同一消息的应答采用继承的相同接口,但每个子类可以有不同的实现,这就是多态。

    在猫和狗的例子中,猫类、狗类都继承了哺乳动物父类的“叫”的方法,但猫类、狗类的叫声并不一样,所以猫类、狗类可以采用不同的“叫”的实现。

    以下代码演示了多态。

    代码:

    1. #include
    2. struct Mammal {
    3. int eyeColor;
    4. void (*ShowEyeColor)(struct Mammal *this);
    5. int callNum;
    6. void (*Call)(struct Mammal *this);
    7. };
    8. void ShowEyeColor(struct Mammal *this)
    9. {
    10. if (this->eyeColor == 1) {
    11. printf("眼睛是绿色\n");
    12. } else {
    13. printf("眼睛是蓝色\n");
    14. }
    15. return;
    16. }
    17. void Call(struct Mammal *this)
    18. {
    19. printf("叫%d声\n", this->callNum);
    20. return;
    21. }
    22. /* struct Mammal 的构造函数 */
    23. void Mammal(struct Mammal *this, int eyeColor, int callNum)
    24. {
    25. this->eyeColor = eyeColor;
    26. this->ShowEyeColor = ShowEyeColor;
    27. this->callNum = callNum;
    28. this->Call = Call;
    29. return;
    30. }
    31. struct Dog {
    32. struct Mammal mammal;
    33. };
    34. void Bark(struct Dog *this)
    35. {
    36. int i;
    37. for (i = 0; i < this->mammal.callNum; i++) {
    38. printf("汪 ");
    39. }
    40. printf("\n");
    41. return;
    42. }
    43. /* struct Dog 的构造函数 */
    44. void Dog(struct Dog *this, int eyeColor, int callNum)
    45. {
    46. Mammal(this, eyeColor, callNum);
    47. this->mammal.Call = Bark;
    48. return;
    49. }
    50. // struct Dog 的析构函数
    51. void _Dog(struct Dog *this)
    52. {
    53. }
    54. struct Cat {
    55. struct Mammal mammal;
    56. };
    57. void Meow(struct Cat *this)
    58. {
    59. int i;
    60. for (i = 0; i < this->mammal.callNum; i++) {
    61. printf("喵 ");
    62. }
    63. printf("\n");
    64. return;
    65. }
    66. /* struct Cat 的构造函数 */
    67. void Cat(struct Cat *this, int eyeColor, int callNum)
    68. {
    69. Mammal(this, eyeColor, callNum);
    70. this->mammal.Call = Meow;
    71. return;
    72. }
    73. // struct Cat 的析构函数
    74. void _Cat(struct Cat *this)
    75. {
    76. }
    77. int main(void)
    78. {
    79. struct Dog myDog;
    80. Dog(&myDog, 1, 3);
    81. struct Cat myCat;
    82. Cat(&myCat, 2, 5);
    83. struct Mammal *myMammal;
    84. myMammal = &myDog;
    85. myMammal->Call(myMammal);
    86. myMammal = &myCat;
    87. myMammal->Call(myMammal);
    88. _Dog(&myDog);
    89. _Cat(&myCat);
    90. return 0;
    91. }

    组合

            组合与继承都是面向对象中代码复用的方式,也只有通过组合和继承两种方式能够实现使用其他类构建新类。

    在前面讲的继承关系中,我们把通用属性和行为抽象出来作为父类。

    例如:猫、狗都是哺乳动物,它们具有哺乳动物通用的属性和行为。猫、狗与哺乳动物的关系是“is-a”,即猫、狗(is-a)哺乳动物。而组合关系体现的是“has-a”。以房子和窗户的关系举例。

    我们可以单独构建窗户类,然后把窗户类应用到各种房子类上。此时房子(has-a)窗户,但绝不是窗户(is-a)房子。

    以下UML和代码演示了组合。

    UML:

    代码

    1. #include
    2. struct Window {
    3. int length;
    4. int width;
    5. void (*ShowWindow)(struct Window *this);
    6. };
    7. void ShowWindow(struct Window *this)
    8. {
    9. printf("这是长%d厘米、宽%d厘米的窗户\n", this->length, this->width);
    10. return;
    11. }
    12. void Window(struct Window *this, int length, int width)
    13. {
    14. this->length = length;
    15. this->width = width;
    16. this->ShowWindow = ShowWindow;
    17. return;
    18. }
    19. void _Window(struct Window *this)
    20. {
    21. }
    22. struct House {
    23. struct Window *window;
    24. int livingRoomNum;
    25. int bedRoomNum;
    26. int bathRoomNum;
    27. void (*ShowHouse)(struct House *this);
    28. };
    29. void ShowHouse(struct House *this)
    30. {
    31. printf("这是%d室%d厅%d卫的房子\n", this->bedRoomNum, this->livingRoomNum, this->bathRoomNum);
    32. return;
    33. }
    34. void House(struct House *this, int livingRoomNum, int bedRoomNum, int bathRoomNum)
    35. {
    36. this->livingRoomNum = livingRoomNum;
    37. this->bedRoomNum = bedRoomNum;
    38. this->bathRoomNum = bathRoomNum;
    39. this->ShowHouse = ShowHouse;
    40. return;
    41. }
    42. void _House(struct House *this)
    43. {
    44. }
    45. void main()
    46. {
    47. struct House myHouse;
    48. House(&myHouse, 2, 3, 2);
    49. /* 组合是一种低耦合,如果不初始化,子类只是存放了一个空指针来占位关联。
    50. 此处是与继承关系的区别。继承是一种强耦合,在继承关系中,无论如何子类拥有父类全部的信息。*/
    51. struct Window myWindow1;
    52. myHouse.window = &myWindow1;
    53. Window(myHouse.window, 100, 50);
    54. /* 通过获得其它对象的引用而在“运行时”动态定义 */
    55. myHouse.ShowHouse(&myHouse);
    56. myHouse.window->ShowWindow(myHouse.window);
    57. _Window();
    58. _House();
    59. return;
    60. }

    组合和继承的区别有以下几点:

    组合关系体现的是“has-a”。继承关系体现的是“is-a”。

    温馨提示

            由于微信公众号近期改变了推送规则,如果您想经常看到我们的文章,可以在每次阅读后,在页面下方点一个「赞」或「在看」,这样每次推送的文章才会第一时间出现在您的订阅列表里。

  • 相关阅读:
    java保留两位小数4种方法
    qt工程文件中根据编译环境进行不同操作
    用C++标准库生成制定范围内的整数随机数
    pycharm2020无法打开,点击无反应
    【Rust 笔记】15-字符串与文本(下)
    C++二叉树实验
    laravel 异步队列的使用
    【UE5 Cesium】19-Cesium for Unreal 建立飞行跟踪器(4)
    tasklet的实现(原理篇)
    虹科案例|太赫兹技术如何看透文物下的秘密?
  • 原文地址:https://blog.csdn.net/qq_36075612/article/details/133933710