随笔 - 6  文章 - 0 评论 - 4 trackbacks - 0

1、乘法运算符
 
2、定义指针
int *p = 0; 还是 int* p = 0;?
后一种比较容易这样理解:定义了一个变量p,它是指针型的(更详细一点,是指向int的指针型),相比而言,前面一种定义似乎是定义了*P这个奇怪的东西。但是后面一种写法会带来一个容易产生的误解:
int* p1, p2;
这儿给人的感觉似乎是定义了两个指针型变量p1和p2,但是,事实上,这种直觉是错误的,正确的理解方式是int *p1, p2;即p1是指针型的,而p2确是整型的。
在MS VC++ 6.0中,是按照后面一种格式写的。
 
3、何谓指针?
指针仅仅表示一个内存中的某个地址?
非也,注意到,我们在定义指针的时候,都关联了一个类型,如int,char,或者是string等等,如果说指针仅仅表示一个内存中的地址,那何必要关联这么多变化的东西呢?完全可以DWORD p=0;这样解决问题。
关联了的数据类型是作何用的呢?
它可以指示编译器怎样解释特定地址上内存的内容,以及该内存区域应该跨越多少内存单元。如 int *p;
编译器可以从这个定义中获得信息:1、p指向的内存存放的是整型数据,2、由于该内存区域只存放了一个数据,跨越的内存区域为4个字节,即p+1的效果是跳过了四个字节。
另一个复杂一点的例子,如
struct a
{int x1;
short x2;
a *next;
}
定义指针 a *p;那么编译器对这个指针又作何解释呢?
1、p指向的内存区域依次存放了三种类型的数据,分别是int,short和一个指针型数据。
2、p指向的内存区域跨越了12个字节,即p+1的效果是跳过了12个字节。(为何不是10?对齐的原因)
 
但是,C++中定义了一种特殊的指针,它去处了一般指针中对内存区域内容以及大小的解释,以满足特定定的需要,如我们只需要某块内存的首地址,不需要考虑其中的数据类型以及大小。这种形式为 void *; 这种类型的指针可以被任意数据类型的指针赋值,如上面的a* 型,void *q = p; 唯一例外的是,不能把函数指针赋给它。
 
4、关于const修饰符
当const遇到指针,麻烦事就来了,看:const int* p; int* const p; const int* const p;
这三个表达式,第一个表示p是一个指针,p本身平凡无比,但是p所指向的对象是一个特殊的对象--整型常量;第二个表示:这个p指针不是一个普通的指针,它是个常量指针,即只能对其初始化,而不能赋值,另外,这个指针所指向的对象是一平凡的int型变量;第三个则结合了前两者:指针和指向的对象都非同寻常,都是常量。
有了const,赋值的问题就变得麻烦起来,
首先,对于 const int* p;这儿由于p指向的对象是个常量,所以在通过p来引用这个对象的时候不可对其进行赋值!对于一个常量对象,不可以用普通的指针指向,而必须用这种指向常量的指针,原因很简单,通过普通指针可以改变指向的那个值,但是对于一个非常量对象,即普通变量,可不可以将其地址赋给指向常量的指针呢?是可以的,但是一旦这样指向之后,由于这个指针本身定义的是指向常量的指针,因而编译器统一认为其是指向变量的,因而此时不可以通过该指针修改所指向的对象的值。
第二,对于 int* const p;这儿p本身是个常量指针,所以根本就不能赋值,所以不存在赋值的问题。不可以用常量对其进行初始化,因为这个指针不是指向常量的;只能用变量对其初始化。
第三,对于 const int* const p;这儿,只能初始化,不能赋值。可以利用常量进行初始化;也可以利用变量对其初始化,不过不可以利用该指针对该变量进行赋值。
 
const int* p这种指向常量对象的指针常用来用作某些函数的形参,用意是从编译器的角度防止用户在函数中将传递进去的参数修改,虽然用户本身也可以避免,但是这样更可靠一点--当用户不小心作出修改实参的行为时,编译器发现并阻止这种行为。
 
this指针是const xx* const型的。
 
5、函数与指针
指向函数的指针:可以利用它代替函数名来调用函数。
如何定义函数指针,由于一个程序中可以用多个函数名相同的情形(即函数的重载),因而,定义函数指针的时候,必须包含函数的参数,这样才能准确地将指针指向某函数。
定义:int (*p)(const char*, int); 表示p是一个指向函数的指针,该函数的两个参数为const char* 和int,另外该函数返回int型值。
容易混淆的是:int *p(const char *, int); 缺少了一个括号,此时编译器的解释是 int* p(const char*, int);即其含义是一个函数的声明,函数名为p,返回一个指向int型的指针。那么 int* (*p)(const char*, int);则是定义了一个函数指针p,它指向一个函数,该函数的两个参数为const char*和int,该函数返回一个指向int型的指针
 
函数指针的初始化与初始化:
函数名如同数组名,编译器将其解释为指向该类型函数的指针,故而,可以领用函数名,或者&函数名对函数指针进行初始化或者赋值,另外,可以用另一个函数指针对该指针进行初始化以及赋值。重要的一点是指针与函数名,指针与指针必须具有完全相同的参数表和返回类型(必须完全完全一样,任何一点不同都不可以)。不存在隐式的类型转换,用户必须保证完全的一致性。
初始化或者赋值为0,表示不指向任何函数。
 
利用函数指针调用函数是可以p(x,y)这样调用,也可以(*p)(x,y)这样调用,前提是p已经正确的赋值或者初始化。
 
函数返回指针:可以返回一个非基本类型的对象。
 
6、数组与指针
int a[3] = {1,2,3};
考虑 a,a[0], &a, 以及 &a[0]这三个表达式的含义:
首先这三个表达式的数值结果是一样的--数组的首地址(即数组中第0个元素的地址),但是编译器对三者的解释不同:
对于a,编译器将其解释为一个指针,指向的是一个整型数据,因而利用a+1即指向数组中的第一的元素,a+2指向第二个元素。
对于a这个指针有些特殊的性质:
a不是一个普通的指针,它同时是一个数组名,即关联了一个数组,因而某些性质上与普通的指针不同。
普通的指针可以被赋值,即可以用一个地址或者另一个指针修改当前指针的指向,然而对于a这种关联了一个数组的指针,如果允许这样赋值的话,那么数组中的元素将无法被访问,所以不允许对数组名代表的指针进行赋值。在这一点上a相当于指针常量,即只能被初始化,不可以进行赋值。
   虽然a不可以被赋值,但是将a赋给其他的元素是完全可以的,这一点同普通的指针没有不同。
综上,a相当于一个指针常量。(type* const型的)
 
本质上a[i]操作被编译器解释为*(a+i)操作,即[]运算符是通过数组名指针实现的,因而&a[0]的含义即&(*a),显然对一个指针先*(解引用),再&(引用),等价于什么都没做,还是这个指针本身,因而a完全等价于&a[o],--(&a[0])[i]等价于a[i],形式有点诡异,呵呵。而对于&a这个表达式,奇怪的是这个也是数组的首地址,那么就是说,这个数组的首地址中存放了一个指针常量(即数组名),但是数组的首地址中不是存放的一个int型的数字吗?这是怎么回事呢?难道一个地址能存放两个东西?
暂时无法解释,可以这样认为编译器发现这种&和数组名的结合运算时,即返回数组首地址,只不过,这是,这仅仅是个纯粹的地址,它不再具有指针的特性,即编译器不再将其解释为指针,用户不可以通过+1运算来访问下一个数组元素。它的+1就是数学上的+1。
 
当数组变为多维,问题变成怎么样了呢?
考虑二维数组 int b[4][3] = {{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}}; b,&b, b[0], &b[0], &b[0][0]几个表达式的含义:
首先,c++中数组元素的存放是以行序为主序,即第一段存放第一行的数据,第二段存放第二行的数据,....,如此。
首先考虑数组名b,编译器同样将数组名b解释为一个指针,但是显然这个指针不是普通的指针。这个b指向数组所有元素的首地址,这一点是勿庸置疑的,那么这个指针一步跨越的内存是多大呢?在本例中,b一步跨越12个字节,即b一步跨越数组中的一行元素,实际上b是一个指针的指针,或者说指向指针的指针,即b所指向的内容是一个指针,(同样对于b+1,b+2),b[i][j]这种访问方式本质上即:先通过+i,将指针跳跃到第i行,从而获得了指向第i行首地址的指针b[i],然后通过这个指针,再通过+j,跳跃j步,到达了第j个元素,即找到第i行,第j列的元素。
所以b是指针的指针,b[i]是指针,这儿,b[i]类似于一维中的a。
那么&b呢?&b仍然是数组的首地址,但是跟一维类似的是,这是个纯粹的地址,不再具有指针的特性,它的+1就是数学上的+1,不可以利用+1来访问下一个元素。同样的道理对于&b[i],&运算符加上之后,本来是作为指针的b[i]被剥夺的指针的资格,返回一个纯粹的地址。
实际上,由于[]本质上是对指针的解引用,那么我们访问数组元素时可以不拘于a[i][j]这种方式,可以这样:(*(a+i))[j], 或者*(a[i]+j),或者 *(*(a+i)+j),这几种写法是等价的。
对于&b[i][j]呢?我们把b[i][j]换一种写法,写成*(*(b+i)+j),这样问题就容易看清楚了,原来的*b[i][j]就等价于&(*(*(b+i)+j)),我们可以把最外层的括号脱掉,就成了*(b+i)+j,即b[i]+j,显然这是一个指针,指向第i行,第j列元素的指针,对该指针的解释是一次跨越一个int型的数据。
 
让我们再变态一点,考虑三维的情形,虽然三维的数组不多见,还是考虑一下吧,毕竟空间的坐标是用三维表示的。
int c[2][3][4] = {{{1,2,3,4},{5,6,7,8},{9,10,11,12}}, {{13,14,15,16},{17,18,19,20},{21,22,23,24}}};
首先,数组名c,编译器将c解释为一个指针,指向数组的首地址,由于行序是主序,所以,该指针一步跨越12个整型数,共48个字节,实际上即跨越了一个二维数组。
对于&c,跟一维二维的情形类似,是一个纯粹的地址.
c[i]呢?可以推测,c[i]与二维中的b类似,即指向指针的指针,c[i]一步跨越4个整数,16个字节。c[i]是指向指针的指针,那么c便是指向指向指针的指针的指针(晕~)。
c[i]亦等价于*(c+i)
至于c[i][j],这才是真正的int型的指针,即指向真实数据的指针,一步跨越一个int型,4个字节。跟二维类似,对于&c[i][j],编译器返回一个地址,虽然跟c[i][j]的值一样,但是只是一个纯粹的地址,跨越单元为一个字节。
对于c[i][j][k],不需要废话,对于&c[i][j][k],这是一个地址吗?这是一个指针吗?我们还是要借助[]的另一种表示方法:c[i][j][k]等价于*(*(*(c+i)+j)+k),那么&c[i][j][k]就等价于*(*(c+i)+j)+k,即c[i][j]+k,即指向第(i,j,k)个元素的指针,一步跨越单元为一个int型。
让我们来看一看,寻找第(i,j,k)个元素有哪些写法:
1、c[i][j][k]
2、*(c[i][j]+k)
3、*(*(c[i]+j)+k)
4、*(*(*(c+i)+j)+k)
5、(*(c+i))[j][k]
6、(*(*(c+i)+j))[k]
7、*((*(c+i))[j]+k)
8、(*(c[i]+j))[k]
可见,共八种写法,实际上就是一共有三个解引用,选择用[]还是用*,这样,总共有8个组合。(那么二维的就是4种,一维的2种)
 
7、typedef与指针
typedef似乎很简单,如typedef int integer;然而,这些简单的typedef语句容易让人产生一种误解,typedef就是一种宏替换,把后面的自定义类型替换成前面的已知类型,事实是这样的吗?显然不是!
考虑这样的问题:如何定义一个指向整型的指针类型?如何定义一个函数指针类型?
第一个问题很简单:typedef int* int_pointer;即可,对于第二个问题,似乎就没有那么简单了,首先,看函数指针的定义方法:int (*p)(const&, int); 这个p指向的函数必须返回int,形参必须是const&和int。现在要将这种指针类型命名为func_pointer,其定义的方法如下:
typedef int (*func_pointer)(const&, int);
可以这样来理解:typedef int integer;将typedef去掉,那就是个变量的定义,这儿即定义了一个int型的变量integer,考虑这个integer是什么类型的,那么这个typedef语句就是将integer定义为这个类型的。将typedef int (*func_pointer)(const&, int);中的typedef去掉,就成了一个函数指针定义,即func_pointer被定义为函数指针类型变量,那么原来的typedef即将func_pointer定义为函数指针类型。
 
8、函数,数组与指针
int (*testCases[10])();
这个表达式是什么意思?指针,数组,函数糅合在了一起问题变得复杂起来。它定义了数组,testCases[10],数组中的元素是函数指针,函数指针的类型是 int (*)();
怎么来理解这种定义呢?首先考虑数组的定义,数组的定义一般模式是:
类型 数组名[大小];
考虑这个表达式,似乎是定义了一个数组,但是数组名[大小]被夹在了中间,那么类型是什么呢,发现类型并不是简单的数据类型,而是一个函数指针类型int (*p)(),这个函数没有参数,返回int型。从而这个表达式的含义是:定义了一个函数指针型的数组,大小是10。
可以利用typedef来简化这种定义:
typedef int (*PFV)();
PFV testCases[10];
 
其实int (*testCases[10])();这儿我们定义了一个函数指针数组,数组是主体。
下面考虑这样的问题:如何定义一个指向数组的指针?
指向数组的指针,好像比较新鲜,所谓指向数组的指针,即指针的一步跨越是一个数组,跟指向整型的指针一步跨越一个整型一个道理。事实上前面已经碰到了指向数组的指针,如二维数组名,实际上就是一个指向数组的指针,它一次跨越一行的数据,实际上即是跨越了一个一维数组,而三维数组名呢,也是一个指向数组的指针,它一次跨越的是低维组成的一个二维数组。
数组指针(即指向数组的指针)的定义:
int (*ptr)[3];  这个表达式定义了一个数组指针ptr,ptr一次跨越一个由3个int型组成的一维数组。发现其定义的方式与函数指针定义的方式很相似,只是把()换作了[]。
更进一步,如果要定义一个指向数组的指针,而数组中的元素不是简单的int型,而是比较复杂的类型,那该如何定义呢?事实上数组指针这种东西就已经够稀有的了,一般编程绝对不会用到,我们只需要能读懂一些比较复杂的东西就行了,自己没有必要构造这么复杂的类型。
 
比较复杂的表达式:
      1、int (*(*(*p())[])())[];
首先,根据p()判断p是一个函数,再根据p()前面的*号判断该函数返回一个指针,下面就看这个指针指向的是什么类新了,我们可以把*p()替换成一个*pointer,这个pointer就是函数p返回的指针,那么就成了int (*(*(*pointer)[])())[];再根据(*pointer)[],这说明了指针pointer是指向的一个数组,那么这个数组中的元素是什么类型呢?由于数组名实际上就是个指针,我们把(*pointer)[](即(*p())[])替换成一个array,这样就成了 int (*(*array)())[];发现array是一个函数指针,从而数组中的每个元素是函数指针,而这个函数呢,又返回一个指针类型,把(*array)()用func代替,就成了int (*func)[];这说明了func函数返回的是指向数组的指针,数组中的元素是int型。
这个表达式够酷!!!
2、p = (int( * (*)[20])[10])q;
这是一个强制类型转换,q被强制类型转换成一个这样的指针类型,这个指针呢直线一个具有20个元素的数组,这个数组中的元素也是指针,是指向另外一种数组,这种数组是含有10个int型数据的一维数组。
 
可见,分析复杂的表达式时(所谓复杂,即糅合了指针,数组,函数三样,缺少了一样就不会复杂了),从括号的最里层做起,最里层的东西是复杂表达式的“根节点”,然后一层一层脱,脱的时候,是这样的,比如里层是个数组,那么就是说这个数组的元素是什么呢,那就是外层的东西,如果里层是个有返回值的函数,那么就是说这个函数返回什么值呢?那就是外层的东西,就这样一层一层地把表达式解析清楚。
 
 
关于typedef还有一些要说的:
typedef  int (*PFV)(); 这是定义了一个函数指针,那么PFV p;就可以定义了一个指向函数的指针。
typedef int (*p[10])(); 这是把p定义为函数指针数组,那么 p array;语句就可以定义了一个函数指针数组,数组名即为array,array这个数组含10个元素。
typedef int (*parray)[3];这是定义了一个指向整型数组的指针,那么 parray ptr;就定义了一个指向数组的指针。如何对这个ptr赋值或者初始化呢?事实上,是通过二维数组名来对其进行赋值(初始化)的,因为二维数组名作为指针来讲,就是一个指向数组的指针,一次跨越一个数组。
typedef int a[3][3]; 这个语句什么意思呢?这是把a定义为一个3*3的整型数组类型。当a b = {1}时就完成了一个3×3的整型数组的定义初始化的工作。
同样,简单一点 typedef int a[3];这个语句是把a定义为一个一维数组类型。
typedef  void func(int); 这个语句定义了一个函数类型。通过这个typedef,我们可以比较清晰地定义出函数指针,func* p;即可。
 
typedef char* string;
const string str;
这个str是什么类型的呢?const char * str,即指向常量的指针类型?事实上,答案有些不可思议,str是一个常量指针,而不是指针常量,即const修饰符针对的是指针,而不是char。
 
9、引用与指针
引用类似与指针常量,只可初始化,不可赋值。别名(alias)是引用(reference)的另一种叫法。通过引用可以间接地操操纵对象。
常量引用,即类似与指向常量的常量指针,对常量引用的初始化,有一点特殊,可以用常量,变量,甚至是常数对其进行初始化。
对于用变量初始化常量引用,那么不能通过这个引用修改这个变量,但是本来的变量名可以。这一点,类似变量地址赋给常量指针。通过常量指针不可以修改变量,但是变量自身的变量名可以。
可以有指针的引用,如 int a = 1; int* p = &a; int* &r = p; 那么r就成了指针p的引用。如果是const int* p = &a;说明是常量指针,那么定义引用的时候,就要如此定义,const int* &r = p;这个语句说明r是一个指针的引用,这个指针是个指向常量的指针变量,而并不意味着这个引用是个常量引用。那如果说这个指针不仅仅是常量指针,而且是个指针常量,即const int* const p;那么定义引用时要注意应该const int* const &r = p;这样表明该引用是个常量引用。这里有一个问题当用一个变量的地址初始化引用时如,int a = 22; int* const &pi_ref = &a;需要注意应该定以为常量引用,因为&a不是变量名,而是类似常数。而若const int a = 22;则应该const int* const &pi_ref = &a;即中间的const是用来定义常量引用的,而前面的const反映的是引用指向的对象(指针)是指向的的const对象。
 
我们有对象名,或者对象的指针,这些都可以操纵对象,为何要引入引用的概念呢?
事实上,引用最常用的是用作函数的形参。要在函数中操纵一个外部对象的时候,利用引用是一个好办法。
 
关于引用
首先,引用只可初始化,不可被赋值,因为,被初始化后的引用,就成了被引用的对象的别名,再行赋值,就不是对引用本身的赋值了,而是对所引用的对象的赋值了。
posted @ 2012-03-12 11:02 diorlv 阅读(3) 评论(0) 编辑

   为什么使用const?采用符号常量写出的代码更容易维护;指针常常是边读边移动,而不是边写边移动;许多函数参数是只读不写的。const最常见用途是作为数组的界和switch分情况标号(也可以用枚举符代替),分类如下:

  常变量:  const 类型说明符 变量名

  常引用:  const 类型说明符 &引用名

  常对象:  类名 const 对象名

  常成员函数:  类名::fun(形参) const

  常数组:  类型说明符 const 数组名[大小]    

  常指针:  const 类型说明符* 指针名 ,类型说明符* const 指针名

首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:

     const int a=5;  int const a=5; 等同

     类名 const 对象名  const 类名 对象名 等同

 

用法1:常量
    取代了C中的宏定义,声明时必须进行初始化(!c++类中则不然)。const限制了常量的使用方式,并没有描述常量应该如何分配。如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。―《C++ Program Language》
    用const声明的变量虽然增加了分配空间,但是可以保证类型安全。
    C标准中,const定义的常量是全局的,C++中视声明位置而定

用法2:指针和常量
    使用指针时涉及到两个对象:该指针本身和被它所指的对象。将一个指针的声明用const“预先固定”将使那个对象而不是使这个指针成为常量。要将指针本身而不是被指对象声明为常量,必须使用声明运算符*const。
    所以出现在 * 之前的const是作为基础类型的一部分:
char *const cp; //到char的const指针
char const *pc1; //到const char的指针
const char *pc2; //到const char的指针(后两个声明是等同的)
    从右向左读的记忆方式:
cp is a const pointer to char. 故pc不能指向别的字符串,但可以修改其指向的字符串的内容
pc2 is a pointer to const char. 故*pc2的内容不可以改变,但pc2可以指向别的字符串

且注意:允许把非 const 对象的地址赋给指向 const 对象的指针,不允许把一个 const 对象的地址赋给一个普通的、非 const 对象的指针。

用法3:const修饰函数传入参数
    将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。
    通常修饰指针参数和引用参数:
void Fun( const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数

用法4:修饰函数返回值
    可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。

用法5:const修饰成员函数(c++特性)
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。

具体展开来讲:
(一). 常量与指针

 常量与指针放在一起很容易让人迷糊。对于常量指针和指针常量也不是所有的学习C/C++的人都能说清除。例如:

    const int *m1 = new int(10);

    int* const m2 = new int(20);

在上面的两个表达式中,最容易让人迷惑的是const到底是修饰指针还是指针指向的内存区域?其实,只要知道:const只对它左边的东西起作用,唯一的例外就是const本身就是最左边的修饰符,那么它才会对右边的东西起作用。根据这个规则来判断,m1应该是常量指针(即,不能通过m1来修改它所指向的内容。);而m2应该是指针常量(即,不能让m2指向其他的内存模块)。由此可见:

   1. 对于常量指针,不能通过该指针来改变所指的内容。即,下面的操作是错误的:

      int i = 10;

      const int *pi = &i;

      *pi = 100;

      因为你在试图通过pi改变它所指向的内容。但是,并不是说该内存块中的内容不能被修改。我们仍然可以通过其他方式去修改其中的值。例如:

      // 1: 通过i直接修改。

      i = 100;

      // 2: 使用另外一个指针来修改。

      int *p = (int*)pi;

      *p = 100;

      实际上,在将程序载入内存的时候,会有专门的一块内存区域来存放常量。但是,上面的i本身不是常量,是存放在栈或者堆中的。我们仍然可以修改它的值。而pi不能修改指向的值应该说是编译器的一个限制。
   2. 根据上面const的规则,const int *m1 = new int(10);我们也可写作:

      int const *m1 = new int(10);

      这是,理由就不须作过多说明了。
   3. 在函数参数中指针常量时表示不允许将该指针指向其他内容

      void func_02(int* const p)

      {

      int *pi = new int(100);

      //错误!P是指针常量。不能对它赋值。

      p = pi;

      }

      int main()

      {

      int* p = new int(10);

      func_02(p);

      delete p;

      return 0;

      }

   4. 在函数参数中使用常量指针时表示在函数中不能改变指针所指向的内容。

    void func(const int *pi)

    {

    //错误!不能通过pi去改变pi所指向的内容!

    *pi = 100;

    }

    int main()

    {

    int* p = new int(10);

    func(p); 

    delete p;

    return 0;

    }

  我们可以使用这样的方法来防止函数调用者改变参数的值。但是,这样的限制是有限的,作为参数调用者,我们也不要试图去改变参数中的值。因此,下面的操作是在语法上是正确的,但是可能破还参数的值:

    #include <iostream>

    #include <string>

    void func(const int *pi)

    {

    //这里相当于重新构建了一个指针,指向相同的内存区域。当然就可以通过该指针修改内存中的值了。

    int* pp = (int*)pi;

    *pp = 100;

    }

    int main()

    {

    using namespace std;

    int* p = new int(10);

    cout << "*p = " << *p << endl;

    func(p);

    cout << "*p = " << *p << endl;

    delete p;

    return 0;

    }

(二):常量与引用

    常量与引用的关系稍微简单一点。因为引用就是另一个变量的别名,它本身就是一个常量。也就是说不能再让一个引用成为另外一个变量的别那么他们只剩下代表的内存区域是否可变。即:

    int i = 10;

    // 正确:表示不能通过该引用去修改对应的内存的内容。

    const int& ri = i;

    // 错误!不能这样写。

    int& const rci = i;

    由此可见,如果我们不希望函数的调用者改变参数的值。最可靠的方法应该是使用引用。下面的操作会存在编译错误:

    void func(const int& i)

    {

    // 错误!不能通过i去改变它所代表的内存区域。

    i = 100;

    }

    int main()

    {

    int i = 10;

    func(i);

    return 0;

    }

    这里已经明白了常量与指针以及常量与引用的关系。但是,有必要深入的说明以下。在系统加载程序的时候,系统会将内存分为4个区域:堆区 栈区全局区(静态)和代码区。从这里可以看出,对于常量来说,系统没有划定专门的区域来保护其中的数据不能被更改。也就是说,使用常量的方式对数据进行保护是通过编译器作语法限制来实现的。我们仍然可以绕过编译器的限制去修改被定义为“常量”的内存区域。看下面的代码:

    const int i = 10;

    // 这里i已经被定义为常量,但是我们仍然可以通过另外的方式去修改它的值。

    // 这说明把i定义为常量,实际上是防止通过i去修改所代表的内存。

    int *pi = (int*) &i;

(三):常量函数

    常量函数是C++对常量的一个扩展,它很好的确保了C++中类的封装性。在C++中,为了防止类的数据成员被非法访问,将类的成员函数分成了两类,一类是常量成员函数(也被称为观察着);另一类是非常量成员函数(也被成为变异者)。在一个函数的签名后面加上关键字const后该函数就成了常量函数。对于常量函数,最关键的不同是编译器不允许其修改类的数据成员。例如:

    class Test

    {

    public:

    void func() const;

    private:

    int intValue;

    };

    void Test::func() const

    {

    intValue = 100;

    }

    上面的代码中,常量函数func函数内试图去改变数据成员intValue的值,因此将在编译的时候引发异常

    当然,对于非常量的成员函数,我们可以根据需要读取或修改数据成员的值。但是,这要依赖调用函数的对象是否是常量。通常,如果我们把一个类定义为常量,我们的本意是希望他的状态(数据成员)不会被改变。那么,如果一个常量的对象调用它的非常量函数会产生什么后果呢?看下面的代码:

    class Fred{

    public:

    void inspect() const;

    void mutate();

    };

    void UserCode(Fred& changeable, const Fred& unChangeable)

    {

    changeable.inspect(); // 正确,非常量对象可以调用常量函数

    changeable.mutate(); // 正确,非常量对象也允许修改调用非常量成员函数修改数据成员。

    unChangeable.inspect(); // 正确,常量对象只能调用常理函数。因为不希望修改对象状态。

    unChangeable.mutate(); // 错误!常量对象的状态不能被修改,而非常量函数存在修改对象状态的可能

    }

    从上面的代码可以看出,由于常量对象的状态不允许被修改,因此,通过常量对象调用非常量函数时将会产生语法错误。实际上,我们知道每个成员函数都有一个隐含的指向对象本身的this指针。而常量函数则包含一个this的常量指针。如下:

    void inspect(const Fred* this) const;

    void mutate(Fred* this);

     也就是说对于常量函数,我们不能通过this指针去修改对象对应的内存块。但是,在上面我们已经知道,这仅仅是编译器的限制,我们仍然可以绕过编译器的限制,去改变对象的状态。看下面的代码:

    class Fred{

    public:

    void inspect() const;
    private:

    int intValue;

    };

    void Fred::inspect() const

    {

    cout << "At the beginning. intValue = "<< intValue << endl;

    // 这里,我们根据this指针重新定义了一个指向同一块内存地址的指针

    // 通过这个新定义的指针,我们仍然可以修改对象的状态。

    Fred* pFred = (Fred*)this;

    pFred->intValue = 50;

    cout << "Fred::inspect() called. intValue = "<< intValue << endl;

    }

    int main()

    {

    Fred fred;

    fred.inspect();

    return 0;

    }

    上面的代码说明,只要我们愿意,我们还是可以通过常量函数修改对象的状态。同理,对于常量对象,我们也可以构造另外一个指向同一块内存的指针去修改它的状态。这里就不作过多描述了。

    另外,也有这样的情况,虽然我们可以绕过编译器的错误去修改类的数据成员。但是C++也允许我们在数据成员的定义前面加上mutable以允许该成员可以在常量函数中被修改。例如:

    class Fred{

    public:

    void inspect() const;

    private:

    mutable int intValue;

    };

    void Fred::inspect() const

    {

    intValue = 100;

    }

    但是,并不是所有的编译器都支持mutable关键字。这个时候我们上面的歪门邪道就有用了。

    关于常量函数,还有一个问题是重载。

    #include <iostream>

    #include <string>

    using namespace std;

    class Fred{

    public:

    void func() const;

    void func();

    };

    void Fred::func() const

    {

    cout << "const function is called."<< endl;

    }

    void Fred::func()

    {

    cout << "non-const function is called."<< endl;

    }

    void UserCode(Fred& fred, const Fred& cFred)

    {

    cout << "fred is non-const object, and the result of fred.func() is:" << endl;

    fred.func();

    cout << "cFred is const object, and the result of cFred.func() is:" << endl;

    cFred.func();

    }

    int main()

    {

    Fred fred;

    UserCode(fred, fred);

    return 0;

    }

    输出结果为:

    fred is non-const object, and the result of fred.func() is:

    non-const function is called.

    cFred is const object, and the result of cFred.func() is:

    const function is called.

    从上面的输出结果,我们可以看出。当存在同名同参数和返回值的常量函数和非常量函数时,具体调用哪个函数是根据调用对象是常量对像还是非常量对象来决定的。常量对象调用常量成员;非常量对象调用非常量的成员。

    总之,我们需要明白常量函数是为了最大程度的保证对象的安全。通过使用常量函数,我们可以只允许必要的操作去改变对象的状态,从而防止误操作对对象状态的破坏。但是,就像上面看见的一样,这样的保护其实是有限的。关键还是在于我们开发人员要严格的遵守使用规则。另外需要注意的是常量对象不允许调用非常量的函数。这样的规定虽然很武断,但如果我们都根据原则去编写或使用类的话这样的规定也就完全可以理解了。
(四):常量返回值

     很多时候,我们的函数中会返回一个地址或者引用。调用这得到这个返回的地址或者引用后就可以修改所指向或者代表的对象。这个时候如果我们不希望这个函数的调用这修改这个返回的内容,就应该返回一个常量。这应该很好理解,大家可以去试试。

+++++++++++++++++++++++++++++++++++++++

c++ 中const 

+++++++++++++++++++++++++++++++++++++++

1. const常量,如const int max = 100;  
优点:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)
2.  const 修饰类的数据成员。如:
class A
{

    const int size;

    …

}

const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。如

class A

{

 const int size = 100;    //错误

 int array[size];         //错误,未知的size

}
const数据成员的初始化只能在类的构造函数的初始化表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现。如

class A

{…

 enum {size1=100, size2 = 200 };

int array1[size1];

int array2[size2];

}

枚举常量不会占用对象的存储空间,他们在编译时被全部求值。但是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。

3. const修饰指针的情况,见下式:

int b = 500; 
const int* a = &                  [1] 
int const *a = &                  [2] 
int* const a = &                  [3] 
const int* const a = &       [4]

如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也没关系,我们可以参考《Effectivec++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 3;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量

4. const的初始化

先看一下const变量初始化的情况 
1) 非指针const常量初始化的情况:A b; 
const A a = b;

2) 指针const常量初始化的情况:

A* d = new A(); 
const A* c = d; 
或者:const A* c = new A(); 
3)引用const常量初始化的情况: 
A f; 
const A& e = f;      // 这样作e只能访问声明为const的函数,而不能访问一般的成员函数;

    [思考1]: 以下的这种赋值方法正确吗? 
    const A* c=new A(); 
    A* e = c; 
    [思考2]: 以下的这种赋值方法正确吗? 
    A* const c = new A(); 
    A* b = c;

5. 另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中,const可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰是整个函数。有如下几种情况,以下会逐渐的说明用法:A&operator=(const A& a); 
void fun0(const A* a ); 
void fun1( ) const; // fun1( ) 为类成员函数 
const A fun2( );

1) 修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a); 
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A*a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为const A&a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。 
[注意]:参数const通常用于参数为指针或引用的情况,且只能修饰输入参数;若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。

[总结]对于非内部数据类型的输入参数,因该将“值传递”的方式改为“const引用传递”,目的是为了提高效率。例如,将void Func(A a)改为void Func(const A &a)

      对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int &x)

2)  修饰返回值的const,如const A fun2( ); const A* fun3( ); 
这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。const Rational operator*(const Rational& lhs, const Rational& rhs) 

return Rational(lhs.numerator() * rhs.numerator(), 
lhs.denominator() * rhs.denominator()); 
}

返回值用const修饰可以防止允许这样的操作发生:Rational a,b; 
Radional c; 
(a*b) = c;

一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。 
[总结]

1.  一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A实例)或某个对象的引用为const(const A& test = A实例),则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。

2.  如果给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。如:

const char * GetString(void);

如下语句将出现编译错误:

char *str=GetString();

正确的用法是:

const char *str=GetString();

3.     函数返回值采用“引用传递”的场合不多,这种方式一般只出现在类的赙值函数中,目的是为了实现链式表达。如:

class A

{…

 A &operate = (const A &other);  //负值函数

}
A a,b,c;              //a,b,c为A的对象

a=b=c;            //正常

(a=b)=c;          //不正常,但是合法

若负值函数的返回值加const修饰,那么该返回值的内容不允许修改,上例中a=b=c依然正确。(a=b)=c就不正确了。
[思考3]: 这样定义赋值操作符重载函数可以吗? 
const A& operator=(const A& a);

6.     类成员函数中const的使用 
一般放在函数体后,形如:void fun() const; 
任何不会修改数据成员的函数都因该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。如:

class Stack

{

 public:

      void Push(int elem);

      int Pop(void);

      int GetCount(void) const;   //const 成员函数

 private:

      int m_num;

      int m_data[100];

};

int Stack::GetCount(void) const

{

  ++m_num;              //编译错误,企图修改数据成员m_num

  Pop();                    //编译错误,企图调用非const函数

  Return m_num;

}

7. 使用const的一些建议

1) 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委; 
2) 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题; 
3) 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上; 
4) const在成员函数中的三种用法(参数、返回值、函数)要很好的使用; 
5) 不要轻易的将函数的返回值类型定为const; 
6) 除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;

[思考题答案] 
1) 这种方法不正确,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确; 
2) 这种方法正确,因为声明指针所指向的内容可变; 
3) 这种做法不正确; 
在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了: 
A a,b,c: 
(a=b)=c; 
因为a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。

++++++++++++++++++++++++++++++++++++++++

const 在c和c++中的区别  http://tech.e800.com.cn/articles/2009/722/1248229886744_1.html

++++++++++++++++++++++++++++++++++++++++

1. C++中的const正常情况下是看成编译期的常量,编译器并不为const分配空间,只是在编译的时候将期值保存在名字表中,并在适当的时候折合在代码中.所以,以下代码:
using namespace std;
int main()
{
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (int i = 0; i < sizeof array / sizeof *array; i++)
{
cout << array << endl;
}
}
在可以通过编译,并且正常运行.但稍加修改后,放在C编译器中,便会出现错误:
int main()
{
int i;
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (i = 0; i < sizeof array / sizeof *array; i++)
{
printf("%d",array);
}
}
错误消息:
c:\test1\te.c(8): error C2057: 应输入常数表达式
c:\test1\te.c(8): error C2466: 不能分配常数大小为 0 的数组
出现这种情况的原因是:在C中,const是一个不能被改变的普通变量,既然是变量,就要占用存储空间,所以编译器不知道编译时的值.而且,数组定义时的下标必须为常量.
2. 在C语言中: const int size; 这个语句是正确的,因为它被C编译器看作一个声明,指明在别的地方分配存储空间.但在C++中这样写是不正确的.C++中const默认是内部连接,如果想在C++中达到以上的效果,必须要用extern关键字.即C++中,const默认使用内部连接.而C中使用外部连接.
(1) 内连接:编译器只对正被编译的文件创建存储空间,别的文件可以使用相同的表示符或全局变量.C/C++中内连接使用static关键字指定.
(2) 外连接:所有被编译过的文件创建一片单独存储空间.一旦空间被创建,连接器必须解决对这片存储空间的引用.全局变量和函数使用外部连接.通过extern关键字声明,可以从其他文件访问相应的变量和函数.
/* C++代码  header.h */
const int test = 1;
/* C++代码  test1.cpp */
#include "header.h"
using namespace std;
int main() { cout << "in test1 :" << test << endl; }
/* C++代码 test2.cpp */
#include "header.h"
using namespace std;
void print() { cout << "in test2:" << test << endl;}
以上代码编译连接完全不会出问题,但如果把header.h改为:
extern const int test = 1;
在连接的时候,便会出现以下错误信息:
test2 error LNK2005: "int const test" (?test@@3HB) 已经在 test1.obj 中定义
    因为extern关键字告诉C++编译器test会在其他地方引用,所以,C++编译器就会为test创建存储空间,不再是简单的存储在名字表里面.所以,当两个文件同时包含header.h的时候,会发生名字上的冲突.
此种情况和C中const含义相似:
/* C代码 header.h */
const int test = 1;
/* C代码 test1.c */ 
#include "header.h"
int main() { printf("in test1:%d\n",test); }
/* C代码 test2.c */
#include "header.h"
void print() { printf("in test2:%d\n",test); }
错误消息:
test3 fatal error LNK1169: 找到一个或多个多重定义的符号
test3 error LNK2005: _test 已经在 test1.obj 中定义

也就是说:在c++ 中const 对象默认为文件的局部变量。与其他变量不同,除非特别说明,在全局作用域声明的 const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定 const 变更为 extern,就可以在整个程序中访问 const 对象:
      // file_1.cc
      // defines and initializes a const that is accessible to other files
      extern const int bufSize = fcn();
      // file_2.cc
      extern const int bufSize; // uses bufSize from file_1
      // uses bufSize defined in file_1
      for (int index = 0; index != bufSize; ++index)
            // ...

3. C++中,是否为const分配空间要看具体情况.如果加上关键字extern或者取const变量地址,则编译器就要为const分配存储空间.
4. C++中定义常量的时候不再采用define,因为define只做简单的宏替换,并不提供类型检查.

posted @ 2012-03-12 10:54 diorlv 阅读(9) 评论(0) 编辑

键盘太脏了,洗洗吧。。。。。。。。。。。。一个一个扣啊,扣完洗啊。。。。。。。。

OK!搞定了,装上去试试,我勒个去,TAB坏了,咋办,用ENTER键当TAB用吧(实际不会这么做滴,呵呵)。好了,编码实现~~

 

首先我们讲的是winform程序,其他语言请勿对号入座,如有雷同,纯属巧合~

 

言归正传,ACTION~

 

其实监控键盘按键有两种方式:

1、重写ProcessDialogKey方法:

官方是这样解释ProcessDialogKey方法的

在消息预处理过程中调用此方法,以处理对话框字符,比如 Tab、Return、Esc 和箭头键。只有当 IsInputKey 方法指示控件未在处理该键时,才调用此方法。ProcessDialogKey 方法只是将字符发送给父级的 ProcessDialogKey 方法,或者,在该控件没有父级的情况下返回 falseForm 类重写此方法来执行对话框键的实际处理。仅当控件承载在 Windows 窗体应用程序中或充当 ActiveX 控件时,才调用此方法。

给继承者的说明 在派生类中重写 ProcessDialogKey 方法时,控件应返回 true 以指示它已处理该键。对于未由该控件处理的键,应返回调用基类的ProcessDialogChar 方法所得的结果。控件很少需要重写此方法(即使有的话)。

 

2、给控件增加KeyDown事件

 

代码如下:

 1 protected override bool ProcessDialogKey(Keys keyData) 
2 {
3 if (keyData == Keys.Return)
4 {
5 return base.ProcessDialogKey(Keys.Tab);
6 }
7 else
8 {
9 return base.ProcessDialogKey(keyData);
10 }
11 }
 1 foreach(Control ct in groupBox1.Controls) 
2 {
3 if ((ct is TextBox) || (ct is ComboBox))
4
5 {
6 ct.KeyDown += new System.Windows.Forms.KeyEventHandler(this.EnterKeyDown);
7 }
8 }
9
10
11 private void EnterKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
12 {
13 if(e.KeyCode==Keys.Enter)
14 {
15 System.Windows.Forms.SendKeys.Send("{TAB}");
16 }
17 }


好了,这样我们就将回车键替换为了TAB,大家试试看,好用不。

 

最后附上keycode对应如下:

  1 keycode    8 = BackSpace BackSpace
2 keycode 9 = Tab Tab
3 keycode 12 = Clear
4 keycode 13 = Enter
5 keycode 16 = Shift_L
6 keycode 17 = Control_L
7 keycode 18 = Alt_L
8 keycode 19 = Pause
9 keycode 20 = Caps_Lock
10 keycode 27 = Escape Escape
11 keycode 32 = space space
12 keycode 33 = Prior
13 keycode 34 = Next
14 keycode 35 = End
15 keycode 36 = Home
16 keycode 37 = Left
17 keycode 38 = Up
18 keycode 39 = Right
19 keycode 40 = Down
20 keycode 41 = Select
21 keycode 42 = Print
22 keycode 43 = Execute
23 keycode 45 = Insert
24 keycode 46 = Delete
25 keycode 47 = Help
26 keycode 48 = 0 equal braceright
27 keycode 49 = 1 exclam onesuperior
28 keycode 50 = 2 quotedbl twosuperior
29 keycode 51 = 3 section threesuperior
30 keycode 52 = 4 dollar
31 keycode 53 = 5 percent
32 keycode 54 = 6 ampersand
33 keycode 55 = 7 slash braceleft
34 keycode 56 = 8 parenleft bracketleft
35 keycode 57 = 9 parenright bracketright
36 keycode 65 = a A
37 keycode 66 = b B
38 keycode 67 = c C
39 keycode 68 = d D
40 keycode 69 = e E EuroSign
41 keycode 70 = f F
42 keycode 71 = g G
43 keycode 72 = h H
44 keycode 73 = i I
45 keycode 74 = j J
46 keycode 75 = k K
47 keycode 76 = l L
48 keycode 77 = m M mu
49 keycode 78 = n N
50 keycode 79 = o O
51 keycode 80 = p P
52 keycode 81 = q Q at
53 keycode 82 = r R
54 keycode 83 = s S
55 keycode 84 = t T
56 keycode 85 = u U
57 keycode 86 = v V
58 keycode 87 = w W
59 keycode 88 = x X
60 keycode 89 = y Y
61 keycode 90 = z Z
62 keycode 96 = KP_0 KP_0
63 keycode 97 = KP_1 KP_1
64 keycode 98 = KP_2 KP_2
65 keycode 99 = KP_3 KP_3
66 keycode 100 = KP_4 KP_4
67 keycode 101 = KP_5 KP_5
68 keycode 102 = KP_6 KP_6
69 keycode 103 = KP_7 KP_7
70 keycode 104 = KP_8 KP_8
71 keycode 105 = KP_9 KP_9
72 keycode 106 = KP_Multiply KP_Multiply
73 keycode 107 = KP_Add KP_Add
74 keycode 108 = KP_Separator KP_Separator
75 keycode 109 = KP_Subtract KP_Subtract
76 keycode 110 = KP_Decimal KP_Decimal
77 keycode 111 = KP_Divide KP_Divide
78 keycode 112 = F1
79 keycode 113 = F2
80 keycode 114 = F3
81 keycode 115 = F4
82 keycode 116 = F5
83 keycode 117 = F6
84 keycode 118 = F7
85 keycode 119 = F8
86 keycode 120 = F9
87 keycode 121 = F10
88 keycode 122 = F11
89 keycode 123 = F12
90 keycode 124 = F13
91 keycode 125 = F14
92 keycode 126 = F15
93 keycode 127 = F16
94 keycode 128 = F17
95 keycode 129 = F18
96 keycode 130 = F19
97 keycode 131 = F20
98 keycode 132 = F21
99 keycode 133 = F22
100 keycode 134 = F23
101 keycode 135 = F24
102 keycode 136 = Num_Lock
103 keycode 137 = Scroll_Lock
104 keycode 187 = acute grave
105 keycode 188 = comma semicolon
106 keycode 189 = minus underscore
107 keycode 190 = period colon
108 keycode 192 = numbersign apostrophe
109 keycode 210 = plusminus hyphen macron
110 keycode 211 =
111 keycode 212 = copyright registered
112 keycode 213 = guillemotleft guillemotright
113 keycode 214 = masculine ordfeminine
114 keycode 215 = ae AE
115 keycode 216 = cent yen
116 keycode 217 = questiondown exclamdown
117 keycode 218 = onequarter onehalf threequarters
118 keycode 220 = less greater bar
119 keycode 221 = plus asterisk asciitilde
120 keycode 227 = multiply division
121 keycode 228 = acircumflex Acircumflex
122 keycode 229 = ecircumflex Ecircumflex
123 keycode 230 = icircumflex Icircumflex
124 keycode 231 = ocircumflex Ocircumflex
125 keycode 232 = ucircumflex Ucircumflex
126 keycode 233 = ntilde Ntilde
127 keycode 234 = yacute Yacute
128 keycode 235 = oslash Ooblique
129 keycode 236 = aring Aring
130 keycode 237 = ccedilla Ccedilla
131 keycode 238 = thorn THORN
132 keycode 239 = eth ETH
133 keycode 240 = diaeresis cedilla currency
134 keycode 241 = agrave Agrave atilde Atilde
135 keycode 242 = egrave Egrave
136 keycode 243 = igrave Igrave
137 keycode 244 = ograve Ograve otilde Otilde
138 keycode 245 = ugrave Ugrave
139 keycode 246 = adiaeresis Adiaeresis
140 keycode 247 = ediaeresis Ediaeresis
141 keycode 248 = idiaeresis Idiaeresis
142 keycode 249 = odiaeresis Odiaeresis
143 keycode 250 = udiaeresis Udiaeresis
144 keycode 251 = ssharp question backslash
145 keycode 252 = asciicircum degree
146 keycode 253 = 3 sterling
147 keycode 254 = Mode_switch




posted @ 2012-01-13 15:45 diorlv 阅读(133) 评论(0) 编辑

?运算符,大家应该熟知,两个作用

1、三元运算符x==true?y=1:y=-1 

2、.Net里面定义空类型的一种表示 int?表示int型可以含空置

 

那么??干什么用的呢!!!

其实他是?运算符的综合体,我个人这么理解,也便于记忆。

MSDN上是这样说明的如果 ?? 运算符的左操作数非空,该运算符将返回左操作数,否则返回右操作数。

例子直接看MSDN上的吧,已经很清晰明了了

// nullable_type_operator.cs
using System;
class MainClass
{
    static int? GetNullableInt()
    {
        return null;
    }

    static string GetStringValue()
    {
        return null;
    }

    static void Main()
    {
        // ?? operator example.
        int? x = null;

        // y = x, unless x is null, in which case y = -1.
        int y = x ?? -1;

        // Assign i to return value of method, unless
        // return value is null, in which case assign
        // default value of int to i.
        int i = GetNullableInt() ?? default(int);

        string s = GetStringValue();
        // ?? also works with reference types. 
        // Display contents of s, unless s is null, 
        // in which case display "Unspecified".
        Console.WriteLine(s ?? "Unspecified");
    }
}

  

posted @ 2011-12-12 11:42 diorlv 阅读(10) 评论(0) 编辑
摘要: 简单的冒泡,演绎北京一号线地铁上的一幕阅读全文
posted @ 2011-09-06 15:31 diorlv 阅读(12) 评论(0) 编辑

其实大家可能经常用多重循环去判断这个判断那个,然后判断完了赋值,然后就直接退出了,或者在退回到某层循环的时候执行些什么,有什么好方法可以直接退到我们想退的层呢?

刚好这个项目有好几处这样的判断,所以我就对这里进行一下自己的总结。

我在代码里面使用了两种方法,大家可以等我讲完了,看看那种更适合自己,以后写代码的时候也就可以直接简化一下复杂的判断了。

第一种便是break,肯定有童鞋会问了,break不是跳出一层循环吗?怎么可以跳出多层循环呢,那就多放点break嘛,呵呵,其实答案就是你们想的这样,每层都放上break,然后定义函数级变量来记录状态,把break放在if里面,这就是最好的方法。

第二种就是用goto。直接goto到你需要的层面上。很多人都说goto不能用,其实是不能乱用,主要是怕你驾驭不了这个函数,如果你注意方法,再该用的地方再用,那就可以使用了。

一般情况我是这样的,如果一个程序中,有三层以下的循环,而且每一层都要赋值或者操作的话,建议用if-break来处理。而当你发现你已经写了快五六个状态变量的话,就可以重写一下,将if-break改成goto了。

当然大家肯定各自都有各自的思路,我的这种想法,肯定也是大家想过的,我只是在这里小结一下,其实说白了,就是把他们用到该用的地方,花最少的力气,解决最复杂的事情。有时间,写段代码上来,供大家研究

posted @ 2011-07-29 14:55 diorlv 阅读(513) 评论(4) 编辑
仅列出标题