一点一滴成长

导航

C语言常用数据类型说明

 1、取值范围:

   short一般占两个字节,取值范围:-32768 - 32767
   int一般占两个或四个字节,取值范围:-2147483648 - 2147483647

   unsigned int一般占四个字节,取值范围:0 - 4294967295

   long long一般占8个字节,取值范围:-9223372036854775808 - 9223372036854775807
   float一般占4个字节,取值范围:1.17549 e-038 - 3.40282 e+038
   double一般占8个字节,取值范围:2.22507 e-308 - 1.79769e+308

   对unsigned类型进行取负操作是无意义的,因为得到的数还是unsigned,比如这个代码中的n永远不会是负数:int n = -sizeof(DataType)。

   不要将unsigned类型与signed类型进行运算或者比较操作,因为默认的类型转换会发生不可预期的结果,如下所示:

  unsigned int n = 0;
  long long i = -1 + n; //-1默认转换为了unsigned int类型,导致i的结果为4294967295
  bool b = n > -1; //-1默认转换为了unsigned int类型,导致b为fasle

   可以使用std::numeric_limits<int>::max()、std::numeric_limits<double>::lowest()、std::numeric_limits<double>::min()来获得各类型的最大、最小值(min获得的是最小正值,lowest获得的是最小负值或者0)。

   isnan():判断一个浮点型(或整形)变量是否是一个非正常的数值。

   isinf():判断一个浮点型变量是否是一个无穷大值(正无穷大或负无穷大)。

   isalnum(int): 判断所传的字符是否是字母(a-z,A-Z)和数字(0-9)。

   isdigit() / isaplha(): 判断所传的字符是否是数字 / 字母。

   isspace():判断判断传入的字符是否是空格。

   isblank() : 判断传入的字符是否是空白字符('\t'、' ')。

   isupper() / islower():判断传入的字符是否是大写 / 小写字母。

   toupper() / tolower():获得对应字符的大写 / 小写版本。

  %d: int
  %l : long
  %lld : long long
  %f,%lf : printf中%f对应float和double,在scanf中%f对应float,%lf对应double。
  %u : unsigned int
  %lu : unsigned long
  %llu : unsigned long long
  %e/%E: 浮点数的科学计数法
  %g 自动选择%f或%e
  %x 无符号十六进制整数

    %5.2:指定字符串宽度的最小值为5,字符串宽度达不到的话使用空格补充;对于%f指定小数位数为2,对于%d指定指定输出数字的最小位数为2(达不到2位的话补0),对于%s指定输出字符的最大个数。

    %-:字符串左对齐,默认为右对齐。

    %+:显示正负号。

    %05:指定字符串宽度最小为5,字符串宽度达不到的话使用0补充。

    对于格式化输出,%f、%lf 默认输出的小数位数是6位:

    double dd = 123.45678912;
    printf("%lf", dd); //输出为123.456789

    double d = 1234.56;
    char buf[100] = { 0 };
    sprintf_s(buf, "%f", d); //buf为1234.560000

    d = 123.123456789;
    memset(buf, 0, 100);
    sprintf_s(buf, "%lf", d); //buf为123.123457

    对于浮点型推荐使用double来代替float。以下是浮点型数值的一些截取方法:

    //地板
    double n1 = std::floor(-13.9); //-14
    double n2 = std::floor(13.9); // 13

    //天花板
    double n3 = std::ceil(-13.1); //-13
    double n4 = std::ceil(13.1); // 14,天花板

    //取最近的数,即四舍五入
    double num5 = std::round(-14.5); //-15
    double num6 = std::round(14.5); // 15

    //与round不同的是如果左右最近的两个数距离相等(小数部分是0.5)的话取最接近的偶数
    double num7 = std::rint(-14.5); //-14
    double num8 = std::rint(14.5); // 14

 

    后缀意义:F表示float,U表示unsigned int,L表示long, LL表示long long。

    前缀意义:0b为二进制表示,0为八进制表示,0x为十六进制表示。

2、由于各数据类型所占字节数与编译器和CPU有关,所以我们永远不要想当然的认为int大小为4(16位下int大小为2)、指针大小为4(64位程序下指针大小为8)、short大小为2等来使用,稳妥的方法是使用sizeof()。
3、一般情况下我们使用int来存储整型,因为它既可以满足4字节对齐,也可以满足我们存储日常使用的数字,但当数值可能超过十亿这种等级的时候我们应该选用long long。

4、关于移位运算

 右移>>的话,对于左边空出来的位补上与符号位相同的值。

 对于short、char等低于int的类型会先自动转换为int后再移位。

 对于int类型的移位,移动的位数超出31位的话实际移动位数是对32进行的求余结果,如 3 >> 32 相当于 3 >> 0,3 >> 33相当于 3 >> 1。 

5、运算符的结合性与优先级

  结合性为从右向左运算的有三种:赋值=、单目运算符、三目运算符,各运算符的优先级为:

  

6、内存中存储方式

字符型:
char占一个字节,最高位用来表示正负,取值范围为-2^7到2^7-1(-128-127),故可表示2* 22^7个数值,即-128到127。对应ascii取值范围为。
unsigned char占一个字节,由于其没有符号位,取值范围为0到2^8-1(0-255),故可表示2^8个数值,即0到255。

字符串中一个反斜杠表示转义,比如'\n'表示换行符,'\t'表示制表符,如果想要表示反斜杠字符的话使用'\\',如下所示:

    char c = '\n'; //换行符
    char* p = "abc\tdef";  // abc     def

  在字符串常量中,如果想要表示反斜杠字符的话使用'\\',比如下面的字符串常量以及对应的表示内容:

 

在字符串常量中,如果想要在引号中使用引号的话,使用\",比如面的字符串常量以及对应的表示内容:

 

 

   

如果使用字符串常量来表示一个JSON对象,这个JSON中的一个元素值又是一个JSON对象的话,那么这个里面JSON对象属性的引号需要使用\\\"来表示,前面两个\\表示一个反斜杠,后面的\"表示一个引号,比如下面的字符串常量以及对应的表示内容:

  

除了L、_T()包围,我们还可以使用R"()"包围,使用它的话不会对字符串里进行任何转义,同时使用多行文本的话更方便,如下所示:

使用R"()"包围的话可以很方便的表示JSON字符串,u8R"()"表示使用UTF-8字符编码:

整型:

int在32位系统下占4个字节,最高位用来表示正负,故可表示2* 2^31个数值,取值范围为-2^31-2^31-1。

最小取值问题:
有一个问题就是char跟int的最小取值为什么是-2^7和-2^31而不是2^7-1和-2^31-1?以下答案部分转载和参考CSDN网友daiyutage的文章。
拿char类型来说,一共8位,1位为符号位,所以剩下7位来表示数值,根据排列组合得出char可表示的数值总数为2^7 * 2,它们就是+0到127和-0到-127,那么已经看出问题来了:出现了两个0,一正一负,原码分别为0000 0000、1000 0000,我们知道,0其实既不是正数也不是负数,所以0只用原码0000 0000来表示,而0的补码为全部位数取反后再加一即1 0000 0000,忽略溢出的1比特(0是唯一计算补码过程中会出现溢出的数字),得出0的补码跟原码相同。那么就多出了一个-1的原码1000 0000,其补码也是全部位数取反后加一,得到跟原码相同的补码1000 0000。使用这个多出来的-1的原码可以用来表示-128,那么为什么它用来表示-128而不是其它值呢?理论上-128的原码应为1 1000 0000,最高位为符号位,取反加一后的补码也为1 1000 0000,可以看出-128的原码跟补码丢弃最高位后与-0的相同,而且即使截断后的-128和char 型范围的其他数(-127~127)运算也不会影响结果, 所以才可以用-0来表示-128。简而言之就是:因为有一个符号位,所以就会出现+0和-0,而用-0就用来表示-2^7即-128了。

负数:
在计算机中,数值是用补码来存储的,正数的补码是其原码,负数的补码是原码除符号位外各位取反后加1。

例1:
char a = 1; //a的补码:0000 0001
char b = -1; //b的补码:1111 1111
char c = a ^ b; //c的补码即为1111 1110, 而原码为1000 0010即-2
printf("%d\n", c);//所以输出为-2

例2:

int A = 468; //a的二进制:0000 0001 1101 0100
char B = A; //int赋给char,舍去高位,只剩低位:1101 0100,符号位是1,所以是负数的补码,再减1取反后得1010 1100即-44
printf("%d", B); //所以输出为-44

浮点型:
浮点型在内存中是以二进制的科学计数法形式存储的,比如86.5的十进制科学计数法为8.65*e1,86.5的二进制表示为1010110.1,使用二进制的科学计数法则为1.0101101e6,符号位为0,指数位为6,有效数字为1.0101101。在内存中一个float使用32位存储,一个double使用64位存储,如下图所示的float,符号位S占一位,指数位E占8位,有效数字M占23位。

float:

double:

符号位为0表示正数,1表示负数。

指数位对于float类型的话,其值范围为0-255,而指数有正有负,所以指数位上存储的值为指数值再加127,比如上面的86.5F的二进制指数为6,则指数位其值为6+127,当读取指数的时候再减去127,这样就能存取负数了。对于float来说,其指数的范围是-127~128。

有效数字因为小数点前肯定为1,所以不用存储它,所以对于上面的86.5F的有效数字1.0101101,实际存储的值为0101 1010 0000 0000 0000 000(23位)。

对于float类型的有效数字,其使用23位存储,再加上有效数字前面固定为1不用存储,所以float的二进制值其有效位数是24位,而如果是float的十进制值的话那么有效位数就是7-8位(2e24 = 16777216,10e7 < 16777216 < 10e8),比如对于12.34567899F,12.34567之后的数字是不精确的。所以对于float来说,1.192092896e-7(FLT_EPSILON)是能精确的最小数。

由于浮点型使用二进制的指数形式来保存,所以很多浮点型的数值是存在误差的,如下所示的d5,即0.3表示成二进制的话是永远无法凑整或者即便能凑整其有效数字也超出了位数,所以0.3在内存中就是0.2999999... :

double d1 = 0.5; //2^-1,二进制为0.1
double d2 = 0.25; //2^-2, 二进制为0.01
double d3 = 0.125; //2^-3; 二进制为0.001
double d4 = 0.875; //2^-1 + 2^-2 + 2^-3, 二进制为0.111

double d5 = 0.3; //2^-2 + 2^-5 + 2^-6......,二进制为0.010011...

由于浮点型的值可能存在误差,所以我们判断两个浮点型是否相等的话直接使用==是不准确的,如果两个double之差的绝对值小于DBL_EPSILON的话就可以认为是相等的:

    float f = 0.7F; // 0.699999988
    double d = f;
    double _d = 0.7; // 0.69999999999999996

    if (d == _d)
    {
        int a = 0; //不会进入
    }
    if (abs(d - _d) < FLT_EPSILON)
    {
        int a = 0; //会进入
    }
    if (abs(d - _d) < DBL_EPSILON)
    {
        int a = 0; //不会进入
    }

    double d1 = 0.1; //0.10000000000000001
    double d2 = 0.1;
    double d3 = 0.1;
    double dSum = d1 + d2 + d3; //0.30000000000000004
    double ddd = 0.3; //0.29999999999999999
    if (dSum == ddd)
    {
        int a = 0; //不会进入
    }
    if (abs(ddd - 0.3) < DBL_EPSILON)
    {
        int a = 0; //会进入
    }

  虽然我们可以使用FLT_EPSILON或DBL_EPSILON来判断两个浮点数是否相等,但是浮点数的误差还会引起多个浮点数相加的结果与预期不符问题。比如下面的float类型的f,有效位数为7-8位,所以其实际值为0.000234559993,这里就有了误差,而下面的123将其相加后理论上sum实际值应该为123.00023455993,但因为小数点前面的整数123又占用了三个有效位数,所以小数位数只剩下了四位有效数字,所以sum的实际值为123.000237,与我们期许的sum的准确值123.00023456存在0.00000244的误差:

    float f = 0.00023456; 
    float sum = 123.0F + f;

 而如果相加的浮点数不包含整数,全部是小数的话也会因为浮点数特殊的存储方式或者精度问题而产生误差:

    double d = 0.3; // 0.29999999999999999
    double sum = 0.0;
    for (int i = 0; i < 10; i++)
    {
        sum += d; // 2.9999999999999996
    }

    if (abs(sum - 3.0) < DBL_EPSILON)
    {
        int a = 0;
    }
    else
    {
        int a = 0; //会进入这里
    }

 对于上面的问题,可以将浮点型转换为整型使用来解决这个问题,如下所示,比如我们业务上仅会使用两位小数的话,那么可以将该浮点数乘100后使用整形保存。Java的话可以使用BigDecimal类型解决这个问题:

    double d = 0.03; //0.029999999999999999
    long long l = d * 100; //3
    
    long long sum = 0.0;
    for (int i = 0; i < 10; i++)
    {
        sum += l;
    }
    
    double _d = sum / 100.0; //0.29999999999999999
    if (abs(_d - 0.3) < DBL_EPSILON)
    {
        int a = 0; //进入这里
    }
    else
    {
        int a = 0;
    }

  上面的方法适用于业务上对于小数位数使用确定的情况,比如对于股价来说其最小单位为分,而对于小数位数无限制的业务情况,会出现问题,如下所示:

int main()
{
    double d = 0.009;
    long long l = d * 100;

    long long sum = 0.0;
    for (int i = 0; i < 10; i++)
    {
        sum += l;
    }

    double _d = sum / 100.0;
    if (abs(_d - 0.0) < DBL_EPSILON)
    {
        int a = 0; //进入这里
    }
    else
    {
        int a = 0;
    }
}

 

posted on 2016-09-23 16:08  整鬼专家  阅读(1554)  评论(0编辑  收藏  举报