面向对象的 C 语言

一、前言——对象与过程 碎碎念:这篇文章里提到的语言是真的多:c、c++、c#、java、python、golang c 语言怎么能面向对象呢?c 语言的设计当然并非为面向对象做出考虑,但是其拥有的语法却足以使我们写出具有面向对象味道的代码了。因为无论是面向过程或面向对象,其背后的本质思想都是相同的,那就是这样一个著名的公式: $$ 程序 = 数据结构 + 算法 $$ 面向过程无非是强调其算法的一面;面向对象无非是强调其数据结构的一面。当我们使用面向过程的思想编写代码时,我们所想的是数据是函数中的参数和变量,数据在过程中流动和变化。而在面向对象中情况则反了过来:方法成为了类的成员,被类型所划分,并从属于一定数据的集合。 了解了这一点之后,再看程序语言从面向过程到面向对象的发展过程也能有新的认识。这一发展背后实际上是程序的关注点由机器向人的转变。在面向过程的时代,人们所关注的是如何操纵数据。那时的机器还没有蒙在名为抽象的面纱之下,呈现在操作者面前的依旧是赤裸裸的整个内存空间,数据与数据之间没有清晰的边界,是操作者自己组织起整个系统,为各个空间划分边界,定下名称。而在这一构筑起来的系统之上,数据本身就没有那么重要了,因为更底层已经为其提供了随时取用的接口。这时,管理流程成了另一个关键问题。因为在底层的支持下构建起来的日益庞大的应用,其自身的结构却往往不能支持其质量。于是人们以数据为界,将面条一般的数据流切割成彼此独立却又相互关联的部分。这样对象才得以诞生。 二、c语言的面向对象何以可能 说回 c 语言,当其以结构体的方式组织起数据的时候,就已经有了对象的雏形了。如果我们将函数视为所属于其第一个参数类型的方法,那么对象的方法也可以表示。但是只有这两点并非真正的面向对象,因为面向对象的三大特征——封装、继承和多态,其中的后两者还未实现。 让我们来详细分析一下继承和多态到底在表明什么。继承是两个类型间的关系,类型 A 继承了类型 B,则类型 A 具有类型 B 所具有的一切属性和方法,这意味着对于 A 和 B 这两个不同的类型,都具有所属于 B 的部分。从这一点来说,两者是相同的(也因为这种相同,子类才能不加转换的赋给父类变量)。而多态(在这里指方法的重写而不包括重载)则指子类 A 对从 B 所继承的方法的重写,使得虽是相同方法,其表现却能有所不同。 明确了继承和多态,接下来我们从数据的角度分析 c 语言为何可以面向对象。所谓的一个对象,即在地址空间中的一段连续区域。此时继承中所谓的相同,即对两个不同类型的对象,其内存空间中相同位置所表达的含义相同。如果对于 B 类型来说,偏移 4 个字节之后的 4 个字节表示一个 int 字段,那么对于继承 B 类型的 A 类型来说,偏移 4 个字节之后的 4 个字节应同样表示一个 int 字段。类似的,多态中所谓的不同,可以表达为类型中相同的方法名对应的具体过程不同。由于过程在机器码中表现为地址,那么本质上来说,多态的这种不同不过是指相同字段中的值不同罢了。 此时 c 语言中实现继承和多态的方法呼之欲出,那就是使用指针。地址指示了变量所处空间的起始位置,却不表明按何种方式解释这块区域,而指针完成了这份工作。对于所有赋给指针的地址值,其都如实翻译其中的数据,那么如果想要子类与父类按照同样的方式进行翻译,就需要子类在组织其结构时保持和父类一致。而对于多态来说,事情则更简单了,函数指针同样是指针,只要使其指向不同的函数即可。 这样也可以理解为什么 c++ 中只能使用指针实现多态(引用本质还是指针)。 Child c; Father *f = &c; // 正确 Father f2 = c; // 错误 而 c++ 中使用 new 关键字申请内存这一点也被 java、c# 等面向对象语言学了过去。java、c# 等中的类变量,实际上也和指针或引用没有区别...

十月 1, 2023 · 5 分钟 · 962 字 · Wokron

CMake 实用语法教程

一、前言 最近一段时间在用 c++ 写一个项目,因此学了学 cmake。说实话,cmake 奇怪的语法在一开始实在容易让人望而生畏。但是上手使用的话就会发现平常会用到的不过是其中的一小部分,并且通常有规律可循。掌握这一部分的内容,大概率就可以组织起一个规模较大的项目了。因此本文也就旨在讲述 cmake 的这一部分的内容。 当然,阅读本教程之前需要了解代码编译、链接的相关知识。关于编译相关的命令,可见我的文章系统编程之命令行编译。 本文的所有代码保存在仓库 practical-cmake 中,欢迎 star :)。 cmake 下载方式如下(apt) sudo apt-get install build-essential sudo apt-get install cmake 二、第一步 说到第一个程序,那当然要请出经典的 hello, world 了。 #include <stdio.h> int main() { printf("hello, world\n"); return 0; } 在本文中我会首先给出使用 gcc 编译的命令,之后再使用 cmake 做同样的事情。那么对于第一步,我们的 gcc 命令如下 gcc main.cpp -o main 当然很简单,而对于 cmake 也类似。要使用 cmake,我们需要添加一个配置文件 CMakeLists.txt,其中包含要执行的操作。本小节中,CMakeLists.txt 的内容是 cmake_minimum_required(VERSION 3.10) project(main) add_executable(main main.cpp) 其中第一条指定了 cmake 版本要求,第二条指定了当前项目名,而第三条 add_executable 则实现了和 gcc 命令相同的操作:指定源文件 main.cpp 和输出文件名 main,生成一个可执行文件。...

九月 3, 2023 · 5 分钟 · 905 字 · Wokron

Pybind11 实现 Python 与 C++ 混合编程

一、前言 最近在尝试写一个简单的游戏引擎,我决定用 python 作为脚本,所以了解了一些混合编程的知识。 (1)python api 从原理来说,根据文档所述,python 提供了 Python.h 头文件,能够将 c 或 c++ 代码编译成可供 python 引入的动态链接库。该库中定义的可供 python 调用的函数中所有的入参都是名为 PyObject 结构体的指针。在代码中可以通过一系列函数对 PyObject 进行操作。 举一个简单的例子,我们希望 python 调用一个由 c 编写的简单的加法函数 int add(int a, int b) { return a + b; } 我们期望在 python 中这样调用 # test_mymodule.py from mymodule import add a = 10 b = 20 c = add(a, b) assert c == 30 那么我们首先需要对该函数进行包装,包装函数 _add 的参数和返回值都应该是 PyObject *。在包装函数中调用了 PyArg_ParseTuple 将传入的参数转换为 int 类型,调用原本的 add 函数得到返回值,之后又通过 PyLong_FromLong 将 int 转换为 PyObject。...

八月 17, 2023 · 3 分钟 · 631 字 · Wokron

Hexo 博客迁移教程

一、前言 因为用了新的笔记本,为了继续更新自己的博客,我决定把原来那台笔记本上的博客资源迁移过来。不过呢,当然不能用u盘拷贝这种比较low的方法,最好还是把资源放到 github 上,这样不仅方便现在的迁移,更能防止数据丢失。 二、将博客资源推送到仓库 如果你使用 hexo 搭建了自己的博客,并且把博客放到了 github 上,那么很容易注意到使用 hexo 部署时并不是将本地的所有内容推送到了 github,实际推送的只是 ./public 路径下的文件。而现在我们要做的就是将博客的所有资源推送到仓库,不仅是用于网页的部分。 我们选择就在博客网站所在的仓库存储博客资源,为了做到这一点,首先要在本地克隆一个仓库 git clone https://github.com/<username>/<username>.github.io.git 随后我们新建一个分支用于存储博客资源。该分支与博客网站所使用的 master 分支无关,因此最好创建成一个“孤儿”分支。 git checkout --orphan <branch_name> 切换到该分支后,原本随着克隆拉取到本地的文件现在依旧存在,需要将这些文件删除 git rm -rf . 接着将位于本地的博客资源复制到该文件夹下。 cp -r <old_blog_dir>/* . 这里需要注意,如果你使用了 next 等主题,并且是通过克隆仓库的方式下载的,那么此时应该把主题对应的项目路径下的 .git 文件夹删除。 # take next theme as example rm -r ./themes/next/.git 以上的工作都完成后,将这些复制到仓库中的博客资源文件添加并提交 git add . git commit -m "commit info" 最后将本地分支推送到远程仓库的新分支中 git push --set-upstream origin <remote_branch_name> 三、迁移博客 接下来要将博客迁移到另一台设备上。首先当然要下载 git 并配置用户名和邮箱 sudo apt install git git config --global user....

六月 26, 2023 · 2 分钟 · 268 字 · Wokron

Linux-Mint双系统的安装及美化

一、前言 我的笔记本现在用起来很慢了。每次听到它嗡嗡的风扇声却又看不到它跑不出来结果的时候,就感觉它好似一头老驴,使劲却又力不从心,腿打着颤却也拉不动身后的货。所以我谋划着新买一台,就让之前的那台好好休息吧。 一想到买台新的笔记本,我的思绪就沿着这条道一直走下去:“要买什么样的配置呢?买了新的电脑要做些什么项目呢?要玩什么游戏呢?” 诸如此类。装一个 linux 系统也是这时产生的想法。有人可能会想,“这有什么用呢?难道 windows 就不能用吗?如果不得不用 linux,wsl 也是很好地办法,或者用虚拟机,甚至直接用 docker,都可以解决。” 确实,如果就满足当下的使用而言,将 linux 作为一个个人使用的真正的系统,相对于 windows 似乎并没有什么优点。 不过呢,我选择折腾这么一阵也并没有什么经过考量的理由,而仅仅是因为自己在主观上更加喜欢 linux 罢了。在我不算太长的接触并学习 linux 的时间里,我从这个系统中感受到了设计的一致性,这是我在更长时间的对 windows 的接触和学习中所没有体会到的。当然,或许在之后看来,我现在的理解也不过是浅薄的认识罢了。但是现在,我还是决定安装一个 linux 系统。 如果有读者的话,希望不要嫌弃我太过啰嗦(笑)。 二、为什么是薄荷 众所周知,linux 是内核,许多不同的组织在 linux 内核的基础上增加了其他必要的软件和应用,开发了不同的发行版。发行版的江湖中帮派林立,主要有三大派系,debian 系、redhat 系和 suse 系。各个派系中又有无数相互关联却又相互区分的发行版。如 debian 的 ubuntu、deepin;redhat 的 fedora、suse 的 opensuse。只需要把这些发行版的大名亮出来,就足以让人眼花缭乱了。 本人也在这些发行版中漂移不定了一段时间,但最终选择了 debian 系的 mint(薄荷)。主要有一下几个原因 debian 系有着 apt 的超级牛力加持,.deb 格式的软件包使用作为广泛 mint 基于 ubuntu,ubuntu 是使用最为广泛的 linux 发行版 mint 精简了 ubuntu 下的一些功能,如 snap;并对初学者较为友好 三、安装操作系统 我新买的电脑是联想拯救者 R9000P,配置如下 设备 配置 处理器 AMD Ryzen 9 7945HX 内存 16G 硬盘 1T 显卡 Nvidia 4060 要安装的操作系统配置...

六月 24, 2023 · 2 分钟 · 386 字 · Wokron

BUAA-OS 实验笔记之 Lab6

一、Lab6 前言 操作系统实验的最后一篇笔记,不说什么了。本文主要讲了 Shell 的实现机制,管道通信略有说明。 二、Shell 程序的启动 这次我们还要回到 Init/init.c 文件。我们的 MOS 的所有实验都结束之后,mips_init 函数应该是这样的 void mips_init() { printk("init.c:\tmips_init() is called\n"); // lab2: mips_detect_memory(); mips_vm_init(); page_init(); // lab3: env_init(); // lab6: ENV_CREATE(user_icode); // This must be the first env! // lab5: ENV_CREATE(fs_serv); // This must be the second env! // lab3: kclock_init(); enable_irq(); while (1) { } } 其中我们使用 ENV_CREATE 创建了两个用户进程。这两个进程的代码在编译时便写入了内核 ELF 文件中。其中第二个进程 fs_serv 就是 Lab5 中用到的文件系统服务进程;而第一个进程 user_icode 则是整个操作系统中除文件系统服务进程外所有进程的共同祖先进程,该进程便用于启动 Shell 进程。user_icode 或为 “user init code” 之意。...

五月 19, 2023 · 16 分钟 · 3311 字 · Wokron