,这条语句等价于 j = i+10; 当然,我们希望的是 j = (i+1)*10; 在宏定义中缺少圆括号会导致C语言中最让人讨厌的错误。 上面提到了两种将宏定义为空的定义方式,看上去一样,实际上只要明白了宏都只是简单的代码替换就知道该如何选择了。 8. 预定义宏 在C语言中预定义了一些有用的宏, 见表预定义宏。 实际上,C语言库提供了一个通用的、用于错误检测的宏——assert宏 再如: #line 838 "Zend/zend_language_scanner.c" #line预处理用于改变当前的行号 如上所示代码,将当前的行号改变为838,文件名Zend/zend_language_scanner.c 它的作用体现在编译器的编写中,我们知道 编译器对C 源码编译过程中会产生一些中间文件,通过这条指令 C语言中常用的宏 01: 防止一个头文件被重复包含 #ifndef COMDEF_H #define COMDEF_H //头文件内容 #endif 02: 重新定义一些类型
本文链接:https://blog.csdn.net/solaraceboy/article/details/102729793 C语言中宏的定义与使用 三种类型的预处理指令 宏定义 宏是比较常用的一种预处理指令 ,这里我们主要讨论带参数的宏。 带参数的宏(函数式宏)定义如下: #define EXAMPLE(x,y,z) 替换列表 注意:在宏的名字和左括号之间没有空格。 宏的优点: 程序可能会更快一些; 宏更同意。 宏的缺点: 编译后代码通常会变大。 宏的参数没有类型检查。 无法用指针来指向一个宏。 宏可能会不止一次地计算它的参数。 文件包含 条件编译 适用于预处理指令的规则 指令都以 # 开始 在指令的符号之间可以插入任意数量的空格和水平制表符。 指令总是在第一个换行符处结束,除非明确地指明要延续。
代码传递思想,技术创造回响!Techo Day热忱欢迎每一位开发者的参与!
******* ********* ******* ******* ***** ***** *** *** * * 同时,如果我们所要显示的不是 *,而是任何一个字符,其参数为DispChar ; char DispChar='*'; 对于该参数我们可以参用输入的方式。 以下是我们本次的作业,根据上述分析,设计程序,要求四个输入参数:DispChar,n ,m ,k; 假如有时间,还可以进行详细设计,设计出更多、更精细的程序,譬如,将4个参数放在一个配置文件中,将输出直接送到文件中等等 实现方法: 1、直接printf输出, 多字符输出 printf(“******”) 2、采用循环,单字符输出 printf(“*”) 3、动态给出格式化字符数据长度n,通过 printf("n%c”, str); 输出 4、构建数组,先初始化数组,然后输出数组 5、直接计算每个*在屏幕中的显示位置,将光标移动所确定位置上,进行输出 6、。。。
有同学写过或者想写这样的宏定义吗? 求两个或几个数的乘积: #define SQU(x) x*x 我们正常使用没有问题: ? 但如果这样写呢? ? 原因在于,宏定义的本质是文本替换!所以在预处理期间SQU(5+5)这段代码被宏替换为5+5*5+5,结果因为乘法优先级高于加法,变成5+25+5,可想而知! 那么解决这个问题的办法,相信大家看完之后心里应该有答案了,就是给x加个小括号,使它变成一个整体,如下: ? 就可以解决了。 然而,这并不没有完! 与此类似的,当我们想算两个数的和的时候呢? 为嘛不是20*20的400呢? 还是遵循宏的本质,我们展开来看:10+10*10+10=120 又是一个优先级问题,又一个陷阱,防不胜防呀! 那怎么解决呢?答:干脆一了百了,整体全加括号吧! 你的宏,从此百毒不侵! 有什么学习中遇到的问题,请联系我们! C语言研究中心(www.dotcpp.com)
咳咳咳,今天讲讲C中宏定义(片面),希望对小伙伴们有帮助,开始了: 有同学写过或者想写这样的宏定义吗? 原因在于,宏定义的本质是文本替换!所以在预处理期间SQU(5+5)这段代码被宏替换为5+5*5+5,结果因为乘法优先级高于加法,变成5+25+5,可想而知! 那么解决这个问题的办法,相信大家看完之后心里应该有答案了,就是给x加个小括号,使它变成一个整体,如下: ? 就可以解决了。 然而,这并不没有完! 与此类似的,当我们想算两个数的和的时候呢? 为嘛不是20*20的400呢? 还是遵循宏的本质,我们展开来看:10+10*10+10=120 又是一个优先级问题,又一个陷阱,防不胜防呀! 那怎么解决呢?答:干脆一了百了,整体全加括号吧! 你的宏,从此百毒不侵!
va_arg宏,是头文件 stdarg.h 中定义的,获取可变参数的当前参数。 如果你熟悉c++中内存模型就应该明白。array 在内存栈或者堆中是连续的一段空间。 如果我们对一个数组 int a[10]进行a[-1]操作,那么就可能出现错误,因为我们这时候出现了不可控的指针操作,返回的值是不可预料的。 为了能够构造 a[-1]的操作,我们进行如下构造,并比较了内存地址的值(va_list.c): #include <stdio.h> int main(){ int a[]={1, 2, 3, ); printf("paddr=%d, aaddr=%d, addr2=%d\n", &p[-1], &a[0], a+0); return 0; } 编译: cc va_list.c
一、文件包含相关预处理命令 #include #include "header" C语言中使用的包含文件的指令""和<>的区别为,""是从当前目录开始寻找文件,<>是从系统库中寻找文件 #include_next "header" #include_next 这两个指令是C中的指令,OC也支持,只是很少使用,它的作用是在找到名字匹配的头文件后跳过,寻找下一个相同名字的导入 二、宏定义 宏定义是开发中会经常用到的一个指令了,我们还会将许多简单的函数定义为宏,省去系统压栈的时间,提高代码效率。因为这篇博客的主题是预处理命令,所以宏的用法和高级用法就不再多写了,下次再讨论。 五、更改文件名和行号 在OC中,有一个系统的定义的宏: __LINE__ 这个宏表示当前行的行号,可以打印。 #line number 改变当前行的行号,会影响下面所有的行 #line number "filename" 改变当前行号和编译后的文件名 六、编译器控制指令 #pragma 参数 这个预编译指令是最复杂的
C语言可变参数 C函数可变参数 c语言中使用可变参数最熟悉应该就是printf, 其是通过...来从代码语句中表示可变化的参数表。 比如我当前的模块名为moduleName,我就可以使用一个包含模块名、文件名、代码行号、函数名等来进行输出调试信息。 #endif //end for #ifdef _DEBUG 1) FILE 宏在预编译时会替换成当前的源文件名 2) LINE宏在预编译时会替换成当前的行号 3) FUNCTION宏在预编译时会替换成当前的函数名称 这里的可变主要指两点可变: 1.参数数量可变 2.参数类型可变 具体的实现主要是借助于C语言中这个头文件 #include <stdarg.h> /* va_list, va_start, va_arg +的可变参数模板 C/C++可变参数,“## VA_ARGS”宏的介绍和使用
对于C++语言的分配方式,原理是通过重载new操作符,让new执行到带文件名和行号参数的operator new函数上(注意这里是函数)。 所以,要想检测C语言分配的内存泄漏,就要包含头文件<crtdbg.h>,并且在包含头文件前,定义宏_CRTDBG_MAP_ALLOC。 并非绝对需要该宏定义,但如果没有该宏定义,内存泄漏转储包含的有用信息将较少。这是因为当没有包含这个宏时,malloc函数只接收size_t nSize参数,不再包含文件名和行号。 并且C++分配的内存,也需要调用_CrtDumpMemoryLeaks打印报告(可通过程序入口出调用_CrtSetDbgFlag来避免对_CrtDumpMemoryLeaks的直接调用)。 为了在程序结束时可以打印泄漏报告,在程序入口处调用: _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); MFC程序检测c和C+
一、前言 平时开发C语言程序时,经常需要调试代码,C语言有一些宏,可以打印出当前的行号、文件名称、日期、时间,对程序的调试起到很大的帮助,可以快速定位问题。 特别是开发单片机程序时,使用这些宏打印这些信息或者在LCD上显示程序的编译日期、时间,可以知道这个单片机上的固件是什么时候编译。帮助判断版本。 ANSIC标准定义了可供C语言使用的预定义宏: __LINE__ : 在源代码中插入当前源代码行号 __FILE__ : 在源代码中插入当前源代码文件名 __DATE__ : 在源代码中插入当前编译日期 ,如果程序稳定后,不需要打印调试信息,就可以将DEBUG的定义取消掉即可。 printf是一样的,通过这个函数就可以实现数据打印到任意地方,包括改成存储到SD卡上。
就比如 C 语言中的宏定义,好像跟我犯冲一样,我一直觉得宏定义是 C 语言中最难的部分,就好比有有些小伙伴一直觉得指针是 C 语言中最难的部分一样。 除了上面的操作系统相关宏,还有另一类宏定义,在日志系统中被广泛的使用: FILE:当前源代码文件名; LINE:当前源代码的行号; FUNCTION:当前执行的函数名; DATE:编译日期; TIME: 参数名的定义和使用 宏定义的参数个数可以是不确定的,就像调用 printf 打印函数一样,在定义的时候,可以使用三个点(...)来表示可变参数,也可以在三个点的前面加上可变参数的名称。 如果不需要打印语句,通过把打印日志信息那条语句定义为空语句来达到目的。 转发的推荐语已经帮您想好了: 道哥总结的这篇总结文章,写得很用心,对我的技术提升很有帮助。好东西,要分享! ----
,可以理解为系统当前的时间戳; ③ 最后一个方括号是指定的打印内容; 可让我感到非常疑惑不解的是: 第三个方括号中竟然打印的是该条打印语句所在的函数名称和所在文件中的位置(行数),并且打印出的行号和实际对应 揭晓谜底 其实,这些RTOS系统之所以准确的打印出了代码所在函数及所在位置,不是用于了多么复杂高深的技术,同样也只是在代码里巧妙的利用了C语言的一个不常用知识点 —— 编译器内置宏定义。 C语言编译器中内置了一些宏定义,这些内置宏定义可以巧妙地帮我们输出非常有用的调试信息,在RTOS的日志打印组件中通常用到了这三个内置宏定义: __FILE__:在源文件中插入当前源文件名; __FUNCTION __:在源文件中插入当前函数名; __LINE__:在源代码中插入当前源代码行号; 利用这三个宏定义,使用一行代码即可编写一个最简单的日志打印组件: #define DEBUG(format,...) RTOS中的完整日志打印组件 当然,一个完整的日志打印组件不能仅仅靠这一行代码来实现,还需要添加很多功能,比如: 设置日志输出等级,区分不同的日志输出; 底层使用自己优化后的printf函数; 添加宏定义控制输出信息是否启用
不信的话我们可以列举一下常见的预处理指令,预处理器有其区别于Objective-C的独特语法,语法形式如下: #指令名 指令参数 有点眼熟了? 而对于#include和#import这两者,区别在于#import可以确保头文件只被引用一次,这样就可以防止递归包含,什么叫递归包含,A引用B和C,B也引用了C,那就都包含了C,这就重复包含了。 条件编译 条件编译特别像我们在所有编程语言中都能看到的 if ... else if ... else 形式,也就是条件判断语句。 第三种诊断指令: #line 行号 "文件名" //假设这里有一行会发生错误的代码 这个指令理解起来有些复杂,首先line定义了一个行号,那么之后每一行都会有一个在此基础上依次加一的行号,比如下一行的错误代码就是第 预处理器之宏 要知道,宏也是预处理器范畴内的内容,我们用的也很多: // 定义常量值 #define 宏名 值 //定义函数宏 #define 宏名(参数) 代码 // 移除宏 #undef 宏名 宏被定义后
如果表达式的值为假,assert()宏就会调用_assert函数在标准错误流中打印一条错误信息,并调用abort()(abort()函数的原型在stdlib.h头文件中)函数终止程序。 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数。 可见,程序蹦的同时还会在标准错误流中打印一条错误信息: Assertion failed:c, file hello.c, line 12 这条信息包含了一些对我们查找bug很有帮助的信息:问题出在变量 这时候细心的朋友会发现,上边我们对assert()的介绍中,有这么一句说明:如果表达式的值为假,assert()宏就会调用_assert函数在标准错误流中打印一条错误信息,并调用abort()(abort ; abort(); } 这样,也可以给我们起到提示的作用: ? 但是,使用assert()至少有几个好处: 1)能自动标识文件和出问题的行号。
不过本文讲解的微软DBUG的CRT库采用的是另外的方式,记录内存申请时候文件名和行号等信息。这样虽然没有函数调用栈精确,但是也基本可以用于定位问题了。 接下来看看_CrtMemBlockHeader是如何记录调用相关的信息的呢? 我们看下它的结构便一目了然。其是一个双向链表的节点,有前后指针,还有文件名,行号等。 看到这里可能有同学会发现了,那还有C++的关键字new和delete呢。首先我们要知道new是C++的关键字,对于有构造函数的类一般做了以下两个事情: 申请对象所需的内存空间。 这个时候其实就是遍历上述的双向链表,查看正在使用的内存,并将其打印到Visual Studio的output窗口中。 就是通过在申请的内存头部记录当前分配内存的相关信息,比如文件名和行号,并且通过双向链表将所有申请的节点串起来。然后在合适的时间点(比如感知到内存泄露的情况下)打印出可能的内存泄露的内存关联的信息。
c); return0; } 打印输出为:a=2 b=0 c=2 亲们不知道做对没有? 呵呵 接下来进入我们这篇的主题-聊聊C语言中预处理功能中的宏定义。 ? C代码如何变成可执行程序? C语言的宏预处理器的工作只是简单的文本搜索和替换。 C语言怎么定义宏? 在C语言中定义宏我们用的关键字是#define ? C语言中宏定义的分类 不带参数的宏定义 格式:#define 标识符 字符串 其中的标识符就是所谓的符号常量,也称为“宏名”。 C语言中宏的使用 用无参宏定义一个简单的常量 例:#define LEN 20 带参宏一般用法 例:#define MAX(a,b)((a)>(b)?
宏指令 不得不重复进行的某些文本编辑任务会让人觉得很烦躁,做那些需要操作者重复几十次的任务就更糟糕了。宏指令能够有效地解除这些麻烦。 • 需要重复保存的系列动作时,输入‘@a’即可。 2. 相对行号 没人喜欢心算。即使你能心算得很快,算出23=23总要快于141-118=23。 相对行号和绝对行号不同,它显示的是你光标所在行的相对数字。这个功能不仅便利了删除行的操作,也使跳到指定位置的过程变得更为简洁。 你可以在你的.vimrc中加入: cnoremap kj <C-C> cnoremap jk <C-C> 这样,你就可以通过键入’jk’或者‘kj’来离开一个指令,同时,你的手一直呆在主键区。 5. w’词(word) • ‘(’插入语(parenthesis) • ‘t’标签(tag) • ‘s’句子(sentence) • ‘“’引用 举个例子,‘diw’就是删除光标所在的词,无论光标是在词首还是词尾
注意:由于历史原因, runtime.Caller 和 runtime.Callers 中的 skip 含义并不相同, 后面会讲到. 下面是一个简单的例子, 打印函数调用的栈帧信息: ? 其中 skip = 0 为当前文件("caller.go")的 main.main 函数, 以及对应的行号. 这里省略的无关代码, 因此输出的行号和网页展示的位置有些差异. 通过查阅 runtime/proc.c 文件的代码, 我们可以知道对应的函数分别为 runtime.main 和 runtime.goexit. 这样就可以方便的输出函数调用者的信息了. Go语言中函数的类型 在Go语言中, 除了语言定义的普通函数调用外, 还有闭包函数/init函数/全局变量初始化等不同的函数调用类型. 因此, 程序的入口也就不是自己写的 main.main 函数了. 2015.06.09补充: 更深入的可以看下这个文章 GO语解惑:从源码分析GO程序的入口 总结 Go语言 runtime 包的 runtime.Caller
'a' '\0' 'a'字符常量,字符常量只能是一个ASCII字符 int 4个BYTE、2个WORD 、1个DWORD c语言中不能直接书写二进制,用8进制和16进制来替代(和默认的十进制 b, c); //打印结果测试下 if (c == '+') { printf("%d\n", a + b); } else if (c == '-') { b, c); //打印结果测试下 switch (c) { case '+': printf("%d\n", a + b); break; case '- printf(" *\n"); printf(" ***\n"); printf(" *****\n"); 分析: 每一行的*和行号的关系是:行号 * 2 - 1 每一行的*和减号的关系是 :行号 - 1;行号 - 2;行号 - 3;......
校园优惠套餐升级,云服务器1核2G10元/月起购
扫码关注云+社区
领取腾讯云代金券