Java教程

程序环境和预编译

本文主要是介绍程序环境和预编译,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

程序的编译和链接

预定义符号

一、#define

1、#define定义标识符

2、#define定义宏

3、#define替换规则

4、#和##

二、宏和函数的对比

文件包含(#include)

1、头文件被包含的方式

2、避免头文件重复引用


程序的编译和链接

源文件->预编译->编译->汇编->链接(可执行程序)

以linux环境下代码为例:

  • 预编译阶段:gcc -o test.i -E test.c          .i预处理文件

展开头文件,不进行语法检查,进行宏替换

  • 编译阶段:gcc -o test.s -S test.i             .s汇编文件

语法检查(语法分析,词法分析,语义分析,符号汇总)

  • 汇编阶段:gcc -o test.o -c test.s              .o目标文件

形成符号表(汇编指令->二进制指令)

  • 链接:gcc -o test test.o

合并段表,符号表的合并和符号表的重定位

程序执行过程

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2.  程序的执行便开始。接着便调用main函数。
  3.  开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

预定义符号

_FILE_    //进行编译的源文件
_LINE_    //文件当中的行号
_DATE_    //文件被编译的日期
_TIME_    //文件被编译的时间

这些预定义符号都是内置的。例如:

printf("file:%s line:%d\n",_FILE_,_LINE_);

一、#define

1、#define定义标识符

语法:#define name stuff

例如:

#define MAX 1000
#define reg register     //为register关键字创建一个简短的名字
#define CASE break;case  //在写case语句的时候自动把break写上
#define do_forever for(;;)//用更形象的符号来替换一种实现

注:如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。每一个换行符后面不能够有任何符号(空格也不能有,否则运行时会出错)。建议在define定义标识符的时候不要加上;,容易出现语法错误。

#define DEBUG_PRINT printf("file:%s\tline:%d\t  \
                           date:%s\ttime:%s\n", \
                           _FILE_,_LINE_,_DATE_,_TIME_)

2、#define定义宏

宏的申明方式:

#define  name(parament-list)   stuff 

//parament-list是由逗号隔开的符号表,可能出现在stuff中

注意:参数列表的左括号必须与name相邻。如果两者之间有空格存在,参数列表就会被解释为stuff的一部分。

如下面一个宏:

#define SQUARE(x) x * x
SQUARE(5) //结果为5*5=25
SQUARE(5+1) //结果为5+1*5+1=11

对于以上代码,当参数x被替换为5+1时,并没有按照预想的次序进行求值。对于这种问题,我们只需要在进行定义时对每个参数加上括号即可。而在进行宏的相关运算时,我们也需要考虑运算的优先级问题,当存在运算的优先级高于宏的优先级时,我们需要给宏整体添加括号。

3、#define替换规则

在程序中扩展#define定义符号和宏时,有几个步骤:

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  • 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  • 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

4、#和##

char* p="hello"  "world";
//打印时字符串会自动连接

注:字符串有自动连接的特点

(1)#将宏参数变成相应的值

#define SQUARE(X) printf(#x "*" #x "=%d\n",x*x)
//如果x=5,运行后会显示5*5=25

(2)两个##的作用

##可以把位于它两边的符号合成一个符号。允许宏定义从分离的文本片段创建标识符。

#define ADD_TO_SUM(num, value)
        sum##num += value;
int sum5=10;
ADD_TO_SUM(5, 10);//作用是:给sum5增加10,sum5=20

注:连接必须产生一个合法的标识符。否则结果未定义。

二、宏和函数的对比

宏通常被应用与较简单的计算,比如:

#define MAX(a,b) ((a) > (B) ? (a) : (b))

那为什么不用函数来实现?原因如下:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  •  更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。而宏与类型无关。

当然和函数相比,宏也有劣势的地方:

  •  每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  • 宏是没法调试的
  • 宏由于类型无关,也就不够严谨。
  • 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

属性#define定义宏函数
代码长度每次使用,宏代码都会被插入到程序中。代码长度会大幅度增加。函数代码只出现在一个地方:每次使用函数时,都调用同一份代码。
执行速度更快参在函数的调用和返回的额外开销。
操作符优先级宏参数求值是在所有周围表达式的环境中,所以邻近操作符的优先级可能会产生不可预料的后果,建议在宏的书写中多些括号。函数参数只在函数调用的时候求值一次,结果传递给函数。求值结果更容易预测。
参数类型宏的参数与类型无关,只要对参数的操作合法,可以使用于任何参数类型。函数的参数与类型有关,如果参数类型不同,则需要不同类型的函数。
调试宏没法调试函数可以逐语句调试
带有副作用的参数参数可能被替换到宏中的多个位置,可能产生不可预料的结果函数参数只在传参的时候求值一次,结果更容易控制
递归宏不能递归函数可以递归

文件包含(#include)

#include指令可以使另外一个文件被编译。

1、头文件被包含的方式

  • 本地文件包含

#include"filename"

查找:先在源文件所在目录下查找。如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。

注:按照自己的安装路径去找。

  • 库文件包含

#include<filename.h>

查找:直接在标准路径下去查找,找不到提示编译错误。对于库函数也可以使用“”的形式包含

2、避免头文件重复引用

对于两个源文件都需要用到同一个头文件,这样就造成了文件内容的重复。如何解决这个问题?答案:条件编译。

每个头文件的开头写:

#ifndef  _TEST_H_
#define  _TEST_H_
//头文件内容
#endif   //_TEST_H_

或者,这样就可以避免头文件的重复引入。

#pragma once
这篇关于程序环境和预编译的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!