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

news2024/11/28 2:47:17

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, abstractmethod

class MyAbstractClass(ABC):
    @abstractmethod
    def my_abstract_method(self):
        pass

class MyDerivedClass(MyAbstractClass):
    pass

obj = 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.coloradmin.cn/o/968427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vivado 添加FPGA开发板的Boards file的添加

1 digilent board file 下载地址 下载地址 &#xff1a; https://github.com/Digilent/vivado-boards 2 下载后 3 添加文件到 vivado 安装路径 把文件复制到 Vivado\2019.1\data\boards\board_files4 创建工程查看是否安装成功

微信小程序修改vant组件样式

1 背景 在使用vant组件开发微信小程序的时候&#xff0c;想更改vant组件内部样式&#xff0c;达到自己想要的目的&#xff08;van-grid组件改成宫格背景色为透明&#xff0c;默认为白色&#xff09;&#xff0c;官网没有示例&#xff0c;通过以下几步修改成功。 2 步骤 2.1 …

zabbix模版和监控项、触发器

zabbix添加监控主机的流程 自定义监控项实现流程 被控端添加监控项 /etc/zabbix_agent2.d/xxx.conf UserParameterkey , 命令 ; restart服务器端测试 zabbix_get -s 主机 -k keyweb 创建模板web 在模板添加监控项web 模板关联至主机观察数据和图形 创建监控项名称 获取监控项…

Latex表格内换行

遇到表格内容太长&#xff0c;需要换行。 宏包&#xff1a; \usepackage{makecell}使用方法 \begin{center}\tabcaption{表格}\label{tab:2}\renewcommand\tabcolsep{7pt}%调整表格长度\begin{tabular} {cccccccccc}\toprule参数&参数&\makecell{最大\\数值} \\$a$&a…

登录校验的相关知识点

登录校验的相关知识点 【1】会话技术1)会话:2)会话跟踪:3)常见的几种会话跟踪&#xff1a; 【2】JWT令牌1)定义解释2&#xff09;测试生成Jwt令牌并解析3&#xff09;注意事项 【3】过滤器Filter1)过滤器工作原理如下&#xff1a;2)简单使用示例3)自定义拦截路径4)疑问5)过滤器…

06-限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景?【Java面试题总结】

限流策略有哪些&#xff0c;滑动窗口算法和令牌桶区别&#xff0c;使用场景&#xff1f; 常见的限流算法有固定窗口、滑动窗口、漏桶、令牌桶等。 6.1 固定窗口 概念&#xff1a;固定窗口&#xff08;又称计算器限流&#xff09;&#xff0c;对一段固定时间窗口内的请求进行…

1780_添加鼠标右键空白打开命令窗功能

全部学习汇总&#xff1a; GitHub - GreyZhang/windows_skills: some skills when using windows system. 经常执行各种脚本&#xff0c;常常需要切换到命令窗口中输入相关的命令。从开始位置打开cmd然后切换目录是个很糟糕的选择&#xff0c;费时费力。其实Windows 7以及Windo…

鸿蒙学习笔记之资源管理器(十一)

本次要点&#xff1a; 1.什么是资源管理器 2.资源管理器的应用 1.什么是资源管理器 资源管理器是系统提供的资源管理工具&#xff0c;我们可以用它查看本台电脑的所有资源&#xff0c;特别是它提供的树形的文件系统结构&#xff0c;使我们能更清楚、更直观地认识电脑的文件和…

学会Mybatis框架:让你的开发事半功倍【五.Mybatis关系映射】

目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 导语 一、一对一的关系映射 1.表结构 2.resultMap配置 3.测试关系映射 二、一对多的关系映射 1.表结构 2.resultMap配置 3.测试关系映射 三、多对多的关系映射 1.表结构…

一文讲通嵌入式现状

近年来&#xff0c;随着计算机技术和集成电路技术的迅速发展&#xff0c;嵌入式技术在通讯、网络、工控、医疗、电子等领域日益普及&#xff0c;并发挥着越来越重要的作用。嵌入式系统已成为当前最为热门和前景广阔的IT应用领域之一。 随着信息化、智能化、网络化的不断推进&am…

基于Citespace、vosviewer、R语言的文献计量学可视化分析技术及全流程文献可视化SCI论文高效写作

文献计量学是指用数学和统计学的方法&#xff0c;定量地分析一切知识载体的交叉科学。它是集数学、统计学、文献学为一体&#xff0c;注重量化的综合性知识体系。特别是&#xff0c;信息可视化技术手段和方法的运用&#xff0c;可直观的展示主题的研究发展历程、研究现状、研究…

ARM DIY(七)麦克风调试

前言 上篇文章介绍了扬声器调试&#xff0c;今天介绍下麦克风调试。 硬件 焊接&#xff1a;咪头、电阻、电容 驱动 && 应用程序 音频调试时已完成&#xff0c;参考上篇文章 测试 使能 mic1 # ./amixer -c 0 cset numid12 2 numid12,ifaceMIXER,nameMic1 Captu…

【杂言】写在研究生开学季

这两天搬进了深研院的宿舍&#xff0c;比中南的本科宿舍好很多&#xff0c;所以个人还算满意。受台风 “苏拉” 的影响&#xff0c;原本的迎新计划全部打乱&#xff0c;导致我现在都还没报道。刚开学的半个月将被各类讲座、体检以及入学教育等活动占满&#xff0c;之后又是比较…

【数据结构篇】线性表1 --- 顺序表、链表 (万字详解!!)

前言&#xff1a;这篇博客我们重点讲 线性表中的顺序表、链表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列... 线性表在逻辑上是…

【learnopengl】Assimp构建与编译

文章目录 【learnopengl】Assimp构建与编译1 前言2 Assimp构建与编译2.1 下载源码2.2 CMake构建2.3 VS2022编译 3 在VS中配置Assimp库4 验证 【learnopengl】Assimp构建与编译 1 前言 最近在跟着LearnOpenGL这个网站学习OpenGL&#xff0c;这篇文章详细记录一下教程中关于Ass…

vue的第3篇 第一个vue程序

一 vue的mvvm实践者 1.1 介绍 Model&#xff1a;模型层&#xff0c; 在这里表示JavaScript对象 View&#xff1a;视图层&#xff0c; 在这里表示DOM(HTML操作的元素) ViewModel&#xff1a;连接视图和数据的中间件&#xff0c; Vue.js就是MVVM中的View Model层的实现者 在M…

SpringBoot复习:(60)文件上传的自动配置类MultipartAutoConfiguration

可以看到&#xff0c;定义了一个类型为StandartServletMultipartResolver的bean 用来进行文件上传&#xff0c;定义了一个类型为MultipartConfigElement的bean用来进行上传相关的配置&#xff0c;其中使用了MultipartProperties中的属性&#xff0c;这个类的定义如下&#xff1…

【Day-27满就是快】代码随想录-二叉树-二叉树的最大深度

给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 ———————————————————————————————————— 1. 递归法 可以使用前序和后序遍历。前序就是…

Linux Day12 ---进程间通信

一、管道 1.1 有名管道 有名管道可以在任意两个进程之间通信 1.1.1 有名管道的创建&#xff1a; 命令创建&#xff1a; mkfifo 管道名 系统调用创建 1.1.2 与普通文件区别 打开管道文件&#xff0c;在内存分配一块空间&#xff0c;往管道文件里面写数据&#xff0c;实际是…

产品思维用户思维

用户思维是一种关注用户需求、体验和价值的思维方式,将用户放在产品设计、开发和提供服务的核心位置。它强调了理解用户在不同场景下的需求,提供与之相匹配的解决方案,从而帮助用户实现他们的目标。 描述一个用户时,可以从不同角度来考虑: 按人口属性描述用户: 个人属性…