c语言tips-c语言的虚函数实现

0. 前言

学过面对对象的同学都知道虚函数是面向对象编程中的一个重要概念,它允许在基类和派生类之间实现多态性(polymorphism)。我们可以在基类去定义一个成员函数,然后再派生类再去覆盖写它,这样在不同派生类使用相同函数名就可以实现不同的功能。下面可以看一下c++和python是如何做的

1. 面对对象语言实现

cpp

#include <iostream>class Base {
public:virtual void foo() {std::cout << "Base::foo() called" << std::endl;}
};class Derived : public Base {
public:void foo() override {std::cout << "Derived::foo() called" << std::endl;}
};int main() {Base* ptr = new Derived();  // 使用基类指针指向派生类对象ptr->foo();  // 调用派生类中的虚函数delete ptr;  // 释放内存return 0;
}

这是cpp的虚函数实现,当子类继承基类后如果没有重写foo()函数,则就输出Base::foo() called,如果重写了则会输出Derived::foo() called

python

from abc import ABC, abstractmethodclass MyAbstractClass(ABC):@abstractmethoddef my_abstract_method(self):passclass MyDerivedClass(MyAbstractClass):passobj = MyDerivedClass()  # 引发 TypeError 异常,因为未实现抽象方法

在这个例子中,MyAbstractClass 是一个抽象类,其中的 my_abstract_method 是一个抽象方法。MyDerivedClass 派生自 MyAbstractClass,但没有提供对 my_abstract_method 的具体实现,因此在实例化 MyDerivedClass 对象时会引发异常。这其实有点类似于cpp的纯虚函数了,需要强制在子类重写具体的方法
抽象类的优势在于它可以提供一种约束,确保派生类实现了抽象类中定义的所有抽象方法。这有助于编写更可靠和可维护的代码,并在运行时捕获未实现的方法调用。同时,抽象类也可以提供公共的默认实现,以减少派生类的代码重复。

2. c语言实现

由于在芯片原厂做维护sdk的工作,很多时候我们不希望去改底层的一些代码而实现不同客户需要的功能,我就在想c语言能不能有一个类似于虚函数的功能来根据编译的文件中不同的同名函数而实现不同的功能呢?然后我就发现了__attribute__((weak))

  • 以下是__attribute__((weak))的介绍

  • attribute((weak))是一种GCC编译器的属性(attribute),用于将符号(函数或变量)标记为弱引用。 在C语言中,当你声明一个函数或变量时,编译器会生成一个对应的符号。当链接器在不同的编译单元(源文件)中遇到相同的符号时,它需要解决这些符号的引用。通常情况下,链接器会选择具有强引用的符号作为最终的定义。

  • 然而,通过使用__attribute__((weak))属性,你可以将一个符号标记为弱引用。这意味着如果在链接过程中存在具有强引用的符号定义,那么它将被选择作为最终的定义;否则,将使用具有弱引用的符号定义。

  • 这种覆盖行为是因为具有强引用的函数定义优先于具有弱引用的函数定义。在链接过程中,链接器会选择具有强引用的函数定义,而忽略具有弱引用的函数定义。

  • 需要注意的是,覆盖具有__attribute__((weak))属性的函数时,函数签名(函数名和参数列表)必须完全匹配。否则,链接器将无法正确解析符号引用。

  • 此外,当覆盖具有__attribute__((weak))属性的函数时,覆盖函数的定义必须在链接器解析符号引用之前可用。否则,强引用的函数定义将无法覆盖弱引用的函数定义。

说再多概念不如上一个例子
在这里插入图片描述

我编写了三个.c文件,main.c去调用test1.c test2.c的函数,代码分别如下

main.c

#include <stdio.h>extern void func_print1();
extern void func_print2();// void my_function1(void)
// {
//     printf("This is the overriding function.\n");
// }// void my_function2(void)
// {
//     printf("This is the overriding function.\n");
// }int main()
{func_print1();func_print2();return 0;
}

test1.c

// test1.c
#include <stdio.h>void __attribute__((weak)) my_function1(void) {printf("This is the weak function. from test1.c \n");
}void func_print1()
{my_function1();
}

test2.c

// test2.c
#include <stdio.h>void __attribute__((weak)) my_function2(void)
{printf("This is the weak function. from test2.c \n");
}void func_print2()
{my_function2();
}

毫无疑问,现在输出的肯定是
This is the weak function. from test1.c
This is the weak function. from test2.c

假设我们不想改变test1.c test2.c的代码而只在main.c修改代码来影响底层的操作,那我们就可以在main.c去写一个同名的函数去覆盖它们

#include <stdio.h>extern void func_print1();
extern void func_print2();void my_function1(void)
{printf("This is the overriding function1.\n");
}void my_function2(void)
{printf("This is the overriding function2.\n");
}int main()
{func_print1();func_print2();return 0;
}

This is the overriding function1.
This is the overriding function2.

这在sdk的开发中可以带来三个好处

  • 可选性覆盖:使用 attribute((weak)) 属性声明的函数或变量可以在链接时被覆盖。这意味着,在SDK的使用者中可以选择是否提供自定义的实现来替换SDK中的默认实现。这种灵活性使得SDK更加通用和可配置。
  • 默认实现:通过在SDK中使用弱符号,可以为函数或变量提供默认实现。如果SDK的使用者没有提供自定义的实现,编译器会选择使用SDK中的默认实现。这样可以减少使用SDK的开发者的工作量,同时保证SDK的功能完备性。
  • 扩展性:使用弱符号属性可以为SDK的使用者提供扩展接口。使用者可以通过覆盖弱符号来添加新的功能、修改行为或提供自定义的回调函数。这种扩展性使得SDK适应不同的应用场景和需求。

3. 总结

虽然c语言是个面向过程的语言,但是使用__attribute__((weak))属性依旧能够实现面向对象的虚函数的概念,在某些场合中对于整体代码的维护和开发有着重大作用。全网好像也没有比较详细的对__attribute__((weak))属性比较详细的解释,如果你也遇到这个属性的问题,希望这篇文章能够帮到你!有帮助的话希望能给我点个赞吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/95950.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter 项目结构文件

1、Flutter项目的文件结构 先helloworld项目&#xff0c;看看它都包含哪些组成部分。首先&#xff0c;来看一下项目的文件结构&#xff0c;如下图所示。 2、介绍上图的内容。 -litb/main.dart文件&#xff1a;整个应用的入口文件&#xff0c;其中的main函数是整个Flutter应…

TCP Header都有啥?

分析&回答 源端口号&#xff08;Source Port&#xff09; &#xff1a;16位&#xff0c;标识主机上发起传送的应用程序&#xff1b; 目的端口&#xff08;Destonation Port&#xff09; &#xff1a;16位&#xff0c;标识主机上传送要到达的应用程序。 源端&#xff0c;目…

开开心心带你学习MySQL数据库之第三篇上

学校的项目组有必要加入吗? 看你的初心. ~~如果初心是通过这个经历能够提高自己的技术水平 ~~是可以考虑的 ~~如果初心是通过这个经历提高自己找工作的概率 ~~这个是不靠谱的,啥用没有 ~~如果初心是通过这个体验更美好的大学生活 ~~靠谱的 秋招,应届生,找工作是非常容易的!!! …

MySQL InnoDB 是怎么使用 B+ 树存数据的?

这里限定 MySQL InnoDB 存储引擎来进行阐述&#xff0c;避免不必要的阅读歧义。 首先通过一篇文章简要了解下 B 树的相关知识&#xff1a;你好&#xff0c;我是B树 。 B 树是在 B 树基础上的变种&#xff0c;主要区别包括&#xff1a; 1、所有数据都存储在叶节点&#xff0c;其…

C语言深入理解指针(非常详细)(二)

目录 指针运算指针-整数指针-指针指针的关系运算 野指针野指针成因指针未初始化指针越界访问指针指向的空间释放 如何规避野指针指针初始化注意指针越界指针不使用时就用NULL避免返回局部变量的地址 assert断言指针的使用和传址调用传址调用例子&#xff08;strlen函数的实现&a…

stable diffusion实践操作-宽高设置以及高清修复

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、SD宽高怎么设置&#xff1f;1.1 宽高历史 二、高清修复1. 文生图中的高清修复1.按钮Hires.fix2.不同放大算法对比1.第一类2.第二类3.第三类4.第四类5.第五类6.第六类7.第七类8.第八类9.第九类10.第十类11…

SQL查询本年每月的数据

--一、以一行数据的形式&#xff0c;显示本年的12月的数据&#xff0c;本示例以2017年为例&#xff0c;根据统计日期字段判断&#xff0c;计算总和&#xff0c;查询语句如下&#xff1a;selectsum(case when datepart(month,统计日期)1 then 支付金额 else 0 end) as 1月, sum…

OceanBase社区版4.x核心技术解密

数字化时代&#xff0c;各行各业的数据量呈现爆发式增长&#xff0c;对于海量数据价值的挖掘和应用&#xff0c;正成为推动创新的主要力量&#xff0c;与此同时&#xff0c;数据计算复杂度正在提升。在此背景下&#xff0c;对于数据处理的基石数据库而言&#xff0c;正面临市场…

华为云 sfs 服务浅谈

以root用户登录弹性云服务器。 以root用户登录弹性云服务器。 安装NFS客户端。 查看系统是否安装NFS软件包。 CentOS、Red Hat、Oracle Enterprise Linux、SUSE、Euler OS、Fedora或OpenSUSE系统下&#xff0c;执行如下命令&#xff1a; rpm -qa|grep nfs Debian或Ubuntu系统下…

深度学习推荐系统(二)Deep Crossing及其在Criteo数据集上的应用

深度学习推荐系统(二)Deep Crossing及其在Criteo数据集上的应用 在2016年&#xff0c; 随着微软的Deep Crossing&#xff0c; 谷歌的Wide&Deep以及FNN、PNN等一大批优秀的深度学习模型被提出&#xff0c; 推荐系统全面进入了深度学习时代&#xff0c; 时至今日&#xff0c…

UDP协议的重要知识点

UDP&#xff0c;即用户数据报协议&#xff08;User Datagram Protocol&#xff09;&#xff0c;是一个简单的无连接的传输层协议。与TCP相比&#xff0c;UDP提供了更少的错误检查机制&#xff0c;并允许数据包在网络上更快地传输。在这篇博客中&#xff0c;我们将深入探讨UDP的…

C++基础语法——多态

目录 1.什么是多态&#xff1f; 2.多态的定义与实现 ①多态的构成条件 ②虚函数 ③虚函数的重写 1.协变 2.析构函数的重写 ④final和override 1.final 2.override ⑤重载、重写&#xff08;覆盖&#xff09;与重定义&#xff08;隐藏&#xff09;的区别 3.什么是抽…