C语言----程序环境和预处理

目录

1.程序的翻译环境和执行环境

2.C语言程序的编译+链接

3.预处理

        预处理指令 #define

        宏和函数的对比

        预处理操作符###的介绍

        命令定义

        预处理指令 #include

        预处理指令 #undef

        条件编译

正文

1.程序的翻译环境和执行环境

        在C语言标准实现过程中,存在两种环境:翻译环境和执行环境,前者用于将源代码转化为可执行的机器指令,后者用于实际执行代码

2.C语言程序的编译、链接

        一个C语言文件的翻译过程如下:

        

        每个源文件通过编译转换成目标文件;再由链接器捆绑在一起形成一个单一完整的可执行程序;链接器可同时引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库,将其需要的函数也链接到程序中。

        2.1 编译过程

编译过程分成三个阶段,预编译,编译和汇编。

预编译:进行头文件的包含,宏、定义的替换以及注释的删除等,预编译后会生成.i文件

编译:把C语言代码翻译成汇编代码,进行了语法词法语义的分析,并进行了符号汇总,生成.s文件            《编译原理》

汇编:把汇编代码转换成二进制指令,生产.o文件(object目标文件),形成符号表

链接:合并段表,并进行符号表的合并和重定位

        原理:

        推荐书籍:《程序员的自我修养》

3.预处理 

    3.1预定义符号

        常见C语言内置的预定义符号:

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

 可以尝试运行一下这个

printf("%s %d %s %s", __FILE__, __LINE__,__DATE__,__TIME__);

    3.2 #define

        3.2.1#define 定义标识符:

        

#define MAX 100
#define reg register       //为register创建一个更短的名字
#define do_forever for(;;)//  形象直观
#define CASE break;case  //自动补上break的case
#define DEBUG_PRINT  printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__,       \
                          __DATE__,__TIME__ )//定义内容过长时\充当续行符(不能加其他东西)

注:#define 定义时不要加;

#define MAX 100; //会把MAX替换成100; 

if(condition)
 max = MAX;
else
 max = 0;

想一想这段代码有什么问题?

        3.2.2#define 定义宏

        #define定义时加上参数替换即为宏,一般声明方式为:

        #define name(parament-list) stuff

        parament-list为逗号隔开的符号表(参数),它们可能出现在 stuff 中

        注:左括号(必须紧挨name

        由于宏展开替换是在预编译阶段,因此如果参数中有表达式时,不会计算它的值而是直接把表达式替换进去,宏的本质就是替换。

        

#define SQUARE(x) x*x
int a = 5;
printf("%d",SQUARE(a+1));//想一想这个打印出来是什么??
//实际上上面打印出来是11,打印的是5+1*5+1
//所以我们最好带上括号:#define SQUARE(x) (x)*(x)
//可是这样也有意外:
#define DOUBLE(x) (X)+(X)
int a=5;
printf("%d\n",10*DOUBLE(a));//这个呢??
//这个打印出来是55而不是100,因为表达式为:10*(5)+(5)
//所以最好再加上一个括号#define DOUBLE(x) ((x)+(x))
3.2.3替换规则
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先 被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上 述处理过程。

注:宏不能递归;#define不会在字符串内替换
3.2.4#和##

首先明白,字符串有自动连接的功能:

char* p = "hello ""bit\n";
printf("hello"" bit\n");
printf("%s", p);

打印出来直接就是hello bit

所以我们可以这样写:

#define PRINT(FORMAT, VALUE)\
 printf("the value is "FORMAT"\n", VALUE);
...
PRINT("%d", 10);

(FORMAT也为一中宏参数类型,例如"%d" "%f"等,同样的还有type mem...)

#的作用是把一个宏参数变成字符串

#define PRINT(FORMAT, VALUE)\
 printf("the value of "#VALUE" is "FORMAT"\n", VALUE);
...
PRINT("%d", 11);

输出:the value of 11 is 11

##的作用是把两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。需要注意的是连接之后必须是合法的存在的标识符,否则结果是未定义的。

#define ADD_TO_SUM(num, value) \
 sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.
3.2.5带副作用的宏参数
#define MAX(a,b) (a)>(b)? (a):(b)
int a=5;
int b=7;
int c=MAX(a++,b++);
printf("%d\n",c);
printf("%d\n",a);
printf("%d\n",b);

这个宏替换就很容易产生副作用:宏替换后,int c =MAX(a++,b++);被替换为:

int c =(a++)>(b++)?(a++):(b++);

因此最终结果输出为 8 6 9

宏和函数的对比:

1.使用宏,宏代码会插入到程序中,而函数代码只出现在一个地方

2.函数需要在调用和返回时进行函数栈帧的创建和销毁,而宏不需要,因此宏更快

3.宏是把表达式替换,函数是把表达式求值后代入(参数为表达式时),因此宏可能会因为操作符优先级或者副作用产生bug

4.宏的参数与类型无关,因此只要对参数操作合法即可,而函数参数与类型有关

5.宏不方便调试,不能递归,函数可以。

注:一般行业规定把宏名字全部大写,函数名不全大写

3.3#undef

#undef 可以用于移除一个宏定义,如果应该名字需要被重新定义,那么它的旧名字需要首先被移除。

#define A 10
#undef A
#define A 100

3.4命令行定义

        许多C编译器运行在命令行中定义符号用于启动编译过程

#include <stdio.h>
int main()
{
    int array [ARRAY_SIZE];
    int i = 0;
    for(i = 0; i< ARRAY_SIZE; i ++)
   {
        array[i] = i;
   }
    for(i = 0; i< ARRAY_SIZE; i ++)
   {
        printf("%d " ,array[i]);
   }
    printf("\n" );
    return 0;
}
//linux下可以通过这行命令启动程序
gcc -D ARRAY_SIZE=10 programe.c

3.5条件编译

其实和条件语句很像:

1.#if  常量表达式//注意必须是常量表达式,因此在预编译阶段还没有给变量开辟空间
    //...
  #endif
如:
#define __DEBUG__ 1
#if __DEBUG__//预编译阶段,常量表达式由预处理器求值
 //..
#endif

加个else:

2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif

还有:

3.判断是否被定义
//判断是
#if defined(symbol)
#ifdef symbol
//判断否
#if !defined(symbol)
#ifndef symbol

综合嵌套起来:

4.嵌套指令
#if defined(OS_UNIX)
 #ifdef OPTION1
 unix_version_option1();
 #endif
 #ifdef OPTION2
 unix_version_option2();
 #endif
#elif defined(OS_MSDOS)
 #ifdef OPTION2
 msdos_version_option2();
 #endif
#endif

3.6文件包含

#include会引入文件,在预编译阶段,预处理器会把这条指令删掉然后用包含文件的内容替换。如果出现10次那么就会被编译十次

头文件被包含的方式:

#include"filename.h"   以及#include<filename.h>

后者查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

前者是现在源文件所在目录下查找,如果没有找到,编译器就像查找库函数头文件一样在标准位置查找头文件。再找不到就提示编译错误。

所以理论上#include"stdio.h"也是可以的,但是效率低些。

如果文件嵌套包含,比如:

 为了避免头文件的重复引入,可以在每个头文件的开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif//思考一下为什么这样可以避免重复引入

 或者直接加上#pragma once

3.7其他预处理指令

自行查阅资料了解,如《高质量C/C++编程指南》等

附加:

offsetof宏的实现:

#define offsetof(StructType, MemberName) (size_t)&(((StructType *)0)->MemberName)

StructType是结构体类型名,MemberName是成员名。具体操作方法是:

1、先将0转换为一个结构体类型的指针,相当于某个结构体的首地址是0。此时,每一个成员的偏移量就成了相对0的偏移量,这样就不需要减去首地址了。

2、对该指针用->访问其成员,并取出地址,由于结构体起始地址为0,此时成员偏移量直接相当于对0的偏移量,所以得到的值直接就是对首地址的偏移量。

3、取出该成员的地址,强转成size_t并打印,就求出了这个偏移量。


 

  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《Objective-C程序设计》(作者杨正洪、郑齐心、李建国)通过大量的实例系统地介绍了Objective-C语言的基本概念、语法规则、框架、类库及开发环境。读者在阅读本书后,可以掌握Objective-C语言的基本内容,并进行实际的iPhone/iPad和Mac应用开发。   《Objective-C程序设计》共分成11章。前6章讲述Objective-C语言,包括数据类型、运算符、表达式、条件语句、循环语句、类、协议、继承、类别、编译预处理等内容。第7章到第10章讲述Objective-C的基础框架,以及文件操作、内存管理、数据保存等内容。第11章讲述了应用工具框架。第12、13章分别讲述了如何开发iPhone/iPad应用程序。第14章讲述了Objective-C++和访问Mysql数据库的编程知识。 第1章 启程.1 1.1 预备知识1 1.2 历史背景1 1.3 内容简介2 1.4 小结3 第2章 对C的扩展4 2.1 最简单的Objective-C程序4 2.2 解构HelloObjective-C程序7 2.2.1 #import7 2.2.2 NSLog()和@"字符串"8 2.3 布尔类型10 2.3.1 BOOL强大的实用功能11 2.3.2 比较13 2.4 小结14 第3章 面向对象编程基础知识15 3.1 间接15 3.1.1 变量与间接16 3.1.2 使用文件名的间接18 3.2 在面向对象的编程中使用间接24 3.2.1 过程式编程24 3.2.2 实现面向对象编程29 3.3 学习有关的术语33 3.4 Objective-C中的OOP34 3.4.1 @interface部分34 3.4.2 @implementation部分38 3.4.3 实例化对象40 3.4.4 扩展Shapes-Object41 3.5 小结43 第4章 继承45 4.1 为何使用继承45 4.2 继承语法48 4.3 继承的工作机制51 4.3.1 方法调度51 4.3.2 实例变量53 4.4 重写方法55 4.5 小结57 第5章 复合58 5.1 什么是复合58 5.1.1 Car程序58 5.1.2 自定义NSLog()59 5.2 存取方法62 5.2.1 设置发动机的属性64 5.2.2 设置轮胎的属性64 5.2.3 跟踪汽车的变化66 5.3 扩展CarParts程序67 5.4 复合还是继承68 5.5 小结69 第6章 源文件组织70 6.1 拆分接口和实现部分70 6.2 拆分Car程序73 6.3 使用跨文件依赖关系75 6.3.1 重新编译须知75 6.3.2 让汽车开动77 6.3.3 导入和继承79 6.4 小结80 第7章 深入了解Xcode82 7.1 改变公司名称82 7.2 使用编辑器的技巧与诀窍83 7.3 在Xcode的帮助下编写代码85 7.3.1 首行缩进85 7.3.2 代码自动完成85 7.3.3 括号匹配88 7.3.4 批量编辑88 7.3.5 代码导航91 7.3.6 emacs不是Mac程序91 7.3.7 任意搜索92 7.3.8 芝麻开门93 7.3.9 书签93 7.3.10 集中注意力94 7.3.11 开启导航条95 7.4 获取信息98 7.4.1 研究助手98 7.4.2 文档管理程序99 7.5 调试100 7.5.1 暴力调试100 7.5.2 Xcode的调试器100 7.5.3 精巧的调试符号101 7.5.4 开始调试101 7.5.5 检查程序104 7.5 备忘表105 7.6 小结106 第8章 FoundationKit快速教程107 8.1 一些有用的数据类型108 8.1.1 范围的作用108 8.1.2 几何数据类型108 8.2 字符串109 8.2.1 创建字符串109 8.2.2 类方法109 8.2.3 关于大小110 8.2.4 比较的策略110 8.2.5 不区分大小写的比较112 8.2.6 字符串内是否还包含别的字符串..112 8.3 可变性113 8.4 集合家族115 8.4.1 NSArray115 8.4.2 可变数组118 8.4.3 枚举“王国”119 8.4.4 快速枚举120 8.4.5 NSDictionary120 8.4.6 使用,但不要扩展122 8.5 各种数值122 8.5.1 NSNumber122 8.5.2 NSValue123 8.5.3 NSNull124 8.6 示例:查找文件124 8.7 小结128 第9章 内存管理129 9.1 对象生命周期129 9.1.1 引用计数130 9.1.2 对象所有权132 9.1.3 访问方法中的保留和释放133 9.2 自动释放134 9.2.1 所有对象全部入池135 9.2.2 自动释放池的销毁时间135 9.2.3 自动释放池的工作过程136 9.3 Cocoa内存管理规则138 9.3.1 临时对象138 9.3.2 拥有对象139 9.3.3 垃圾回收141 9.4 小结142 第10章 对象初始化143 10.1 分配对象143 10.2 初始化对象143 10.2.1 编写初始化方法144 10.2.2 初始化时做什么146 10.3 便利初始化函数146 10.4 更多部件改进147 10.4.1 Tire类的初始化147 10.4.2 更新main()函数149 10.4.3 清理Car类152 10.5 支持垃圾回收风格的Car类清理155 10.6 指定初始化函数156 10.6.1 子类化问题157 10.6.2 改进Tire类的初始化函数159 10.6.3 添加AllWeatherRadial类的初始化函数160 10.7 初始化函数规则160 10.8 小结161 第11章 特性162 11.1 修改特性值162 11.1.1 简化接口163 11.1.2 简化实现164 11.1.3 点表达式的妙用166 11.2 特性扩展167 11.2.1 名称的使用171 11.2.2 只读特性172 11.2.3 特性不是万能的173 11.3 小结173 第12章 类别175 12.1 创建类别175 12.1.1 声明类别175 12.1.2 实现类别176 12.1.3 类别的局限性178 12.1.4 类别的作用178 12.2 利用类别分散实现178 12.3 使用类别创建前向引用182 12.4 非正式协议和委托类别183 12.4.1 ITunesFinder项目184 12.4.2 委托和类别187 12.4.3 响应选择器187 12.4.4 选择器的其他应用188 12.5 小结189 第13章 协议190 13.1 正式协议190 13.1.1 声明协议190 13.1.2 采用协议191 13.1.3 实现协议192 13.2 复制192 13.2.1 复制Engine192 13.2.2 复制Tire194 13.2.3 复制Car196 13.2.4 协议和数据类型199 13.3 Objective-C2.0的新特性199 13.4 小结200 第14章 AppKit简介201 14.1 构建项目201 14.2 构建AppController@interface203 14.3 InterfaceBuilder203 14.4 布局用户界面205 14.5 连接207 14.5.1 连接输出口207 14.5.2 连接操作208 14.6 AppController实现210 14.7 小结212 第15章 文件加载与保存213 15.1 属性列表213 15.1.1 NSDate213 15.1.2 NSData214 15.1.3 写入和读取属性列表215 15.2 编码对象216 15.3 小结221 第16章 键/值编码222 16.1 入门项目222 16.2 KVC简介224 16.3 路径225 16.4 整体操作226 16.4.1 中途小憩227 16.4.2 流畅地运算231 16.5 批处理233 16.6 nil仍然可用234 16.7 处理未定义的键235 16.8 小结236 第17章 NSPredicate237 17.1 创建谓词237 17.2 燃料过滤器239 17.3 格式说明符240 17.4 运算符241 17.4.1 比较和逻辑运算符242 17.4.2 数组运算符243 17.5 SELF足够了243 17.6 字符串运算符245 17.7 LIKE运算符245 17.8 小结246
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值