操作符(2)
位操作符
与其它语言不同,c++提供了完整的位操作符。C语言设计的目的就是为了取代汇编语言来完成大部分的编程任务。这就要求C语言必须支持所以汇编语言可以完成的操作,包括位操作。位操作包括位测试、置位、移位等基于字节或者字中的自然位的操作。对应的数据类型往往是char和int等。不能对float、double、long double、void、bool等复杂的数据类型进行位操作。表2-6列出了所有位操作符。这些操作符是对操作数的每一位分别进行操作的。
表2-6. 位操作符
Operator Action
& AND
| OR
^ Exclusive OR(XOR)
~ One's complement(NOT)
>> Shift right
<< Shift left
位操作符AND、OR和NOT和它们所对应的逻辑操作符有相同的真值表,不同的是它们是对每一位分别进行的。 下面是XOR异或的真值表:
p q p^q
0 0 0
1 0 1
0 1 1
1 1 0
正如真值表所展示的,XOR在两个操作数有且仅有一个为1时才返回true。
位操作符常常在编写设备驱动的时候用到,如modem驱动,硬盘文件驱动,打印机驱动。因为位操作符可以用来划分特定的位,例如奇偶校验位。(奇偶校验位的作用时用来确定字节中的其余各位没有意外的改变。常常用最高位作为一个字节的奇偶校验位。)
AND位操作符常常被当作位清除(置为0)工具 。一个操作数的某一位为0,则AND操作后另一个操作数的对应位一定会被置为0。例如,下面的整个函数会从modem中读取一个字节并且把它的奇偶校验位重新置为0:
char get_char_from_modem( void )
{
char ch;
ch = read_modem(); /* get a charactor from the modem */
return( ch & 127 );
}
奇偶校验位通常时第8位。通过与一个第1到7为1,第8位为0的操作数执行AND操作会将该字节的第8未置为0,而其它位则保留原来的状态。
OR位操作符常常作为置位工具(置为1)。可以通过OR操作将对象操作数的指定位置为1。例如:
10000000 128 in binary
00000011 3 in binary
|________ bitwise OR
10000011 result
异或(Exclusive OR)常常简写成XOR,它的作用是当两个操作符对应位上的状态不同时,产生1。如:
01111111 127 in binary
01111000 120 in binary
^________ bitwise XOR
00000111 result
记住,关系和逻辑操作符表达式得出的结果一定时true或者false,然而位操作后会产生与该操作相对应的数值。换句话说,位操作会产生除了0和1之外的各种数值。
移位操作符<<和>>会把一个数值中的所有位都左移或者右移。移位操作语句通常格式如下:
value >> number of bit positions
value << number of bit positions
当一端的位被移除时,另一端的空位会补充0。(当遇到带符号数的负数时,右移操作执行后会在左边空出的位上补1,以确保该数值的符号不变。)记住,移位和循环(循环移位)不同,也就是说当一位从末端被移出之后不会补充到另一端,而是被丢弃。
移位操作在对外设的输入进行解码的时候非常有用,如D/A转换(数模转换),读取状态信息等。移位操作也可以快捷的完成整数的乘或者除运算。右移移位等价于将一个数除以2,左移则等价于乘以2。表2-7中有详细的描述。下面先看一个程序的例子:
/* A bit shift example. */
#include <stdio.h>
int main( void )
{
unsigned int i;
int j;
i = 1;
/* left shifts */
for( j = 0; j < 4; j++ )
{
i = i << 1; /* left shift i by 1, which is same as a multiply by 2 */
printf( "Left shift %d: %d/n", j, i );
}
return 0;
}
取反操作符~会把操作数的每一位取反。也就是把1置为0,0置为1。
位操作符还经常用在加密程序中。比如,需要让硬盘文件变得不直接可读,可以对它们进行一系列位操作。最简单的做法是把每一个字节中的各位都取反。如下:
Original byte 00101100
After 1st complement 11010011
After 2nd complement 00101100
注意,两次取反后会返回原数值。因此,第一次取反代表对字节进行了编码,第二此相当于解码。如下的函数可以用来对字符编码。
/* A simple cipher function. */
char encode( char ch )
{
return( ~ch ); /* complement it */
}
当然,用这个函数加密的文件会很容易破解。
?操作符
c++中定义了一个非常强大和方便的操作符,它可以替代简单的if-than-else语句。三元操作符?通常使用格式如下:
Exp?Exp2:Exp3;
其中,exp1、exp2、exp3都是表达式。注意冒号的用法和位置。
?操作符的作用是这样的:先检查exp1的值,如果返回true则对exp2求值,反之对exp3求值,这个值将是整个表达式的值。例如:
x = 10;
y = x>9 ? 100 : 200;
y最终被赋值为100,因为x大于9所以,?前面的表达式返回值为true。如果x的值是小于9的,那么y最终会被赋值为200。如果用if和else写出相同作用的语句,将会是这样:
x = 10;
if( x>9 )
y = 100;
else
y = 200;
我们将在第三章讲述条件语句的时候进一步讲述?操作符。
&和*指针操作符
指针就是一个对象的内存地址。指针变量则用来存储一个指向某类型对象的指针。获得某些变量的内存地址对于一些特定的程序来说非常有用。指针在c++中的应用主要有三个主要作用:使用它们可以快捷的访问数组对象的某一元素。使用指针使得函数可以对实参进行操作。使用指针可以支持链表等动态数据类型。第五章将会对指针进行专门的介绍。
首先介绍指针操作符&,这是个一元操作符,用来返回它的操作数的内存地址。如:
m = &count;
这个语句将变量count的内存地址存入了变量m。你可以把&看作“the address of”。于是上面的赋值语句可以描述为“m获得变量count的地址”。为了进一步理解这个赋值语句,我们先假设count变量在内存中的地址为2000,它的值为100,那么在上面的赋值语句执行后,m将获得的值为2000而不是100。
下一个要介绍的指针操作符是*,它和&符号刚好互补。*操作符返回的是以随后的操作数为地址的那个变量的值。例如,假定m中存储这count变量的地址:
q = *m;
这个语句将会把count的值存入变量q。这时q的值应该是100,因为100这个值是存储在地址为2000的变量count中的。*可以看作“at address”。
不幸的是,乘法操作符(*)和“at address”操作符是一样的,位操作符AND(&)和“address of”操作符是一样的,但是这些操作符本身实际上是没有联系的。&和*指针操作符的优先级都高于普通的算术运算操作符,不过-例外,它们有相同的优先级。
指针变量必须在声明时加前缀*,这样做是为了告知编译器这个变量用来存储一个指针。例如,要将ch定义为指向字符的指针:
char *ch;
这里定义的ch不是一个字符变量而是一个指向字符的指针变量,它们是有很大区别的。指针所指向的数据类型,被称为该指针的基类。而指针本身是一个变量,它存储的是一个地址,该地址指向某个数据类型与指针基类相同的变量。因此,指针必须有足够的大小来容纳该计算机体系中的任意地址的长度。不过,有个规则,那就是指针只能指向声明它时所定义的基类的数据类型。
在同一个声明语句中可以混合指针与非指针变量。如:
int x, *y, count;
下面的程序利用&和*操作符,完成把数值10赋予变量target的功能。正如预期,程序运行后屏幕上会显示10。程序如下:
#include <stdio.h>
int main( void )
{
int target, source;
int *m;
source = 10;
m = &source;
target = *m;
printf( "%d", target );
return 0;
}
编译时操作符sizeof
sizeof是一种一元编译时操作符,它的作用是返回变量或者加了括号的类型定义符的字节大小,sizeof是作为前缀使用的。例如,假定某环境下,整型是4个字节,双精度是8个字节:
double f;
printf( "%d", sizeof f );
printf( "%d", sizeof(int) );
这个程序运行之后会显示8 4。
记住,如果需要获得一个类型定义符对应的类型的大小,那么使用sizeof的时候需要用括号将类型定义符括起来。对于变量来说是否使用括号都无关紧要,当然,用了括号也没有坏处。c++定义了一个特别的数据类型size_t,对应数据类型为整型。所以你可以把size_t这种类型的变量当作整型来处理。sizeof主要用来获取内建数据类型的大小,以用来在不同的环境中移植。假设现在编写一个数据库程序,需要每条记录存储6个变量。而这个数据库要在多个操作系统环境下都能运行,那么你无法确定每一个整型的长度,这时就需要用到sizeof来确定整型的长度。下面的例程演示了如正确的将包含留个整型的记录存入硬盘。
/* Write 6 integers to a disk file. */
void put_rec( int rec[6], FILE *fp )
{
int len;
len = fwrite( rec, sizeof(int)*6, 1, fp )
if( len != 1 )
printf( "Write error !" );
}
无论在16位还是32位环境下,上面的代码都会运行正常。
需要强调的是,sizeof是在编译时获得数值的操作符,随后在程序中这个获得的数值被当作常量处理。
逗号
逗号操作符链接多个表达式,逗号左侧的操作数往往是空类型。也就是说,用逗号格开的几个表达式的总值应该等于最右边的那个表达式的值。例如:
x = ( y = 3, y + 1 );
这个语句执行的时候首先对y赋值为3,然后将c赋值为4。这里的括号是必须的,因为逗号操作符的优先级低于赋值操作符=。
本质上,逗号操作符导致了一系列的操作按照某一顺序完成。假如,在某一赋值语句的等号右边使用了逗号操作符分开多个表达式,那么最右边的表达式的值会被该赋值语句采用。逗号操作符的意义有些类似于平常说的and,就是“do this and this and this”。
Dot(.)和Arrow(->)操作符
在C语言中这两个操作符用来访问某一数据结构或者联合的私有元素.在C语言中,结构和联合把多个各种数据类型组合到一个整体中.使用一个总的名称.在c++中,这两个操作符常用来访问类的成员.
当直接访问结构(structure)或者联合(union)的内部成员时,往往使用Dot. 而当通过指向结构和联合的指针访问其成员时则使用Arrow(->).例如:
struct employee
{
char name[80];
int age;
float wage;
}emp;
struct employee *p = &emp; /* address of emp into p */
当你要对该结构的wage成员赋值为123.23的时候,则需要通过以下方式:
emp.wage = 123.23;
不过,使用指针的方式语句就要写成这样:
p->wage = 123.23;
方括号和圆括号
圆括号可以用来提高它所括的操作式的优先级.方括号则用来展示数组的索引.例如:
#include <stdio.h>
char s[80];
int main( void )
{
s[3] = 'X';
printf( "%c", s[3] );
return 0;
}
首先将'X'赋值给s数组的第四个元素,然后将这个元素在屏幕上显示.记住,所有的数组都是从下标0开始,所以,s[3]代表的是数组的第四个元素.
优先级概括
表2-8 列出了所有C语言中定义的操作符的优先级.除了一元操作符和?外,其余的操作符都是从左到右结合的.一元操作符*,&,-和?都是从右到左结合.
注:c++还增加了一些新操作符,将在第二部分中讨论.
表2-8. C语言操作符的优先级
Highest () [] ->
! ~ ++ -- (type) * & sizeof
* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
||
Highest
?:
= += -= *= /= etc.
Lowest ,