LemonHX

LemonHX

CEO of Limit-LAB 喜欢鼓捣底层的代码,意图改变世界
twitter
tg_channel

论头文件

在计算机编程中,头文件(header file)是一个包含函数、类、变量和其他声明的文件。头文件通常用于在程序中引用外部库或模块。

头文件的作用是让程序员在编写代码时可以使用外部库或模块提供的函数、变量等。这些外部库或模块的功能可能比较复杂,或者需要很多行代码才能实现,而头文件则提供了一种方便的方式来引用这些功能,让程序员可以更加轻松地使用它们。

头文件通常包含了函数、类、变量的声明,以及一些预处理器指令等。在使用头文件时,程序员需要在代码中引用这些头文件,以便程序可以找到并使用其中定义的函数、类、变量等。头文件的引用方式因编程语言而异.

现在大量的语言都没有头文件了,为什么?#

在现代编程语言中,许多语言已经不再使用显式的头文件。这主要是因为类 C 语言头文件存在一些缺点,如下:

  1. 头文件容易出错:在使用头文件时,开发人员需要确保头文件中的所有定义与实现文件中的定义匹配。这很容易出错,特别是当头文件发生更改时。这些错误可能会导致编译错误或运行时错误,从而浪费时间和资源。
  2. 头文件使编译时间变慢:在使用头文件时,编译器需要在编译期间处理头文件,这会使编译时间变慢。如果头文件非常大或者包含复杂的依赖关系,这会导致更长的编译时间。
  3. 头文件不利于代码重构:当代码需要进行重构时,头文件可能会成为障碍。如果头文件中的定义与实现不匹配,或者定义不再使用,这可能会导致代码重构变得更加困难。

因此,现代编程语言倾向于使用其他方法来处理这些问题。例如,许多语言使用模块或名称空间来组织代码,并使用自动化工具来处理依赖关系。这些方法可以减少头文件所引入的问题,并提高代码的可维护性和可读性。同时,现代编译器也变得更加智能化,可以更好地处理代码结构和依赖关系,从而减少头文件所引入的问题。

我为什么认为现在还需要头文件#

头文件提供了一种自顶而下的开发方式,让开发人员能够先定义接口,再实现接口的功能。这种方式可以使代码更加清晰、易于维护和扩展。

传统的 C 语言中的头文件也存在一些缺点,比如可能导致编译速度变慢,代码维护复杂,以及不利于代码的模块化等。因此,很多现代编程语言和框架都采用了其他的方式来实现代码的组织和管理,比如命名空间、模块、包等概念。

不过我想从多个方面来反驳这种说法:

有没有一种可能,头文件也能模块化#

其实我一直认为模块是一种头文件的新方式,不过没有头文件的语言通常把原来需要写头文件的内容放在业务代码里面,这样耦合性更高,同时也更难维护.

我的观点是,我们可以同时使用头文件和模块,但是头文件的作用应该是声明,而不是实现,这样就可以避免头文件的缺点,同时也可以避免模块的缺点.

打个比方来说,现在有这么一种幻想中存在的 rust 语言,它同时支持头文件和模块,但是头文件只能用来声明,不能用来实现

// header xxx.mod.rs
struct Something

impl Something {
    fn method1(&self) -> sometype
    fn method2(&self) -> sometype
}


// source xxx.impl.rs
struct Something {
    // ...
}

fn Something::method1(&self) -> sometype {
    // do something
}

fn Something::method2(&self) -> sometype {
    // do something
}

这样做的好处是,我们在实现业务代码的时候,可以只实现头文件,然后再写完业务代码之后,再去实现头文件里面的方法,这样就可以避免头文件的缺点,同时也可以避免模块的缺点.

// source of some business code

fn use_something(input: sometype) -> sometype {
    let something = Something::new();
    let i1 = something.method1();
    let i2 = something.method2();
    somefunc(i1, i2)
}

我们可以假设头文件里面的方法一开始都会给我们生成一个空的实现,这样我们就可以先写单元测试,然后再去实现头文件里面的方法.

// source of some business code test
#[test]
fn test_use_something() {
    let res1 = use_something(input1);
    assert_eq!(res1, output1);
    let res2 = use_something(input2);
    assert_eq!(res2, output2);
    // ...
}

然后我们再去实现头文件里面的方法,让它们通过单元测试

有没有一种可能,我们现在已经不怎么需要自己写代码了#

如果有了这套机制,我们就可以更大规模的利用 ai 来生成代码,甚至是自动生成代码,这样我们就可以更专注于抽象问题,而不是具体的实现问题.

还有一个问题就是没有头文件的语言并不能很好的支持动态链接#

在没有头文件的语言中,链接器需要在不知道函数参数和返回类型的情况下处理函数调用。

而编译器的实现方式决定了需要预先将所有的代码都加载一遍,这样其实造成了很多浪费,因为我们并不是每次都需要所有的代码,有时候我们只需要其中的一部分.

有头文件的语言可以通过链接器在运行时动态加载共享库来支持动态链接。这种方法可以更精确地在需要时加载和链接代码

有人这时候可能又要跟我吵了,说什么我们不需要动态链接,静态链接多好啊,我们可以把所有的代码都编译进去,这样就不需要动态链接了.

我只觉得这群人是傻逼.

动态链接有以下好处:

节省内存:当多个程序共享同一个库时,动态链接可以让它们共享库中的代码和数据,从而节省内存空间。

更新方便:如果库中的代码需要更新,只需要替换库文件,而不需要重新编译使用该库的程序。

灵活性:动态链接可以让程序在运行时加载库文件,从而增强了程序的灵活性。这意味着程序可以动态地适应用户的需求,而不需要重新编译或重新部署程序。

共享库可以被多个程序使用:共享库中的代码可以被多个程序使用,从而避免了重复编写相同的代码的问题。

减少可执行文件大小:使用动态链接可以减少可执行文件的大小,因为库中的代码和数据不需要在每个程序中都复制一份。

我觉得现在的风气不对,干啥都想全静态,你 unikernel 去啊,又不去,你让用户安装你软件的时候开 vm 跑啊!开 docker 跑啊!有病

接口描述语言#

有头文件还需要这个?

直接写个 transformer, 把头文件转成 idl 就完了呗

没有头文件的语言想支持 idl... 体验挺噩梦的

闭源的问题#

我觉得现在依赖模块和包管理的风气是不对的,完全没有考虑到闭源代码的问题

假如没有头文件,那么我们就必须分发全部的源码,就算公布闭源 binary 都有可能会被逆向,更何况是源码.

结语#

当我们谈论头文件的优缺点时,我们不应该只看到它的缺点而忽略了它的意义。

头文件是计算机编程发展历程中的一个重要发明,它极大地简化了代码的编写和维护,加速了程序的开发进程。

在现代编程中,我们可以选择使用模块化系统、静态分析工具等来解决头文件带来的问题,同时也可以选择使用不需要头文件的新型编程语言来实现更加优秀的开发体验。但这并不意味着我们应该完全抛弃头文件,因为它依然有着不可替代的作用,特别是在大型项目中。

因此,我们应该看到头文件的优缺点,正确地使用它,同时也不断地探索新的技术手段来提高编程效率和代码质量。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。