预处理指令

什么是预处理指令


源代码指定了程序的定义,预处理指令(preprocessor directive)指示编译器如何处理源代码。例如,在某些情况下,我们可能希望编译器忽略一部分代码,而在其他情况下,我们可能希望代码被编译。预处理指令给了我们这样的选项。
在C和C++中有实际的预处理阶段,此时预处理程序遍历源代码并且为之后的编译阶段准备文本输出流,在C#中没有实际的预处理程序。“预处理”指令由编译器来处理,而这个术语保留了下来。

基本规则


下面是预处理指令的最重要的一些语法规则。

  • 预处理指令必须和C#代码在不同的行
  • 与C#语句不同,预处理指令不需要以分号结尾
  • 包含预处理指令的每一行必须以#字符开始
    • 在#字符前可以有空格
    • 在#字符和指令之间可以有空格
  • 允许行尾注释
  • 在预处理指令所在的行不允许分隔符注释

这里的一些示例阐释了这些规则:

  1. #define PremiumVersion //没有分号 正确
  2. #define BudgetVersion //前面有空格 正确
  3. # define MediumVersion //中间有空格 正确
  4. 不允许分隔符注释

  5. #define PremiumVersion /* all bells & whistles */ 错误
  6.  
  7. #define BudgetVersion // Stripped-down version 允许行尾注释 正确

#define和#undef指令


编译符号是只有两种可能状态的标识符,要么被定义,要么未被定义。编译符号有如下特性。

  • 它可以是除了true或false以外的任何标识符,包括C#关键字,以及在C#代码中声明的标识符,这两者都是可以的
  • 它没有值。与C和C++不同,它不表示字符串

如上表所示:

  • #define指令声明一个编译符号
  • #undef指令取消定义一个编译符号
  1. #define PremiumVersion
  2. #define EconomyVersion

  3. #undef PremiumVersion

#define#undef指令只能用在源文件的第一行,也就是任何C#代码之前使用。在C#代码开始后,#define#undef指令就不能再使用。

  1. using System; // C#代码的第1行
  2. #define PremiumVersion //错误
  3. namespace Eagle
  4. {
  5. #define PremiumVersion //错误

编译符号的范围被限制于单个源文件。只要编译符号在任何C#代码之前,重复定义已存在的编译符号也是允许的。

  1. #define AValue
  2. #define BValue
  3.  
  4. #define AValue //重复定义

条件编译


条件编译允许我们根据某个编译符号是否被定义标注一段代码被编译或跳过。
有4个指令可以用来指定条件编译:

  • #if
  • #else
  • #elif
  • #endif

条件是一个返回true或false的简单表达式。

  • 如下表所总结的,条件可以由单个编译符号、符号表达式或操作符组成。子条件可以使用圆括号分组
  • 文本true或false也可以在条件表达式中使用


如下是一个条件编译的示例:

  1. 表达式

  2. #if !DemoVersion

  3. #endif
  4. 表达式

  5. #if(LeftHanded && OemVersion)||完整版

  6. #endif
  7.  
  8. #if true //下面的代码片段总是会被编译

  9. #endif

条件编译结构


指令在条件编译结构中需要配对使用。只要有#if指令,就必须有配对的#endif#if#if ...#else结构如下图所示。

  • 如果#if结构中的条件运算结果为true,随后的代码段就会被编译,否则就会被跳过
  • #if...#else结构中,如果条件运算结果为true,CodeSection1就会被编译,否则,CodeSection2会被编译

例如,如下的代码演示了简单的#if...#else结构。如果符号RightHanded被定义了,那么#if#else之间的代码会被编译。否则,#else#endif之间的代码会被编译。


  1. #if RightHanded
  2. //实现右边函数的代码

  3. #else
  4. //实现左边函数的代码

  5. #endif

下图演示了#if ...#elif以及#if ...#elif...#else的结构。

  • #if...#elif结构中;

    • 如果Cond1运算结果为true,CodeSectionl就会被编译,然后就会继续编译#endif之后的代码
    • 否则,如果Cond2运算结果为true,CodeSection2就会被编译,然后会继续编译#endif之后的代码
  • 直到条件运算结果为true或所有条件都返回false,如果这样,结构中没有任何代码段会被编译,会继续编译#endif之后的代码
  • #if...#elif...#else结构也是相同的工作方式,只不过没有条件是true的情况下,会编译#else之后的代码段,然后会继续编译#endif之后的代码


如下的代码演示了#if...#elif...#else结构。包含程序版本描述的字符串根据定义的编译符号被设置为各种值。

  1. #define DemoVersionWithoutTimeLimit

  2. const int intExpireLength = ;
  3. string strVersionDesc = null;
  4. int intExpireCount = ;
  5.  
  6. #if DemoVersionWithTimeLimit
  7. intExpireCount = intExpireLength;
  8. strVersionDesc = "This version of Supergame Plus will expire in 30 days";
  9. #elif DemoVersionWithoutTimeLimit
  10. strVersionDesc = "Demo Version of Supergame Plus";
  11. #elif OEMVersion
  12. strVersionDesc = "Supergame Plus, distributed under license";
  13. #else
  14. strVersionDesc = "The original Supergame Plus!!";
  15. #endif
  16. Console.WriteLine( strVersionDesc );

诊断指令


诊断指令产生用户自定义的编译时警告及错误消息。
下面是诊断指令的语法。Message是字符串,但是需要注意,与普通的C#字符串不同,它们不需要被引号包围。

  1. #warning Message
  2. #error Message

当编译器遇到诊断指令时,它会输出相关的消息。诊断指令的消息会和任何编译器产生的警告和错误消息列在一起。
例如,如下代码显式了一个#error指令和一个#warning指令。

  • #error指令在#if结构中,闪此只有符合#if指令的条件时才会生成消息
  • #warning指令用于提醒程序员回头来清理这段代码
  1. #define RightHanded
  2. #define LeftHanded
  3.  
  4. #if RightHanded && LeftHanded
  5. #error Can't build for both RightHanded and LeftHanded
  6. #endif
  7.  
  8. #warning Remember to come back and clean up this code!

行号指令


行号指令可以做很多事情,诸如:

  • 改变由编译器警告和错误消息报告的出现行数
  • 改变被编译源文件的文件名
  • 对交互式调试器隐藏一些行

#line指令的语法如下:

  1. #line integer //设置下一行值为整数的行的行号
  2. #line "filename" //设置文件名
  3. #line default //重新保存实际的行号和文件名
  4.  
  5. #line hidden //在断点调试器中隐藏代码
  6. #line //停止在调试器中隐藏代碼

#line指令加上一个整数参数会使编译器认为下面代码的行是所设置的行,之后的行数会在这个行数的基础上递增。

  • 要改变外观文件名,可以在双引号内使用文件名作为参数。双引号是必需的
  • 要返回真实行号和真实文件名,可以使用default参数
  • 要对交互调试器的断点调试功能隐藏代码段,可以使用hidden作为参数。要停止隐藏,可以使用不带任何参数的指令。到目前为止,这个功能大多用于在ASP.NET和WPF中隐藏编译器生成的代码。

下面的代码给出了行号指令的示例:

  1. #line 226
  2. x=y+z; //编译器将执行第226行

  3. #line 330 "SourceFile.cs"//改变报告的行号和文件名
  4. var1=var2+var3;

  5. #line default //重新保存行号和文件名

区域指令


区域指令允许我们标注和有选择性地命名一段代码。#region指令的特性如下:

  • 被放置在希望标注的代码段之上
  • 用指令后的可选字符串文本作为其名字
  • 在之后的代码中必须由#endregion指令终止

尽管区域指令被编译器忽略,但它们可以被源代码工具所使用。例如,Visual Studio允许我们很简单地隐藏或显式区域。
作为示例,下面的代码中有一个叫做Constructors的区域,它封闭了MyClass类的两个构造函数。在Visual Studio中,如果不想看到其中的代码,我们可以把这个区域折叠成一行,如果又想对它进行操作或增加另外一个构造函数,还可以扩展它。

  1. #region Constructors
  2. MyClass()
  3. {

  4. }
  5. MyClass(string s)
  6. {

  7. }
  8. #endregion

如下图所示,区域可以被嵌套。

#pragma warning 指令

#pragma warning指令允许我们关闭及重新开启警告消息。

  • 要关闭瞀告消息,可以使用disable加上逗号分隔的希望关闭的警告数列表的形式
  • 要重新开启警告消息,可以使用restore加上逗号分隔的希望关闭的警告数列表的形式

例如,下面的代码关闭了两个警告消息:618和414。在后面的代码中又开启了618警告消息,但还是保持414消息为关闭状态。

  1. 要关闭的警告消息

  2. #pragma warning disable 6l8, 414
  3. 列出的警告消息在这段代码中处于关闭状态
  4. #pragma warning restore 618

如果我们使用任一种不带警告数字列表的形式,这个命令会应用于所有警告。例如,下面的代码关闭,然后恢复所有警告消息。

  1. #pragma warning disable
  2. 所有警告消息在这段代码中处于关闭状态
  3.  
  4. #pragma warning restore
  5. 所有譬告消息在这段代码中处于开启状态
 
 
 
 

C#图解教程 第二十三章 预处理指令的更多相关文章

  1. C#图解教程 第二十一章 命名空间和程序集

    命名空间和程序集 引用其他程序集 mscorlib库 命名空间 命名空间名称命名空间的补充命名空间跨文件伸展嵌套命名空间 using 指令 using命名空间指令using别名指令程序集的结构 程序集 ...

  2. C#图解教程 第十三章 委托

    委托 什么是委托委托概述声明委托类型创建委托对象给委托赋值组合委托为委托添加方法从委托移除方法调用委托委托示例调用带返回值的委托调用带引用参数的委托匿名方法 使用匿名方法匿名方法的语法 Lambda ...

  3. Flask 教程 第二十三章:应用程序编程接口(API)

    本文翻译自The Flask Mega-Tutorial Part XXIII: Application Programming Interfaces (APIs) 我为此应用程序构建的所有功能都只适 ...

  4. 《Linux命令行与shell脚本编程大全》 第二十三章 学习笔记

    第二十三章:使用数据库 MySQL数据库 MySQL客户端界面 mysql命令行参数 参数 描述 -A 禁用自动重新生成哈希表 -b 禁用 出错后的beep声 -B 不使用历史文件 -C 压缩客户端和 ...

  5. [汇编学习笔记][第十三章int指令]

    第十三章int指令 13.1 int指令 格式: int n, n 为中断类型码 可以用int指令调用任何一个中断的中断处理程序(简称中断例程). 13.4 BIOS和DOS 所提供的中断例程 BIO ...

  6. Gradle 1.12用户指南翻译——第二十三章. Java 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  7. “全栈2019”Java多线程第二十三章:活锁(Livelock)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. “全栈2019”Java第二十三章:流程控制语句中决策语句switch上篇

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. 2017.2.15 开涛shiro教程-第二十一章-授予身份与切换身份(二) controller

    原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 开涛shiro教程-第二十一章-授予身份与切换身份(二) 1.回顾 ...

随机推荐

  1. 使用sed修改配置项的值

    起先我的想法是根据等号来求得配置项所在的行号, sed -n '/aaa/=' config.ini 然后根据行号删除这一行,再增加一行比如行号是9 sed -i  '9d' config.ini s ...

  2. Vue脚手架(vue-cli)安装总结

    单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序. 提供一 ...

  3. 使用IntelliJ IDEA(PHPStorm)和xdebug在firefox、chrome中远程调试PHP

    很多PHP程序员都习惯于使用echo.var_dump和exit来中断和调试web应用程序,本文主要介绍结合xdebug.IntelliJ IDEA.Firefox/chrome/IE来远程调试PHP ...

  4. Netty基础点滴

    编写一个应答服务器 编写一个应答服务器 写一个Netty服务器主要由两部分组成: 配置服务器功能,如线程.端口 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么 启动服务器 ...

  5. ssh快速登录远程服务器

    以前我的做法是创建一个shell脚本运行,今天了解了可以按照下面的方式: vi .ssh/config ,写入如下内容 HOST api User abc Port HostName 123.123. ...

  6. 浅谈Java SE、Java EE、Java ME三者的区别

    本文把JAVA SE.JAVA EE.JAVA ME拿来做下区别,同时也分享一下作者的一些成果.目前的Java平台根据软件开发人员.服务提供商和设备生产商可以针对特定的市场可以分为三个版本JAVA S ...

  7. 通过实例介绍持续集成的应用--基于Jenkins

    1.测试工程师为什么要掌握持续集成 一个程序员如果想发布一个产品,他需要编码.编译.测试,发布的过程.对于一个企业来说,如果也想发布一个产品的话,同样的也是需要上述的过程,区别在于企业要发布的产品的需 ...

  8. bzoj 2073 暴力

    2073: [POI2004]PRZ Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 442  Solved: 327[Submit][Status][D ...

  9. iptables转发备忘

    iptables -F sysctl net.ipv4.ip_forward=1 iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 8766 - ...

  10. python调用百度语音(语音识别-斗地主语音记牌器)

    一.概述 本篇简要介绍百度语音语音识别的基本使用(其实是斗地主时想弄个记牌器又没money,抓包什么的又不会,只好搞语音识别的了) 二.创建应用 打开百度语音官网,产品与使用->语音识别-> ...