Chinaunix首页 | 论坛 | 博客
  • 博客访问: 295689
  • 博文数量: 52
  • 博客积分: 814
  • 博客等级: 军士长
  • 技术积分: 689
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-21 19:41
文章分类
文章存档

2017年(1)

2016年(2)

2014年(1)

2012年(42)

2011年(6)

分类: C/C++

2012-03-16 16:31:12

CONST

.一般应用
1.const
修饰各种变量的用法.
a.
取代define
#define D_INT 100
#define D_LONG 100.29
………
const int D_INT = 100;
const D_INT = 100;     //
如果定义的int类型,可省略int.
const long D_LONG = 100.29;
………
const int& a = 100;
const
替代define虽然增加分配空间,可它却保证了类型安全.
C标准中,const定义的数据相当于全局的,C++中视声明的位置而定.
b.
修饰指针相关的变量
以三组简单的定义示意:
Group1:  
int a = 0;   
const int* b = &a;------------ [1]               
int const *b = &a;------------ [2]                    
const int* const b = &a;---- [4]  
Group2:
const char *p = "const";--------------[1]
char const *p = "const";--------------[2]  
char* const p = "const";--------------[3]  
const char * const p = "const";----[4]     
Group3:
int a=0;
const int &b = a;---------------[1]
int const &b = a;---------------[2]
int & const b = a;--------------[3] //--->
修饰引用时,const被忽略
const int & const b = a;-----[4]
总结:
1.
如果const位于星号左侧,const用来修饰指针所指向的变量,即指针指向的为不可变的.
2.
如果const位于星号右侧,const就是修饰指针本身,即指针本身是不可变的.因此,[1][2]的情况相同,指针所指向内容不可变(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改,如不能
*a = 3 ;
3.[3]
中指针本身是不可变的,而指针所指向的内容是可变的,这种情况下不能对指针本身进行更改操作,a++是错误的

4.[4]
中指针本身和指向的内容均为常量.(引用特殊:引用在使用增加遇义时,增加它代表的变量.

5 const 在定义的时候就必须初始化。否则,不对 。

所以qualifiers on reference are ignoredv.延伸点:
注意示例:
1.const int& reference = 1000;
2.char* p = "const"
char*& q ;
2.const
在函数环境下的各种应用
常用法示例如下:
const A& _Fun(const A& _in); //
修饰引用型传入参数
// A _Fun(const A& _in);
//A& _Fun(const A& _in);
//
上面的两种,在函数内部有特殊的步骤,这里不详提了…..
const A* _Fun( const A* _in);   //
修饰指针型传入参数
void _Fun( ) const;   //
修饰class成员函数
const A& _Fun(A& _in ); //
修饰返回值
const A & operator(const A& _in); //
同时修饰传入参数和返回值

   a.修饰参数
void _Fun(const A* _in) void _Fun(const A& _in);
它们被修饰后,在函数执行期间行为特性同于上面的讲解,
注意:这不会改变原来数据的是否是const的属性.

   b.修饰函数返回值
const A& _Fun( )
const A*   _Fun( );
注意:由于生命期不同步的问题,不可将局部的变量的指针或引用返回(static除外).
另外,传出来的视情况,代表不同的意思
对于A&返回类型,你若将之赋与其它变量,那么它实际执行的是将返回的变量
(
或引用)代表的数据赋出..而你若将其它值赋予之,那么被赋予的是变量或引
用代表的数据. const A& 一般是防止之做为左值被赋值.

这个地方还有很多的细节问题(譬如在连续赋值、返回的临时对象的处理、
重载的const和非cosnt运算符等等),读者自己在实践中需要多多总结.

使用可变(mutable)成员隐藏实现细节

键字 mutable 是一个奇怪的修饰符(specifier),它只能够用于一个类的非静态数据成员。下面我将讨论 mutable 的语义和用法,但是首先我要解释一下 C++ 对象模型的一个关键概念。

对象的状态

一个对象的状态由其非静态数据成员的值构成,因此,修改一个数据成员将会改变整个对象的状态。将一个成员函数声明为 const 能够保证它不会改变对象的状态。

然而在一些情况下,对象的逻辑状态与基物理状态之间可能有差别。例如,对于一个表示绘画图像的对象就存在这种情况。如果图像还没有更改,那么我们就 认为其状态没有发生变化。然而,从底层实现方面来说,如果大对象在一段时间没有活动,那么它们的内存通常会被交换到一个文件中。交换一个图像并不会真地影 响其状态,但是对象的一些数据成员可能会发生变化,在这里可能会发生变化的是指针、标志位等。

在用户调用一个诸如 Redraw() 之类的 const 成员函数时,他们并不关心这个函数在内部是如何实现的。从他们的角度来说,这个函数并不改变对象的逻辑状态,因此被声明为 constRedraw() 有可能修改对象的物理状态这一事实是一个他们不应该关心的实现细节。例如:

int Image::Redraw() const
{
if (isLoaded==false)
{
//..read image data from a disk into a local buffer
isLoaded=true; //changing a data member's value
}
//..paint image in the screen
}

class Image

可变(mutable)数据成员

如果尝试编译这段代码,你会得到一个编译错误。虽然 Redraw() 声明为 const,但是它修改了一个数据成员。解决这个编译错误的方法是将 isLoaded 声明为一个 mutable 数据成员:

class Image
{
public:
int Redraw() const;
//..
private:
mutable bool isLoaded;//can be changed by a const function
};

不像普通的数据成员,const 成员函数可以修改 mutable 数据成员。

Mutable 数据成员的使用看上去像是骗术,因为它能够使 const 函数修改对象的数据成员。然而,明智地使用 mutable 关键字可以提高代码质量,因为它能够让你向用户隐藏实现细节,而无须使用不确定的东西,比如 const_cast<>

volatile关键字

  volatilec/c++中一个鲜为人知的关键字,该关键字告诉编译器不要持有变量的临时拷贝,它可以适用于基础类型
如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者
类的所有成员都会被视为volatile.

  使用volatile并不会否定对CRITICAL_SECTION,Mutex,Event等同步对象的需要
例如:
int i;
i = i + 3;
无论如何,总是会有一小段时间,i会被放在一个寄存器中,因为算术运算只能在寄存器中进行。一般来说,volatitle
关键字适用于行与行之间,而不是放在行内。

  我们先来实现一个简单的函数,来观察一下由编译器产生出来的汇编代码中的不足之处,并观察volatile关键字如何修正
这个不足之处。在这个函数体内存在一个busy loop(所谓busy loop也叫做busy waits,是一种高度浪费CPU时间的循环方法)

  oid getKey(char* pch)
{
while (*pch == 0)
;
}

  当你在VC开发环境中将最优化选项都关闭之后,编译这个程序,将获得以下结果(汇编代码)

  ;       while (*pch == 0)
$L27
; Load the address stored in pch
mov eax, DWORD PTR _pch$[ebp]
; Load the character into the EAX register
movsx eax, BYTE PTR [eax]
; Compare the value to zero
test eax, eax
; If not zero, exit loop
jne $L28
;
jmp $L27
$L28
;}

  这段没有优化的代码不断的载入适当的地址,载入地址中的内容,测试结果。效率相当的低,但是结果非常准确

  现在我们再来看看将编译器的所有最优化选项开关都打开以后,重新编译程序,生成的汇编代码,和上面的代码
比较一下有什么不同

  ;{
; Load the address stored in pch
mov eax, DWORD PTR _pch$[esp-4]
; Load the character into the AL register
movsx al, BYTE PTR [eax]
; while (*pch == 0)
; Compare the value in the AL register to zero
test al, al
; If still zero, try again
je SHORT $L84
;
;}

 

 

 

关键字const是什么含意?
我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1). 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

Volatile

8. 关键字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;
}

阅读(742) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~