c/c++面试精选题-01 关键字作用总结

开篇介绍 -文章搬运

cpp软件架构狮

在这里插入图片描述

《C语言深度解剖》

常用关键字总结

const关键字作用总结

在这里插入图片描述

规则

  • const是constant的简写,只要一个变量前面用cons来修饰,就意味着该变量里的数据可以被访问,不能被修改。也就是说const意味着“只读”readonly
  • const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
    我们看看它与 define 宏的区别。(很多人误以为 define 是关键字,在这里我提醒你再回到本章前面看看 32 个关键字里是否有 define)。

规则:const离谁近,谁就不能被修改;

const修饰一个变量,一定要给这个变量初始化值,若不初始化,后面就无法初始化。

本质:const在谁后面谁就不可以修改,const在最前面则将其后移一位,二者等效。

const关键字作用

  1. 为给读你代码的人传达非常有用的信息,声明一个参数为常量是为了告诉用户这个参数的应用目 的;
  2. 通过给优化器一些附加信息,使关键字const也许能产生更紧凑的代码;
  3. 合理使用关键字const可以使编译器很自然的保护那些不希望被修改的参数,防止无意的代码修改,可以减少bug的出现;

const关键字应用

  1. 欲阻止一个变量被改变,可使用const,在定义该const变量时,需先初始化,以后就没有机会改变他了;
  2. 对指针而言,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
  3. 在一个函数声明中,const可以修饰形参表明他是一个输入参数,在函数内部不可以改变其值;
  4. 对于类的成员函数,有时候必须指定其为const类型,表明其是一个常函数,不能修改类的成员变量;
  5. 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。

举例

const 修饰的只读变量

定义 const 只读变量,具有不可变性。
例如:
constintMax=100;
intArray[Max];
这里请在 Visual C++6.0 里分别创建.c 文件和.cpp 文件测试一下。你会发现在.c 文件中,
编译器会提示出错,而在.cpp 文件中则顺利运行。为什么呢?我们知道定义一个数组必须指
定其元素的个数。这也从侧面证实在 C 语言中,const 修饰的 Max 仍然是变量,只不过是只
读属性罢了;而在 C++里,扩展了 const 的含义,这里就不讨论了。

留一个问题:case 语句后面是否可以是 const 修饰的只读变量呢?   
    ------解决思路1----------------------
因为它在语义上是不可修改的。    
------解决思路-2---------------------
如果不在定义的时候初始化,他就会是一个随机值,之后又不能赋值,那要他有什么用。

节省空间,避免不必要的内存分配,同时提高效率

编译器通常不为普通 const 只读变量分配存储空间,而是将它们保存在符号表中,这使
得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。
例如:
#define M 3 //宏常量
const int N=5; //此时并未将 N 放入内存中
......
in ti=N; //此时为 N 分配内存,以后不再分配!
in tI=M; //预编译期间进行宏替换,分配内存
in tj=N; //没有内存分配
int J=M; //再进行宏替换,又一次分配内存!
const 定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define
一样给出的是立即数,所以,const 定义的只读变量在程序运行过程中只有一份拷贝(因为
它是全局的只读变量,存放在静态区),而#define 定义的宏常量在内存中有若干个拷贝。
#define 宏是在预编译阶段进行替换,而 const 修饰的只读变量是在编译的时候确定其值。
#define 宏没有类型,而 const 修饰的只读变量具有特定的类型。

修饰一般变量

一般常量是指简单类型的只读变量。这种只读变量在定义时,修饰符 const 可以用在类
型说明符前,也可以用在类型说明符后。例如:
int const i=2; 或 const int i=2;

修饰数组

定义或说明一个只读数组可采用如下格式:
intconsta[5]={1,2,3,4,5};或
constinta[5]={1,2,3,4,5};

修饰指针

const int*p; // p 可变,p 指向的对象不可变
int const *p; // p 可变,p 指向的对象不可变
int * const p; // p 不可变,p 指向的对象可变

const int *const p; //指针 p 和 p 指向的对象都不可变
在平时的授课中发现学生很难记住这几种情况。这里给出一个记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const 离哪个近。“近水楼
台先得月”,离谁近就修饰谁。

const in t p; //const 修饰p,p 是指针,*p 是指针指向的对象,不可变
int const p; //const 修饰p,p 是指针,p 是指针指向的对象,不可变
int
const p; //const 修饰 p,p 不可变,p 指向的对象可变
const int const p; //前一个 const 修饰p,后一个 const 修饰 p,指针 p 和 p 指向的对象都不可变

修饰函数的参数

const 修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使
用。例如:
void Fun(constinti);
告诉编译器 i 在函数体中的不能改变,从而防止了使用者的一些无意的或错误的修改。

修饰函数的返回值

const 修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
constintFun(void);
在另一连接文件中引用 const 只读变量:
extern const int i; //正确的声明
extern const int j=10; //错误!只读变量的值不能改变。

volatile关键字的用法及意义

在这里插入图片描述
volatile 影响编译器编译的结果,volatile指出 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)

例如:
volatile int i=10; 
int j = i; 
... 
int k = i;

volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。

而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。

这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

常用案例

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

  1. 并行设备的硬件寄存器(如:状态寄存器)

  2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

  3. 多线程应用中被几个任务共享的变量 回答不出这个问题的人是不会被雇佣的。

我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容

将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。

常见问题

1)一个参数既可以是const还可以是volatile吗?解释为什么。

2); 一个指针可以是volatile 吗?解释为什么。

3); 下面的函数有什么错误:

int square(volatile int *ptr)
 { 
 return *ptr * *ptr; 
}

下面是答案:

1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

    int square(volatile int *ptr)
    { 
     int a,b;
     a = *ptr;
     b = *ptr; 
     return a * b; 
    }
    由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下: 
    long square(volatile int *ptr)
    {
     int a;
     a = *ptr;
     return a * a;
     } 

用法总结:

  1. 告诉compiler不能做任何优化
    比如要往某一地址送两指令:

    int *ip =…; //设备地址
    *ip = 1; //第一个指令
    *ip = 2; //第二个指令
    以上程序compiler可能做优化而成:

    int *ip = …;

    *ip = 2;

    结果第一个指令丢失。

    如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:

    volatile int *ip = …;

    *ip = 1; *ip = 2;

    即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。

  2. 表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。
    volatile char a;
    a=0;
    while(!a)
    { //do some things; }
    doother();
    如果没有volatile doother()不会被执行

static关键字的作用

在这里插入图片描述

修饰局部变量

static修饰局部变量时,使得被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。(无论是局部静态还是全局静态)

修饰全局变量

全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性。被static修饰的全局变量只能被该包含该定义的文件访问(即改变了作用域)。

修饰函数

static修饰函数使得函数只能在包含该函数定义的文件中被调用。对于静态函数,声明和定义需要放在同一个文件夹中。

修饰成员变量

用static修饰类的数据成员使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象,所有的对象都只维持同一个实例。 因此,static成员必须在类外进行初始化(初始化格式:int base::var=10;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化。

修饰成员函数

用static修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含this指针,因而只能访问类的static成员变量。静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。例如可以封装某些算法,比如数学函数,如ln,sin,tan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类Math,调用Math::sin(3.14);还可以实现某些特殊的设计模式:如Singleton;

最重要的特性:隐藏

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏。

不可以同时用const和static修饰成员函数。

C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。

extern关键字的作用

在这里插入图片描述
extern置于变量或函数前,用于标示变量或函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。它只要有两个作用:

  1. 当它与“C”一起连用的时候,如:extern “C” void fun(int a,int b);则告诉编译器在编译fun这个函数时候按着C的规矩去翻译,而不是C++的(这与C++的重载有关,C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同)

  2. 当extern不与“C”在一起修饰变量或函数时,如:extern int g_Int;它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或其他模块中使用。记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值