关键字mutable是C++中一个不常用的关键字,他只能用于类的非静态和非常量数据成员 我们知道一个对象的状态由该对象的非静态数据成员决定,所以随着数据成员的改变, 对像的状态也会随之发生变化! 如果一个类的成员函数被声明为const类型,表示该函数不会改变对象的状态,也就是 该函数不会修改类的非静态数据成员.但是有些时候需要在该类函数中对类的数据成员 进行赋值.这个时候就需要用到mutable关键字了
例如: class Demo { public: Demo(){} ~Demo(){} public: bool getFlag() const { m_nAccess++; return m_bFlag; } private: int m_nAccess; bool m_bFlag; }; int main() { return 0; }
编译上面的代码会出现 error C2166: l-value specifies const object的错误 说明在const类型的函数中改变了类的非静态数据成员.
这个时候需要使用mutable来修饰一下要在const成员函数中改变的非静态数据成员 m_nAccess,代码如下:
class Demo { public: Demo(){} ~Demo(){} public: bool getFlag() const { m_nAccess++; return m_bFlag; } private: mutable int m_nAccess; bool m_bFlag; }; int main() { return 0; }
这样再重新编译的时候就不会出现错误了!
volatile关键字
volatile是c/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时间的循环方法)
void 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 ; ;}
从代码的长度就可以看出来,比没有优化的情况要短的多。需要注意的是编译器把MOV指令放到了循环之外。这在单线程中是一个非常好的优化,但是,在多线程应用程序中,如果另一个线程改变了变量的值,则循环永远不会结束。被测试的值永远被放在寄存器中,所以该段代码在多线程的情况下,存在一个巨大的BUG。解决方法是重新
写一次getKey函数,并把参数pch声明为volatile,代码如下:
void getKey(volatile char* pch) { while (*pch == 0) ; }
这次的修改对于非最优化的版本没有任何影响,下面请看最优化后的结果:
;{ ; Load the address stored in pch mov eax, DWORD PTR _pch$[esp-4] ; while (*pch == 0) $L84: ; Directly compare the value to zero cmp BYTE PTR [eax], 0 ; If still zero, try again je SHORT $L84 ; ;}
这次的修改结果比较完美,地址不会改变,所以地址声明被移动到循环之外。地址内容是volatile,所以每次循环 之中它不断的被重新检查。
把一个const volatile变量作为参数传递给函数是合法的。如此的声明意味着函数不能改变变量的值,但是变量的 值却可以被另一个线程在任何时间改变掉。
explicit关键字
我们在编写应用程序的时候explicit关键字基本上是很少使用,它的作用是"禁止单参数构造函数"被用于自动型别转换,其中比较典型的例子就是容器类型,在这种类型的构造函数中你可以将初始长度作为参数传递给构造函数.
例如: 你可以声明这样一个构造函数
class Array { public: explicit Array(int size); ...... };
在这里explicit关键字起着至关重要的作用,如果没有这个关键字的话,这个构造函数有能力将int转换成Array.一旦这种情况发生,你可以给Array支派一个整数值而不会引起任何的问题,比如:
Array arr; ... arr = 40;
此时,C++的自动型别转换会把40转换成拥有40个元素的Array,并且指派给arr变量,这个结果根本就不是我们想要的结果.如果我们将构造函数声明为explicit,上面的赋值操作就会导致编译器报错,使我们可以及时发现错误.需要注意的是:explicit同样也能阻止"以赋值语法进行带有转型操作的初始化";
例如: Array arr(40);//正确 Array arr = 40;//错误
看一下以下两种操作: X x; Y y(x);//显式类型转换 另一种 X x; Y y = x;//隐式类型转换
这两种操作存在一个小小的差别,第一种方式式通过显式类型转换,根据型别x产生了型别Y的新对象;第二种方式通过隐式转换产生了一个型别Y的新对象. explicit关键字的应用主要就是上面所说的构造函数定义种,参考该关键字的应用可以看看STL源代码,其中大量使用了该关键字
__based关键字
该关键字主要用来解决一些和共享内存有关的问题,它允许指针被定义为从某一点开始算的32位偏移值,而不是内存种的绝对位置 举个例子:
typedef struct tagDEMOSTRUCT { int a; char sz[10]; } DEMOSTRUCT, * PDEMOSTRUCT; HANDLE hFileMapping = CreateFileMapping(...); LPVOID lpShare = (LPDWORD)MapViewOfFile(...);
DEMOSTRUCT __based(lpShare)* lpDemo;
上面的例子声明了一个指针lpDemo,内部储存的是从lpShare开始的偏移值,也就是lpHead是以lpShare为基准的偏移值.上面的例子种的DEMOSTRUCT只是随便定义的一个结构,用来代表任意的结构.
虽然__based指针使用起来非常容易,但是,你必须在效率上付出一定的代价.每当你用__based指针处理数据,CPU都必须为它加上基地址,才能指向真正的位置.
在这里我只是介绍了几个并不时很常见的关键字的意义即用法,其他那些常见的关键字介绍他们的文章已经不少了在这里就不再一一介绍了.希望这些内容能对大家有一定的帮助!
在网上看到的介绍性文章,不错
1. 保留字
C++中,保留字也称关键字,它是预先定义好的标识符。见关键字的解释。
2.关键字
C++中已经被系统定义为特殊含义的一类标识符。C++中的关键字有:
3.标识符
对变量、函数、标号和其它各种用户自定义对象的命名。在C++中,标识符长度没有限制,第一个字符必须是字母或下划线,其后若有字符则必须为字母、数字或下划线。例如count2,_x是正确的标识符形式,而hello!,3th则是错误的。在C++中标识符区分大小写,另外标识符不能和C++中的关键字相同,也不能和函数同名。
4.声明
将一个标识符引入一个作用域,此标识符必须指明类型,如果同时指定了它所代表的实体,则声明也是定义。
5.定义
给所声明的标识符指定所代表的实体。
6.变量
某个作用域范围内的命名对象。
7.常量
常量是不接受程序修改的固定值,可以是任意数据类型。可以用后缀准确的描述所期望的常量类型,如浮点类型常量在数字后加F,无符号整型常量加后缀U等等。此外还有串常量如"Please input year:",反斜线字符常量如\n表示回车符。
8. const说明符
const是在变量声明或函数声明时所用到的一个修饰符,用它所修饰的实体具有只读属性。
9. 输入
当程序需要执行键盘输入时,可以使用抽取操作付">>"从cin输入流中抽取字符。如:
int myAge;
cin >> myAge;
10.输出
当程序需要在屏幕上显示输出时,可以使用插入操作符"<<"向cout 输出流中插入字符。如:
cout << "This is a program. \n ";
11.流
流是既产生信息又消费信息的逻辑设备,通过C++系统和物理设备关联。C++的I/O系统是通过流操作的。有两种类型的流:文本流,二进制流。
12.标准输入输出库
它是C++标准库的组成部分,为C++语言提供了输入输出的能力。
13.内置数据类型
由C++直接提供的类型,包括int、float、double、char 、bool、指针、数组和引用。
14.字符类型
包括 char、signed char、unsigned char三种类型。
15.整数类型
包括 short、 int、long 三种类型。
16.long
只能修饰 int , double.
long int 指一种整数类型,它的长度大于等于int型.
long double 指长双精度类型,长度大于等于double型。
17.short
一种长度少于或等于int型的整数类型。
18.signed
由它所修饰的类型是带符号的. 只能修饰 int 和 char .
19.布尔型
一种数据类型,其值可为:true, false 两种。
20.浮点类型
包括float, double , long double 三种类型。其典型特征表现为有尾数或指数。
21.双精度类型
浮点类型中的一种。在基本数据类型中它是精度最高,表示范围最大的一种数据类型。
22.void类型
关键字之一,指示没有返回信息。
23.结构类型
类的一种,其成员默认为public型。大多用作无成员函数的数据结构。
24.枚举类型
一种用户自定义类型,由用户定义的值的集合组成。
25.类型转换
一种数据类型转换为另一种,包括显式,隐式两种方式。
26.指针
一个保存地址或0的对象。
27. 函数指针
每个函数都有地址,指向函数地址的指针称为函数指针,函数指针指向代码区中的某个函数,通过函数指针可以调用相应的函数。其定义形式为:
int ( * func ) ( char a, char b);
28.引用
为一个对象或函数提供的另一个名字。
29.链表
一种数据结构,由一个个有序的结点组成,每个结点都是相同类型的结构,每个结点都有一个指针成员指向下一个结点。
30.数组
数组是一个由若干同类型变量组成的集合。
31.字符串
标准库中的一种数据类型,一些常用操作符如+=,==支持其操作。
32.运算符
内置的操作常用符号,例如+,* ,& 等。
33.单目运算符
只能对一个操作数进行操作
34.双目运算符
可对两个操作数进行操作
35.三目运算符
可对三个操作数进行操作
36.算术运算符
执行算术操作的运算符,包括:+,-,*,/,%。
37.条件运算符
即"?: " 。
其语法为:
(条件表达式)?(条件为真时的表达式):(条件为假时的表达式)
如:x = a < b ? a : b;
相当于:
if ( a < b)
x = a;
else
x = b;
38.赋值运算符
即:" = "及其扩展赋值运算符
39.左值
能出现在赋值表达式左边的表达式。
40.右值
能出现在赋值表达式右边的表达式。
41.运算符的结合性
指表达式中出现同等优先级的操作符时该先做哪个的规定。
42.位运算符
" & "," | " , " ^ "," >> "," << "
43.逗号运算符
即" , "
44.逻辑运算符
" && ", " || " ," ! "
45.关系运算符
">",">=","<=","< "," <= ","== "
46.new运算符
对象创建的操作符。
47.delete运算符
对象释放操作符,触发析构函数。
48.内存泄露
操作堆内存时,如果分配了内存,就有责任回收它,否则这块内存就无法重新使用,称为内存泄漏。
49.sizeof运算符
获得对象在内存中的长度,以字节为单位。
50.表达式
由操作符和标识符组合而成,产生一个新的值。
51.算术表达式
用算术运算符和括号将运算对象(也称操作数)连接起来,符合C++语法规则的式子。
52.关系表达式
用关系运算符和括号将运算对象(也称操作数)连接起来,符合C++语法规则的式子。
53.逻辑表达式
用逻辑运算符和括号将运算对象(也称操作数)连接起来,符合C++语法规则的式子。
54.赋值表达式
由赋值运算符将一个变量和一个表达式连接起来,符合C++语法规则的式子。
55.逗号表达式
由逗号操作符将几个表达式连接起来,符合C++语法规则的式子。
56.条件表达式
由条件运算符将运算对象连接起来,符合C++语法规则的式子。
57.语句
在函数中控制程序流程执行的基本单位,如if语句,while语句,switch语句, do语句, 表达式语句等。
58.复合语句
封闭于大括号{}内的语句序列。
59.循环语句
for 语句, while 语句, do 语句三种。
60.条件语句
基于某一条件在两个选项中选择其一的语句称为条件语句。 61.成员函数
在类中说明的函数称为成员函数。
62.全局函数
定义在所有类之外的函数。
63.main函数
由系统自动调用开始执行C++程序的第一个函数
64.外部函数
在定义函数时,如果冠以关键字extern,表示此函数是外部函数。
65.内联函数
在函数前加上关键字inline说明了一个内联函数,这使一个函数在程序行里进行代码扩展而不被调用。这样的好处是减少了函数调用的开销,产生较快的执行速度。但是由于重复编码会产生较长代码,所以内联函数通常都非常小。如果一个函数在类说明中定义,则将自动转换成内联函数而无需用inline说明。
66.函数重载
在同一作用域范围内,相同的函数名通过不同的参数类型或参数个数可以定义几个函数,编译时编译器能够识别实参的个数和类型来决定该调用哪个具体函数。需要注意的是,如果两个函数仅仅返回类型不同,则编译时将会出错,因为返回类型不足以提供足够的信息以使编译程序判断该使用哪个函数。所以函数重载时必须是参数类型或者数量不同。
67.函数覆盖
对基类中的虚函数,派生类以相同的函数名及参数重新实现之。
68.函数声明
在C++中,函数声明就是函数原型,它是一条程序语句,即它必须以分号结束。它有函数返回类型,函数名和参数构成,形式为:
返回类型 function (参数表);
参数表包含所有参数的数据类型,参数之间用逗号分开。如下函数声明都是合法的。
int Area(int length , int width ) ;
或 int Area ( int , int ) ;
69.函数定义
函数定义与函数声明相对应,指函数的具体实现,即包括函数体。如:
int Area( int length , int width )
{
// other program statement
}
70.函数调用
指定被调用函数的名字和调用函数所需的信息(参数)。
71.函数名
与函数体相对,函数调用时引用之
72.函数类型
(1) 获取函数并返回值。
(2) 获取函数但不返回值。
(3) 没有获取参数但返回值。
(4) 没有获取参数也不返回值。
73.形式参数
函数中需要使用变元时,将在函数定义时说明需要接受的变元,这些变元称为形式参数。形式参数对应于函数定义时的参数说明。其使用与局部变量类似。
74.实际参数
当需要调用函数时,对应该函数需要的变元所给出的数据称为实际参数。
75.值传递
函数调用时形参仅得到实参的值,调用结果不会改变实参的值。
76.引用传递
函数调用时形参为实参的引用,调用结果会改变实参的值。
77.递归
函数的自我调用称为递归。每次调用是应该有不同的参数,这样递归才能终止。
78.函数体
与函数名相对,指函数最外边由{}括起来的部分。
79.作用域
指标识符在程序中有效的范围,与声明位置有关,作用域开始于标识符的生命处。分:局部作用域,函数作用域,函数原型作用域,文件作用域,类作用域。
80.局部作用域
当标识符的声明出现在由一对花括号所括起来的一段程序内时,该标示符的作用域从声明点开始到块结束处为止,此作用域的范围具有局部性。
81.全局作用域
标识符的声明出现在函数,类之外,具有全局性。
82.类作用域
指类定义和相应的成员函数定义范围。
83.全局变量
定义在任何函数之外,可以被任一模块使用,在整个程序执行期间保持有效。当几个函数要共享同一数据时全局变量将十分有效,但是使用全局变量是有一定弊端的:全局变量将在整个程序执行期间占有执行空间,即使它只在少数时间被用到;大量使用全局变量将导致程序混乱,特别是在程序较复杂时可能引起错误。
84.局部变量
定义在函数内部的变量。局部变量只在定义它的模块内部起作用,当该段代码结束,这个变量就不存在了。也就是说一个局部变量的生命期就是它所在的代码块的执行期,而当这段代码再次被执行时该局部变量将重新被初始化而不会保持上一次的值。需要注意的是,如果主程序和它的一个函数有重名的变量,当函数被调用时这个变量名只代表当前函数中的变量,而不会影响主程序中的同名变量。
85.自动变量
由auto修饰,动态分配存储空间,存储在动态存储区中,对他们分配和释放存储空间的工作是由编译系统自动处理的。
86.寄存器变量
存储在运算器中的寄存器里的变量,可提高执行效率。
87.静态变量
由连接器分配在静态内存中的变量。
88.类
一种用户自定义类型,有成员数据,成员函数,成员常量,成员类型组成。类是描叙C++概念的三个基本机制之一。
89.外部变量
由extern修饰的变量
90.堆
即自由存储区,new 和delete 都是在这里分配和释放内存块。
91.栈
有两个含义:(1)指内存中为函数维护局部变量的区域。(2)指先进后处的序列。
92.抽象类
至少包含一个纯虚函数的类。抽象类不能创建对象,但可以创建指向抽象类的指针,多态机制将根据基类指针选择相应的虚函数。
93.嵌套类
在一个类里可以定义另一个类,被嵌入类只在定义它的类的作用域里有效。
94.局部类
在函数中定义的类。注意在函数外这个局部类是不可知的。由于局部类的说明有很多限制,所以并不常见。
95.基类
被继承的类称为基类,又称父类、超类或范化类。它是一些共有特性的集合,可以有其它类继承它,这些类只增加它们独有的特性。
96.派生类
继承的类称为派生类。派生类可以用来作为另一个派生类的基类,实现多重继承。一个派生类也可以有两个或两个以上的基类。定义时在类名后加":被继承类名"即可。
97.父类
即基类。见95基类的解释。
98.子类
即派生类。见96派生类的解释。
99.对象
有两重含义:
1. 内存中含有某种数据类型值的邻近的区域。
2. 某种数据类型的命名的或未命名的变量。一个拥有构造函数的类型对象在构造函数完成构造之前不能认为是一个对象,在析构函数完成析构以后也不再认为它是一个对象。
100. 数据成员
指类中存储数据的变量。
101.实例化
即建立类的一个对象。
102.构造函数
是一个类的实例的初始化函数,将在生成类的实例时被自动调用,用于完成预先的初始化工作。一个类可以有几个构造函数,以不同的参数来区别,即构造函数可以被重载,以便不同的情况下产生不同的初始化;也可以没有构造函数,此时系统将调用缺省的空构造函数。需要注意的是构造函数没有返回类型。
103.成员初始化表
成员初始化表可用于初始化类中的任何数据成员,放在构造函数头与构造函数体之间,用":"与构造函数头分开,被初始化的数据成员的值出现在一对括弧之间,它们之间用逗号分开。
104.析构函数
是一个类的实例的回收函数,将在该实例结束使用前被自动调用,用于完成资源的释放。一个类只可以有一个析构函数,当析构函数执行后,该实例将不复存在。析构函数同样没有返回值。
105.虚析构函数
由virtual 修饰的析构函数,当用基类指针释放派生类对象时可根据它所指向的派生类对象释放准确的对象。
106.继承
面向对象的程序设计语言的特点之一。即一个对象获得另一个对象的特性的过程。如将公共属性和服务放到基类中,而它的各派生类除了有各自的特有属性和服务外还可以共享基类的公共属性和服务。这样的好处是容易建立体系,增强代码重复性。
107.单继承
一个派生类只有一个基类,成为单继承。
108.重继承
一个派生类拥有多个基类,成为多继承。
109.虚函数
在基类中说明为virtual并在派生类中重定义的函数。重定义将忽略基类中的函数定义,指明了函数执行的实际操作。当一个基类指针指向包含虚函数的派生对象时,C++将根据指针指向的对象类型来决定调用哪一个函数,实现了运行时的多态性。这里的重定义类似于函数重载,不同的是重定义的虚函数的原型必须和基类中指定的函数原型完全匹配。构造函数不能是虚函数,而析构函数则可以是。
110.纯虚函数
在基类中只有声明没有实现的虚函数。形式为:
virtual type funname(paralist)=0。这时基函数只提供派生类使用的接口,任何类要使用必须给出自己的定义。
111.多态性
给不同类型的实体提供单一接口。虚函数通过基类接口实现动态多态性,重载函数和模板提供了静态多态性。
112.复制构造函数
以自身类对象为参数的构造函数,如Z::Z(const Z&). 用在同类对象间进行初始化。
113.运算符重载
C++中可以重载双目(如+,×等)和单目(如++)操作符,这样可以使用户像使用基本数据类型那样对自定义类型(类)的变量进行操作,增强了程序的可读性。当一个运算符被重载后,它将具有和某个类相关的含义,同时仍将保持原有含义。
114.静态成员函数
成员函数通过前面加static说明为静态的,但是静态成员函数只能存取类的其他静态成员,而且没有this指针。静态成员函数可以用来在创建对象前预初始化专有的静态数据。
115.静态成员变量
在成员变量之前加static关键字将使该变量称为静态成员变量,该类所有的对象将共享这个变量的同一拷贝。当对象创建时,所有静态变量只能被初始化为0。使用静态成员变量可以取代全局变量,因为全局变量是违背面向对象的程序设计的封装性的。
116.私有成员
只能由自身类访问的成员。
117.保护成员
只能由自身类及其派生类访问的成员。
118.友元
被某类明确授权可访问其成员的函数和类。
119.友元函数
在函数前加上关键字friend即说明了一个友元函数,友元函数可以存取类的所有私有和保护成员。友元在重载运算符时有时是很有用的。
120.友元类
被某类明确授权可访问其成员的类
121.例外处理
报告局部无法处理某错误的基本方式。由try., throw , catch组成。
122.文件
是用于从磁盘文件到终端或打印机的任何东西。流通过完成打开操作与某文件建立联系。
|