CC++关键词必知必会(一)

CC++关键词必知必会(一),第1张

C/C++关键词必知必会 1. 宏定义define和#、## 1.1define的常见用法及取代办法

以#开头的行,是预处理指令,用于告诉编译器去做一些预处理工作。比如#inldue "xxx.h"NULLRAND_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 = 10b = 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一般用在如下几个地方:

  1. 中断服务程序中修改供其他程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志位应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同的意义
3. static的作用 3.1. static修饰全局变量或函数具有隐藏的作用

当我们同时编译多个文件时,所有未加 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. 前置自增与后置自增

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/676131.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-19
下一篇 2022-04-19

发表评论

登录后才能评论

评论列表(0条)

保存