【转载】编写高质量 C 代码一:数据

转载自:https://blog.csdn.net/u011797751/article/details/51297799

(1)注意数据类型及其范围

不同数据类型的表示方法和范围是不同的,整型如下图:
这里写图片描述
这里要注意符号数和无符号数是有区别的,符号的最高位要牺牲出来作为符号位,符号位为1表示负数,符号位为0表示正数。实质上,我们对内存中的数据进行解释,是按照他的数据类型进行解释的。举个例子,-3在内存中的补码(假设8位)表示为1000 0011,如果定义-3为无符号型,那么解释得到的数值就是131。所以,有时候会发生一些看似“奇怪”的事情。举两个例子:

这里写图片描述

这里写图片描述
char的表示范围是-127~127,这里定义char a=150,超过了其表示范围,编译器就会按照符号数进行处理,所以最后结果是负数。

(2)注意不同数据类型的转换

我们再来看例外一个例子:
这里写图片描述
按理说,最后的结果应该是i < sizeof(array)啊?
原来是这样,sizeof返回值是size_t类型,是一个无符号型。当有符号数和无符号数进行运算时,有符号数会转换成无符号数。这样,-1用无符号数表示的话就是一个非常大的正数。所以会出来“看似奇怪”的结果。

由此可见,有符号数和无符号数混合使用是危险的,所以在代码中要尽量少使用无符号型。

(3)防止无符号整数回绕

涉及无符号数的计算是永远不会发生溢出的,也就是说,如果数值超过了无符号数的最大上限,就会发生回绕。如果无符号数超过了最大值,就会返回0,然后从0开始增大;如果低于无符号数的下限,那么就会达到无符号数的上界,然后从上界开始减小。举个例子:
这里写图片描述

因此,在无符号数的运算中,要采取适当的方法防止回绕。例如做如下修改:

#include<stdio.h>
int main(){
    unsigned int a = 4294967295;
    unsigned int b = 2;
    unsigned int c = 4;
    if(UINT_MAX - a > b)
        printf("%u\n",a+b);
    if(UINT_MIN + c < b)
        printf("%u\n",b-c);
    return 0;
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(4)防止符号整数溢出

当两个符号数运算溢出时,会导致“不确定的行为”,大多数编译器会忽略溢出,这样就会导致不确定或错误的值保存在变量中。溢出跟回绕类似,会使变量的值发生“意想不到”的变化,可以逃过边界检查,导致很多问题,比如缓冲区溢出。

(5)少使用浮点数

大多数平台下,浮点数都遵循IEEE754标准。浮点类型在不同平台下的实现差异很大,有的平台有浮点运算单元(FPU);有的处理器只能做整型运算,需要用整数运算实现模拟浮点运算(软浮点)。
5.1 避免浮点数进行精确计算,用分数精确表示浮点数,避免浮点数因舍入误差导致的不精确问题。
5.2 避免浮点数直接使用==进行判断,一般采用|V0 - V1 < MIN|的形式。
5.3 避免使用浮点数作为循环计数器。
5.4 尽量将浮点运算中的整数转换为浮点数。看个例子:
这里写图片描述
为什么第一个结果跟后面四个不一样呢?而且第一个结果明显精确度不高。因为x/9会首先执行,然后将结果35转换成浮点数赋给a。还有一点要注意,整数与浮点数运算,会将整数首先转化为浮点数。因此,为了避免这种信息缺失的情况,我们使用整数运算并把结果赋给一个浮点数时,要首先将一个整数转换成浮点数!

(6)数据类型转化时做范围检查

不同类型的数据进行转化,要先转化为级别最高的数据类型,然后再进行运算。例如int类型的数据和long类型的数据运算,int先要转化成long。还有一个要注意的是,所有的浮点操作都是双精度的,即使表达式中只有float类型,也要首先转化成double类型。
如果一个无符号类型转化成有符号类型,就有可能发生高位截断而数据丢失,主要是防止溢出。

(7)使用有严格定义的数据类型

首先看两个例子:
这里写图片描述

这里写图片描述
为什么会这样呢?因为ch与0xff比较时,首先将ch转换为int型,如果int是32位的,那么如果ch是有符号的,那么补为0xffff ffff;如果ch是无符号的,那么补为0x0000 00ff。
尽量使用严格形式定义的、可移植的数据类型,尽量不使用与具体硬件或软件关系密切的变量。

(8)尽量使用const声明值不会改变的变量

很多人认为const表示常量的意思,其实这是不确切的。准确的说,const表示的是只读的变量,是不能被修改的。例如:

const int i = 10 ;
i++;
    
    
  • 1
  • 2

这个是不允许的。那么const变量真的是不能修改的吗?下面我们试图修改一下:
这里写图片描述
从结果可见,通过指针的方式是可以对const变量进行修改的。真的是这样吗?我又在linux下用g++编译器编译,结果却不一样:
这里写图片描述
为什么结果不一样呢?在不同编译器下,对const变量的处理是不一样的。找到一篇文章解释的很不错:C++中如何修改const变量

(1)注意数据类型及其范围

不同数据类型的表示方法和范围是不同的,整型如下图:
这里写图片描述
这里要注意符号数和无符号数是有区别的,符号的最高位要牺牲出来作为符号位,符号位为1表示负数,符号位为0表示正数。实质上,我们对内存中的数据进行解释,是按照他的数据类型进行解释的。举个例子,-3在内存中的补码(假设8位)表示为1000 0011,如果定义-3为无符号型,那么解释得到的数值就是131。所以,有时候会发生一些看似“奇怪”的事情。举两个例子:

这里写图片描述

这里写图片描述
char的表示范围是-127~127,这里定义char a=150,超过了其表示范围,编译器就会按照符号数进行处理,所以最后结果是负数。

(2)注意不同数据类型的转换

我们再来看例外一个例子:
这里写图片描述
按理说,最后的结果应该是i < sizeof(array)啊?
原来是这样,sizeof返回值是size_t类型,是一个无符号型。当有符号数和无符号数进行运算时,有符号数会转换成无符号数。这样,-1用无符号数表示的话就是一个非常大的正数。所以会出来“看似奇怪”的结果。

由此可见,有符号数和无符号数混合使用是危险的,所以在代码中要尽量少使用无符号型。

(3)防止无符号整数回绕

涉及无符号数的计算是永远不会发生溢出的,也就是说,如果数值超过了无符号数的最大上限,就会发生回绕。如果无符号数超过了最大值,就会返回0,然后从0开始增大;如果低于无符号数的下限,那么就会达到无符号数的上界,然后从上界开始减小。举个例子:
这里写图片描述

因此,在无符号数的运算中,要采取适当的方法防止回绕。例如做如下修改:

#include<stdio.h>
int main(){
    unsigned int a = 4294967295;
    unsigned int b = 2;
    unsigned int c = 4;
    if(UINT_MAX - a > b)
        printf("%u\n",a+b);
    if(UINT_MIN + c < b)
        printf("%u\n",b-c);
    return 0;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(4)防止符号整数溢出

当两个符号数运算溢出时,会导致“不确定的行为”,大多数编译器会忽略溢出,这样就会导致不确定或错误的值保存在变量中。溢出跟回绕类似,会使变量的值发生“意想不到”的变化,可以逃过边界检查,导致很多问题,比如缓冲区溢出。

(5)少使用浮点数

大多数平台下,浮点数都遵循IEEE754标准。浮点类型在不同平台下的实现差异很大,有的平台有浮点运算单元(FPU);有的处理器只能做整型运算,需要用整数运算实现模拟浮点运算(软浮点)。
5.1 避免浮点数进行精确计算,用分数精确表示浮点数,避免浮点数因舍入误差导致的不精确问题。
5.2 避免浮点数直接使用==进行判断,一般采用|V0 - V1 < MIN|的形式。
5.3 避免使用浮点数作为循环计数器。
5.4 尽量将浮点运算中的整数转换为浮点数。看个例子:
这里写图片描述
为什么第一个结果跟后面四个不一样呢?而且第一个结果明显精确度不高。因为x/9会首先执行,然后将结果35转换成浮点数赋给a。还有一点要注意,整数与浮点数运算,会将整数首先转化为浮点数。因此,为了避免这种信息缺失的情况,我们使用整数运算并把结果赋给一个浮点数时,要首先将一个整数转换成浮点数!

(6)数据类型转化时做范围检查

不同类型的数据进行转化,要先转化为级别最高的数据类型,然后再进行运算。例如int类型的数据和long类型的数据运算,int先要转化成long。还有一个要注意的是,所有的浮点操作都是双精度的,即使表达式中只有float类型,也要首先转化成double类型。
如果一个无符号类型转化成有符号类型,就有可能发生高位截断而数据丢失,主要是防止溢出。

(7)使用有严格定义的数据类型

首先看两个例子:
这里写图片描述

这里写图片描述
为什么会这样呢?因为ch与0xff比较时,首先将ch转换为int型,如果int是32位的,那么如果ch是有符号的,那么补为0xffff ffff;如果ch是无符号的,那么补为0x0000 00ff。
尽量使用严格形式定义的、可移植的数据类型,尽量不使用与具体硬件或软件关系密切的变量。

(8)尽量使用const声明值不会改变的变量

很多人认为const表示常量的意思,其实这是不确切的。准确的说,const表示的是只读的变量,是不能被修改的。例如:

const int i = 10 ;
i++;
  
  
  • 1
  • 2

这个是不允许的。那么const变量真的是不能修改的吗?下面我们试图修改一下:
这里写图片描述
从结果可见,通过指针的方式是可以对const变量进行修改的。真的是这样吗?我又在linux下用g++编译器编译,结果却不一样:
这里写图片描述
为什么结果不一样呢?在不同编译器下,对const变量的处理是不一样的。找到一篇文章解释的很不错:C++中如何修改const变量

猜你喜欢

转载自blog.csdn.net/yld10/article/details/80377150