• 关于库函数和标准库(转)


      首先从课本中和资料中经常出现的一句话说起:C语言是门简单的语言。

       C语言本身只有32个关键字,9种控制语句,34种运算符,的确是一门简单的语言。

       这样一门简单的语言在实用的时候并非一件好事,比如C语言没有输入输出语句,程序员在输入输出时不得不借助于库函数。

       库函数是什么?简单的说,就是把常用的函数写好放到库里便于以后直接使用,这个库就称为函数库,函数被称为库函数。

        库(library)的本质是共享。我们从library这个单词的本义就可以看出,正如图书馆是为了实现图书资料的共享,函数库是为

    了实现函数的共享。共享是函数库的根本目的也是最大意义,它体现在这样两个方面:

         1.资源可以被他人使用:一个函数一旦被放入库中,其他程序员也就获得它的使用权,这就好比一个人写了一本书放进图书馆(我们将其想象为词典这种工具书,这样类比一个函数更贴切),那么其他人也就可以使用这本词典。而且,我们仅仅使用一本词典时,不用去关心词典的作者是如何去编写词典的,我们只关心词典的用法。对于库函数也是一样,我们只关心如何使用库函数,而不去关心库函数的具体实现。举个例子:余弦函数cos被放入库中,尽管九成的程序员都不知道cos函数真正的写法,但是所有程序员都会用它来求余弦。

         2.资源可以被重复使用:一个函数被放入库中,那么下次再使用余弦函数就无需再写一遍。这就好比一个人写了一本词典放进图书馆,下次再用这本词典就肯定不会再去写一遍,而是直接从图书馆取出词典然后使用。同样用cos函数的例子:就算一个程序员知道cos函数的真正求法,他也不会每次都去写一遍,一定会选择写一遍放进库中,下次直接调用库函数。

        回过头来体会这两个意义,会发现第二点更容易产生,但是第一点意义更重大。这就好比你也可以把自己遇到的单词积累成

    一个单词本,仅供自己随时取用,但是不出版的话,只有自己能使用。而一旦你出版了,就有很多的人能使用它了。同时,你拥

    有了这本书的版权,而其他人只获得使用权。对于函数库里的函数也一样,让你被提供一个函数库时,你只拥有使用权,版权是

    属于编写者的,而且编写者会对使用者隐藏源代码来防止盗版。因此,虽然我们可能经常使用编译器给我们提供的cos函数,但

    是即使我们想看,也看不到实现cos函数的代码。

        虽然用图书馆类比函数库可以说明函数库的意义,但是函数库究竟是什么?它和我们自己写的函数有什么不同?它存在于我

    们计算机的什么位置?是什么样的文件?根据我的理解,函数库在我们的计算机中对应着这样三种文件:

         1.头文件(Header Files),后缀为 .h。这个文件就是实实在在的源代码,只要符合C语法的句子都可以写进去,但是为了发挥出它真正的作用,里面只写了库函数和变量的声明,后文会提到原因。

        2.静态库文件(Static Library ),在Windows下后缀为 .lib,在Linux下后缀为 .a。这个文件中真正包含了某些库函数的实现。但是这个文件不是C语言的代码,而是二进制码。

        3.动态链接库文件(Dynamic Linked Library),在Windows下后缀为 .dll,在Linux下后缀为 .so。这个文件也包含了某些库函数的实现,同样是二进制码。

        下面结合C语言程序的完整编译和运行过程,介绍这三种文件怎样被用到:

          1.预处理阶段,预处理器从头至尾扫描文件,寻找#开头的预处理指令,进行文本替换和插入。这时,我们#include的头文件将被完全插入到include所在位置,就好像替你在写include的位置写了一遍相应的头文件里的代码。

      2.编译阶段,编译器对上阶段预处理好的程序进行分析和合成,生成目标模块。这个目标模块其实就是被翻译好的二进制的机器代码,又叫目标文件,后缀是 .obj,在win7下是.o文件。

    需要指出的两点:

       一:静态库文件 .lib,在以往的Windows环境中静态库文件后缀为.lib,但在win7下的后缀其实不是 .lib,而是和Linux下同样的 .a。而这个 .a文件被 视为是.o的归档,也就是说.a和.o本质相同,都是二进制代码,只是.a相当于放了多个.o中的内容。因此,静态库文件.a完全可以当成目标文件.o来用,或者说它也是另一种形式的目标文件。

       二:在编译的过程中对于调用的函数并不需要具体的实现,只要知道函数的原型即可,编译器在函数调用的地方生成一个引用符号,这个引用符号会被下个阶段识别。因此,调用只声明而未定义的函数在这一阶段是不会报错的。

      3.链接阶段,连接器将将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件(executable file)。这个可执行文件就是真正的可以被加载到内存中,然后能被底层硬件所执行的文件。比如Windows下的.exe文件。

    同样说明两点:

       一:上个阶段编译器在函数调用的地方生成了引用符号,在链接阶段必须去找到引用符号的实现,如果函数定义在C文件中,链接器就会去找到那个对应的目标文件.o,如果是静态库函数,则在 .a中,对于这两者链接器都会将它们加入到 .exe文件中。如果找不到对应的函数定义,会报错,如果找到多个而导致无法区分,也会报错。

       二:对于标准库文件只需要引用头文件,链接的时候编译器自动查找对应的.a。而对于自己的库,不但在要源码中包含头文件,在链接时还要指明库文件。

      4.运行阶段,程序真正运行过程中,还可以调用动态链接库.dll,这个dll文件,我们可以认为它是exe文件的半成品,它没有main函数,因此不能被直接执行,但它可以被exe“动态”地引用和卸载,它和静态库的最大不同在于,静态库.a中内容最后都要被加入到exe中,而dll是独立于exe的,它只是被调用,然后又被释放,dll中的内容并没有被加入exe。这样带来的好处是,内存中只有DLL的一份复制品,节约了内存资源。Dll的调用分为静态调用和动态调用,静态调用需要相应的导入库.a文件来提供DLL 导出函数的符号名及序号,动态调用则需要用LoadLibrary和GetProcAddress语句去寻找函数的入口。

    回过头来看这三种文件,.h文件给出了函数的声明,在预处理阶段被加入程序代码中,至于函数的定义,如果放在.a文件中,则

    在链接阶段就把.a文件加入到exe文件中,如果放在.dll文件中,则在程序运行阶段才会由应用程序去调用dll中的函数。用这样的

    方式来实现库函数,带来的意义是:

      1.函数的实现过程被隐藏为二进制码的形式,而不是源码,既保护了函数库编写者的版权,又提升了可移植性

      2.函数的声明以头文件形式被提供给用户,用户通过头文件中的声明了解库函数的功能和使用方法。

        那么我们如何自己写出一个函数库呢,其实也很简单,在集成开发环境下,我们新建一个工程,平时我们总是新建控制台工

    程,其实我们还可以新建静态库工程或是动态链接库工程,这样编译出来的就是.a和.dll。如果是自己亲自写编译命令,就要先把

    C或CPP文件编译成.o目标文件,再用ar 命令把.o文件打包归档为.a文件,或是用gcc-shared命令将其创建为.dll文件。

        既然人人都可以编写库函数,于是就出现了各种公司和编译器提供的各种不同的库函数,这也造成了极大的混乱。后来标准

    化组织ANSI制定了C语言的标准,同时也制定了函数库的标准,就成为 标准库 函数。后来这个ANSI C标准上升为ISO标准,但

    是内容并没有任何变化。C++也是一样,ISO为C++制定了标准之后,所有的C++编译器也就按照这个标准提供库函数。

        所谓的标准库,就是ISO给出一些函数库,并规定它们是标准的。标准的意思就是,编译器都要提供这些既定的函数库。我们

    可以直接使用,而无需再去编写它们。需要强调的是,标准库并非C语言的一部分,它只是C语言的一个辅助工具。但是事实

    上,这个工具对于C语言来说又是极为重要的,离开它编写C程序将变的十分困难,使用它就会带来很多益处。因此,学习标准

    库已经成为学习C语言和C++过程中的一部分。

        为了学习标准库,我们必须再次认识一下头文件(header file),头文件中包含了变量和函数的声明,而不包含它们的定义,这

    样做带来的意义是:

      1.使程序编写变得简单:对于多个C文件都要调用的全局实体(可能是函数,变量,结构,类等等),如果在每个文件中声明一次,显得非常麻烦。我们把对于这些实体的声明写进头文件中,再用include指令把头文件加入到C文件中,就会变得简单。而且修改起来变得更加容易,因为只需要修改头文件中的内容。如果把对于那些实体的定义也写头文件,那么当多个C文件include那个头文件,将会多次定义同一个实体,链接时就会出错。

      2.头文件承担了一个重大责任,就是作为函数库的目录与接口,和用户进行交流。头文件中包含了对于函数的声明,这就意味着,通过查看头文件,我们就可以知道库中有哪些函数,以及这些函数的原型,也就知道了这些函数需要什么参数,返回什么值,如何去调用它。这就好比图书馆给用户一份目录清单,告诉你馆里有哪些书,以及对那些书进行简单的介绍。如果把函数定义也写进去,就如同把馆里全部的书的内容都放到目录里,这样庞大的目录没有人看得下去,也没有意义。

      3.保护了函数库编写者的版权,头文件中没有函数的具体实现,这意味着我们只能通过看头文件来知晓库函数的使用方法,而不能了解它的实现方法。真正的函数实现作为二进制码被隐藏于.a和.dll 文件中,只能被机器识别。

        在C语言的标准中,规定头文件后缀为 .h ,例如包含了scanf函数的<stdio.h>。而在C++的标准中头文件则不采取加后缀的形

    式,如<iostream>。注意,C++作为C的超集,是包含C的,我们完全可以在C++程序中包含一个<stdio.h>,但是这就造成了程序

    设计风格上的混乱(所以不建议这么写)。为此,C++又提供了<cname>形式的标准头文件,其内容与ISO标准C包含的name.h

    头文件相同,但容纳了C++扩展的功能。在 <cname>形式标准的头文件中,与宏相关的名称在全局作用域中定义,其他名称在std

    命名空间中声明。

    下面给出C89标准中标准库的15个头文件:

    <assert.h> 验证程序断言,用于诊断

    <ctype.h> 字符类型和字符类测试

    <errno.h> 说明出错码

    <float.h> 浮点运算有关的常量

    <limits.h> 实现和定义时的限制

    <locale.h> 声明了C语言本地化函数 

    <math.h> 说明了数学函数和宏

    <setjmp.h> 非局部跳转

    <signal.h> 信号处理部分, 规定了了程序执行时如何处理不同的信号。 

    <stdarg.h> 可变参数表,提供了依次处理含有未知数目和类型的函数变元表的机制

    <stddef.h> 标准定义,定义了一些标准宏以及类型 

    <stdio.h> 标准I/O库,定义了用于输入和输出的函数、类型和宏。

    <stdlib.h> 实用程序库函数

    <string.h> 字符串操作处理函数

    <time.h>  日期和时间的类型和函数

    95年对C89的修订版新增了三个

    <iso646.h> 替代关系操作符宏 

    <wchar.h> 宽字符支持

    <wctype.h> 宽字符分类和映射支持

    C99版本又新增了六个

    <complex.h> 支持复数算术运算

    <fenv.h> 浮点环境

    <inttypes.h> 整型格式转换

    <stdbool.h> 布尔类型和值

    <stdint.h> 整型

    <tgmath.h> 通用类型数学宏

    C++ 标准库一共包含18 个 C的标准库头文件:

    <cassert> 用于在程序运行时执行断言    <cctype> 字符处理

    <cerrno> 错误码                              <cfloat> 用于测试浮点类型属性 

    <ciso646> ISO646变体字符集            <climits> 测试整数类型属性

    <clocale> 本地化函数                        <cmath> 数学函数

    <csetjmp> 执行非内部的goto语句       <csignal> 信号

    <cstdarg> 访问参数数量变化的函数     <cstddef> 用于定义实用的类型和宏

    <cstdio> 输入/输出                          <cstdlib> 杂项函数及内存分配

    <cstring> 字符串                             <ctime> 时间

    <cwchar> 宽字符处理及输入/输出        <cwctype> 宽字符分类

    和33 个C++ 头文件:

    <algorithm> STL通用算法   <bitset> STL位集容器               <complex> 复数类

    <deque> STL双端队列容器    <exception> 异常处理类            <fstream> 文件流

    <functional> STL函数对象    <iomanip> 参数化输入/输出        <ios> 基本输入/输出支持

    <iosfwd> 输入/输出前置声明  <iostream> 数据流输入/输出       <istream> 基本输入流

    <iterator> 遍历序列的类       <limits> 各种数据类型最值常量     <list> STL线性列表容器

    <locale> 国际化支持            <map> STL映射容器                   <memory> 专用内存分配器

    <new> 基本内存分配和释放   <numeric> 通用的数字操作            <ostream> 基本输出流

    <queue> STL 队列容器        <set> STL 集合容器                     <sstream> 基于字符串的流

    <stack> STL 堆栈容器         <stdexcept> 标准异常                  <streambuf> iostream 的缓冲区类

    <string>字符串类                <strstream>非内存字符序列的流类  <typeinfo> 运行时类型标识

    <utility> STL 通用模板类      <valarray>支持值数组的类和模版类  <vector> STL 动态数组容器

    以及附加的3个头文件(非必须)

    <hash_map> <hash_set> <slist>

    关于库函数和标准库就先总结到这里。

  • 相关阅读:
    将python对象序列化成php能读取的格式(即能反序列化到对象)
    Atitit.研发管理---api版本号策略与版本控制
    Atitit.研发管理---api版本号策略与版本控制
    Atitit.jsou html转换纯文本 java c# php
    Atitit.jsou html转换纯文本 java c# php
    atitit.基于bat cli的插件管理系统.doc
    atitit.基于bat cli的插件管理系统.doc
    atititi.soa  微服务 区别 联系 优缺点.doc
    atititi.soa  微服务 区别 联系 优缺点.doc
    userService 用户 会员 系统设计 v2 q224 .doc
  • 原文地址:https://www.cnblogs.com/mangci/p/3153108.html
Copyright © 2020-2023  润新知