C++ 案例指导(2)C++数据类型及运算符

0.       C++语言的基础构成


字符集

字符集是构成C++语言的基本元素。C++语言的字符集由英文字母、数字字符及特殊字符构成。其中特殊字符包括空格 ! # % ^ & * _ + = - ~ <> / \ ' " ; . , () [] {}。

关键字

关键字是C++中预定义的单词。这些关键字是我们定义变量常量时不能使用的。C++中的关键字如下:auto  bool  break  case  catch char class  const  const_case continue  default delete do double  dynamic_cast  else enum  explicit extern  false float for  friend  goto if  inline  int long  mutable namespace  new  operate private  protected  public register  reinterpret_cast  return short  signed sizeof  static static_cast  struct switch  template  this throw  true  try typedef typeid  typename  union  unsigned using  virtual void volatile while。

标识符

标识符是我们软件开发者自己定义的一些单词(即标记,做命名用的),它用来命名程序中一些实体,如函数名,变量名,类名,对象名等。C++对大小写敏感,即Name和name这个两个标识符代表两个不同的标识符。

文字

文字指在程序中用符号表示的数据,包括数字、字符、字符串和布尔文字(true和false)。

操作符

操作符用于实现各种运算的符号,如加减乘除

分隔符

分隔符用于分隔各个词法记号或程序体,C++中的分隔符有() {} , : ;等。

1.       案例指导:设计一个仅包含C++程序基本构成元素的程序


代码注解


/*                                       //注释行开始

 * This is thefirst C++ program.

 *  Created on: 2013-1-13

 *      Author: Jason

 */                                     //注释行结束

#include <iostream>                      //包含预处理文件

#include <cppbasic.h>                    //包含头文件

using namespace std;                     //打开命名空间std

// This is the Main function             //单行注释语句

int main(void)                          //主函数,程序入口处

{                                        //块作用域开始

    int age;                            //声明一个表明

    age = 20;                            // 赋值语句

    cout <<"The age is :"<<endl;        //输出一个字符串

    cout<<age<<endl;                   //输出变量中的值

    return (0);                         //主函数返回0

}                                       //块作用域结束

思考与提高


为了使用预定义的cout输出对象及其相关操作,需要预先进行必要的说明,这样的说明都包含在文件iostream中,因此程序中#include<iostream>指示编译器在对程序进行预处理时,将文件iostream中的代码嵌入到程序中该指令所在的地方,#include称为预编译指令。

那么C++输入输出是如何实现的?

C++输入输出是通过“流”来实现的。在C++中,将数据从一个对象到另一个对象的流动抽象为“流”。Cin和cout 是预定义的流类对象,用来处理标出输入输出,即键盘输入和屏幕输出。>>和<<是流操作符,用于执行提取(输入)和插入(输出)操作。

2.  基本数据类型


C++的基本数据类型有bool(布尔型)、char(字符型)、int(整型)、float(浮点型,表示实数)、double(双精度浮点型)。除了bool型外,有两大类:整数和浮点数。因为char型本质上就是整型,只不过是一个字节的整数,用来存放字符的ASCII。还有几个关键字signedunsignedshortlong起修饰作用。

ANSI C/C++基本数据类型

Type

Size

数值范围

无值型void

0 byte

无值域

布尔型bool

1 byte

true   false

有符号短整型short [int] /signed short [int]

2 byte

-32768~32767

无符号短整型unsigned short [int]    

2 byte

0~65535

有符号整型int /signed [int]

4 byte

-2147483648~2147483647

无符号整型unsigned [int]

4 byte

0~4294967295

有符号长整型long [int]/signed long [int]

4 byte

-2147483648~2147483647

无符号长整型unsigned long [int]

4 byte

0~4294967295

long long

8 byte

0~18446744073709552000

有符号字符型char/signed char

1 byte

-128~127

无符号字符型unsigned char

1 byte

0~255

宽字符型wchar_t (unsigned short.)

2 byte

0~65535

单精度浮点型float

4 byte

-3.4E-38~3.4E+38

双精度浮点型double

8 byte

1.7E-308~1.7E+308

long double

8 byte

 

表格解析


(1)类型修饰符signed和unsigned用于修饰字符型和整型。

(2)类型修饰符short和long用于修饰字符型和整型。

(3)当用signed和unsigned、short和long修饰int整型时,int可省略。

(4)其中bool和wchar_t是C++特有的。对于条件判断,零为假,非零为真,对bool变量可赋非0非1的其他真值。

(5)float的精度(6位有效数字)通常是不够的,double类型可以保证10位有效数字,能够满足大多数计算的需要。使用double类型基本不会出错,在float类型中存在隐式的精度损失。默认的浮点字面值常量为double类型,在数值后面加上F或f表示单精度,例如3.14159F。浮点数float、double的存储设计,从本质上来说是设计了一个数值映射,充分利用了二进制存储的特点。参考IEEE754浮点数表示标准。

(6)除上表以外,C/C++都可以自定义枚举enum、联合union和struct结构体类型。

(7)以上sizeof通过Windows XP 32位平台测试,其中某些类型数据的字节数和数值范围由操作系统和编译平台决定。比如16位机上,sizeof(int) = 2,而32位机上sizeof(int) = 4;32位机上sizeof(long) = 4,而64位机上sizeof(long) = 8。除此之外,注意64位机上的pointer占8byte。

(8)void的字面意思是“无类型”,不能用来定义变量。void真正发挥的作用在于:<1> 对函数返回和函数参数的限定,例如自定义既不带参数也无返回值的函数void MyFunc(void);<2>定义无类型通用指针void *,指向任何类型的数据

(9)标准C++库及STL还提供了通用数据结构:字符串string;向量模板vector;双端队列模板deque;链表模板list;容器适配器堆栈stack(实现先进后出的操作);容器适配器队列queue(实现先进先出的操作);集合set;多重集合multiset;映射map;多重映射multimap;位集合bitset;迭代器iterator (似指针的功能,对容器的内容进行访问)。

(10)在标准c++中,int的定义长度要依靠你的机器的字长,也就是说,如果你的机器是32位的,int的长度为32位,如果你的机器是64位的,那么int的标准长度就是64位,而vc中__int64是为在32机位机器长实现64位长度的整型数

(11)关于32位平台下的int和long

long从字面上看,应该是64位才更合理,把long当成32位实在是一个历史的包袱。像C#那样新起炉灶的程序语言,由于没有需要支持老代码的问题,就把long当作64位来处理了。

在32位平台下,long是相对short而言,long(short)类型是long(short) int类型的简称sizeof(long) = sizeof(int) = 4。int和long的范围虽然一样,但输入输出格式不同,printf int的格式为%d,而printf long的格式为%ld

考虑到程序的可移植性,还是要将他们区分开来。但当要求的数值范围为4byte时,建议使用int类型,因为第一版的C语言只有一种内置类型,那就是int。

long类型的位数总是和机器的指针位数相等

字面值整数常量的类型默认为int或long,其精度类型取决于精度值,其值适合int型就是int型,比int型(INT_MAX)大的就是long类型。通过增加后缀可强制将字面值整数常量转换成long、unsigned或unsigned long类型。通过在数值后面添加L或l(推荐使用L,防l与1混淆)指定常量为long类型。例如128u,1L,1024UL,8Lu。没有short类型的字面值常量。

(12)在Win32 API及MFC中为了使类型名称在语意上更明了,对以上基本类型进行了大量的typedef。例如WINDEF.H中的BYTE,WORD,DWORD。

(13)计算机内部内存的基本单位是1byte(8个电子开关)!

常量

所谓常量就是在程序运行过程中其值始终不会变的量,就是直接用符号表示的值。常量又分整型常量、实型常量、字符常量、字符串常量和布尔常量。

整型常量包括正整数、负整数和零。整型常量的形式有十进制、八进制和十六进制。十进制我们都知道了,八进制常量的数字必须以数字0开头,比如0324,-0123。十六进制整型常量的数字必须以0x开头,比如0x3af。

实数常量有两种表示方式:一般形式和指数形式。系统默认的实数常量类型为double型,如果后缀是F(f),可以使其成为float型后缀L(或l)为long double型

字符常量是单引号括起来的一个字符,比如:'b','?'。还有一些不可显示字符,例如响铃、换行、制表符等等,C++提供了一种转义序列的表示法来表示这些字符。比如:\a表示响铃,\n表示换行,\t表示水平制表符,\b表示退格,\r表示回车,\\表示字符'\',\"表示双引号,\'表示单引号。ASCII字符常量占用1个字节。

字符串常量是用双引号括起来的字符序列,简称字符串。字符串常量会在字符序列末尾添加'\0'作为结尾标记。

字符串和字符是不同的,字符串在内存中按串中字符的排列次序顺序存放,每一个字符占一个字节,并在末尾添加’\0’作为结尾标记。即char array[5]中真正只能存放4个字符,因为还有存放一个’\0’,即array[5] = “1234”如果初始化为array[5] =“12345”则溢出报错。

布尔常量只有两个:false(假)和true(真)。

变量

变量与常量一样也有自己的类型,在使用之前必须首先声明它的类型和名称。变量名也是标识符,因此命名规则应遵从标识符的命名规则。同一个语句中可以声明同一个类型的多个变量,变量声明语句的形式是这样的:数据类型  变量名1,变量名2,...,变量名n;。例如下面两条语句分别声明了两个int变量和两个float变量:int num,sum;    float  a,b;。在声明一个变量的同时可以赋一个初值,int  num=3;  double d=2.53;  char c='a';。C++赋初值时还有一种默认赋初值形式,比如:int  num(3); 表示在使用变量num前如果没有接收到其它值,那么它的值默认为3,如果接收了新值比如5,那么此时num = 5了。

符号常量

除了可以用文字表示常量以外,还可以给常量起个名字,这就是符号常量。这个符号常量就代表了那个常量。符号常量在使用之前必须声明,跟变量相似。

符号常量声明形式:

const  数据类型说明符  常量名=常量值; 

或 

数据类型说明符  const  常量名=常量值;

例如,我们给圆周率起个名字,就是符号常量,const  float pi=3.1415926;。还有一点必须注意,符号常量声明时必须赋初值在其他时候不能改变它的值。使用符号常量与文字常量相比有很多好处:程序的可读性更高,我们看到这个名字就能看出它的具体意思,再就是最重要的,如果我们多个地方都用了上面那个pi常量,但后来圆周率的值精度我想改一下,只用3.14,这个时候怎么把所有的pi都换掉呢?我们只需要修改pi的声明就行了。

3.       案例指导:正确输出“50 000” --- 整数数值溢出


代码注解


#include <iostream>

#include <bitset>

using namespace std;

int main(void)

{

    short int i;

    short unsigned int j;

    j = 50000;

    i = j;

    cout<<”The short int is:”<<i<<endl;

    cout<<”The short unsigned int is:”<<j<<endl;

//输出 50 000 和 – 15536 在计算机中存储的二进制形式

cout<< bitset<sizeof(shortunsignedint)*8>(j) <<endl;

    cout<<bitset<sizeof(shortunsignedint)*8>(i) <<endl;

    return (0);

}

输出结果:

The short int is : -15536

The short unsigned int is: 50 000

思考与提高


计算机中的数据是用补码的形式来表示和运算的,即数据是以其补码的形式存储在计算机中的。计算机里的数都是补码形式,因为CPU只会做加法,数的补码形式就可以用加法实现减法运算,进而以加法完成所有的运算。至于数以什么码的形式输入和输出,编程人员是可以控制的。补码的两个好处:一就是统一加减法,二就是可以让符号位作为数值直接参加运算,而最后仍然可以得到正确的结果符号,符号位无需再单独处理。

当然我说计算机中的数据是以其补码形式存储的,你说怎么相信我说的就是真的?其实我们可以将 50000 和 -15536 的二进制代码输出来一看就知道了,如上面的代码中我们加了两句把整型转为二进制的代码 bitset 输出的结果都是:1100 0011 0101 0000即告诉我们数据在计算机中的存储形式是它的补码形式。

无符号整数unsigned short 取值范围为0 至 65 535,而符号整数short(signed short)取值范围为-32 768 至 32 768. 如果超出数值类型取值范围,则会出现数值溢出。现在 j 是声明为 short unsigned int 型 所以 j = 50 000 < 65 535 所以不存在溢出。 当把 50 000 赋值给 i 后, 因为i 声明为 short int 型 此时 i的最大取值只能是32 768 < 50 000 那么将发生溢出!那么计算机怎么处理呢? 请看下面的分析

在知道了数据是以补码的形式存储在计算机中之后,现在从50 000 和 -15536的原码补码分析入手

50000  的原码:11000011 0101 0000

-15536  的原码:10111100 1011 0000

50 000 是正数, 正数的原反补一模一样,所以50 000 的补码也是:1100 0011 0101 0000

-15536是负数,那么得原码取反加1(取反时符号位不变)的补码:11000011 0101 0000

我们知道上面把 50 000 赋值给了i, 而数据是以补码的形式存储在计算机中的, 所以现在i是一个指向存放50 000这个数字的补码的内存区域的变量名。即此时i变量指向的这个内存区域中存放的是1100 0011 0101 0000 这样的一串二进制数字,即50 000 的补码形式。那么当i 变量按照其自己的数据类型(short int)来读取这串二进制数时, 读取出来的应该是 – 32 768 至32768 中某个数的补码,而1100 0011 0101 0000 刚好对应 – 15536 的补码1100 0011 0101 0000,这样原本的 50 000 就变成了 -15536 了 这就产生了上溢。即50 000超出了32768 溢出之后解释为 – 15536 了就产生了溢出。

4.       运算符及表达式


运算符,顾名思义,就是用于计算的符号,比如+(加),-(减),*(乘),/(除)。表达式是用于计算的公式,由运算符、运算量(操作数)和括号组成。有些运算符需要两个操作数,使用形式为:操作数 运算符 操作数,这样的运算符就叫做二元运算符或双目运算符,只需要一个操作数的运算符叫做一元运算符或单目运算符。运算符具有优先级和结合性。如果一个表达式中有多个运算符则先进行优先级高的运算,后进行优先级低的运算。结合性就是指当一个操作数左边和右边的运算符优先级相同时按什么样的顺序进行运算,是自左向右还是自右向左。

[01]  算术运算符和算术表达式

算术运算符包括基本算术运算符和自增自减运算符。由算术运算符、操作数和括号组成的表达式称为算术表达式。基本算术运算符有:+(加),-(减或负号),*(乘),/(除),%(求余)。其中"-"作为负号时为一元运算符,作为减号时为二元运算符。优先级跟我们数学里的是一样的,先乘除,后加减。"%"是求余运算,它的操作数必须是整数,比如a%b是要计算a除以b后的余数,它的优先级与"/"相同,这里要注意的是,"/"用于两个整数相除时,结果含有小数的话小数部分会舍掉,比如2/3的结果是0

[02]  赋值运算符和赋值表达式

最简单的赋值运算符就是"=",带有赋值运算符的表达式被称为赋值表达式。例如n=n+2就是一个赋值表达式,赋值表达式的作用就是把等号右边表达式的值赋给等号左边的对象。赋值表达式的类型是等号左边对象的类型,它的结果值也是等号左边对象被赋值后的值,赋值运算符的结合性是自右向左什么叫自右向左呢?请看这个例子:a=b=c=1这个表达式会先从右边算起,即先算c=1c的值变为1这个表达式的值也是1,然后这个表达式就变成了a=b=1,再计算b=1,同样b也变为1b=1这个表达式的值也变成1,所以a也就变成了1

除了"="外,赋值运算符还有+=-=*=/=%=<<=>>=&=^=|=。其中前五个是赋值运算符和算术运算符组成的,后五个是赋值运算符和位运算符组成的,这几个赋值运算符的优先级跟"="相同,结合性也是自右向左。这里举个例子说明下,a+=5就等价于a=a+5x*=y+3等价于x=x*(y+3)

[03]  逗号运算符和逗号表达式

逗号也是一个运算符,它的使用形式为:表达式1,表达式2。求这个表达式的值就要先解表达式1,然后解表达式2最终这个逗号表达式的值表达式2的值。比如计算a=1*2,a+3,应先计算a=1*2(a的结果为2),再来计算a+3的值,a的值已经变成了2,再加上35,这个逗号表达式的最终结果就是5

[04]  逻辑运算和逻辑表达式

C++中提供了用于比较的关系运算符和用于逻辑分析的逻辑运算符。

关系运算符包括<(小于)、<=(小于等于)、>(大于)、>=(大于等于)、==(等于)、!=(不等于)。前四个的优先级相同,后两个的优先级相同,而且前四个比后两个的优先级高。用关系运算符把两个表达式连起来就是关系表达式,关系表达式的结果类型为bool型,值只能是truefalse。比如,a>ba大于b时表达式a>b表达式的值是true,否则就是false。更复杂的表达式也算是一个道理。

逻辑运算符包括!(非)&&(与)||(或),优先级依次降低。用逻辑运算符将关系表达式连起来就是逻辑表达式,逻辑表达式的结果也是bool类型,值也只能是truefalse"!"是一元运算符,使用形式是!操作数。非运算是对操作数取反。比如!aa的值是true,则!a的值是false"&&"是二元运算符,用来求两个操作数的逻辑与,只有两个操作数的值都是true,逻辑与的结果才是true,其他情况下结果都是false"||"也是二元运算符,用来求两个操作数的逻辑或,只有两个操作数的值都是false时,逻辑或的结果才是false,其他情况下结果都是true。比如,int a=3,b=5,c=2,d=1; 则逻辑表达式(a>b)&&(c>d)的值为false

[05]  条件运算符和条件表达式

C++中唯一的一个三元运算符是条件运算符"?"。条件表达式的使用形式是:表达式1?表达式2:表达式3。条件表达式会先解表达式1,如果表达式1的值是true,则解表达式2(此时不再计算表达式3),表达式2的值就是条件表达式的值,而如果表达式1的值是false,则解表达式3(此时不再计算表达式2),其值就是条件表达式的最终结果。

例如 int x =1,y= 5;

x>y ? x = 6:y = 1;

cout<<x<<endl;

cout<<y<<endl;

则输出结果为1 1因为 1 > 5 false所以执行赋值语句 y= 1执行完毕之后就完了,x =6这条语句是没有执行的,所以输出为 11 

[06]  sizeof运算符

sizeof运算符用来计算某个对象在内存中占用的字节数。此运算符的使用形式为:sizeof(类型名)sizeof(表达式)。计算结果是这个类型或者这个表达式结果在内存中占的字节数。注意,sizeof()的计算过程中,如果括号中是一个表达式,则并不对括号中的表达式本身求值

例如  int a = 2;

Cout<< sizeof(a +=a)<<endl;

Cout<<a<<endl;

则输出结果为 4 2不会去计算a+= a所以a的值仍然是4而不是 a= a+a = 4!

[07]  位运算 (下面是假设机器字长为8来分析的)

1按位与(&。它是对两个操作数的二进制形式的每一位分别进行逻辑与操作。比如3的二进制形式为000000115的二进制形式为00000101,按位与后结果是0000 0001

2按位或(|。它对两个操作数的二进制形式的每一位分别进行逻辑或操作。还是比如35按位或运算后结果是00000111

3按位异或(^。它对两个操作数的每一位进行异或,也就是如果对应位相同则运算结果为0,若对应位不同则计算结果为1。例如35按位异或后结果为00000110

4按位取反(~。这是一个一元运算符。它对一个二进制数的每一位求反。比如,3按位取反就是11111100

5移位。包括左移运算(<<)和右移运算(>>),都是二元运算符。移位运算符左边的数是需要移位的数值,右边的数是移动的位数左移是按指定的位数将一个数的二进制值向左移位,左移后,低位补0,移出的高位舍弃右移是按照指定的位数将一个数的二进制值向右移位,右移后,移出的低位舍弃,如果是无符号数则高位补0,如果是有符号数,则高位补符号位或0一般补符号位。(符号位向右移动后,正数的话补0,负数补1)比如,char型变量的值是-8,则它在内存中的二进制补码值是11111000,所以a>>2则需要将最右边两个0移出,最左边补两个1,因为符号位是1,则结果为11111110,对其再求补码就得到最终结果-2

[08]  混合运算时数据类型的转换。

表达式中的类型转换分为:隐含转换和强制转换。

在算术运算和关系运算中如果参与运算的操作数类型不一样,则系统会对其进行类型转换,这是隐含转换,转换的原则就是将低类型的数据转换为高类型数据。各类型从低到高依次为charshortintunsigned intlongunsigned longfloatdouble。类型越高范围越大,精度也越高。隐含转换是安全的,因为没有精度损失。逻辑运算符的操作数必须是bool型,如果不是就需要将其转换为bool型,非0数据转换为true0转换为false。位运算操作数必须是整数,如果不是也会自动进行类型转换,也是低类型数据转换为高类型数据。赋值运算要求赋值运算符左边的值和右边的值类型相同,不同的话也要进行自动转换,但这个时候不会遵从上面的原则而是一律将右值转换为左值的类型。比如,int iVal; float fVal; double dVal;dVal=iVal*fVal;计算时先将iVal转换为跟fVal一样的float型,乘法的结果再转换为double

强制类型转换是由类型说明符和括号来实现的,使用形式为:类型说明符(表达式   (类型说明符)表达式。它是将表达式的结果类型强制转换为类型说明符指定的类型。比如,float fVal=1.2;  int iVal = (int)fVal;计算后面表达式的值时会将1.2强制转换成1,舍弃小数部分。

5.       案例指导


【案例1】实现逻辑“异或”运算;代码注解


#include <iostream>

using namespace std;

void xorfunc()

{

    bool p, q;

    p = true;

    q = true;

    cout<<p<<" XOR"<<q<<" is "<<((p || q)&& !(p && q))<<endl;

    p = false;

    q = true;

    cout<<p<<" XOR"<<q<<" is "<<((p || q)&& !(p && q))<<endl;

    p = true;

    q = false;

    cout<<p<<" XOR"<<q<<" is "<<((p || q)&& !(p && q))<<endl;

    p = false;

    q = false;

    cout<<p<<" XOR"<<q<<" is "<<((p || q)&& !(p && q))<<endl;

}

思考与提高


C++运算符具有优先级,插入运算符<<优先级比逻辑运算符高,所以程序中的外括号是必需的。如果去掉外层括号,则编译会出错。

    cout<<p<<" XOR"<<q<<" is "<<((p || q)&& !(p && q))<<endl;

【案例2】位运算;代码注解


// 位运算

void bitcomputing()

{

    cout <<setw(10)<< "20&10 : "<<(20&10)<<endl//按位与运算

    cout <<setw(10)<< "20^10 : "<<(20^10)<<endl//按位异或运算

    cout <<setw(10)<< "20|10 : "<<(20|10)<<endl//按位或运算

    cout <<setw(10)<< "~20 : "<<(~20)<<endl;     //按位取反运算

    cout <<setw(10)<< "20<<3 : "<<(20<<3)<<endl//左移位运算

    cout <<setw(10)<< "-20<<3 : "<<(-20<<3)<<endl;//左移位运算

    cout <<setw(10)<< "20>>3 : "<<(20>>3)<<endl//右移位运算

    cout <<setw(10)<< "-20>>3 : "<<(-20>>3)<<endl;//右移位运算

}

思考与提高


由前面的分析,我们知道数据在计算机中是以补码形式存储的,在32位的XP系统中

-20的原码:10000000 0000 0000 0000 0000 0001 0100

-20的反码:11111111 1111 1111 1111 1111 1110 1011

-20的补码:11111111 1111 1111 1111 1111 1110 1100

-20<<3 即 -20左移3位 ,左移低位补0 ,得到:

11111 1111 1111 1111 1111 1110 1100 000

1111 1111 1111 1111  1111 1111 0110 0000

特别注意:1.这个结果目前保存在CPU的寄存器中;2.此时的结果是补码形式,要求得最后的结果,还要对其求补,以上式子求补之后得到:

10000000 0000 0000 0000 0000 1010 0000

那么此时的二进制值转为十进制则为:-160

如果是下面的情况则是:

chara = -20 ;

cout<<a<<3<<endl;

则输出的还是 -160,就是上面的分析结果

因为 char 类型的值 先转为int 型 ,就是从char型的8位 扩充到 int 型的 32位计算,

还有就是我们上面提到的特别注意两点中第一点:a<<3 运算的结果是保存在cpu的寄存器中,而不是保存在a中,所以 输出a<<3的值并不等于是输出a本身的值,为什么要这样讲? 因为 a 是char型, 只有8位 , 那么它的取值范围只有 -128 到 127 现在输出的是 -160 明显超出了它的取值范围, 所以你若认为此时输出的是a的值就不能理解了,而实际它输出的是保存在CPU寄存器中的这个表达式a<<3提升为int型的值。你若还不能理解,请看下面:

charb = -20;

b= b<<3;

cout<<b<<endl;

你猜现在会输出什么东西?

它输出一个类型英文所有格中右上角的那个一点的符号 如: ’

为什么输出的是符号而不是数字和其他的东西?我们查ASCII表,得到 这个 ‘ 在ASCII表中所对应的十进制是:96 十六进制是:60  二进制是 :0110 0000

?这个二进制的形式貌似在哪见过吧?

就是见过,分析和上面一样:b<<3 得到的结果还是和上面一样为:

11111111 1111 1111  1111 1111 0110 0000

此时你看地8位是不是就是上面的b输出的那个图像对应的二进制啊?

没错,就是的,因为b<<3 的运算结果是保存在CPU寄存器中,不是在char型的b中, 现在我们用 b = b<<3: 把这个CPU寄存器中的值强行保存到b中, 但是cpu寄存器中的这个值是32位的就是上面求得的那个补码,而b只有8位,且一切数据在计算机内的表示形式是补码的形式进行存储和表示的, 所以这里就只能装下低8位 (为什么不装高8位,我想这个和数据大小端存储模式是有关,你可以查一下大小端存储并检测你的32位系统是什么端存储)装下的低8位就是 0110 0000 其它位丢失, 看这底8位中的最高位是0 , 即符号位是0 所以变成了正数, 正数原码反码补码相同, 所以这个底8位的补码形式也是它的原码形式, 所以我们计算得到 64 + 32 = 96 这也就解释了输出的那个图形为什么对应ASCII表中的十进制96了

接着来看如下分析:

在32位的XP系统中

-20的原码:1000 0000 0000 0000 00000000 0001 0100

-20的反码:1111 1111 1111 1111 11111111 1110 1011

-20的补码:1111 1111 1111 1111 11111111 1110 1100

-20<<27,即-20左移27位 ,即包括符号位在内的高27位移除,地位补27个0,如下:

1111 1111 1111 1111 11111111 1110 1100000 0000 0000 00000000 0000 0000

即:0 1100 000 0000 0000 00000000 0000 0000

注意看此时的最高位为0 ,即符号位是0,即说明-20左移27位变成了正数,正数的原码和补码是一样的, 所以这个数转为十进制为:161 0612 736

int(signed int)的表示范围是:-2 147 483 648 至 2 147 483 648 ,而161 0612 736 < 2 147483 648 所以做为正数在int型中正确输出。

右移操作要分正负数,正数补0,负数补1,也就是常说的右移操作补符号位,因为正数的符号位就是0,相当于补0,负数的符号位就是1,相当于补1,所以右移多少为就补多少个0或者1.

那么-20>>3 即是-20 右移3位 -20是负数 所以最高位补1 ,那么运算的结果是:

11111111111 1111 1111 1111 1111 1110 1100

注意此时得到的是补码形式, 因为一切数据在计算机内的表示形式都是以补码的形式进行存储和表示的,那么要求得这个数是多少还要对这个补码求补得到其原码才行。对其求补得到:1000 0000 0000 0000 00000000 0000 0011 换成十进制即为 -3 与实际输出的是一样的。

我们说了,数据右移如果数据是正数则高位补0 ,数据是负数则高位补1 上面就是补1的情况,下面来看数据是正数的情况

20>>3,即20右移3位, 注意这里的数据20是正数,所以高位补0。

20的原码:0000 0000 0000 0000 00000000 0001 0100 补码和原码一样,现在右移3位,得到:0000000 0000 0000 0000 00000000 0001 0100

即得到:00000000 0000 0000 0000 0000 0000 0010因为是正数,则直接转为为十进制数为:2  与实际输出一样。

在移位运算时,byte、short和char类型移位后的结果会变成int类型(这里是说这些类型移位后的结果会变成int类型,不是说移位后这些类型的变量变成了int型啊!),对于byte、short、char和int进行移位时,规定实际移动的次数是移动次数和32的余数,也就是移位33次和移位1次得到的结果相同。移动long型的数值时,规定实际移动的次数是移动次数和64的余数,也就是移动66次和移动2次得到的结果相同。

进一步解析移位运算


左移

先说左移,左移就是把一个数的所有位都向左移动若干位,C++中用<<运算符.例如:

int i = 1;

i = i << 2; //i里的值左移2位,再赋值给i

也就是说,12进制是000...0001(这里1前面0的个数和int的位数有关,32位机器,gcc里有310),左移2位之后变成000...0100,也就是10进制的4,所以说左移1位相当于乘以2,那么左移n位就是乘以2n次方了(有符号数不完全适用,因为左移有可能导致符号变化,下面解释原因)

需要注意的一个问题是int类型最左端的符号位和移位移出去的情况.我们知道,int是有符号的整形数,最左端的1位是符号位,01,那么移位的时候就会出现溢出,例如:

int i = 0x40000000; //16进制的40000000,2进制的01000000...0000

i = i << 1;

那么,i在左移1位之后就会变成0x80000000,也就是2进制的100000...0000,符号位被置1,其他位全是0,变成了int类型所能表示的最小值,32位的int这个值是-2147483648,溢出.如果再接着把i左移1位会出现什么情况呢?C语言中采用了丢弃最高位的处理方法,丢弃了1之后,i的值变成了0.

  int i = 0x40000000;

   i = i<<1;

   cout<<i<<endl;

cout<<hex<<i<<endl;

  cout<<dec<<i<<endl;

输出的结果是:

80000000

80000000

-2147483648

看到上面的结果,i明明是int却输出为十六进制型,这里要注意到cincout指明数制后,该数制将一直有效,直到重新指明使用其他数制。虽然我们把i声明为int型,但是赋值的时候是赋值为hex值,所以导致后面如果没哟偶重新为cincout指明数制,那么它将安装hex制输出。

左移里一个比较特殊的情况是当左移的位数超过该数值类型的最大位数时,编译器会用左移的位数去模类型的最大位数,然后按余数进行移位,:

int i = 1, j =0x80000000; //int32

i = i << 33; // 33 % 32 = 1 左移1,i变成2

j = j << 33; // 33 % 32 = 1 左移1,j变成0,最高位被丢弃

在用gcc编译这段程序的时候编译器会给出一个warning,说左移位数>=类型长度.那么实际上i,j移动的就是1,也就是33%32后的余数.gcc下是这个规则,Eclipsevs中则提示警告:移动的位数大于或等于数据类型的宽度.提示这个的警告如果继续执行,则全部以0填充。即结果为0.

总之左移就是:丢弃最高位,0补最低位

右移

右移的概念和左移相反,就是往右边挪动若干位,运算符是>>.

右移对符号位的处理和左移不同,对于有符号整数来说,比如int类型,右移后,如果移动的数据为正数,则高位补0,如果是负数则补1,即补符号位,例如:

int i = 0x80000000;

i = i >> 1; //i的值不会变成0x40000000,而会变成0xc0000000

就是说,符号位向右移动后,正数的话补0,负数补1,也就是汇编语言中的算术右移.同样当移动的位数超过类型的长度时,会取余数,然后移动余数个位.

   负数10100110 >>5(假设字长为8),则得到的是 11111101 

一般来讲,左移动N位,是乘以2的N次方右移N位,是除以2的N次方。移位运算在实际应用中可以根据情况用左/右移做快速的乘/除运算,这样会比循环效率高很多

参考来源


http://blog.csdn.net/phunxm/article/details/5071772

http://www.jizhuomi.com/software/29.html

http://www.jizhuomi.com/software/32.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值