数学建模社区-数学中国

标题: C++中几个比较不常用的关键字 [打印本页]

作者: xShandow    时间: 2005-1-6 18:20
标题: C++中几个比较不常用的关键字
<><FONT face=宋体>mutable关键字</FONT> </P>
5 ?3 ~0 X4 ^7 E) V( j$ m<>- o( M+ Y  V/ O3 Q: {; I9 }- e7 F; }* Z7 d
<FONT face=宋体 size=2>关键字mutable是C++中一个不常用的关键字,他只能用于类的非静态和非常量数据成员; n: ]8 @, c. B: V0 ]6 F
我们知道一个对象的状态由该对象的非静态数据成员决定,所以随着数据成员的改变,  d! \, d/ ]! n4 m7 @  y4 r2 B
对像的状态也会随之发生变化!</FONT></P>
; @5 r+ l9 ]% _% E<><FONT face=宋体 size=2>如果一个类的成员函数被声明为const类型,表示该函数不会改变对象的状态,也就是' u# O# E5 `* {2 f! O( K
该函数不会修改类的非静态数据成员.但是有些时候需要在该类函数中对类的数据成员
1 m0 r9 w* K6 N% l/ V进行赋值.这个时候就需要用到mutable关键字了</FONT></P>5 B3 a" z' s! r! r# P2 Y) Y" L
<><FONT face=宋体 size=2>例如:3 H8 X" I) N( @4 p5 q
class Demo
' r$ q( _$ @2 w% w( Z7 Q; I2 X" p{# K; ^+ ?  O4 p: _6 p1 u3 `
public:
4 v/ v3 Z2 ?/ @7 B$ {  }' G+ ]' F    Demo(){}
# U2 E9 l6 S6 a0 B! \    ~Demo(){}2 c8 D) |- s! i- R7 e# a- [
public:2 B' g; n+ ^( N, @
    bool getFlag() const+ K) u3 P2 T( k/ g9 o0 T
    {
& r) g$ U9 m3 p3 K5 O        m_nAccess++;0 ~, w( `3 A& H! Q
        return m_bFlag;
- [$ C1 y& \2 f. j    }4 W, g; g) V6 D4 n) ?- C# c
private:
6 w9 c& l8 x. u    int  m_nAccess;
* ?" Z% O. J: x    bool m_bFlag;$ S4 X5 T% x5 B6 D" H$ ], r$ Z! z# v
};</FONT></P>
9 L  r+ R; `# T) B<><FONT face=宋体 size=2>int main()
0 K+ I2 j6 F* \3 ^- x{
1 l  _4 G' @4 g9 y- r. [% d; S    return 0;
' q4 \! S3 t- K* n: C; d8 V}</FONT></P>
% L3 l! U' ]% U6 K# s<><FONT face=宋体 size=2>编译上面的代码会出现 error C2166: l-value specifies const object的错误
, _7 n0 S  q; h( }( T说明在const类型的函数中改变了类的非静态数据成员.</FONT></P>! k7 z/ h) \2 r7 |! O8 j4 v6 R
<><FONT face=宋体 size=2>这个时候需要使用mutable来修饰一下要在const成员函数中改变的非静态数据成员& x; K3 G: U' a9 K9 N4 d6 d
m_nAccess,代码如下:</FONT></P>
0 {' H6 g* t+ H<><FONT face=宋体 size=2>class Demo
, i3 g- O2 H# A  l  m, s3 P{
) ]3 n2 |$ _  l& O2 l; Kpublic:
# h* U' L  ]; k7 F/ F$ j" p    Demo(){}
: k8 y$ k0 G# g* u7 x0 A5 N: w    ~Demo(){}" V) w+ x& `2 t, \2 [! Z5 r5 c
public:; O  Z- n' W- H, W1 Y
    bool getFlag() const
1 D3 M' u2 \5 [7 D    {
& t. ^3 {8 S/ w5 w  d$ ]3 p9 b        m_nAccess++;& _4 V2 y% |% v% i( g0 e9 s  Y: p2 _
        return m_bFlag;
+ X$ D0 z# ]* U1 ^8 p8 @( l- a2 b/ a    }
1 `! N7 Q7 M1 @private:2 o. V2 s. B! _2 n- F. G9 ]
    mutable int  m_nAccess;( ?( a5 W3 z% B* M  s: V, b4 B8 Z, B
    bool m_bFlag;
: O9 ]' l2 X! z/ Q  H. S};</FONT></P>6 ~  c* J: J7 u+ ^
<><FONT face=宋体 size=2>int main()
, @5 H# G* E# n* }4 C6 z6 R; |{
/ B* j. E! P  y; Z+ e    return 0;
4 r; |! M( t% K' Y: p}</FONT></P>! P5 [" x2 R7 o2 A1 P
<><FONT face=宋体 size=2>这样再重新编译的时候就不会出现错误了!</FONT></P>
$ {; b5 w. |$ R: P% {; M# k0 ^<><FONT face=宋体 size=2> </P>
- t6 M4 X7 \! [# {6 H# y. z<><FONT face=宋体 size=2> </P>
  w2 U8 q7 C0 \2 X: j0 I<><FONT face=宋体 size=2>volatile关键字</FONT></P>
  f$ N- I: J4 v0 G<><FONT face=宋体 size=2>volatile是c/c++中一个鲜为人知的关键字,该关键字告诉编译器不要持有变量的临时拷贝,它可以适用于基础类型
  h! P+ N, e( w: d: h( G) Q$ k如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者
, X# U% \  u, X. L% o* p* u类的所有成员都会被视为volatile.</FONT></P>
& w2 h- [$ U7 U- |9 x$ K' J<><FONT face=宋体 size=2>使用volatile并不会否定对CRITICAL_SECTION,Mutex,Event等同步对象的需要
% H% O6 B9 m( I/ g& E: T例如:
5 b; o  x, V9 H: B7 d5 {int i;
/ X# n! D' _, X5 @i = i + 3;
9 D0 t0 j& }8 t" @( g6 `& c; V无论如何,总是会有一小段时间,i会被放在一个寄存器中,因为算术运算只能在寄存器中进行。一般来说,volatitle
( a) [5 w& @9 D; |& Z# X3 ?  K关键字适用于行与行之间,而不是放在行内。</FONT></P>+ e" ?# U4 S: {& t
<><FONT face=宋体 size=2>我们先来实现一个简单的函数,来观察一下由编译器产生出来的汇编代码中的不足之处,并观察volatile关键字如何修正
& U" G5 ]4 F) ~8 S) R- d这个不足之处。在这个函数体内存在一个busy loop(所谓busy loop也叫做busy waits,是一种高度浪费CPU时间的循环方法)</FONT></P># t! X3 u  j- C. n
<><FONT face=宋体 size=2>void getKey(char* pch)# F/ D3 f9 H. u4 R0 `6 k# c
{% I2 N  J2 Q. Y* e
while (*pch == 0)
+ M$ M: r+ I( T; w  ;; a: B  U& o" t4 z! m
}</FONT></P>& G; J  |( F& S+ {1 h9 X* `* B) Z( a
<><FONT face=宋体 size=2>当你在VC开发环境中将最优化选项都关闭之后,编译这个程序,将获得以下结果(汇编代码)
  n. p7 y* [1 z5 u& I* @;       while (*pch == 0); n7 s, @9 H" T1 e( R! A9 l5 v
$L27
- S1 V% L, H; g$ a" @ ; Load the address stored in pch( b: S+ B9 E) _, ]9 e$ _- @! S$ V
mov eax, DWORD PTR _pch$[ebp]
% u% [+ `& A5 m/ |+ A1 m ; Load the character into the EAX register  y& v# V! P9 q% D- _3 v- H
movsx eax, BYTE PTR [eax]) Q) ~4 U. {; i
; Compare the value to zero
3 v3 a( j; r, X' }/ M  o test eax, eax- U& Q: I. w9 y8 f$ _2 ?/ w* U
; If not zero, exit loop
* A. r3 J% y* \6 r. m" O jne $L28. v2 @- ~+ M) C+ O5 f
;# O$ w+ h  m( K" E* Q  w5 r7 g
jmp $L27
# W' `2 W( y+ @+ ~$ ]4 Y$L289 D7 e- H9 z7 O% B2 c( `2 H
;}</FONT></P>
" p& h# _# l( Z4 R8 f9 J<><FONT face=宋体 size=2>这段没有优化的代码不断的载入适当的地址,载入地址中的内容,测试结果。效率相当的低,但是结果非常准确</FONT></P>, [  C; F: k6 h# `8 ^4 D" N* G
<><FONT face=宋体 size=2>现在我们再来看看将编译器的所有最优化选项开关都打开以后,重新编译程序,生成的汇编代码,和上面的代码
, k: Z& W6 v; l" g1 w  I" q比较一下有什么不同6 Z. a9 o( I5 r8 p% R, G" U5 r
;{ 3 w: V* p# }0 n
; Load the address stored in pch
% a' S. t" q; P& E: v9 m; P mov eax, DWORD PTR _pch$[esp-4]$ H6 B7 X( Q7 e/ [2 P( q3 n
; Load the character into the AL register
) l6 c* d6 H" q" I9 A6 A- | movsx al, BYTE PTR [eax]) `% u' T5 h; u4 H! ~
; while (*pch == 0)4 T. T2 j4 v3 g/ G
; Compare the value in the AL register to zero
7 l' c9 ?( s8 Q+ ?  [8 \9 | test al, al
+ h+ K$ _* E4 ^" |& V4 a( m/ o2 J ; If still zero, try again% R) w% T' J, G% B' O2 {' F
je SHORT $L842 o% A/ I" j8 U/ [
;4 y+ ?0 ]$ m) T6 M+ i: {
;}</FONT></P>* U" H- V( A/ X
<><FONT face=宋体 size=2>从代码的长度就可以看出来,比没有优化的情况要短的多。需要注意的是编译器把MOV指令放到了循环之外。这在$ K& t- a7 v( e+ ^
单线程中是一个非常好的优化,但是,在多线程应用程序中,如果另一个线程改变了变量的值,则循环永远不会
% D: c0 Q) X% a9 |; u4 {! Q结束。被测试的值永远被放在寄存器中,所以该段代码在多线程的情况下,存在一个巨大的BUG。解决方法是重新
! K8 ?# ]8 y" k5 R% A  y写一次getKey函数,并把参数pch声明为volatile,代码如下:</FONT></P>! g/ j6 s: m# ]8 Z8 R/ t
<><FONT face=宋体 size=2>void getKey(volatile char* pch)
, u' y, N( A, ~' a0 k" D) X( m0 Q{
7 K3 b5 S+ ~8 N8 n$ [8 L while (*pch == 0)
& i& a2 w: |0 `: @/ K  ;
& y9 J( \$ w  N4 e/ z" g: q}</FONT></P>* ~/ y/ s; u1 W  r8 ^( m
<><FONT face=宋体 size=2>这次的修改对于非最优化的版本没有任何影响,下面请看最优化后的结果:</FONT></P>
4 Z; q. ^- O  x& d" j- |<><FONT face=宋体 size=2>;{. c/ e& h& |( D: n3 ~
; Load the address stored in pch2 h& j5 \) O& [1 i! H  z- N0 ~) d4 q( ^
mov eax, DWORD PTR _pch$[esp-4]6 \; P2 n0 f6 C% z5 J" k% E
;       while (*pch == 0)
, y  O2 W! c" V7 ?! X$L84:
% o* x, I1 g: j7 s6 R, N3 N ; Directly compare the value to zero
; F' \. G. ^( p* }; N cmp BYTE PTR [eax], 0
0 |( i2 {  d; @  D# _1 m, j ; If still zero, try again
# T3 [" G2 v5 N/ L" H4 ^ je SHORT $L847 c; o+ E( z: a& t3 I4 K
;
3 @, J+ x* s$ [# O;}</FONT></P>
, {2 |5 P' d6 \2 ]( i% V<><FONT face=宋体 size=2>这次的修改结果比较完美,地址不会改变,所以地址声明被移动到循环之外。地址内容是volatile,所以每次循环4 S% L1 c8 s$ C5 D: I
之中它不断的被重新检查。</FONT></P>
  N: T+ @; d" e. y* x<><FONT face=宋体 size=2>把一个const volatile变量作为参数传递给函数是合法的。如此的声明意味着函数不能改变变量的值,但是变量的# _' _' U7 s. f% _+ l6 i8 {
值却可以被另一个线程在任何时间改变掉。</FONT></P>4 x7 `2 z9 Q" e
<>
+ w# m* G# I2 h( }& n* w7 m( I<FONT face=宋体 size=2>explicit关键字</FONT></P># s1 L! n  X. T' p  Q, |, x/ J( J
<>. @. e  ]3 b% @
<FONT face=宋体 size=2>我们在编写应用程序的时候explicit关键字基本上是很少使用,它的作用是"禁止单参数构造函数"被用于自动型别转换,
0 T$ ~( A( {' N. z其中比较典型的例子就是容器类型,在这种类型的构造函数中你可以将初始长度作为参数传递给构造函数.  }  l" k, F6 a4 S# [5 i! j: }
例如:
$ \0 `8 u, r, e2 z% a: a你可以声明这样一个构造函数* f' V' ?6 A6 X. L& w% {% s
class Array( @2 y& N" ?( [: e1 Z* |" |2 G: {' x- V
{
2 L, a+ A! L" C7 T3 H  Mpublic:& t1 ?& r# v# B5 M+ `* }+ z, l4 X8 z
explicit Array(int size);
8 z0 R6 h4 P6 b1 T6 ~. r) l$ G ......( ^7 |3 ~+ Y9 k( _% d; N* j
};
+ Z" F, Q/ P  [在这里explicit关键字起着至关重要的作用,如果没有这个关键字的话,这个构造函数有能力将int转换成Array.一旦这种
- w6 @- Y$ k' Y( H情况发生,你可以给Array支派一个整数值而不会引起任何的问题,比如:
9 k" |8 S' z$ o3 f/ @; vArray arr;. A( ~" e' N' o$ u8 U" z$ H
...
/ ?8 |' J$ w; g# L3 Rarr = 40;& o/ N  `  G; M, z: Z
此时,C++的自动型别转换会把40转换成拥有40个元素的Array,并且指派给arr变量,这个结果根本就不是我们想要的结果.如果
5 u, }! D! D" H/ v; g7 {我们将构造函数声明为explicit,上面的赋值操作就会导致编译器报错,使我们可以及时发现错误.3 V/ d# a: g" o" Q0 l  _
需要注意的是:explicit同样也能阻止"以赋值语法进行带有转型操作的初始化";) H) A* X- `: R; w
例如:( q/ Z3 B% `* H- O& h6 ?
Array arr(40);//正确& y" w& _: k7 D+ v: H
Array arr = 40;//错误</FONT></P>
# q' Y! r; Z/ q% o# x<><FONT face=宋体 size=2>看一下以下两种操作:
, J, ^& ^# X3 S# f  S3 z4 p  K- ^/ ZX x;
: k* D+ m; s1 L4 ?  LY y(x);//显式类型转换
! O. D4 t  ^2 G& ?0 Y另一种( ^( T& |: L% W! v/ d1 @/ v
X x;; j# u( A" o7 c9 M; n
Y y = x;//隐式类型转换</FONT></P>; }  T' k& i# `# v  |4 v0 _, o; Q
<><FONT face=宋体 size=2>这两种操作存在一个小小的差别,第一种方式式通过显式类型转换,根据型别x产生了型别Y的新对象;第二种方式通过隐式转换6 `) F8 R9 k9 p" B: I# ^0 Y
产生了一个型别Y的新对象.
# G2 Q9 a( S% @" r# nexplicit关键字的应用主要就是上面所说的构造函数定义种,参考该关键字的应用可以看看STL源代码,其中大量使用了该关键字</FONT></P>
9 l2 F' F' @& _; F; N7 Q9 |<P><FONT face=宋体 size=2> </P>( u, _1 p; E/ `- |& o
<P><FONT face=宋体 size=2>__based关键字</FONT></P>
7 G  Q8 i; T- v9 {<P>3 i' X" l% a# \6 P5 Y" O* b5 r
<FONT face=宋体 size=2>该关键字主要用来解决一些和共享内存有关的问题,它允许指针被定义为从某一点开始算的32位偏移值,而不是内存种的绝对位置# o: O1 l- t2 U5 @& Z$ T
举个例子:</FONT></P>
' c3 H: R1 U1 K8 z( o  w3 b+ _<P><FONT face=宋体 size=2>typedef struct tagDEMOSTRUCT {
; J1 s# ^: |/ b int a;9 J; `0 M9 |3 `
char sz[10];
9 \: T5 t; S6 w5 q. ~- _+ t# L/ e" s& g} DEMOSTRUCT, * PDEMOSTRUCT;</FONT></P>3 }2 l4 R6 ]4 C, v6 J" l+ A$ k' K
<P><FONT face=宋体 size=2>HANDLE hFileMapping = CreateFileMapping(...);/ l0 N* T; {' L6 \7 i
LPVOID lpShare = (LPDWORD)MapViewOfFile(...);</FONT></P>8 h7 N) U2 z4 c
<P><FONT face=宋体 size=2>DEMOSTRUCT __based(lpShare)* lpDemo;</FONT></P>% \. e; F' G% T- C5 R2 O
<P><FONT face=宋体 size=2>上面的例子声明了一个指针lpDemo,内部储存的是从lpShare开始的偏移值,也就是lpHead是以lpShare为基准的偏移值.
# l8 M# s* l7 G' V. f$ }, l上面的例子种的DEMOSTRUCT只是随便定义的一个结构,用来代表任意的结构.</FONT></P>4 I- ?  ?* \- I0 Y" E: z
<P><FONT face=宋体 size=2>虽然__based指针使用起来非常容易,但是,你必须在效率上付出一定的代价.每当你用__based指针处理数据,CPU都必须% A# z3 o5 @+ E! |3 Q
为它加上基地址,才能指向真正的位置.</FONT></P>
  p% _1 h# ]' I1 H% }4 D<P><FONT face=宋体 size=2> </P>' W/ S% X, p6 M5 o4 s7 J
<P><FONT face=宋体 size=2>在这里我只是介绍了几个并不时很常见的关键字的意义即用法,其他那些常见的关键字介绍他们的文章已经不少了在这里+ J) c1 n" x; ?" w1 _
就不再一一介绍了.希望这些内容能对大家有一定的帮助!</FONT></P></FONT></FONT></FONT></FONT>
作者: ilikenba    时间: 2005-1-7 01:47
<>好!都是以前不知道的!顶!</P>
作者: pallas1204    时间: 2005-3-13 18:38
ok




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5