1.其实质是一个宏名。由此我们可以防止发生重复定义或声明。
2.编程风格,使标识符含义更清晰易懂
假设你的头文件名为head.h,根据习惯,我们声明一个宏HEAD_H,对应这个头文件,在头文件中开始的地方和结尾的地方加上 对HEAD_H的声明和判断,头文件Head.h如下:
#ifndef HEAD_H
#define HEAD_H
……(头文件内容)
#endif
这样,头文件可以避免被多次包含。头文件中定义的变量不存在重复声明或定义。
对于 条件编译_HEADH_H前的下划线
- 是可以写成 LED.H只不过你接下来要写一句#defined LED.H 因为#ifndef 后面的字符串 不管是 LED.H还是__LED_H和头文件的名字没有半毛钱关系。只不过是为了读程序的时候方便而已。其实那个字符串只要合法的就行了。
- 用法 :
#ifndef 字符串 #define 上述字符串 #endif 一般放在头文件里面,作用就是以防你在.c文件里面不小心重复包含头文件的时候不会报错。 例如,现在你有 main.c LED.H 两个文件。 如果你在main函数里面两次包含头文件LED.H。即 #include “LED.h” #include “LED.h” int main(void) { return 0; } 那么你的程序肯定会报错。因为你的头文件重复添加了。
但是如果你在LED.H里面动一点手脚就把问题解决了,在LED.H添加如下代码 #ifndef abcdefg #define abcdefg #include “stm32f10x.h” void led_init(void); …//其他函数声明 #endif 就可以把问题解决。 分析如下:#ifndef 的是if no define----”如果没有定义“的意思。带#的是预编译命令,在编译之前执行。 如果没有定义abcdefg 那么就#define abcdefg 定义了abcdefg 当你多次包含LED.H这个头文件的时候。第一次肯定是没有定义的,所以肯定会执行 #define abcdefg 但是第二次,第三次,…以后再包含的时候,由于第一次已经定义过了abcdefg字符串,所以第二次的#ifndef abcdefg 不成立,所以往下的语句就不会执行。那么就不会重复包含头文件了。也不会把头文件里面声明过的函数再继续重复声明了。也就不会报错了。所以记住一点:#ifndef 在头文件里的用法是通过判断这个字符串是否被定义过,从而决定是否跳过某些语句来达到,条件编译,防止报错的效果的。 当然条件编译的用处很灵活看你怎么用。
小结一下:#if 表达式 程序段1 #endif 第二种: #ifndef 表达式 程序段2 #endif 第三种 #ifdefine 表达式 程序段3 #endif 以上是三种基本的条件编译,当然可以嵌套。
那_I,_O,_IO又是什么呢?
先看一下ST库里面的宏定义:
#define __I volatile const /*!< defines ‘read only’ permissions /
#define __O volatile /!< defines ‘write only’ permissions /
#define __IO volatile /!< defines ‘read / write’ permissions */
显然,这三个宏定义都是用来替换成 volatile 和 const 的,所以我们先要了解 这两个关键字的作用:
volatile
简单的说,就是不让编译器进行优化 ,即每次读取或者修改值的时候 ,都必须重新从内存或者寄存器中读取 或者修改。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
const
只读变量,即变量保存在只读静态存储区。编译时,如何尝试修改只读变量,则编译器提示出错,就能防止误修改。
const与define
两者都可以用来定义常量,但是const定义时,定义了常量的类型,所以更精确一些(其实const定义的是只读变量,而不是常量)。#define只是简单的文本替换,除了可以定义常量外,还可以用来定义一些简单的函数,有点类似内置函数。const和define定义的常量可以放在头文件里面。(小注:可以多次声明,但只能定义一次)
const与指针
int me;
const int * p1=&me; //p1可变,*p1不可变 const 修饰的是 p1,即p1不可变
int * const p2=&me; //p2不可变,*p2可变 const 修饰的是 p2,即p2不可变
const int *const p3=&me; //p3不可变,*p3也不可变 前一个const 修饰的是 *p3,后一个const 修饰的是p3,两者都不可变
前面介绍了 volatile 和 const 的用法,不知道大家了解了没?了解了后,下面的讲解就更加容易了:
__I :输入口。既然是输入,那么寄存器的值就随时会外部修改,那就不能进行优化,每次都要重新从寄存器中读取。也不能写,即只读,不然就不是输入而是输出了。
__O :输出口,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变,就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出
__IO:输入输出口,同上
为什么加下划线?
原因是:避免命名冲突
一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。
经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。
转载于[(https://blog.csdn.net/chuckfql/article/details/8684207?utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromMachineLearnPai2default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromMachineLearnPai2default-1.no_search_link)]