关键字register static const #define用法总结

关键字registe请求编译器尽可能的将变量存在CPU的寄存器中。有以下几点注意的地方。

对register的使用是有限制条件的,这个体现在具体硬件上。

每个函数中只有很少的变量能够存放在寄存器中,而且,只是确定的类型可以存放在寄存器中。过多的使用寄存器变量并没有坏处,然而,由于“register”会被忽略的。

特别注意:不能用&取地址符号,去获取一个register变量的地址,无论这个变量是否被实际得存放在寄存器里了

register变量必须是能被CPU寄存器所接受的类型,这通常意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。但是,有些机器的寄存器也能存放浮点数。

register变量可能不存放在内存中,所以不能用取址符运算符“ & ”。

只有局部变量和形参可以作为register变量,全局变量不行。

静态变量不能定义为register。

static用法小结

static关键字是C, C++中都存在的关键字, 它主要有三种使用方式, 其中前两种只指在C语言中使用, 第三种在C++中使用(C,C++中具体细微操作不尽相同, 本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数
下面就这三种使用方式及注意事项分别说明

一、局部静态变量
在C/C++中, 局部变量按照存储形式可分为三种auto, static, register
(<c语言程序设计(第二版)>谭浩强, 第174-175页)
与auto类型(普通)局部变量相比, static局部变量有三点不同
1. 存储空间分配不同
auto类型分配在栈上, 属于动态存储类别, 占动态存储区空间, 函数调用结束后自动释放, 而static分配在静态存储区, 在程序整个运行期间都不释放. 两者之间的作用域相同, 但生存期不同.
2. static局部变量在所处模块在初次运行时进行初始化工作, 且只操作一次
3. 对于局部静态变量, 如果不赋初值, 编译期会自动赋初值0或空字符, 而auto类型的初值是不确定的. (对于C++中的class对象例外, class的对象实例如果不初始化, 则会自动调用默认构造函数, 不管是否是static类型)

特点: static局部变量的”记忆性”与生存期的”全局性”
所谓”记忆性”是指在两次函数调用时, 在第二次调用进入时, 能保持第一次调用退出时的值.
示例程序一
#include <iostream>

using namespace std;

void staticLocalVar()
{
 static int a = 0; // 运行期时初始化一次, 下次再调用时, 不进行初始化工作
 cout<<"a="<<a<<endl;
 ++a;
}

int main()
{
 staticLocalVar(); // 第一次调用, 输出a=0
 staticLocalVar(); // 第二次调用, 记忆了第一次退出时的值, 输出a=1
 return 0;
}

应用:
 利用”记忆性”, 记录函数调用的次数(示例程序一)
   利用生存期的”全局性”, 改善”return a pointer / reference to a local object”的问题. Local object的问题在于退出函数, 生存期即结束,. 利用static的作用, 延长变量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
 static char strBuff[16]; // static局部变量, 用于返回地址有效
 const unsigned char *pChIP = (const unsigned char *)&IpAddr;
 sprintf(strBuff, "%u.%u.%u.%u",  pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
 return strBuff;
}

注意事项:
1. “记忆性”, 程序运行很重要的一点就是可重复性, 而static变量的”记忆性”破坏了这种可重复性, 造成不同时刻至运行的结果可能不同.
2. “生存期”全局性和唯一性. 普通的local变量的存储空间分配在stack上, 因此每次调用函数时, 分配的空间都可能不一样, 而static具有全局唯一性的特点, 每次调用时, 都指向同一块内存, 这就造成一个很重要的问题 ---- 不可重入性!!!
这样在多线程程序设计或递归程序设计中, 要特别注意这个问题.
(不可重入性的例子可以参见<effective C++ (2nd)>(影印版)第103-105页)
下面针对示例程序二, 分析在多线程情况下的不安全性.(为方便描述, 标上行号)
① const char * IpToStr(UINT32 IpAddr)
② {
③  static char strBuff[16]; // static局部变量, 用于返回地址有效
④  const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤  sprintf(strBuff, "%u.%u.%u.%u",  pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥  return strBuff;
⑦ }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数, 将32位的IP地址转换成点分10进制的字符串形式. 现A先获得执行机会, 执行IpToStr(), 传入的参数是0x0B090A0A, 顺序执行完应该返回的指针存储区内容是:”10.10.9.11”, 现执行到⑥时, 失去执行权, 调度到B线程执行, B线程传入的参数是0xA8A8A8C0, 执行至⑦, 静态存储区的内容是192.168.168.168. 当再调度到A执行时, 从⑥继续执行, 由于strBuff的全局唯一性, 内容已经被B线程冲掉, 此时返回的将是192.168.168.168字符串, 不再是10.10.9.11字符串.

二、外部静态变量/函数
在C中static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。, 但为了限制全局变量/函数的作用域, 函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
示例程序三:
 
//file1.cpp

static int varA;
int varB;
extern void funA()
{
……
}

static void funB()
{
……
}

//file2.cpp

extern int varB; // 使用file1.cpp中定义的全局变量
extern int varA; // 错误! varA是static类型, 无法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定义的函数
extern void funB(); // 错误! 无法使用file1.cpp文件中static函数

三、静态数据成员/成员函数(C++特有)
C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数. 这是与普通成员函数的最大区别, 也是其应用所在, 比如在对某一个类的对象进行计数时, 计数生成多少个类的实例, 就可以用到静态数据成员. 在这里面, static既不是限定作用域的, 也不是扩展生存期的作用, 而是指示变量/函数在此类中的唯一性. 这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义. 因为它是对整个类来说是唯一的, 因此不可能属于某一个实例对象的. (针对静态数据成员而言, 成员函数不管是否是static, 在内存中只有一个副本, 普通成员函数调用时, 需要传入this指针, static成员函数调用时, 没有this指针. )
请看示例程序四(<effective c++ (2nd)>(影印版)第59页)
class EnemyTarget {
public:
  EnemyTarget() { ++numTargets; }
  EnemyTarget(const EnemyTarget&) { ++numTargets; }
  ~EnemyTarget() { --numTargets; }
  static size_t numberOfTargets() { return numTargets; }
  bool destroy();   // returns success of attempt to destroy EnemyTarget object
private:
  static size_t numTargets;               // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;

在这个例子中, 静态数据成员numTargets就是用来计数产生的对象个数的.
另外, 在设计类的多线程操作时, 由于POSIX库下的线程函数pthread_create()要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用Static成员函数做线程函数.

const用法主要是防止定义的对象再次被修改,定义对象变量时要初始化变量

下面我就介绍一下几种常见的用法

1.用于定义常量变量,这样这个变量在后面就不可以再被修改

 const int Val = 10;

 //Val = 20; //错误,不可被修改

 

2. 保护传参时参数不被修改,如果使用引用传递参数或按地址传递参数给一个函数,在这个函数里这个参数的值若被修改,

则函数外部传进来的变量的值也发生改变,若想保护传进来的变量不被修改,可以使用const保护

 void  fun1(const int &val)

  {

     //val = 10; //出错

}

void fun2(int &val)

{

   val = 10; //没有出错

}

void main()

{

   int a = 2;

   int b = 2;

   fun1(a); //因为出错,这个函数结束时a的值还是2

   fun2(b);//因为没有出错,函数结束时b的值为10

}

如果只想把值传给函数,而且这个不能被修改,则可以使用const保护变量,有人会问为什么不按值传递,按值传递还需要把这个值复制一遍,

而引用不需要,使用引用是为了提高效率//如果按值传递的话,没必要加const,那样根本没意义

 

3. 节约内存空间,

 #define  PI  3.14 //使用#define宏

 const double Pi = 3.14 //使用const,这时候Pi并没有放入内存中

 

 double  a = Pi;  //这时候才为Pi分配内存,不过后面再有这样的定义也不会再分配内存

 double  b = PI;  //编译时分配内存

 double  c = Pi;  //不会再分配内存,

 double  d = PI;  //编译时再分配内存

const定义的变量,系统只为它分配一次内存,而使用#define定义的常量宏,能分配好多次,这样const就很节约空间

 

4.类中使用const修饰函数防止修改非static类成员变量

 class

{

 public:

  void fun() const //加const修饰

   {

     a = 10; //出错,不可修改非static变量

     b = 10; //对,可以修改

}

 private:

  int  a ;

  static int b;

}

 

5.修饰指针
const int *A; 或 int const *A;  //const修饰指向的对象,A可变,A指向的对象不可变
int *const A;               //const修饰指针A, A不可变,A指向的对象可变
const int *const A;           //指针A和A指向的对象都不可变

 

6.修饰函数返回值,防止返回值被改变

  const int fun();

  接收返回值的变量也必须加const

  const int a = fun(); //接收的变量也要是const的,int a = fun()是错误的

 

7.修饰类的成员变量

  使用const修饰的变量必须初始化,在类中又不能在定义时初始化,

如;

class

{

private:

  int a = 10;

  const int b = 10;

  static const int c = 10;

//这样初始化都是错的,

}

 

初始化const int类型(没有static),在类的构造函数上初始化

Class Test

{

Public:

  Test():b(23) //构造函数上初始化b的值为23

   {

}

private:

     const int b ;

}

初始化staticconst int这个类型的(带有static的),在类的外面初始化

class Test

{

private:

  static const int c;

} 

const int Test::c=10; //类的外部初始化c为10

 

8.const定义的对象变量只能作用于这个程序该C/C++文件,不能被该程序的其他C/C++文件调用,

 如file1.cpp中 const int val;

 在file2.cpp中, extern intval; //错误,无法调用,

要想const定义的对象变量能被其他文件调用,定义时必须使用extern修饰为

extern const int val;

 

非const变量默认为extern,要是const能被其他文件访问必须显示指定为extern

先来讲一讲#define的优点与缺点:
[优点]

当在程序中想要修改某一个值得时候,而且这个值又在很多地方引用。这时候就能体现出宏定义的强大优点了。
例如:

#define LINK LED0   //定义LINK的灯为LED0
  • 1

因为在程序中我们可能很多地方要对LINK灯进行操作,当我们想换LINK灯的时候,直接将:

#define LINK LED1
  • 1

这样就很方便了,不用一处处去找,而且不用担心有漏掉的可能。
[缺点]
宏定义有一些缺点:
(1) 无法对宏定义中的变量进行类型检查
此缺点,是相对于const变量来说的

[define与const的区别的简单总结]

define定义的变量,是Compile-Time时期的变量,系统在编译时候,就将其全部替换,而不会对其变量进行类型等属性检查,相对不是很安全,可能存在潜在的问题,而没有发现.
正因为其仅仅是编译时期替换,所以其定义的变量,是不会在运行时候分配内存的,不占用内存空间.
const定义的变量,是 Run-Time时期的变量,如果类型不匹配,系统在运行时候,就会发现并提示或报错,对应的,const变量在运行时期,也是一种变量,系统会为其分配内存.
(2) 边界效应
A. 未加括号带来的边界效应
由于宏定义的时候,其各个分量未加括号,而在使用宏定义的时候,传递的参数是变量的表达式,然后经过系统展开后,由于优先级的原因,导致其结果不是你所希望的.
[例子]

#define MUL(A,B) A*B
  • 1

而在使用的时候,这样的调用:
int a=1,b=2,c=3,d=0;
d=MUL(a+b,c)
经过编译时候展开,就变成了
d=a+b*c
而不是我们所希望的
d=(a+b)*c
[解决办法]
其解决办法也很简单,就是给每个分量,都加上括号,就可以避免此类问题
即,在宏定义的时候,如此定义:

#define MUL(A,B) ((A)*(B))
  • 1
  1. #define的变体,即#ifndef,可以防止头头文件的重复引用
    [释]
    #ifdef和 #define组合,一般用于头文件中,用以实现防止多个文件对此同一个头文件的重复引用.实际使用中,即使你的头文件暂时没有被多个文件所引用,为了增加程序可读性,移植性,健壮性等,还是最好都加上。其用法一般为:
#ifndef <</SPAN>标识>
#define <</SPAN>标识>
………   // include or define sth.
#endif
  • 1
  • 2
  • 3
  • 4

<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的;标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,
如:stdio.h对应的就是:

#ifndef _STDIO_H_
#define _STDIO_H_
………   // include or define sth.
#endif
  • 1
  • 2
  • 3
  • 4

举个例子

#ifndef CHARDEV_H
#define CHARDEV_H

#include


#define MAJOR_NUM 100
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

……

……

#endif

例子中的chardev.h中,就用到了

#ifndef CHARDEV_H
#define CHARDEV_H
…
#endif
  • 1
  • 2
  • 3
  • 4

这样做之后,以后某个文件引用此头文件,就包含了chardev.h文件,而其他文件如果再引用此头文件的话,编译器那么就会判断出来,已经define了CHARDEV_H,已经有其他文件引用了此文件,so,the complier will not include this header file. 就可以发现解决重复引用头文件的问题了.

2. #define的变体,即#ifdef,可以实现加入自己需要的模块(源文件)
[例子]
在源文件中加入

#ifdef MYSELF_H
#include "myself.c"
#endif
  • 1
  • 2
  • 3

可以实现在源文件中加入myself.c的代码,将其实现的功能加进来, 即加入了myself模块
3. 简单的define定义

#define MAXTIME 1000
  • 1

一个简单的MAXTIME就定义好了,它代表1000,如果在程序里面写

if(i<MAXTIME){.........}
  • 1

编译器在处理这个代码之前会对MAXTIME进行处理替换为1000。

这样的定义看起来类似于普通的常量定义CONST,但也有着不同,因为define的定义更像是简单的文本替换,而不是作为一个量来使用,这个问题在下面反映的尤为突出。
4. define的“函数定义”
define可以像函数那样接受一些参数,如下

#define max(x,y) (x)>(y)?(x):(y);
  • 1

这个定义就将返回两个数中较大的那个,看到了吗?因为这个“函数”没有类型检查,就好像一个函数模板似的,当然,它绝对没有模板那么安全就是了。可以作为一个简单的模板来使用而已。

但是这样做的话存在隐患,例子如下:

#define Add(a,b) a+b;
  • 1

在一般使用的时候是没有问题的,但是如果遇到如:c * Add(a,b) * d的时候就会出现问题,代数式的本意是a+b然后去和c,d相乘,但是因为使用了define(它只是一个简单的替换),所以式子实际上变成了

c*a + b*d
  • 1

另外举一个例子:

#define pin (int*);
  • 1
pin a,b;
  • 1

本意是a和b都是int型指针,但是实际上变成int* a,b;
a是int型指针,而b是int型变量。

这是应该使用typedef来代替define,这样a和b就都是int型指针了。
即:

typedef pin (int *)

pin a,b;
  • 1
  • 2
  • 3

此时:a和b都是 int*型的。
所以我们在定义的时候,养成一个良好的习惯,建议所有的层次都要加括号。
5. 宏的单行定义

#define A(x) T_##x
#define B(x) #@x
#define C(x) #x
  • 1
  • 2
  • 3

我们假设:x=1,则有:

A(1)------〉T_1
B(1)------〉'1'
C(1)------〉"1"
  • 1
  • 2
  • 3

6. define的多行定义

define可以替代多行的代码,例如MFC中的宏定义(非常的经典,虽然让人看了恶心)

#define MACRO(arg1, arg2) do { /
/* declarations */ /
stmt1; /
stmt2; /
/* ... */ /
} while(0) /* (no trailing ; ) */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

关键是要在每一个换行的时候加上一个”/”

7 .在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的功能是条件编译。

即:

#ifdef WINDOWS
......
......
#endif
#ifdef LINUX
......
......
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以在编译的时候通过#define设置编译环境

8. 如何定义宏、取消宏

//定义宏
#define [MacroName] [MacroValue]
//取消宏
#undef [MacroName]
//普通宏
#define PI (3.1415926)

带参数的宏
#define max(a,b) ((a)>(b)? (a),(b))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

关键是十分容易产生错误,包括机器和人理解上的差异等等。

9. 条件编译

#ifdef XXX…(#else) … #endif
例如
#ifdef DV22_AUX_INPUT
#define AUX_MODE 3 
#else
#define AUY_MODE 3
#endif
#ifndef XXX … (#else) … #endif

猜你喜欢

转载自blog.csdn.net/linux12121/article/details/74857900