Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1209392
  • 博文数量: 232
  • 博客积分: 7563
  • 博客等级: 少将
  • 技术积分: 1930
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-21 11:17
文章分类

全部博文(232)

文章存档

2011年(17)

2010年(90)

2009年(66)

2008年(59)

分类:

2010-07-06 09:53:07

1、计算机中数值为什么一律使用补码来表示(存储)?

   主要原因:首先使用补码,可以把符号位和其他的数据位统一进行处理。其次,使用补码的话,减法可以按照加法来处理,两个用补码表示的数相加时,如果最高位(符号位)有进位,那么进位被舍弃。

2、求数值的补码:分两种情况
  • 正数的补码:正数的反码、补码均与原码相同;
    如:+9 的补码即为 00001001
  • 负数的补码:将该负数的绝对值的二进制形式,按位取反然后加1。也可以这样理解,首先确定负数的符号位是'1',其他数据位是将该负数的绝对值的原码按位取反(反码),然后整个数加1得到的。
    如:-7的补码: -7的绝对值为+7,+7的原码为0000111,按位取反为1111000,再加1为1111001,最后加上最高位(符号位)'1':11111001。

3、已知一个数的补码,求原码:两种情况
  • 如果补码的符号位为“0”,表示是一个正数,所以补码就是该数的原码。

  • 如果补码的符号位为“1”,表示是一个负数,求原码的操作可以是:符号位为1,其余各位取反,然后再整个数加1。

    例如,已知一个补码为11111001,则原码是10000111(-7):因为符号位为“1”,表示是一个负数,所以该位不变,仍为“1”;其余7位1111001取反后为0000110;再加1,所以是10000111。
4、各数据类型所占内存字节:

   了解各数据类型在内存中所占的字节数,对于各数据类型间的类型转换以及排查因为数据溢出等造成的逻辑错误很有帮助。

   C标准中并没有具体规定各类数据所占的字节数。比如对于整形数据,只要求long型数据长度不短于int型,short型不长于int型。至于具体如何实现,由各计算机系统自行决定。
   同样,对于实型数据,一般在内存中占4个字节(float),由于实型数据和整型数据的存储方式不同,它是按照指数形式存放,至于在这4个字节中,用多少位来表示小数部分,多少位来表示指数部分,标准C中也没有具体规定,由各C编译系统自定
   但是在所有的编译系统中,都规定用一个字节来存放一个字符,或者说一个字符变量在内存中占一个字节。


5、整型数据的溢出:
  在我使用的计算机系统中,int型数据的长度为4字节,因此其取值范围为:
  -2147483648~2147483647.编写以下程序验证整型数据的溢出:
   
 

#include<stdio.h>
int main()
{
  int a,b;
  a=2147483647;
  b=a+1;
  printf("%d,%d\n",a,b);

  return 0;
}


输出结果:

2147483647,-2147483648




可以知道:


 01111111  11111111 11111111
11111111

 10000000  00000000 00000000 00000000

上面的是2147483647在内存中的存储方式,加1以后变成下面的形式,根据上一篇文章:

http://blog.chinaunix.net/u2/69737showart.php?id=2266707 可知:1000...是用来表示负的最小值的补码,即为 -2147483648。

6、实型数据的舍入误差:

   实型数据分为单精度(float,4字节)、双精度(double,8字节)以及长双精度(long double,16字节)型,这三种类型所占内存字节数不同,他们的精度是不一样的,有效数字位数不同。
   float: 6~7位
   double: 15~16位
   long double:18~19位

因为实型数据能够提供的有效数字位数是有限的,有效数字位数之外的数字会被舍去,因此存在舍入误差。

看下面的例子:



#include<stdio.h>

int main()
{
  float a,b;
  a=123456.789e5;
  b=a+20;

  printf("%f,%f\n",a,b);

  return 0;
}


运行结果:

12345678848.000000,12345678848.000000


问题:a+20的理论值应该为 12345678920,但是为什么会出现上述结果呢?
原因:一个float实型变量只能保证的有效数字是7位有效数字,后面的数字是没有意义的,并不是准确地表示该实数。从上面的结果看到,前面的8位是准确的,后几位是不准确的,将20加在后几位上,是没有意义的。所以应该避免把一个很大的数和一个很小的数直接相加或者相减,否则会丢失该很小的数。


7、变量的初始化:
  我们知道,C语言允许在定义变量的同时对其进行赋值(初始化)。可以全部或者部分赋值:
 

int a=3;
float b=2.0;  正确



int a,b=3,c;  正确



int a=b=c=3;  错误,不能连等赋值


那么初始化是在什么时候完成的呢?(important)

在编译阶段,会给程序中定义的变量分配存储单元,但是变量的初始化并不是在编译阶段完成的,而是在程序运行时执行本函数的时候赋予变量初值的。当然也有特殊情况,静态存储变量以及外部变量的初始化则是在编译阶段完成的。

  • 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时 它已经有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行赋值,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
如果没有初始化呢?(important)

如果在定义局部变量的时候没有赋初值的话,即没有初始化,那么对静态变量来说,编译时会自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值那么它的值是一个不确定的值,这是因为每次函数调用结束以后存储单元被释放,下一次调用时又重新分配存储空间,而分配的单元中的值是不确定的。




8、各类数值型数据间的混合运算:

整型、实型和字符型数据之间可以混合运算。进行运算时,不同类型的数据要先转换成同一类型,然后进行运算。下面的来看一下具体的转换规则:

 


必定转换:横向向左的箭头表示必定的转换,即:
        1)float型的数据在运算时一律是先转换为double型的,以提高运算精度,即使

          是两个float型的数据进行运算,也要都先转换成double型的,然后再运算。

        2)字符型数据和short整型数据必定先转换为int型整数。


根据情况进行的转换:纵向的箭头表示当运算对象为不同的数据类型时,需要进行的转换,转

         换的宗旨是总是向高处的类型靠拢。例如:int型和double型数据进行运算,那

         么会先将int型数据转换成double型,然后两个同类型的数据进行运算,结果为

         double型。

         注意:纵向箭头方向仅表示数据类型级别的高低,指示其从低到高转换。并不是

         int型要先转换为unsigned型,再转为long型,最后转为double型。而是直接

         将int型转换为double型。       



上述的各中类型转换是系统自动进行的。

9、强制类型转换:

  

(类型名)(表达式)


注意:

1)括号的使用。表达式应该用括号括起来,因为如果是想将 x+y 的结果转换成整型,若不加括号:(int)x+y,则只是将x转换成了整型,然后与y相加。应该写成 (int)(x+y)。

  这和define宏定义里面的括号使用原因类似。

2)强制类型转换不改变原变量的类型。在强制类型转换时,得到一个所需类型的中间变量,原来变量的类型没有发生变化。



#include<stdio.h>

void main()
{
   float x;
   int i;
   x=3.6;
   i=(int)x;
   printf("x=%f,i=%d\n",x,i);
}


运行结果:

x=3.600000,i=3


如上,x 类型仍为 float 型,值仍为 3.6。

10、自增自减运算符

  • 自增自减运算符只能用于变量,不能用于常量和表达式。5++是不对的,因为5是常量,常量的值是不能改变的。(a+b)++也不对,假如表达式a+b的值为5,那么自增以后的值6放在什么地方呢?没有变量可以存放。

  • 算术运算符(+-*/%)的结合方向为:自左向右;
    ++、--的结合方向为:         自右向左;
    赋值运算符的结合方向也是:     自右向左。

  • 自加自减运算符的副作用:

a. ANSI C中没有具体规定表达式中的子表达式的求值顺序,允许各编译系统自己安排。比如说:   x=f1()+f2();

   并不是所有的编译系统都先调用f1(),然后调用f2()。一般情况下,先调用f1()还是先调用f2()的结果可能是相同的,但是有时候结果是不同的(比如含有++、--时)。举例:

   i的初值为3, 求 (i++)+(i++)+(i++)的值。

  • 有的系统按照自左向右的顺序求解,求完第一个括号中的值后,i的值变为4,再求第二个括号的值,结果表达式相当于3+4+5,最后i的值为6;

  • 另一些系统(Turbo C 和 MS C)把3作为表达式中所有i的值,因此3个i相加,结果为9。求出整个表达式的值以后再实现3次自加,i的值变为6。 
   为了避免出现这种歧义性,如果我们想得到的是12,那么应该这样表达:
  
    int i=3;
    a=i++;
    b=i++;
    c=i++;
    d=a+b+c;

  虽然语句增多了,但是不会引起歧义。不管程序移植到哪一种C编译系统中运行,结果都是一样的。

b、i+++j是理解为 (i++)+j 还是 i+(++j)呢?

   C编译系统在处理时尽可能多地(自左向右)将若干个字符组成一个运算符(在处理标识符、关键字时也按同一原则处理)。
   因此,i+++j将被解释为 (i++)+j,而不是i+(++j)。为了避免误解,最好写成大家都理解的写法,不要写成 i+++j,而是明确地写成 (i++)+j。

c、在调用函数的时候,实参数的求值顺序,C标准并没有统一规定。
   比如,i的初值为3。则函数调用:
        printf("%d,%d",i,i++);   结果如何?

  • 有的系统中,从左向右求值,输出为“3,3”;

  • 在大多数的系统中,对函数参数的求值顺序是自右向左的,因此上面的printf函数中,先求出第二个表达式i++的值3,i自加为4,然后求第一个表达式的值i为4。因此输出为“4,3”。
  还是为了避免这种歧义性,提高程序的可移植性,上面的写法不提倡,最好写成:
    j=i++;
    printf("%d,%d",j, i);

  
总之,不要写别人看不懂的,也不知道系统是怎么执行的程序。


11、实型数据的有效位:
   float: 6~7(但是一般都是以7位为准)
   double: 15~16(以16位为准)
   long double: 18~19

12、复合赋值运算符:
    a += b;
    a *= b;
    ...

注意:如果b是包含若干项的表达式,则相当于它有括号。即使你没有添加括号。

如:     x %= y+3

等价于:  x= x % (y+3)


    凡是二元运算符,都可以与赋值符一起组合成复合赋值符,C语言规定可以使用10种复合赋值符:
    +=、-=、*=、/=、%=、  (和算术运算符的结合)
    <<=、>>=、&=、^=、|=  (和位运算符的结合)

13、逗号运算符和赋值运算符的优先级:
  
   看例子:求解下述表达式
 
   a=3*5,a*4

   如果不是很清楚
逗号运算符和赋值运算符的优先级关系,可能会有两种理解:
  • 认为"3*5,a*4"是一个逗号表达式,因此要先求出该逗号表达式的值,如果a的初值为3,那么逗号表达式的值(第二个表达式的值)为12;然后把12赋给a,因此最后a的值为12。

  • 认为"a=3*5"是一个赋值表达式,“a*4”是另一个表达式,二者用逗号相连,构成一个逗号表达式。先求解"a=3*5",a的值为15,然后求解"a*4"为60,整个逗号表达式的值为60。

C中赋值运算符的优先级高于逗号运算符,因此第二种理解是正确的。

逗号运算符是所有运算符中级别最低的。


14、C中的输入输出语句是它自己的吗?

   C语言本身不提供输入输出语句,输入和输出操作是由函数来实现的(printf、scanf)。这些函数以库的形式存放在系统中,它们不是C语言文本中的组成部分。

   不把输入输出作为C语言提供的语句的目的:使C语言编译系统简单。
   因为将语句翻译成二进制的指令是在编译阶段完成的,没有输入输出语句就可以避免在编译阶段处理与硬件有关的问题,可以使编译系统简化,且通用性强,可移植性好,便于在各种计算机上实现。
  
   C编译系统与C函数库是分别进行设计的,因此不同的计算机系统提供的函数的数量、名字和功能不完全相同。各种版本的C语言函数库是各计算机厂商(或软件开发公司)针对某一类型计算机来编写的,并且已经编译成目标文件(.obj文件)。它们在连接阶段和由源程序编译得到的目标文件相连接,生成可执行的目标程序。比如在源程序中含有printf函数,在编译时并不把它翻译成目标指令,而是连接时直接和它的obj文件连接,在执行程序时直接调用已被连接的函数库中的printf函数。

15、格式输入输出的m、l、n的含义:

1)m ———— 可用于d、o、x、u的前面。指定输出字段的(最小)宽度。若数据的位数小于m,则左端补空格;若大于m,按实际位数输出;

2)在%s中的用法,专讲:

  • %ms:输出的字符串占m列,若字符串本身长度大于m,则按实际长度输出;若串长小于m,则左补空格;

  • %-ms:若串长小于m,则在m列范围内,字符串左靠齐,右补空格;

  • %m.ns:输出占m列,但只取字符串中左端n个字符。这n个字符输出在m列的右侧,左补空格;

  • %-m.ns:m、n含义同上,n个字符输出在m列范围的左侧,右补空格。若n>m,则m自动取n值,即保证要输出的n个字符正常输出。
3)在%f中的用法,专讲:

  • %f:以小数形式输出实数(包括单、双精度)。不指定字段宽度,系统自动指定,使整数部分全部如数输出,并输出6位小数。注意:并非全部数字都是有效数字,单精度实数的有效位数一般为7位;双精度数也可以用%f格式输出,有效位数一般为16位,也输出6位小数;

#include<stdio.h>

int main()
{
  float x,y;
  x=111111.111;
  y=222222.222;
  printf("%f\n",x+y);
  return 0;
}


输出:333333.328125

结论:只有7位数字是有效数字,千万不要以为凡是打印出来的数字都是准确的。超出有效数字位数的数据位是没有意义的。


  • %m.nf:指定输出的数据占m列,其中有n位小数;若数值长度小于m,则左补空格;



  • %-m.nf:与%m.nf基本相同,只是使输出的数值向左靠,右端补空格;

#include<stdio.h>

int main()
{
  float x,y;
  x=111111.1111111;
  
  printf("%f\n%9.0f\n%9.2f\n%9.3f\n%9.7f\n",x,x,x,x,x);
  return 0;
}


结果:

111111.109375
   111111
111111.11
111111.109
111111.1093750

结论:要求输出的小数位数n是一定会满足的,只要有小数部分的输出要求,则整数部分就全部输出,不管是否超出m列的范围。



main()
{
  float f=123.456;
  printf("%f\n%10f\n%10.2f\n%.2f\n%-10.2f\n",f,f,f,f,f);
}

结果:

123.456001
123.456001
    123.46
123.46
123.46

结论:

1)f的值应该是123.456,但是输出为123.456001(这个值呢也是不一定的,书上的结果是123.455994),这是因为实数在内存中的存储误差引起的。

2)在输出占的m列中,小数点也占一列。


4)%e:数值按照规范化指数形式输出,即小数点前有且只有一位非零数字。不指定输出数据所占的宽度和小数位数,不同系统的规定略有不同。


main()
{
  printf("%e\n",123.456);
}


结果:

1.234560e+02


有的系统输出为:1.234560e+002。

  • %m.ne、%-m.ne:m、n、-的含义同前,此处n指输出的数据的小数的位数:

main()
{
  float f=123.456;
  printf("%e\n%10e\n%10.2e\n%.2e\n%-10.2e\n",f,f,f,f,f);
}

结果:

1.234560e+02
1.234560e+02
  1.23e+02
1.23e+02
1.23e+02


结论:如果没有指定n,自动使n=6,即默认尾数小数部分为6位。当然,有的C系统的输出格式和上面略有不同。


16、字符数组使用中应该注意的地方:

  • 初始化:
    1)逐个字符赋值:
       char c[10]={'C','h','i','n','a'};

       内存中的存储情况:
    C h
    i
    n
    a
    \0
    \0
    \0
    \0
    \0
        
   如果提供的初值个数和预定的数组长度相同,那么在定义的时候可以省略
   数组长度,系统会根据初值的个数确定数组的长度:

      char c[]=
{'C','h','i','n','a'};
      内存中的存储情况:
     

 C  h i
 n  a
 
     可以很明显地看到上面两种情况的区别,这也说明了字符数组并不要求它的最后一个字符为'\0',甚至可以不包含'\0'。只是在指定长度,并且初值个数小于数组长度或者使用字符串进行赋值时,才会出现'\0'。
    当然,上面的两个数组使用%s字符串输出的时候结果也是不一样的:
   

main()
{
  char c[10]="china";
  char c1[]={'c','h','i','n','a'};
  
  printf("%s\n",c);
  printf("%s\n",c1);
}

结果:

china
china

阅读(1218) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~