从0学起的C语言笔记(八、预处理、动态库和静态库)

从0学起的C语言笔记(八、预处理、动态库和静态库)



程序中有使用指针,结构体等知识,但要求不高,请大概先行了解后在进行学习

C语言的编译过程

gcc -E 文件名.c -o 文件名.i    预处理
gcc -S 文件名.i -o 文件名.s    编译
gcc -C 文件名.s -o 文件名.o    汇编
gcc 文件名.o -o 文件名.exe     链接
  1. 预编译
    将.c头文件,宏定义展开,生成.i的文件
  2. 编译
    将预处理之后的.i文件编译为.s的汇编文件
  3. 汇编
    将.s的汇编文件生成.o的目标文件,
    注意:linux与windows生成.obj的目标文件,简化为.o文件,mac生成.out的目标文件,也简化为.o文件,具体过程见扩展还没写.这里不做赘述,不是这里的重点
  4. 链接
    具体主要用于window生成.exe,linux中.o文件就为“一个32位ELF的文件,类型是 relocatable ,就是可重定位。所以目标文件在linux又叫做可重定位文件

# include

#include < >  //用尖括号包含的文件是在系统指定目录下的文件
#include " "  //用引号包含的是先在当前目录下寻找,
              //如果没有就在系统指定文件下寻找

例如

#include<stdio.h>
#include"myself.h"
#include"/usr/biff/myself.h"

集成开发环境中(IDE)也有标准路径和系统头文件的路径。一般的,都提供具体可选项,用于指定查找路径。
在UNIX中,使用双引号意味着优先查找本地目录,具体查找那个目录,这取决于编译器的设定。有些编译器会优先查找源文件所在目录,有些会优先查找当前工作目录。

示例与分析

头文件

names.h

//常量
#include<string>
#define SLEN 32

//结构体声明————————下一课笔记的内容
struct names
{
    char first[SLEN];
    char last[SLEN];
}

//类型定义
typedef struct names name;

//函数原型
void get_name(names *);
void show_name(const name *);
char * s_geta(char * st, int n);

头文件一般包含:
#define指令,结构体声明,typedef和函数原型。
注意:这些内容是编译器在创建可执行代码时所需要的数据,而不是可执行代码。为了便于学习这个源代码过于简单,通常的还有#ifndef和#define 防止多次包含头文件。稍后介绍。

源文件

names.c

#include<stdio.h>
#include"names.h"    //引入头文件

//函数定义
void get_names(name * pn)
{
    printf("请输入你的第一个名字")s_geta(pn->first,SLEN);
    
    printf("请输入你的下一个名字")s_geta(pn->last,SLEN);
}
void show_names(const name * pn)
{
    printf("%s, %s", pn->first,pn->last)}
char * s_gets(char * st, int n)
{
    char * ret_val;
    char * find;
    
    ret_val = fgets(st, n, stdin);
    if(ret_val)
    {
        find = strchr(st, '\n');
        if(find)
            *find = '\0';
        else
        while(getchar() != '\n')
            continue;
    }
    return ret_val;
}

注解:在这里get_names()函数通过s_gets()函数调用了fgets()函数,是为了避免目标数组溢出。

主函数文件
main.c

#include <stdio.h>
#include "names.h"
//一定记住names.c文件的位置

int main()
{
    name candidate;
    get_names(&candidate);
    printf("感谢");
    show_names(&&candidate);
    printf("使用这个程序");
    return 0;
}

注意要点

  • 两个源文件都要使用 names 类型结构,所以他们都必须包含 names.h 头文件。
  • 必须编译和链接 names.c 和 main.c 源代码文件。
  • 声明和指令放在 names.h 头文件中,函数定义放在names.c源代码文件中。

#define

明示常量:#define

类对象宏

有些有宏代表值的宏,这一类宏被称为类对象宏

示例与分析
程序

请务必要输入编译器编译后理解!!!

#include<stdio.h>
#define TWO 2
#define OW "当你编出一个程序 ,便能立即看到你的思想的实现!\
            所有的事情以一种非常有趣的方式联系在了一起,也\
            正是这一类的东西促使我进入这一领域。"
#define FOUR TWO*TWO
#define FX printf("X is %d。\n",x);
#define FMT "X is %d.\n"

int main()
{
    int x = TWO;
    FX;
    x = FOUR;
    printf(FMT,x);
    printf("%s\n",OW);
    printf("TWO:OW\n");
    return 0;
}

预处理器指令从#开始执行,到后面的第一个换行符为止。

每一个#define都有三部分组成

  • #define指令本身
  • 选定的缩写,也就是宏。有些宏代表值(如本例),这一类宏被称为类对象宏。宏的名称中不允许有空格,遵循C的变量定义规则
  • (指令剩余部分)称为替换体替换列表
#defineFXprintf(“X is %d。\n”,x);
预处理器指令替换体

记号(了解)

从技术角度,可以把宏的替换体看作记号(token)型字符串,“不是字符型字符串”。C语言预处理中记号是宏定义中单独的“词”。利用空格把这些东西分开。

示例与分析

#define FOUR 2*2

该宏定义有一个记号——“2*2”

#define SIX 3 * 2

该宏定义有三个记号——“3“,”*”,“2”

替换体中如果有多个空格,字符型和记号型处理方式不同,考虑如下

#define EIGHT 4  *  8

如果预处理器把该替换体看作是字符型字符串,将用4 * 8代替EIGHT。即,额外的空格完全是替换体的一部分。如果预处理器把该替换体看作是记号型字符串,则用3个记号 4 * 8 代替EIGHT。
换言之,解释字符型字符串会把空格记为替换体的一部分;记号型字符串会把空格记为多个记号的分隔符。

常量重定义

概念:

假设先把LIMIT定义为21,后来在该文件中又定义为26。这一过程被称之为重定义常量

编程时,头文件中引用头文件,定义全局变量,一定要慎重考虑

不同的实现方式采用不同的重定义方案。除非新旧定义相同,否则这些定义会被编译器认定是错误。还有一些实现方式允许重定义,但会给出警告。
ANSI标准采用第一种结局方案,只有新定义和旧定义完全相同才被允许重定义。
例:
具有相同的定义意味着替换体中的记号必须相同,且顺序相同。

示例与分析

#define SIX 2 * 3
#define SIX 2 * 3

这两条定义都有相同三个记号,额外的空格不算替换体的一部分。

#define SIX 2*3

这条定义只用一个记号,与前两个不同。如果需要重新定义,就需要使用 #undef 指令

若确实需要重定义常量,则使用 const 关键字和作用域规则更容易一些

#define MAX 20
#include "myself.h"
#undef  MAX

只有myself.h中的函数可以使用宏

在#define中使用参数

概念

#define S(a,b) a*b

注意带参宏的形参 a 和 b 没有类型名,
S(2,4) 将来在预处理的时候替换成 实参替代字符串的形参,其他字符保留,2*4

示例与分析

该示例有些问题,请复制执行后再仔细阅读分析

#include<stdio.h>
#define S(X) X*X
#define PR(X) printf("这一宏的返回值为%d",X)
int main()
{
    int z;
    int x = 5;
    
    printf("X的返回值1为%d",x);
    z = S(x);
    printf("S(x): ");
    PR(z);
    z = S(2);
    printf("S(2): ");
    PR(2);
    printf("S(x+2): ");
    PR(S(x+2));
    printf("100/S(x): ");
    PR(100/S(x));
    printf("X的返回值2为%d",x);
    printf("S(++x)");
    PR(S(++x));
    printf("X的返回值2为%d",x);
    return 0; 
}

执行结构如下
在这里插入图片描述
前两行的输出与预期相同。但是,,,这个后面的东西就看起来又点放飞自我了。
在解释之前先说几句:

  • 编译器换值,不计算!!!!

所以第三行,17 的计算方式为 x + 2 * x + 2 = 5 + 2 * 5 + 2 = 5 + 10 + 2 = 17。第四行100的计算方式为100/5*5=100。

第六行 42的计算方式为 ++x*++x 两次自增。
第一次在乘号之前 (5 + 1)*++x ,第二次在乘号之后(5 + 1)*(6 + 1) = 42。但是,由于标准并没有对这类运算顺序做出规定。所以,有的编译器输出为42,也有一些在乘法顺序之前,输出为 7*7=49。
在C的标准中对此种行为定义为未定义行为。无论哪种情况,x初始值为5,最终结果都是x = 7。这也是不被允许的。

注意要点

我们在使用时要避免 ++x 被作为宏参数!

带参宏和带参函数的区别

带参宏被调用多少次就会展开多少次,执行代码的时候没有函数调用的过程,不需要压栈弹栈。
所以带参宏,是浪费了空间,因为被展开多次,节省时间。 带参函数,代码只有一份,存在代码段,调用的时候去代码段取指令,调用的时候要,压栈弹栈。有个调用的过程。
所以说,带参函数是浪费了时间,节省了空间。
带参函数的形参是有类型的,带参宏的形参没有类型名。

条件编译

可以使用指令创建条件编译。告诉编译器根据编译时的条件执行或者忽略信息和代码。

#ifdef、#else和#endif指令

#ifdef MAVIS
    #include "horse.h"
    #define STABLES 5
#else
    #include "cow.h"
    #define STABLES 15
#endif

#ifdef指令:假设在#ifdef MAVIS之前已经定义了MAVIS,则执行#ifdef与#else之间的语句,#include “horse.h” 和 #define STABLES 5语句;假设在#ifdef MAVIS之前没有定义定义了MAVIS,则执行#else与#endif之间的语句,则执行#include “cow.h” 和 #define STABLES 15语句。

静态库

  1. 动态编译 动态编译使用的是动态库文件进行编译 gcc hello.c -o hello 默认的咱们使用的是动态编译方法
  2. 静态编译 静态编译使用的静态库文件进行编译 gcc -static hello.c -o hello
  3. 静态编译和动态编译区别 1:使用的库文件的格式不一样
    动态编译使用动态库,静态编译使用静态库
    注意:

静态编译要把静态库文件打包编译到可执行程序中。
动态编译不会把动态库文件打包编译到可执行程序中,它只是编译链接关系

示例

mytest.c

#include <stdio.h>
#include "mylib.h"
int main(int argc, char *argv[])
{
    int a=10,b=20,max_num,min_num;
    max_num=max(a,b);
    min_num=min(a,b);
    printf("max_num=%d\n",max_num);
    printf("min_num=%d\n",min_num);
return 0; 
}

mylib.c

int max(int x,int y)
{
    return (x>y)?x:y;
}
int min(int x,int y)
{
    return (x<y)?x:y;
}

mylib.h

extern int max(int x,int y);
extern int min(int x,int y);

以linux为例简单了解静态库的建立过程

  • 制作静态态库:
    gcc -c mylib.c -o mylib.o
    ar rc libtestlib.a mylib.o
    注意:静态库起名的时候必须以 lib 开头以.a 结尾
  • 编译程序:
    gcc -static mytest.c libtestlib.a -o mytest
  • 编译程序的命令
    gcc -static mytest.c –o mytest -ltestlib

结语

非常感谢您能看到这里,下一次写C语言的又一大难点——指针(1)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值