对于C++语言的考察,是大多数公司招聘计算机类职位的重点之一。而对于C++里的常考的一些关键字有宏定义、const常量、static静态变量以及其他几个存储关键字。
一、基本介绍
1、#define 宏定义
#define编译指令的工作方式与文本编辑器或字处理器的全局搜索和替换命令相似。它实际上是在进入编译器的预处理阶段,就把源码中相应定义替换成设定的值。从这里我们就可以发现一个潜在的问题,它实质上并没有经过编译器检验。
在《Effective ++》中的第一条就是这样写到的:尽量用const和inline而不用#define。其实它实际就是说“尽量使用编译器而非预处理”。(为什么是const和online将在后面讲到)
但是,既然#define存在,我们就需要了解如何正确的使用它。它的基本语法有两个要注意的地方,一是不能以分号结束,另一个就是括号的使用。例如:
#define SQR_ONE(x) (x * x)
#define SQR_TWO(x) ((x) * (x))
int main()
{
int a,b,c = 3;
a = SQR_ONE(c + 3);
b = SQR_TWO(c + 3);
cout<<”a = ”<<a<<” b = “<<b<<endl;
}
此时,第一个宏定义SQR_ONE在定义时没加括号,则a产生的表达式是c + 3 * c + 3,此时结果为a = 15;而第二个才是(c + 3)*( c + 3),得到想要的结果36。
2、const常量
关键字const叫做限定符,因为它限定了声明的含义。使用const的好处在于它允许指定一种语义上的约束——某种对象不能被修改,且这种约束由编译器来实施。通过const,我们可以通知编译器和其他程序员某个值要保持不变。
const关键字用法比较多,在类外面,它可以用于全局和命名空间常量,以及静态变量(某一文件或程序块范围内的局部对象);在类内部,它可以用于静态与非静态成员;也可以和指针搭配,形成const指针和指向const的指针。例如:
const int MONTHS = 12; //声明了一个整形常量MONTHS,且初始化为12
const char * p = “hello”; //声明了一个指向字符常量的指针
char * const q = “hello”; //声明了一个指向字符类型的指针常量
void fun1(const char * param1); //声明了一个函数,参数为一个指向常量的指针
void Time::fun2(const char * param2) const; //声明fun2为常成员函数,不能修改对象//中的数据成员
注:const必须在声明时完成初始化。在C中和C++的用法在开始时有点差异,在C中比C++限制更多,具体可参照可参考百度百科 CONST。
比如:const int n = 5;
int a[n]; //在ANSI C中将编译不通过,原因在它是只读变量,对于常量//使用#define和enum来定义
3、static静态变量
C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。加了static关键字后,则该对象存在在内存的静态存储区内(具体的c++内存存储方式将在后面单独总结)。
(1)面向过程中的static
在面向过程的static用法中,主要有以下几种:静态全局变量、静态局部变量以及静态函数。
静态变量不管是全局还是局部的,它的生命周期在整个程序运行中一直存在。还有一个需要注意的地方就是未经初始化的静态变量会自动初始化为0,而自动变量的值则不然。
l 静态全局变量
对于全局变量,加上static后,该变量就被定义成为一个静态全局变量。它实现了在整个文件中共享的功能,但是static不能被其他文件所用,其他文件可以定义相同名称的变量,不会起冲突(这与extern外部变量不同)。
l 静态局部变量
在局部变量前加上static后就是静态局部变量。对于静态局部变量我们需要注意就是它的作用域。首先它是静态的,这意味着它的生命周期是和整个程序相同的。其次由于它是局部的,所以其作用域也是局部的,比如属于某个方法,所以其作用域只能在这个方法里。还有就是对于静态局部变量它是程序运行到该对象处时才初始化,如果未指定初始化的值,则自动初始化未0,一旦初始化后,再次调用不再初始化,但可对其运算。
l 静态函数
对于静态函数,需要注意的是静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其他文件使用。
(2)面向对象中的static(类中的static)
在面向对象中的用法和上方类似,有静态数据成员和静态成员函数两种。
l 静态数据成员
对于静态数据成员,是指在类内部加static声明的成员。但需要注意的主要有以下两点:首先对于静态数据成员,它是属于类的,而不是属于某个对象实例,所以所有属于该类的对象恭喜该数据成员。其次是它依然存在这局部性这一特点。
l 静态成员函数
对于类的静态函数,比面向过程中的静态函数有一个不同的要求,就是静态成员函数里不能访问非静态成员。
4、inline内联函数
内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要区别不再于编写方式,而在于C++编译器如何将它们组合到程序中。
内联函数的编译代码与其他程序代码”内联”起来,即编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,然后再跳回来。因此,内联函数的运行速度比常规函数稍快。但代价是需要占用更多内存。
要使用这项特性,必须采取下述措施:
l 在函数声明前加上关键字inline。
l 在函数定义前加上关键字inline。
内联函数和常规函数一样,也是按值来传递参数的。
5、C++其他存储关键字
除了上面讲到的static、const,C++主要还有以下存储关键字:
l auto:将变量声明为自动变量。
l register:在声明中指示寄存器存储类型。
l extern:表明引用声明,即声明引用在其他地方定义的变量。
l mutable:可以用来指定即使结构或类变量为const,对某个成员也可以被修改。
l volatile:表示即使程序代码没有对内存单元进行修改,其值也可能发生变化。该关键字可以改善编译器的优化能力。
二、笔试面试常考问题
1、const与#define相比有什么不同?
对于C++语言来说,两者都可以定义常量。但是前者比后者有更多的优点:
(1)const常量有数据类型,而宏定义没有数据类型。编译器可以对前者进行类型安全检查,而对后者只是进行字符替换,没有类型安全检查,并且在字符替换中可能产意料不到的错误(边际效应)。
(2)有些集成化的调试工具可以对const调试,但不能对宏常量进行调试。
对于#define的这种缺点,除了上面的const外,还可以用inline。内联函数不仅具有实现宏函数的效率,还加上了类型检查和可预计的行为这些优点。比如:
#define max(a,b) ((a) > (b) ? (a) : (b))
int a = 5, b = 0;
max(++a,b); //此时a值增加了2次
max(++a,b+10); //此时a值增加了1次
而换成inline,我们就可以避免这种不可预见的情况,甚至还可以使用C++里的另一个功能——模板函数。比如换成以下的方式:
inline int max(int a, int b) { return a > b ? a : b; }
或者
template<class T>
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; }
2、关键字static的作用是什么?
在C或C++语言中,static主要有以下几个作用:
(1) 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此值在下次调用时仍维持上次的值。
(2) 在模块内的static全局变量可以被模块内的所以函数访问,但不能被模块外的其他函数访问。
(3) 在模块内的static函数只可被这一模块内的其他函数调用,但这个函数的使用范围被限制在声明它的模块内。
(4) 在类中的static成员变量属于整个类所拥有,对类的所以对象只是有一份拷贝。
(5) 在类中的static成员变量属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
注:对于类中的static,我们还可以用enum来替代,例如:
enum:如enum {Len = 30};
static:如static const int Len = 30;
访问时都可直接用ClassName::Len
3、关键字volatile有什么含义?一般用在什么地方?
一个定义为volatile的变量说明这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确的说就是,优化器在用到这个变量时必须每次都小心的重新读取这个变量的值,而不是使用保存在寄存器里的备份。
volatile主要用在以下几个地方:
(1) 并行设备的硬件寄存器(如状态寄存器)。
(2) 一个中断服务子程序中会访问到的非自动变量。
(3) 多线程应用中被几个任务共享的变量。
对于volatile变量作为参数时,它还可以是const。因为它不希望程序去修改它。而对于指针,同样可以使用volatile,比如中断服务子程序修改一个指向buffer的指针时。
4、在C++程序中调用被C编译器编译后的函数,为什么要加extern”C”?
C++语言支持函数重载,C语言不支持。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为:void foo(int x,int y)。该函数被C编译后在库中的名称为_foo,而C++编译器则会产生_foo_int_int之类的名称。
C++提供了C连接交换指定符号extern”C”解决名字匹配问题。
注:extern的用法:extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行链接指定。我们需要注意它和static的区别。
[参考资料]
[1] 百度百科,http://baike.baidu.com/。
[2] C++ Primer Plus第五版。
[3] 程序员面试宝典第三版。
[4] C++程序设计,谭浩强版。