C核心技术手册(二十八) 4.1.5.3 有符号整型转换 整型类型转换中超过目标类型取值范围的问题,不仅存在在无符类型转换中,也存在有符号类型转换中,例如,当一个值由类型long或unsigned int转换为int,结果为有符号整型并溢出,这与转换为无符号整数不同,后者将会丢弃执行结果。 绝大多数编译会丢弃原来值二进制表示的高位,并将最低位解释为新类型,如下例所示,在这种策略下,一个unsigned int类型现有的位模式被解释为一个有符号int值。 #include // Defines macros such as UINT_MAX int i = UINT_MAX; //Result: i = -1 (in two's complement //representation) 然而,由于编译器不同,这样的一个转换有的编译器会抛出一个信号提示值溢出。当一个实数和一个复数转换为有符号整型时,与转换为无符号整型具有同样的规则,这与前面一节描述的一样。 4.1.5.4 浮点类型实数转换 不是所有的整型值可以被浮点型表示,例如,虽然float类型的取值范围包含了long和long long的取值范围,float仅精确到小数点后6位,因而,一起long类型的值不能精确地存储在float对象中,这种转换的结果是下一个或更高的二进制值,如下例所示: long l_var = 123456789L; float f_var = l_var; //Implicitly converts long value to float. printf("The rounding error (f_var - l_var) is %f/n", f_var -l_var); 记住此例中的减操作,像所有浮点型一样,结果至少具有double类型的精度,此代码段输出如下: The rounding error (f_var - l_var;) is3.000000 任意一个浮点类型可以使用精度比它大的另一个浮点类型来表示,因此,当一个double值转换为long double,或者当一个float值转换为double或long double,值会精确地保持。当从一个高精度转换为一个低精度类型时,然而,其值可能会超出新类型的范围,如果超出,转换的结果是未定义的;如果其值在目标类型的范围内,但目标类型的精度不能精确地表示它,此时,结果为下一更小的或下一个更大的二进制值,Example 2-2中的程序描述了转换为一个低精度浮点类型时产生的误差错误。 当一个复数转换为一个实数时,虚数部分被简单地丢弃掉,结果为复数据的实数部分,它之后可能会转换为本章中描述的目标类型。 4.1.5.5 浮点型复数转换 当一个整型或浮点型实数转换为一个复数类型时,结果值的实数部分按照前面的规则转换为浮点型实数,虚数部分为0。 当一个复数转换为另一个不同的复数类型时,实数和虚数数分单独根据浮点型实数的规则进行转换。 #include // Defines macros such as the imaginary // constant I double _Complex dz = 2; float _Complex fz = dz + I; 在两行初始化语句中,整型常量2隐式地转换为double_Complex并赋值给dz, dz的结果为2.0 + 0.0 * I 。 在fz的初始化中,dz的double_Complex值两部分都转换为float,fz的实数部分等于2.0F, 虚数部分为1.0F。 C核心技术手册(二十九) 4.2 非算术类型转换 指针和数组名,还有函数名也遵循隐式和显式类型转换,结构体和联合体不能转换,虽然它们的指导可以转换为其他的指针类型。 4.2.1数组和函数操作指示符 一个数组或函数操作指示符为具有类型的任何表达式,在大多数情况下,编译器隐式地转换一个数组的类型,及数组的名子转为数组首元素的指针,数组表达式在以下情景下不能转换为指针: l 当使用sizeof操作符操作数组时; l 当使用&操作符时; l 当使用字符串变量初始化char或wchar_t数组时; 下面的例子示范了隐式转换数组操作符为指针,使用%p打印指针值: #include int *iPtr = 0; // A pointer to int, initializedwith 0. int iArray[ ] = { 0, 10, 20 }; // An array of int, initialized. int array_length = sizeof(iArray) / sizeof(int); // The number ofelements: // in this case, 3. printf("The array starts at the address %p./n", iArray); *iArray = 5; // Equivalent to iArray[0] = 5; iPtr = iArray + array_length - 1; // Point to the last element ofiArray: // Equivalent to // iPtr =&iArray[array_length-1]; printf("The last element of the array is%d./n", *iPtr); 在初始化array_length时,表达式sizeof(iArray)取得了数组的长度,而不是指针的大小,然而,在其他三个语句中,指示符iArray隐式地转换为一个指针,如下: l 在第一次调用printf()时; l 在使用操作符*时; l 给iPtr赋值时; 字符数组的名子在字符串操作中也当做指针使用,如下例: #include #include // Declares size_t strlen( const char *s ) char msg[80] = "I'm a string literal."; // Initialize an array of char. printf("The string is %d characterslong./n", strlen(msg)); // Answer: 21. printf("The array named msg is %d bytes long./n",sizeof(msg)); // Answer: 80. 本例中strlen(msg)调用中,标识符msg隐式地转换为指向数组首元素的指针,类型为函数的入参类型constchar *, strlen()仅仅统计字符开始到第一个null字符出现时所有的字符个数。 类似地,任何表示函数的表达式,如函数名,也可以隐式地转换为指向函数的指针,同样,当使用地址操作符&时,将不会使用这种转换,sizeof操作符不能使用在函数类型的操作中。下面的例子描述了函数名隐式转换为指针,程序初始化了指向函数的指针,函数的调用在一个循环中。 #include void func0( ) { puts("This is the function func0( ). ");} // Two functions. void func1( ) { puts("This is the function func1( ). "); } /* ... */ void (*funcTable[2])(void) = { func0, func1 }; // Array of two pointersto // functionsreturning void. for ( int i = 0; i < 2; ++i ) // Use the loop counter as the array index. funcTable( ); C核心技术手册(三十) 4.2.2 显式指针转换 将一个指针转换为另一个指针类型,必须使用一个显示转换,一些情景下,编译器会提供隐式的转换,这些描述在本章后面的部分,指针也可以显式地转换为整型,反之亦然。 4.2.2.1 对象指针 你可以显示地转换一个对象指针为另一个对象指针类型。在程序中,你必须确保这种转换有意义。例如: float f_var = 1.5F; long *l_ptr = (long *)&f_var; // Initialize a pointer to long with // theaddress of f_var. double *d_ptr = (double *)l_ptr; // Initialize a pointer to double with // thesame address. // On a system where sizeof(float) equals sizeof(long): printf( "The %d bytes that represent %f, in hexadecimal: 0x%lX/n", sizeof(f_var), f_var, *l_ptr ); // Using a converted pointer in an assignment can cause trouble: /* *d_ptr = 2.5; */ //Don't try this! f_var's location doesn't // have space for adouble value! *(float *)d_ptr = 2.5; // OK: stores a float value in that location. 如果转换后的对象指针没有对齐要求,使用指针的结果将没有定义,在另一个场景中,将指针的值再次转换为它原来的类型,会产生一个与原指针等价的指针。 如果将对象指针转换为字符类型(char, signed char 或 unsigned char), 结果将为此对象首字节的指针,首字节被认为是二进制的低地址,不管系统的字节顺序,下例使用此特性打印一个结构体变量的十六进制值。 #include struct Data { short id; double val; }; struct Data myData = { 0x123, 77.7 }; // Initialize a structure. unsigned char *cp = (unsigned char *)&myData; // Pointer to the first // byte of the structure. printf( "%p: ", cp ); // Print the starting // address. for ( int i = 0; i < sizeof(myData); ++i ) // Print each byte of the printf( "%02X ", *(cp + i) ); // structure, in hexadecimal. putchar( '/n' ); 本例输出如下的结果: 0xbffffd70: 23 01 00 00 00 00 00 00 CD CC CC CC CC 6C 53 40 结果的前两个字节为2301,表示此代码是在一个小端的系统上执行的,在myData结构中字节最低位地址为shoart类型的变量id。 4.2.2.2 函数指针 函数通常具有返回值类型,也包含参数类型,你可以将一个函数指针的类型转换为另一个函数指针类型,在下例中,typedef语句为一个具有double类型参数和double类型返回值的函数定义了一个类型名。 #include // Declares sqrt( ) and pow( ). typedef double (func_t)(double); // Define a type named func_t. func_t *pFunc = sqrt; // A pointer to func_t, initialized with // theaddress of sqrt( ). double y = pFunc( 2.0 ); // A correct function call by pointer. printf( "The square root of 2 is %f./n", y ); pFunc = (func_t *)pow; // Change the pointer's value to the //address of pow( ). /* y = pFunc( 2.0 ); */ // Don't try this: pow( ) takestwo //arguments. 在本例中,函数指针pFunc被赋值为不同类型函数的地址,然而,如果程序使用指针调用函数时,如果与原来的指针类型不匹配,程序的结果将不可预计。 C核心技术手册(三十一) 4.2.3 隐式指针转换 编译器隐式地转换确定的指针类型,分配、条件表达式使用操作符==和!=,函数调用使用三种隐式的指针转换,如下节所描述,三种隐式指针转换: l 一个指针对象类型可以隐式地转换为void指针,反之亦然; l 一个给定类型的指针可以隐式地转换为一个该类型更合格的版本; l 一个null指针常可以隐式地转换为任意指针类型。 4.2.3.1 指针转换为void Void指针为具有void *类型的指针,通常称为多用途指针,可以表示任意对象的地址,而不关心它的类型,例如,malloc()函数返回一个void指针,在你使用内存块前,void指针必须转换为指向一个对象的指针。 Example 4-1示范了void指针的多个用途,程序使用标准函数qsort() 对数组进行排序,此函数定义在头文件stlib.h中,其原型如下: void qsort( void *array, size_t n, size_t element_size, int (*compare)(const void *,const void *) ); qsort()以升序排列数组元素,以array地址开始,使用快速排序算法,假定函数具有n的元素,其大小为element_size。 第4个参数compare为qsort()调用的用做排序的函数指针,用来比较的两个元素的地址通过此函数指针的参数传入,通常,比较函数由程序定义,其返回值必须大于0,或小于0,或等于0,以表示第一个元素大于、或小于、或等于第二个元素。 Example 4-1. A comparison function forqsort( ) #include #define ARR_LEN 20 /* * Afunction to compare any two float elements, *for use as a call-back function by qsort( ). *Arguments are passed by pointer. * *Returns: -1 if the first is less than the second; * 0 if the elements are equal; * 1 if the first is greater than the second. */ int floatcmp( const void* p1, const void* p2 ) { float x = *(float *)p1, y = *(float *)p2; return (x < y) ? -1 : ((x == y) ? 0 : 1); } /* *The main( ) function sorts an array of float. */ int main( ) { /*Allocate space for the array dynamically: */ float *pNumbers = malloc( ARR_LEN * sizeof(float) ); /*... Handle errors, initialize array elements ... */ /*Sort the array: */ qsort( pNumbers, ARR_LEN, sizeof(float), floatcmp ); /*... Work with the sorted array ... */ return 0; } 在上面的例子中,malloc()函数返回一个void *,接着,在qsort()中对pNumbers赋值时被隐式地转换为float*,第一个参数隐式地由float*转换为void*,函数名floatcmp隐式地解释为函数指针,最终,当floatcmp()被qsort()调用时,它接收void*类型的参数,在初始化float变量前必须显式地转换为float*类型。 C核心技术手册(三十二) 4.2.3.2指针转换为限定的对象类型 在C中,限定类型有const, volatile和restrict,例如,编译器隐式地转换任意指针为int,需要使用const int指针,如果你要删除掉限定词,就需要使用显式类型转换,如下例所描述: int n = 77; const int *ciPtr = 0; // Apointer to const int. // The pointeritself is not constant! ciPtr = &n; //Implicitly converts the address to the type // const int *. n= *ciPtr + 3; // OK: this has thesame effect as n = n + 3; *ciPtr *= 2; // Error: youcan't change an object referenced by // a pointer to constint. *(int *)ciPtr *= 2; // OK:Explicitly converts the pointer into a // pointer to anonconstant int. 本例中倒数第二行语句描述了为什么指向const类型的指针有时叫做只读指针,尽管你可以修改指针的值,但不能修改它们所指的对象。 4.2.3.3 Null指针常量 Null指针常量为一个值为0的整型常量,或者是一个表示值0的void指针,NULL宏定义在头文件stdlib.h中,表示一个null指针常量,下例描述了使用NULL宏初始化指针。 #include long *lPtr = NULL; //Initialize to NULL: pointer is not ready for use. /* ... operations here may assign lPtr an object address ... */ if ( lPtr != NULL ) { /* ... use lPtr only if it has been changedfrom NULL ... */ } 当你将null常量指针转换为另一个指针类型,结果称为null指针,null指针的位模式不需要一定为0,然而,当你比较null指针和0,或NULL、或另一个null指针,结果通常为true,null指针与指向有效类型或函数的指针比较时,通常结果为false。 C核心技术手册(三十三) 4.2.4 指针和整型之前的转换 你可以显式地将指针转换为整数类型,反之亦然。此种转换的结果由编译器决定,且与编译器所运行的系统的地址结构有关。指针和整数类型的转换在系统编程中很有用,当程序访问物理地址(例如:ROM、I/O寄存器等)时也会使用。 当你将指针转换为一个取值范围没有指针所表示值大的整数类型时,结果是不可定义的,相反地,转换一个整数为指针类型没有必要必须产生一个有效指针,例如: float x = 1.5F, *fPtr = &x; // A float, and a pointer toit. unsigned int adr_val = (unsigned int)fPtr; // Save the pointer value // as an integer. /* * On an Intel x86 PC in DOS, the BIOS data block begins at the * address 0x0040:0000. * (Compile using DOS's "large" memory model.) */ unsigned short *biosPtr = (unsigned short *) 0x400000L; unsigned short com1_io = *biosPtr; // The first word contains the // I/O address of COM1. printf( "COM1 has the I/O base address %Xh./n", com1_io ); 最后三条语句从系统配置表中读取出硬件信息,假设操作环境允许程序访问内存区,在一个大内存模式下编译的DOS程序中,指针具有32位宽度,在高16位中包含一个段地址和一人低16位的偏移量(通常以segment:offset的形式表示),因而前的biosPtr指针可以使用一个long int型常量初始化 |