C 语言也可以是面向对象的

1、函数指针成员

指针也是一种变量类型,也可以当作结构体的成员,函数指针也不例外。比如我们定义 logger 结构体用于打印 log,其成员为一个函数指针,用于打印日志。

/// log.h
#ifndef _LOG_H_
#define _LOG_H_

typedef struct logger logger;
typedef void (*log_func)(logger* this, const char* msg);

typedef struct logger {
  log_func log;
} logger;

void common_log_init(logger** args);
void common_log_uninit(logger* args);

#endif  // _LOG_H_
/// log.c
#include "log.h"

#include <stdio.h>
#include <stdlib.h>

static void common_log(logger* this, const char* msg) {
  (void)this;
  printf("%s\n", msg);
}

void common_log_init(logger** args) {
  if (!args) {
    perror("args is nullptr");
    return;
  }

  *args = (logger*)malloc(sizeof(logger));
  if (!*args) {
    perror("out of memory");
    return;
  }
  (*args)->log = common_log;
}

void common_log_uninit(logger* args) { free(args); }
/// main.c
#include "log.h"

int main() {
  logger* log;
  common_log_init(&log);
  log->log(log, "test msg");
  common_log_uninit(log);
  return 0;
}

经过编译后执行,

wuyong@Thinkpad:~/debug$ gcc log.c main.c -o log
wuyong@Thinkpad:~/debug$ ./log 
test msg

2、继承与多态

C 语言结构体也可以继承,用于复用代码。比如我们希望在 logger 能够记录模块信息,则可以继承 logger 结构体,复用 log 函数指针,然后添加新的成员。

/// log.h
...

void mylog_init(logger** args, const char* module);
void mylog_uninit(logger* args);

...
/// mylog.c
#include <stdio.h>
#include <stdlib.h>

#include "log.h"

typedef struct mylog {
  logger base;
#define log base.log
  const char* module;
} mylog;

static void my_log(logger* this, const char* msg) {
  mylog* _mylog = (mylog*)(this);
  printf("[%s] %s\n", _mylog->module, msg);
}

void mylog_init(logger** args, const char* module) {
  if (!args) {
    perror("args is nullptr");
    return;
  }

  mylog* _mylog = (mylog*)malloc(sizeof(mylog));
  if (!_mylog) {
    perror("out of memory");
    return;
  }

  _mylog->log = my_log;
  _mylog->module = module;
  *args = (logger*)_mylog;
}

void mylog_uninit(logger* args) { free((mylog*)args); }

/// main.c
#include "log.h"

int main() {
  logger* log;
  mylog_init(&log, "main");
  log->log(log, "test msg");
  mylog_uninit(log);
  return 0;
}

经过编译后执行

wuyong@Thinkpad:~/debug$ gcc main.c mylog.c -o log
wuyong@Thinkpad:~/debug$ ./log 
[main] test msg

我们可以将所有函数指针放到 vtable 中

/// log.h
...
typedef struct vtable {
  log_func log_info;
  log_func log_warn;
  log_func log_error;
  log_func log_fatal;
} vtable;

typedef struct logger {
  vtable log;
} logger;
...

3、dlopen/dlsym 实现多态

Linux 提供了如下 API 来动态装载库

#include <dlfcn.h>

void *dlopen(const char *filename, int flag);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);

dlopen 打开一个动态链接库,并返回动态链接库的句柄。

  • filename 是 so 库的加载路径,如果 filename 包含了路径符号 /,则 filename 被解释为相对或者绝对路径名,否则按照正常 so 搜索路径查查。
  • flag 必须是下列两个值中的一个:RTLD_LAZY 和 RTLD_NOW。RTLD_LAZY 表示延迟绑定,只有在需要的时候解析符号(symbol);RTLD_NOW 表示在 dlopen 返回前,将 so 库中所有符号。

dlsym 根据 so 库的操作句柄和符号,返回符号对应的地址(不仅可以获取函数地址,也可以获取变量地址)。

使用 dlopen 和 dlsym 可以在运行时加载 so,使用不用的实现。

/// log.h

#ifndef _LOG_H_
#define _LOG_H_

typedef struct logger logger;
typedef struct op_callback op_callback;
typedef void (*log_func)(logger* this, const char* msg);
typedef int (*init_func)(logger** this, void* arg);
typedef int (*uninit_func)(logger* this);
typedef int (*entry_func)(op_callback* callback);

typedef struct logger {
  log_func log_info;
  log_func log_warn;
  log_func log_error;
  log_func log_fatal;
} logger;

typedef struct op_callback {
  init_func init;
  uninit_func uninit;
} op_callback;

int log_entry(op_callback* callback);

#endif  // _LOG_H_
/// common_log.c

#include <stdio.h>
#include <stdlib.h>

#include "log.h"

static void log_info(logger* this, const char* msg) {
  (void)this;
  printf("[info] %s\n", msg);
}

static void log_warn(logger* this, const char* msg) {
  (void)this;
  printf("[warn] %s\n", msg);
}

static void log_error(logger* this, const char* msg) {
  (void)this;
  printf("[error] %s\n", msg);
}

static void log_fatal(logger* this, const char* msg) {
  (void)this;
  printf("[fatal] %s\n", msg);
}

static int logger_init(logger** this, void* arg) {
  (void)arg;
  if (!this) {
    perror("args is nullptr");
    return -1;
  }

  *this = (logger*)malloc(sizeof(logger));
  if (!*this) {
    perror("out of memory");
    return -1;
  }
  (*this)->log_info = log_info;
  (*this)->log_warn = log_warn;
  (*this)->log_error = log_error;
  (*this)->log_fatal = log_fatal;
  return 0;
}

static int logger_uninit(logger* args) {
  free(args);
  return 0;
}

int log_entry(op_callback* callback) {
  if (!callback) {
    perror("callback is nullptr");
    return -1;
  }

  callback->init = logger_init;
  callback->uninit = logger_uninit;
  return 0;
}
/// mylog.c

#include <stdio.h>
#include <stdlib.h>

#include "log.h"

typedef struct mylog {
  logger base;
#define log_info base.log_info
#define log_warn base.log_warn
#define log_error base.log_error
#define log_fatal base.log_fatal
  const char* module;
} mylog;

static void log_info2(logger* this, const char* msg) {
  mylog* mylog_ = (mylog*)this;
  printf("[%s] [info] %s\n", mylog_->module, msg);
}

static void log_warn2(logger* this, const char* msg) {
  mylog* mylog_ = (mylog*)this;
  printf("[%s] [warn] %s\n", mylog_->module, msg);
}

static void log_error2(logger* this, const char* msg) {
  mylog* mylog_ = (mylog*)this;
  printf("[%s] [error] %s\n", mylog_->module, msg);
}

static void log_fatal2(logger* this, const char* msg) {
  mylog* mylog_ = (mylog*)this;
  printf("[%s] [fatal] %s\n", mylog_->module, msg);
}

int mylog_init(logger** this, void* arg) {
  if (!this) {
    perror("args is nullptr");
    return -1;
  }

  mylog* _mylog = (mylog*)malloc(sizeof(mylog));
  if (!_mylog) {
    perror("out of memory");
    return -1;
  }

  _mylog->log_info = log_info2;
  _mylog->log_warn = log_warn2;
  _mylog->log_error = log_error2;
  _mylog->log_fatal = log_fatal2;
  _mylog->module = (const char*)arg;
  *this = (logger*)_mylog;
  return 0;
}

static int mylog_uninit(logger* args) {
  free(args);
  return 0;
}

int log_entry(op_callback* callback) {
  if (!callback) {
    perror("callback is nullptr");
    return -1;
  }

  callback->init = mylog_init;
  callback->uninit = mylog_uninit;
  return 0;
}
/// main.c

#include <dlfcn.h>
#include <stdio.h>
#include <string.h>

#include "log.h"

static const char* mylog = "./libmylog.so";
static const char* commonlog = "./libcommonlog.so";

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    printf("<%s> libmylog/libcommonlog\n", argv[0]);
    return -1;
  }

  const char* lib = NULL;
  void* arg = NULL;
  if (!strcmp(argv[1], "libmylog")) {
    lib = mylog;
    arg = "main";
  } else if (!strcmp(argv[1], "libcommonlog")) {
    lib = commonlog;
    arg = NULL;
  } else {
    printf("not supported %s\n", argv[1]);
    return -1;
  }

  void* handle = dlopen(lib, RTLD_LAZY);
  if (!handle) {
    printf("failed to load %s, %s\n", lib, dlerror());
    return -1;
  }

  entry_func entry = dlsym(handle, "log_entry");
  if (!entry) {
    printf("failed to find log_entry, %s\n", dlerror());
    return -1;
  }

  op_callback callback;
  if (entry(&callback)) {
    printf("faield to init callbakc\n");
    return -1;
  }
  logger* log;
  callback.init(&log, arg);
  log->log_info(log, "test msg");
  callback.uninit(log);
  return 0;
}

编译后执行

wuyong@Thinkpad:~/debug$ gcc common_log.c -fPIC -shared -o libcommonlog.so
wuyong@Thinkpad:~/debug$ gcc mylog.c -fPIC -shared -o libmylog.so
wuyong@Thinkpad:~/debug$ gcc log.h main.c -o log -ldl
wuyong@Thinkpad:~/debug$ ./log libcommonlog
[info] test msg
wuyong@Thinkpad:~/debug$ ./log libmylog
[main] [info] test msg

欢迎关注微信公众号《源知源为》,更多技术分享。

#面向对象#
C/C++基础 文章被收录于专栏

C/C++ 语言基础

全部评论

相关推荐

老粉都知道小猪猪我很久没更新了,因为秋招非常非常不顺利,emo了三个月了,接下来说一下我的情况吧本人是双非本&nbsp;专业是完全不着计算机边的非科班,比较有优势的是有两段大厂实习,美团和字节。秋招面了50+场泡池子泡死的:滴滴&nbsp;快手&nbsp;去哪儿&nbsp;小鹏汽车&nbsp;不知名的一两个小厂其中字节13场&nbsp;两次3面挂&nbsp;两次2面挂&nbsp;一次一面挂其中有2场面试题没写出来,其他的都是全a,但该挂还是挂,第三次三面才面进去字节,秋招加暑期总共面了22次字节,在字节的面评可以出成书了快手面了8场,2次实习的,通过了但没去,一次2面挂&nbsp;最后一次到录用评估&nbsp;至今无消息滴滴三面完&nbsp;没几天挂了&nbsp;所有技术面找不出2个问题是我回答不上来的,三面还来说我去过字节,应该不会考虑滴滴吧,直接给我干傻了去哪儿一天速通&nbsp;至今无消息小鹏汽车hr&nbsp;至今无消息美团2面挂&nbsp;然后不捞我了,三个志愿全部结束,估计被卡学历了虾皮二面挂&nbsp;这个是我菜,面试官太牛逼了拼多多二面挂&nbsp;3道题也全写了&nbsp;也没问题是回答不出来的&nbsp;泡一周后挂腾讯面了5次&nbsp;一次2面挂&nbsp;三次一面挂,我宣布腾讯是世界上最难进的互联网公司然后还有一些零零散散的中小厂,但是数量比较少,约面大多数都是大厂。整体的战况非常惨烈,面试机会少,就算面过了也需要和各路神仙横向对比,很多次我都是那个被比下去的人,不过这也正常,毕竟谁会放着一个985的硕士不招,反而去招一个双非读化学的小子感觉现在互联网对学历的要求越来越高了,不仅仅要985还要硕士了,双非几乎没啥生存空间了,我感觉未来几年双非想要进大厂开发的难度应该直线上升了,唯一的打法还是从大二刷实习,然后苟个转正,不然要是去秋招大概率是炮灰。而且就我面字节这么多次,已经开始问很多ai的东西了,你一破本科生要是没实习没科研懂什么ai啊,纯纯白给了
不知名牛友_:爸爸
秋招你被哪家公司挂了?
点赞 评论 收藏
分享
2025-12-31 14:19
门头沟学院 产品经理
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务