C/C++内置类型,数值常量和隐式转换


写这篇,一是想巩固下基础知识;二是发觉网上很多写的也是错漏较多,模糊了去查了看需要很多时间去分辨对错;故自己总结下,自觉应该不会出错吧(错了欢迎指出,我再纠正。)- -|||

一、C/C++内置类型长度

我们知道C++内置类型有bool, char, short, int, long, long long, float, double, long double, wchar_t这些。每种类型的长度都不一样,但是C/C++标准并没有规定每个类型必须要占多少字节,而是规定了长度的最小值等一些约束。所以实际上各个类型的变量长度是由编译器来决定的

那么编译器是根据什么来决定类型的长度呢?操作系统位数orCPU位数?

我们知道这样一个关系:

  • 32位编译器编译出来的是32位应用程序;64位编译器编译出来的是64位应用程序。

而这里编译器是32位or64位指的是利用该编译器生成的应用程序是32位or64位;与该编译器应用程序自身是32和64位的没有关系,也就是与操作系统是32位or64位没有绝对关系,同样与CPU位数也没有绝对关系

比如在64位系统上的32位编译器,其应用本身可以是32位的也可以是64位的;同样的,理论上在32位系统上一样可以做出64位的编译器, 其应用本身是32位的,但是可以生成64位程序(只是32位的系统不能运行64位的应用程序)。

事实上,编译器依据一种数据模型来决定其类型的长度。这些数据模型有:ILP32, ILP32LL, LP64, LLP64, ILP64等。其中I,L,P分别指int,long, pointer (如ILP32LL与ILP32LL64等同,意为int,long,pointer为32位,longlong为64位)。其中各种模型中类型的长度区别为:

类型 ILP32 ILP32LL LLP64 LP64 ILP64
char 8 8 8 8 8
short 16 16 16 16 16
float 32 32 32 32 32
int 32 32 32 32 64
long 32 32 32 64 64
pointer 32 32 64 64 64
long long 64 64 64 64
double 64 64 64 64 64

现如今,32位的windows程序的数据模型为ILP32LL;64位的Windows程序数据模型是LLP64;绝大部分Unix,linux编译器和应用程序都是使用的LP64模型。

二、数值常量的类型

2.1 数值常量的表示方法

C/C++的数字常量有这样几种表示:18, -23, 3., -5.4, 2E-2, 07, -036, 0x3a, -0xFF

其中18, -23, 3., -5.4, 2.9E-210进制表示, 其中2.9E-2为科学计数法(10进制指数表示);07, -0368进制表示0x3a, -0xFF16进制表示

其中18, -23, 07, -036, 0x3a, -0xFF皆为整数型常量3., -5.4, 2E-2皆为浮点数型常量(小数型常量)。

从中我们可以总结出:

  • 以0开头的为8进制常量,0后只能以数字0~7表示;
  • 以0x开头的为16进制常量,其后只能以数字和字母0~F表示;
  • 其他为10进制常量,整形不能以0和0x开头,只能以0~9和小数点表示;
  • 8进制和16进制不能用来表示浮点数;
  • 科学计数法表示的一定是浮点数

2.2 数值常量的类型和后缀

C/C++数值常量后可以跟随一个后缀来给编译器指示类型;如果不加后缀,编译器会根据数值的范围指定合适的类型。

编译器自动选择:

  • 如果是浮点型常量,则指定double类型;
  • 如果是整数型常量,如果在int表示的范围内,则指定为int;如果超出int,对于正数,按照int->unsigned int->long->unsigned long->long long->unsigned long long的方向指定合适的类型;对于负数,按照int->long->long long的方向指定合适的类型。

指定后缀:

  • 整数型后缀有U,L或其组合(UL, LL,ULL或LLU);其中U,L不区分大小写,分别表示unsigned 和long;如ull和llu都表示unsigned long long。

  • 小数型后缀有F, L。分别表示float,long double。即单精度浮点数和长双精度浮点数。

  • 整数后接f,或者小数后接U都会报错。

三、隐式转换

3.1 隐式转换场合

C/C++在以下四种情况下会进行隐式转换:

  1. 算术运算式中,低类型能够转换为高类型。
  2. 赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
  3. 函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
  4. 函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

3.2 算数运算的隐式转换

算数运算中,首先有如下类型转换规则:

  1. char和short先转换为int。
  2. float型数据在运算时一律转换为双精度(double)型,以提高运算精度(同属于实型) 。
  3. 当不同类型的数据进行操作时,应当首先将其转换成相同的类型,然后进行操作,转换规则是由低级向高级转换。转换规则如下图所示:
    隐式转换
  4. 算数表达式的值类型与参与运算的值类型相同。

四则运算中,特别要注意unsigned运算。实际编程中最好避免unsigned值参与运算。

举例:

1
2
3
4
5
6
7
8
//signed与unsigned int运算时,先都转换为unsigned
unsigned int a=20;
signed int b=-130;
cout << a>b?"yes":"no" << endl; //输出no
cout << a+b << endl; //输出4294967186

cout << (1 << 31) << endl; //输出-2147483648
cout << (1L << 31) << endl; //输出2147483648

四、64位编程应注意事项

4.1 格式化字符串:long使用%ld,指针使用%p

32位下,打印地址可以用%x,但在64位下只能打印低4位;所以64位应用%p。

1
2
3
char *ptr = &something;
printf (%x\n", ptr); //32位OK,64位错误
printf (%p\n", ptr); //64位

4.2 64位下的对齐

64位下,因为long和指针的长度可能发生了变化,所以32位下对齐良好的的结构可能变得不太好,导致32位程序在64位系统上运行性能下降。如:

1
2
3
4
5
6
struct A {
int i;
long l;
int j;
char* p;
}

ILP32下,sizeof(A)=16; 但是在LP64下,sizeof(A)=32。