以#开头的行,是预处理指令,用于告诉编译器去做一些预处理工作。比如#inldue "xxx.h"
、NULL
、RAND_MAX
。#define
也是一种指令,称为宏定义,其常用来定义一个值或者一个式子。
define的常见用法
- 使用#define定义一个值
#define PI 3.14
void test()
{
double r = 2.0;
double area = PI*r*r;
}
预处理后的中间代码:
#define PI 3.14
void test()
{
double r = 2.0;
double area = 3.14*r*r;
}
- 使用#define定义带参数的式子
#define MAX(a,b) a > b ? a : b
void test()
{
int value = MAX(3,10);
}
预处理之后的中间代码:
#define MAX(a,b) a > b ? a : b
void test()
{
int value = 3 > 10 ? 3 : 10;
}
注意:#define代入的是文本,而不是值。
#define的取代方法
1.定义变量或者const 常量
const double PI = 3.14;
2.定义内联(inline)函数
inline int max(int a,int b)
{
return a > b ? a : b;
}
1.2 宏定义中#和##的妙用
#的用法
#
运算符作用在预编译时期,用于将宏参数转换为字符串,即是加上双引号。
#define STR(s) #s
void test()
{
char *str = STR(sds124);
printf("str = %s\n",str);
}
预编译后中间代码为:
void test()
{
char *str = "sds124";
printf("str = %s\n",str);
}
输出结果为str = sds124
##的用法
#include
#include
using namespace std;
#define CONS(a,b) int(a##e##b)
void test()
{
printf("%d\n",int(2e3));
printf("%d\n",CONS(2,3));
}
预编译后中间代码为:
void test()
{
printf("%d\n",int(2e3));
printf("%d\n",int(2e3));
}
2. volatile
2.1 volatile的作用
C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量。它是一种类型修饰符,用它声明的类型变量的值可以被某些编译器未知的因素(比如 *** 作系统、硬件、其他线程等)更改。
被这个关键词修饰的变量,编译器不会为访问这个变量的代码进行优化,并未其提供特殊地址的稳定访问。即系统总是要重新从它所在的内存中读取数据。
#include
int main()
{
int i = 10;
int a = i;
printf("a = %d\n",a);
//汇编语句,修改内存中i的内容,将其值改为32
//该语句编译器是不会知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("b = %d\n",b);
}
输出结果: a = 10
和 b = 10
明明内存i
的数值被修改为32,可输出结果b却等于10。这是为什么呢?这是因为编译器本身自带优化功能,它发现两次从内存i
中读数据的代码之间没有对内存i
*** 作的代码。此时它会自动把上次读的数据放在b上,而不是重新从内存i
中去读数据。、
使用volatile修饰变量可以解决这个问题。
volatile int i = 10;
int a = i;
printf("a = %d\n",a);
//汇编语句,修改内存中i的内容,将其值改为32
//该语句编译器是不会知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("b = %d\n",b);
输出结果:a = 10 b = 32
可以看到,b的内容改变了。这说明volatile关键字发挥了作用。
2.2 多线程下的volatile其实不止内嵌汇编 *** 作栈这一种方式是编译器无法识别变量值改变的。此外还有多线程并发访问共享变量时,一个线程改变了变量的值,怎么让改变后的值让其他线程知道,这种情况也需要volatile。
当两个线程都要用到某一个变量,其该变量的值会被改变时,应该用volatile声明,该关键字的作用就是防止编译器优化,把变量从内存装入CPU寄存器中。若变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这就会造成程序的错误执行。而volatile可以让编译器每次 *** 作该变量时一定要从内存中取值,而不是使用已经存在寄存器中的值。
volatile int stop = false;
线程1:
void run()
{
while(!stop){
//...
}
stop = false;
return;
}
线程2:
void run()
{
stop = true;
while(!stop);
}
如果stop不使用volatile申明,那么这个循环将是一个死循环,因为sop已经读取到了寄存器中,寄存器中stop的值永远不会变成false。而加上volatile修饰后,程序在执行时,每次都从内存中读出stop的值,就不会出现死循环了。
2.3 volatile的应用场景volatile一般用在如下几个地方:
- 中断服务程序中修改供其他程序检测的变量需要加volatile;
- 多任务环境下各任务间共享的标志位应该加volatile;
- 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同的意义
当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。
a.c的内容:
char a = 'A';
void msg()
{
printf("Hello\n");
}
main.c的内容:
int main(void)
{
extern char a; // 用extern声明变量a是定义在外部文件的
extern void msg();// 用extern声明函数msg是定义在外部文件的
printf("%c ", a);
msg();
return 0;
}
程序可以成功运行,结果是 A Hello
如果加了stataci去修饰变量和函数,那么它们会对其他源文件隐藏。比如在a.c中变量a和函数msg的定义前加上static,main.c就看不到它们,无法使用它们了。利用这个特性可以在不同的文件中定义同名函数和变量,而不必担心命名冲突。使用static做函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量来说,static还有两个作用。
3.2 static修饰保持变量内容的持久共有两种变量是存储在静态存储区,分别是全局变量和static变量。对于存储在静态存储区的变量来说,它们会在程序刚开始运行时就完成初始化,而且也是唯一的一次初始化。初始化后一直保存在内存区域,且生命周期很长,与程序共存亡。而对于auto变量来说,是存放在栈区。一旦函数调用结束,就会立刻被销毁。
int fun(void)
{
static int count = 10;//事实上该语句没有执行过
return count--;
}
int main()
{
for(int i = 0; i < 10; i++)
printf("%d\n",fun());
return 0;
}
3.3 static修饰变量,其内容默认初始化为0
在静态数据区中,内存中内容默认值都是0x00。因此在定义全局变量和static变量时默认初始化为0。利用这一个特性,可以减少程序员一些工作量。比如初始化一个稀疏矩阵和字符数组,需要将元素一个个置0或’\0’,此时若将矩阵数组和字符数组都用static修饰,可以省下置0 *** 作。
4. extern的作用在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
4.1 extern对变量的作用若main.c要引用a.c中的变量char a,可以直接在main.c中用extern修饰变量a,即extern char a;
。然后main.c就可以引用变量变量a。一般来说,能够被其他文件模块以extern修饰符引用到的变量通常都是全局变量。此外,extern char a;
这个语句可以放在main.c中的任何一个地方。放在不同的地方,其引用的作用域也会有所不同。若放在函数里面,代表只在函数fun内部作用域中引用变量而已。
a.c的内容:
char a = 'A';
void msg()
{
printf("Hello\n");
}
main.c的内容:
int main(void)
{
extern char a; // 用extern声明变量a是定义在外部文件的
printf("%c ", a);
return 0;
}
4.2 extern对函数的作用
extern修饰函数声明。从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。比如在a.c中原型是void msg(),那么就可以在main.c中声明extern void msg();
,然后就能使用msg来做任何事情。就像变量的声明一样,extern void msg();
可以放在main.c中任何地方,而不一定非要放在main.c的文件作用域的范围中。
a.c的内容:
char a = 'A';
void msg()
{
printf("Hello\n");
}
main.c的内容:
int main(void)
{
extern char a; // 用extern声明变量a是定义在外部文件的
extern void msg();// 用extern声明函数msg是定义在外部文件的
printf("%c ", a);
msg();
return 0;
}
4.3 extern可以加速程序编译过程
对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件,且extern可以省略。
extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这样有个明显的好处就是可以加速程序的编译(加快预处理过程),节省时间。对于大型C程序编译过程,这种差异是很明显的。
4.4 extern "C"的作用由于C++和C程序编译完成后再目标代码中命名规则不同,所以当我们需要在C++代码调用用C语言实现的模块代码时,此时要在被调用的文件模块中使用extern "C";
,指示编译器这部分代码按C语言规范进行编译,而不是C++。
后半部分内容可以参考这篇笔记
5. const的作用 6. new/delete与malloc/free 7. '\0’的含义 8. sizeof和strlen 9. 结构体和共用体 10. 左值和右值 11.短路求值 12. 前置自增与后置自增欢迎分享,转载请注明来源:内存溢出
评论列表(0条)