C语言面的向对象编程(OOP)

如果使用过C++、C#、Java语言,一定知道面向对象编程,这些语言对面向对象编程的支持是语言级别的。C语言在语言级别不支持面向对象,那可以实现面向对象吗?其实面向对象是一种思想,而不是一种语言,很多初学者很容易把这个概念搞错!

面向对象编程(OOP)有三大特性:封装、继承、多态,这里以实现矩形与圆形计算面积为例来说明,C++代码如下:

#include <iostream>
using namespace std;

typedef enum {
	Black,
	Red,
	White,
	Yellow,
}Color;

class CShape {
private:
	const char* m_name;
	Color color;

public:
	CShape(const char* name) {
		m_name = name;
		color = Black;
		cout << "CShape Ctor:" << name << endl;
	}
	virtual int GetArea() = 0;
	virtual ~CShape() {
		cout << "CShape Dtor" << endl;
	}
};

class CRect : public CShape {
private:
	int _w, _h;
public:
	CRect(int w, int h) : CShape("Rect"), _w(w), _h(h) {
		cout << "CRect Ctor" << endl;
	}

	virtual ~CRect() {
		cout << "CRect Dtor" << endl;
	}

	virtual int GetArea() {
		cout << "CRect GetArea" << endl;
		return _w * _h;
	}
};

class CCircle : public CShape {
private:
	int _r;
public:
	CCircle(int r): CShape("Circle"), _r(r) {
		cout << "CCircle Ctor" << endl;
	}

	virtual ~CCircle() {
		cout << "CCircle Dtor" << endl;
	}

	virtual int GetArea() {
		cout << "CCircle GetArea" << endl;
		return 3.14 * _r * _r;
	}
};

extern "C" void testCC() {
	cout << "CRect Size:" << sizeof(CRect) << endl;
	cout << "CCircle Size:" << sizeof(CCircle) << endl;
	CRect* r = new CRect(10, 20);
	int a1 = r->GetArea();
	CCircle* c = new CCircle(10);
	int a2 = c->GetArea();
	delete r;
	delete c;
}

先定义了一个形状类CShape,类中定义了一个构造函数CShape、一个虚析构函数~CShape和一个纯虚函数GetArea,矩形以及圆形都继承CShape并各自实现了自己的构造函数、析构函数和计算面积的GetArea函数,调用GetArea函数会调用到各自的实现。

分别看一下CShape、CRect以及CCircle的内存布局:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
可以看到CShape占24字节,CRect以及CCircle都占32字节。

C++很容易就在语言级别实现了面向对象编程,C语言在语言级别无法支持面向对象,就需要手动模拟。

先来看看Shape的定义,它与C++的CShape类等效:

typedef struct _Shape Shape;
typedef int (*ShapeGetArea)(Shape*);
typedef void (*ShapeDtor)(Shape*);

// 定义Shape的虚函数,以实现多态
typedef struct {
	ShapeGetArea GetArea; // 计算面积
	ShapeDtor Dtor; // 析构函数
} vtShape;

typedef enum {
	Black,
	Red,
	White,
	Yellow,
}Color;

struct _Shape {
	const vtShape* vtb; // 指向虚函数表
	// 公有变量
	char* name;
	Color color;
};

// Shape 的构造函数
void shapeCtor(Shape* p, const char* name) {
	printf("shapeCtor:%s\n", name);
	p->name = strdup(name);
}
// Shape 的析构函数
void shapeDtor(Shape* p) {
	printf("shapeDtor:%s\n", p->name);
	free(p->name);
}

为什么Shape中使用一个vtShape指针,而不是把计算面积的函数指针直接放在Shape中? vtShape指针指向的是虚函数表,在代码中矩形与圆形的虚函数表各自只有唯一的一份,这样不管构建了多少实例,每个实例都只有一个指向虚函数表的指针,节约了内存空间。 这样就与C++的类的实现完全一致,可以看一下内存布局:

在这里插入图片描述

再来看矩形与圆形的头文件定义:

typedef struct {
	Shape; // 继承Shape
}Rect;

typedef struct {
	Shape; // 继承Shape
}Circle;

Rect* newRect(int w, int h);
Circle* newCircle(int r);

矩形的实现:


typedef struct {
	Rect; // 这里继承头文件中公开的Rect定义
	// 下面定义私有变量
	int w, h;
}realRect;

// 计算矩形面积
static int RectArea(realRect* s) {
	printf("Rect GetArea\n");
	return s->w * s->h;
}

// 矩形的析构函数
static void RectRelease(realRect* s) {
	if (s) {
		printf("Rect Dtor:%s\n", s->name);
		shapeDtor((Shape*)s);
		free(s);
	}
}

// 矩形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtRect = {
	.GetArea = (ShapeGetArea)RectArea,
	.Dtor = (ShapeDtor)RectRelease,
};

Rect* newRect(int w, int h) {
	// 以realRect大小分配内存
	realRect* p = calloc(1, sizeof(realRect));
	if (NULL == p)
		return NULL;
	// 调用基类的构造函数
	shapeCtor((Shape*)p, "Rect");
	// 设置虚函数表
	p->vtb = &vtRect;
	p->h = h;
	p->w = w;
	printf("Rect Ctor\n");
	printf("Rect Size:%zd\n", sizeof(realRect));
	return (Rect*)p;
}

圆形的实现:

typedef struct {
	Circle; // 这里继承头文件中公开的Circle定义
	// 下面定义私有变量
	int r;
}realCircle;

// 计算圆形面积
static int CircleArea(realCircle* s) {
	printf("Circle GetArea\n");
	return (int)(3.14 * s->r * s->r);
}

// 圆形的析构函数
static void CircleRelease(realCircle* s) {
	if (s) {
		printf("Circle Dtor:%s\n", s->name);
		shapeDtor((Shape*)s);
		free(s);
	}
}

// 圆形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtCircle = {
	.GetArea = (ShapeGetArea)CircleArea,
	.Dtor = (ShapeDtor)CircleRelease,
};

Circle* newCircle(int r) {
	realCircle* p = calloc(1, sizeof(realCircle));
	if (NULL == p)
		return NULL;

	shapeCtor((Shape*)p, "Circle");
	p->vtb = &vtCircle;
	p->r = r;
	printf("Circle Ctor\n");
	printf("Circle Size:%zd\n", sizeof(realCircle));
	return (Circle*)p;
}

定义了好了后,就可以使用它们来计算面积了,示例代码:

Rect* r = newRect(10, 20);
Circle* c = newCircle(10);
r->color = Red;
c->color = Yellow;
const vtShape* rt = r->vtb;
const vtShape* ct = c->vtb;
int a1 = rt->GetArea((Shape*)r);
int a2 = ct->GetArea((Shape*)c);
rt->Dtor((Shape*)r);
ct->Dtor((Shape*)c);

完整代码:

shape.h

#pragma once

typedef struct _Shape Shape;
typedef int (*ShapeGetArea)(Shape*);
typedef void (*ShapeDtor)(Shape*);

// 定义Shape的虚函数,以实现多态
typedef struct {
	ShapeGetArea GetArea;
	ShapeDtor Dtor;
} vtShape;

typedef enum {
	Black,
	Red,
	White,
	Yellow,
}Color;

struct _Shape {
	const vtShape* vtb; // 指向虚函数表
	char* name;
	Color color;
};

// Shape 的构造函数
void shapeCtor(Shape* shape, const char* name);
// Shape 的析构函数
void shapeDtor(Shape* shape);

typedef struct {
	Shape; // 继承Shape
}Rect;

typedef struct {
	Shape; // 继承Shape
}Circle;

Rect* newRect(int w, int h);
Circle* newCircle(int r);

shape.c

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "shape.h"

void shapeCtor(Shape* p, const char* name) {
	printf("shapeCtor:%s\n", name);
	p->name = strdup(name);
}

void shapeDtor(Shape* p) {
	printf("shapeDtor:%s\n", p->name);
	free(p->name);
}


typedef struct {
	Rect; // 这里继承头文件中公开的Rect定义
	// 下面定义私有变量
	int w, h;
}realRect;

// 计算矩形面积
static int RectArea(realRect* s) {
	printf("Rect GetArea\n");
	return s->w * s->h;
}

// 矩形的析构函数
static void RectRelease(realRect* s) {
	if (s) {
		printf("Rect Dtor:%s\n", s->name);
		shapeDtor((Shape*)s);
		free(s);
	}
}

// 矩形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtRect = {
	.GetArea = (ShapeGetArea)RectArea,
	.Dtor = (ShapeDtor)RectRelease,
};

Rect* newRect(int w, int h) {
	// 以realRect大小分配内存
	realRect* p = calloc(1, sizeof(realRect));
	if (NULL == p)
		return NULL;
	// 调用基类的构造函数
	shapeCtor((Shape*)p, "Rect");
	// 设置虚函数表
	p->vtb = &vtRect;
	p->h = h;
	p->w = w;
	printf("Rect Ctor\n");
	printf("Rect Size:%zd\n", sizeof(realRect));
	return (Rect*)p;
}


typedef struct {
	Circle; // 这里继承头文件中公开的Circle定义
	// 下面定义私有变量
	int r;
}realCircle;

// 计算圆形面积
static int CircleArea(realCircle* s) {
	printf("Circle GetArea\n");
	return (int)(3.14 * s->r * s->r);
}

// 圆形的析构函数
static void CircleRelease(realCircle* s) {
	if (s) {
		printf("Circle Dtor:%s\n", s->name);
		shapeDtor((Shape*)s);
		free(s);
	}
}

// 圆形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtCircle = {
	.GetArea = (ShapeGetArea)CircleArea,
	.Dtor = (ShapeDtor)CircleRelease,
};

Circle* newCircle(int r) {
	realCircle* p = calloc(1, sizeof(realCircle));
	if (NULL == p)
		return NULL;

	shapeCtor((Shape*)p, "Circle");
	p->vtb = &vtCircle;
	p->r = r;
	printf("Circle Ctor\n");
	printf("Circle Size:%zd\n", sizeof(realCircle));
	return (Circle*)p;
}

shape.cc

#include <iostream>
using namespace std;

typedef enum {
	Black,
	Red,
	White,
	Yellow,
}Color;

class CShape {
private:
	const char* m_name;
	Color color;

public:
	CShape(const char* name) {
		m_name = name;
		color = Black;
		cout << "CShape Ctor:" << name << endl;
	}
	virtual int GetArea() = 0;
	virtual ~CShape() {
		cout << "CShape Dtor" << endl;
	}
};

class CRect : public CShape {
private:
	int _w, _h;
public:
	CRect(int w, int h) : CShape("Rect"), _w(w), _h(h) {
		cout << "CRect Ctor" << endl;
	}

	virtual ~CRect() {
		cout << "CRect Dtor" << endl;
	}

	virtual int GetArea() {
		cout << "CRect GetArea" << endl;
		return _w * _h;
	}
};

class CCircle : public CShape {
private:
	int _r;
public:
	CCircle(int r): CShape("Circle"), _r(r) {
		cout << "CCircle Ctor" << endl;
	}

	virtual ~CCircle() {
		cout << "CCircle Dtor" << endl;
	}

	virtual int GetArea() {
		cout << "CCircle GetArea" << endl;
		return 3.14 * _r * _r;
	}
};

extern "C" void testCC() {
	cout << "CRect Size:" << sizeof(CRect) << endl;
	cout << "CCircle Size:" << sizeof(CCircle) << endl;
	CRect* r = new CRect(10, 20);
	int a1 = r->GetArea();
	CCircle* c = new CCircle(10);
	int a2 = c->GetArea();
	delete r;
	delete c;
}

main.c

#include "shape.h"

void testCC();

int main(){
	testCC();
	printf("\n\n");
	Rect* r = newRect(10, 20);
	Circle* c = newCircle(10);
	r->color = Red;
	c->color = Yellow;
	const vtShape* rt = r->vtb;
	const vtShape* ct = c->vtb;
	int a1 = rt->GetArea((Shape*)r);
	int a2 = ct->GetArea((Shape*)c);
	rt->Dtor((Shape*)r);
	ct->Dtor((Shape*)c);
	return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.25.0)
project(cobj VERSION 0.1.0)

if(CMAKE_C_COMPILER_ID MATCHES "Clang")
add_compile_options(
	-fms-extensions
	-Wno-microsoft-anon-tag
)
endif()

add_executable(${PROJECT_NAME} ${SRC} shape.c shape.cc main.c)

运行结果:

在这里插入图片描述

这就是C实现面向对象的原理,如果有兴趣的读者可以看看glib中的gobject,不过要看懂它的代码,掌握原理是非常重要的!

如对你有帮助,欢迎点赞收藏!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ 基础思维导图(一)

目录 1、C基础 IO流 namespace 引用、const inline、函数参数 重载 2、类和对象 类举例 3、 内存管理 new/delete 对象内存分布 内存泄漏 4、继承 继承权限 继承中的构造与析构 菱形继承 1、C基础 IO流 #include <iostream> #include <iomanip> //…

聊聊前端框架中的process.env,env的来源及优先级(next.js、vue-cli、vite)

在平时开发中&#xff0c;常常使用vue、react相关脚手架创建项目&#xff0c;在项目根目录可以创建.env、.env.[mode]&#xff08;mode为development、production、test)、.env.local等文件&#xff0c;然后在项目中就可以通过process.env来访问相关的环境变量了。 下面针对如下…

基于云架构Web端的工业MES系统:赋能制造业数字化变革

基于云架构Web端的工业MES系统:赋能制造业数字化变革 在当今数字化浪潮席卷全球的背景下,制造业作为国家经济发展的重要支柱产业,正面临着前所未有的机遇与挑战。市场需求的快速变化、客户个性化定制要求的日益提高以及全球竞争的愈发激烈,都促使制造企业必须寻求更加高效、智…

LeetCode算法题——螺旋矩阵ll

题目描述 给你一个正整数n&#xff0c;生成一个包含1到n2所有元素&#xff0c;且元素按顺时针顺序螺旋排列的n x n正方形矩阵matrix 。 示例 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]题解 思路&#xff1a; 将整个过程分解为逐圈填充的过程&#xf…

MySQL 01 02 章——数据库概述与MySQL安装篇

一、数据库概述 &#xff08;1&#xff09;为什么要使用数据库 数据库可以实现持久化&#xff0c;什么是持久化&#xff1a;数据持久化意味着将内存中的数据保存到硬盘上加以“固化”持久化的主要作用是&#xff1a;将内存中的数据存储在关系型数据库中&#xff0c;当然也可以…

GPU 进阶笔记(四):NVIDIA GH200 芯片、服务器及集群组网

大家读完觉得有意义记得关注和点赞&#xff01;&#xff01;&#xff01; 1 传统原厂 GPU 服务器&#xff1a;Intel/AMD x86 CPU NVIDIA GPU2 新一代原厂 GPU 服务器&#xff1a;NVIDIA CPU NVIDIA GPU 2.1 CPU 芯片&#xff1a;Grace (ARM)2.2 GPU 芯片&#xff1a;Hopper/B…

vite6+vue3+ts+prettier+eslint9配置前端项目(后台管理系统、移动端H5项目通用配置)

很多小伙伴苦于无法搭建一个规范的前端项目&#xff0c;导致后续开发不规范&#xff0c;今天给大家带来一个基于Vite6TypeScriptVue3ESlint9Prettier的搭建教程。 目录 一、基础配置1、初始化项目2、代码质量风格的统一2.1、配置prettier2.2、配置eslint2.3、配置typescript 3、…

ESLint+Prettier的配置

ESLintPrettier的配置 安装插件 ​​​​​​ 在settings.json中写下配置 {// tab自动转换标签"emmet.triggerExpansionOnTab": true,"workbench.colorTheme": "Default Dark","editor.tabSize": 2,"editor.fontSize": …

Cyber Security 101-Web Hacking-JavaScript Essentials(JavaScript 基础)

任务1&#xff1a;介绍 JavaScript &#xff08;JS&#xff09; 是一种流行的脚本语言&#xff0c;它允许 Web 开发人员向包含 HTML 和 CSS&#xff08;样式&#xff09;的网站添加交互式功能。创建 HTML 元素后&#xff0c;您可以通过 JS 添加交互性&#xff0c;例如验证、on…

《机器学习》从入门到实战——逻辑回归

目录 一、简介 二、逻辑回归的原理 1、线性回归部分 2、逻辑函数&#xff08;Sigmoid函数&#xff09; 3、分类决策 4、转换为概率的形式使用似然函数求解 5、对数似然函数 ​编辑 6、转换为梯度下降任务 三、逻辑回归拓展知识 1、数据标准化 &#xff08;1&#xf…

JDK8源码分析Jdk动态代理底层原理

本文侧重分析JDK8中jdk动态代理的源码&#xff0c;若是想看JDK17源码分析可以看我的这一篇文章 JDK17源码分析Jdk动态代理底层原理-CSDN博客 两者之间有着略微的差别&#xff0c;JDK17在JDK8上改进了不少 目录 源码分析 过程 生成的代理类大致结构 本文侧重分析JDK8中jdk…

ZYNQ初识6(zynq_7010)clock时钟IP核

基于板子的PL端无时钟晶振&#xff0c;需要从PS端借用clock1&#xff08;50M&#xff09;晶振 接下去是自定义clock的IP核封装&#xff0c;为后续的simulation可以正常仿真波形&#xff0c;需要注意顶层文件的设置&#xff0c;需要将自定义的IP核对应的.v文件设置为顶层文件&a…

深度学习模型格式转换:pytorch2onnx(包含自定义操作符)

将PyTorch模型转换为ONNX&#xff08;Open Neural Network Exchange&#xff09;格式是实现模型跨平台部署和优化推理性能的一种常见方法。PyTorch 提供了多种方式来完成这一转换&#xff0c;以下是几种主要的方法&#xff1a; 一、静态模型转换 使用 torch.onnx.export() t…

GPU 进阶笔记(一):高性能 GPU 服务器硬件拓扑与集群组网

记录一些平时接触到的 GPU 知识。由于是笔记而非教程&#xff0c;因此内容不求连贯&#xff0c;有基础的同学可作查漏补缺之用 1 术语与基础 1.1 PCIe 交换芯片1.2 NVLink 定义演进&#xff1a;1/2/3/4 代监控1.3 NVSwitch1.4 NVLink Switch1.5 HBM (High Bandwidth Memory) 由…

在Unity中用Ab包加载资源(简单好抄)

第一步创建一个Editor文件夹 第二步编写BuildAb&#xff08;这个脚本一点要放在Editor中因为这是一个编辑器脚本&#xff0c;放在其他地方可能会报错&#xff09; using System.IO; using UnityEditor; using UnityEngine;public class BuildAb : MonoBehaviour {// 在Unity编…

【贪心算法】贪心算法七

贪心算法七 1.整数替换2.俄罗斯套娃信封问题3.可被三整除的最大和4.距离相等的条形码5.重构字符串 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f…

(五)人工智能进阶:基础概念解释

前面我们介绍了人工智能是如何成为一个强大函数。接下来&#xff0c;搞清损失函数、优化方法和正则化等核心概念&#xff0c;才能真正驾驭它&#xff01; 1. 什么是网络模型&#xff1f; 网络模型就像是一个精密的流水线工厂&#xff0c;由多个车间&#xff08;层&#xff0…

SpringMVC(二)原理

目录 一、配置Maven&#xff08;为了提升速度&#xff09; 二、流程&&原理 SpringMVC中心控制器 完整流程&#xff1a; 一、配置Maven&#xff08;为了提升速度&#xff09; 在SpringMVC&#xff08;一&#xff09;配置-CSDN博客的配置中&#xff0c;导入Maven会非…

2、redis的持久化

redis的持久化 在redist当中&#xff0c;高可用的技术包括持久化&#xff0c;主从复制&#xff0c;哨兵模式&#xff0c;集群。 持久化是最简单的高可用的方法&#xff0c;作用就是备份数据。即将数据保存到硬盘&#xff0c;防止进程退出导致数据丢失。 redis持久化方式&…

【算法】模拟退火算法学习记录

写这篇博客的原因是博主本人在看某篇文章的时候&#xff0c;发现自己只是知道SGD这个东西&#xff0c;但是到底是个啥不清楚&#xff0c;所以百度了一下&#xff0c;然后在通过博客学习的时候看到了退火两个字&#xff0c;想到了本科做数模比赛的时候涉猎过&#xff0c;就上bil…