(一)基本数据的表现形式(整数)
1.整数的表现形式
1.1 原码,反码及补码
关于原码,反码,补码的概念,任何有计算机基础的人都不会陌生了,所以这里不提了。
我们都知道数据在内存中都是01二进制的形式表示,整数分无符号整数,跟有符号整数,即正整数跟负整数。最高位代表符号位,当内存中的数据解释为无符号数时,最高位也参与计算。而当解释为有符号数时,最高位不参与计算,仅仅代表符号,0代表是正整数,1代表是负整数。整数都是以补码形式存在在内存中的,对于正整数的补码等于其原码,这个不过多解释了,下面着重说说负整数在内存中的表现形式。
1.2 补码的概念
至于为什么整数在内存中都要以补码形式表示,这个疑问曾今在我初学语言的时候困惑过我很久,希望下面的文字能让同样有此困惑的朋友有一种豁然开朗的感觉。
首先得说说补码的概念。补码的概念还不简单么,大家都知道的: 补码 = 原码取反(反码) + 1,额,朋友没错,但这只是计算补码的公式,而不是其概念。
补码的定义是什么,究竟什么是补码? ---- 补码就是用0减去这个数的绝对值。一定要牢记红色标记的这句话,这句话是让我们豁然开朗的关键。同时我们还要注意补码的概念是跟机器字长(CPU进行整数运算时,一次能最多处理的位数)紧密相关的,没有指定机器字长的补码没有任何意义。我们接下来用补码的概念推导出“补码 = 取反+1”这个公式出来。
1.3 补码公式推导
假设我们求-5的补码。根据概念我们有“-5(补) = 0 - |-5| = 0 - 5”,即“-5(补) + 5 = 0”。那么在32位机器字长中,有”0xFFFFFFFB + 5 = 0“(这里进位丢失了,所以说补码是跟特定的机器字长紧密相关的),所以0xFFFFFFFB就是-5的32位机器字长的补码。(后续的讨论都假设机器字长为32位)同样,反过来看的话,0xFFFFFFFB最高位是1,所以其代表一个负数,那么求其绝对值我们可以0-0xFFFFFFFB,等于"00000005"就是5了。其高位溢出了,我们可以用计算器演示一下,在windows自带的计算器中,我们选择四字长,也就是64位。计算”0-0xFFFFFFFB = FFFFFFFF00000005”,那么只取低32位就是"00000005"。
接下来,我们知道任何一个负整数的绝对值加上它的反码等于0xFFFFFFFFF(注意这里只针对负整数,正整数的原反补三码相同),那么0xFFFFFFFF + 1的话由于进位丢失就等于0。于是我们有“| X | + X(反)+ 1 = 0”,进行一下简单的移项,我们又得出“X(反) + 1 = 0 - | X |”。怎么样,“0 - |X|”不正是我们补码的定义吗?所以我们为了求补码时的计算方便,我们可以套用公式:
“补码 = 取反+1”。
1.4 补码的意义
相信经过上面的公式推导,大家对补码一定有一个全新的认识。那么,我们再来看看补码的意义。我们知道计算机只会做加法,当我们需要做减法的时候怎么办呢?我们继续推导一下。
假设我们要计算"X-Y",那么我们可以表示为“X + (0 - | Y |)”,而0 - |Y |,不正是补码的定义么,那么得出“X - Y = X + Y(补)”,所以补码的意义就是把减法转化成了加法!举个例子,比如我们计算“8-5”,首先转化为“8 + (0 - | -5|)” ,接着"0 - | -5| = -5(补) = 0xFFFFFFFB",最后“8+0xFFFFFFFB = 00000003”(进位丢失),所以8-5=3。呵呵,是不是觉得豁然开朗了?不得不佩服前辈们的智慧啊,简单的一个进位丢失就把减法搞定了。
在这里,还要说一下比如0x8000000这个数,二进制是1000 0000 0000 0000 0000 0000 0000 0000,这个数最高位是1,我们解释为有符号数的时候,那么它代表负零。但是,零是没有正负之分的。怎么办呢?别急,0x80000000除了代表负零之外,还可以代表“0x80000001 - 1”,即-2147483648。这就解释了为什么一个有符号的数其负数区间的范围要比其正数区间的范围要多1个了。
1.5 补码关键字
在这里我总结了关于补码的几个关键字,方便大家理解记忆。负整数,机器字长,进位丢失。
负整数:正整数的原反补三码相同。
机器字长:补码跟特定的机器字长紧密相关。
进位丢失:最重要就是要理解这个进位丢失的精妙之处了。
结束语:
在本次学习中,学习了整数在内存中的表现形式,重点学习了负整数在内存中的表现形式,以及负数补码的相关概念。
在下一次学习中,将会学习浮点数在内存中的表现形式。
本文链接
作者:zyl910
前面测试了各种编译器的执行结果,但为什么它们的执行结果是那样呢?这需要仔细分析。VC2005的测试结果比较典型,而且调试跟踪比较方便,于是本篇对VC2005的crt源码进行分析。
一、须知
开发工具是VC2005,平台为32位的x86,编译模式为Debug,使用MBCS字符集。
二、cout输出窄字符串
2.1 已初始化locale
“已初始化locale”是指——在输出前执行了初始化locale,即执行了下列语句——
locale::global(locale(""));
wcout.imbue(locale(""));
现在开始进行分析。
“cout << psa”表示使用cout输出窄字符串。按F11单步跟踪,它依次进入了下列函数——
operator<<:[C++库] 流输出运算符。
basic_streambuf<char>::sputn:[C++库] 输出字符串(公开方法)。
basic_streambuf<char>::xsputn:[C++库] 输出字符串(内部实现)。循环对源串中的每一个char调用overflow。【注意#1】gbk编码的汉字是2个字节,会调用overflow 2次。
basic_filebuf<char>::overflow:[C++库] 数据溢出,即向文件写入一个字符。【注意#2】因为现在是char版,无需转换编码,直接调用_Fputc。
_Fputc<char>:[C++库]向文件写入一个char。
fputc:[C库] 向文件写入一个char。
_flsbuf:[C库] 刷新缓冲区并输出char。
_write:[C库] 向文件写数据。
_write_nolock:[C库] 向文件写数据(不加锁版)。【注意#3】条件判断存在漏洞,导致汉字的首字节无法输出。返回-1。
此时的调用栈——
> msvcr80d.dll!_write_nolock(int fh=0x00000001, const void * buf=0x0012fb50, unsigned int cnt=0x00000001) 行170 C
msvcr80d.dll!_write(int fh=0x00000001, const void * buf=0x0012fb50, unsigned int cnt=0x00000001) 行74 + 0x11 字节 C
msvcr80d.dll!_flsbuf(int ch=0xffffffba, _iobuf * str=0x10311d20) 行189 + 0x11 字节 C
msvcr80d.dll!fputc(int ch=0xffffffba, _iobuf * str=0x10311d20) 行52 + 0x4b 字节 C
msvcp80d.dll!std::_Fputc<char>(char _Byte=0xba, _iobuf * _File=0x10311d20) 行81 + 0xf 字节 C++
msvcp80d.dll!std::basic_filebuf<char,std::char_traits<char> >::overflow(int _Meta=0x000000ba) 行261 + 0x1c 字节 C++
msvcp80d.dll!std::basic_streambuf<char,std::char_traits<char> >::xsputn(const char * _Ptr=0x0041774d, int _Count=0x00000007) 行379 + 0x1a 字节 C++
msvcp80d.dll!std::basic_streambuf<char,std::char_traits<char> >::sputn(const char * _Ptr=0x0041774c, int _Count=0x00000008) 行170 C++
wchar_crtbug_2005.exe!std::operator<<<std::char_traits<char> >(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}, const char * _Val=0x0041774c) 行768 + 0x3e 字节 C++
wchar_crtbug_2005.exe!main(int argc=0x00000001, char * * argv=0x003b6a58) 行45 + 0x12 字节 C++
发现_write_nolock函数存在Bug,代码摘录——
/* don't need double conversion if it's ANSI mode C locale */
if (toConsole && !(isCLocale && (tmode == __IOINFO_TM_ANSI))) {
UINT consoleCP = GetConsoleCP();
char mboutbuf[MB_LEN_MAX];
wchar_t tmpchar;
int size = 0;
int written = 0;
char *pch;
for (pch = (char *)buf; (unsigned)(pch - (char *)buf) < cnt; ) {
BOOL bCR;
if (tmode == __IOINFO_TM_ANSI) {
bCR = *pch == LF;
/*
* Here we need to do double convert. i.e. convert from
* multibyte to unicode and then from unicode to multibyte in
* Console codepage.
*/
if (!isleadbyte(*pch)) {
if (mbtowc(&tmpchar, pch, 1) == -1) {
break;
}
} else if ((cnt - (pch - (char*)buf)) > 1) {
if (mbtowc(&tmpchar, pch, 2) == -1) {
break;
}
/*
* Increment pch to accomodate DBCS character.
*/
++pch;
} else {
break;
}
++pch;
} else if (tmode == __IOINFO_TM_UTF8 || tmode == __IOINFO_TM_UTF16LE) {
/*
* Note that bCR set above is not valid in case of UNICODE
* stream. We need to set it using unicode character.
*/
tmpchar = *(wchar_t *)pch;
bCR = tmpchar == LF;
pch += 2;
}
if (tmode == __IOINFO_TM_ANSI)
{
if( (size = WideCharToMultiByte(consoleCP,
0,
&tmpchar,
1,
mboutbuf,
sizeof(mboutbuf),
NULL,
NULL)) == 0) {
break;
} else {
if ( WriteFile( (HANDLE)_osfhn
在C++中的一种函数申明被称之为:纯虚函数(pure virtual function).它的申明格式如下:
2 #include <cstdlib>
3 #include <cstdio>
4
5 using namespace std;
6
7
8 class abstractcls
9 {
10 public:
11 abstractcls(float speed,int total) //构造函数
12 {
13 this->speed = speed;
14 this->total = total;
15 }
16
17 virtual void showmember()= 0; //纯虚函数的定义
18 protected:
19 float speed;
20 int total;
21 };
22
23 class car : public abstractcls
24 {
25 public:
26 car(int aird,float speed,int total):abstractcls(speed,total)
27 {
28 this->aird = aird;
29 }
30
31 virtual void showmember()
32 {
33 cout << speed <<"--------" <<total <<"-----------"<<aird<<endl;
34 }
35 protected:
36 int aird;
37 };
38 int main()
39 {
40 car b(250,150,4);
41 b.showmember();
42 return 0;
43 }
运行结果想必大家都知道!!就不写了!!
总结:什么时候需要用纯虚函数
1,当想要在基类中抽象出一个方法,且该类被继承类而不能被实例化时。
2,基类的方法必须在派生类中被实现时。
3,多个对象具有公共的抽象属性,但却有不同的实现要求时。
下面我们看一道某公司的面试的笔试题(含金量到底有多少??)
#include <cstdio>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fuu()
{
printf("2\n");
}
};
class B:public A
{
public :
void foo()
{
printf("3\n");
}
void fuu()
{
printf("4\n");
}
};
int main()
{
A a;
B b;
A *p = &a;
cout<< "p->foo()---" ; p->foo() ;
cout<<"p->fuu()---";p->fuu();
cout <<"-------向上转型-----------"<<endl;
p=&b;
cout<<