从头学C(18)类型转换

0

10厘米和1米,谁比较长?显然,你需要将它们转换为相同的长度单位,才能进行比较。

C语言中存在着不同类型的操作数,它们之间如果要做运算,也需要通过一些规则先把它们转换为某一种相同的类型。

第二章 类型、运算符与表达式 >> 2.7 类型转换

这种一个运算符包含不同操作数类型的情况在实际编程过程中,是非常常见的。那是不是我们不主动对变量进行类型转换,是不是编译器就会报错呢?当然不是,任何C编译器都不会笨到这种地步……

这多亏了“自动转换”的功能,它可以自动把“比较窄”的操作数转换为“比较宽”的操作数,并且不丢失信息。例如在计算float变量+int变量时,int变量的值会自动转换为float类型。(因为float比较“宽”嘛,并且float类型可以完整保留这个int变量的全部信息~~)

对于非法的表达式,编译器会报错。例如不允许把float类型的表达式作为数组的下标。

对于可能导致信息丢失的表达式,编译器可能会给出警告,但这些表达式并不是非法的。例如把float类型赋给int型,至少会丢失小数部分的信息。

char类型是比较小的整型,用它来实现一些字符转换会非常方便。来看下面这个atoi的例子。

#include <stdio.h>
/* atoi: 将字符串s转换为相应的整型数 */
int atoi(char s[])
{
        int i, n = 0;

        for(i = 0; s[i]<= '9' && s[i]>= '0'; ++i)
        {
                n = 10 * n + ( s[i]- '0');
        }
        return n;
}

int main()
{
        char *a;
        int i;

        printf("Please input a string with Number include(Such as 43a1): ");
        scanf("%s", a);
        printf("a = %s\n", a);

        i = atoi(a);
        printf("i = %d\n", i);
}

表达式 s[i]– ‘0’可以计算出s[i]中存储的字符所对应的的数字值,这是因为字符’0’、’1’等在字符集中是一个连续的递增序列。

再看下面这个lower函数的例子。它将ASCII字符集中的字符映射到对应的小写字母。

#include <stdio.h>
/* lower: 把字符c转换为小写形式;只对ASCII字符集有效 */
int lower(int c)
{
        if(c >= 'A' && c <= 'Z')
                return c + 'a' - 'A';
        else
                return c;
}

int main()
{
        char *s;
        int i;

        printf("Please input a string with capital letters inside(Such as 12AaC1): ");
        scanf("%s", s);
        printf("ori = %s\n", s);

        printf("des = ");
        for(i = 0; *(s+i) != '\0'; ++i)
                printf("%c", lower(*(s+i)));
        printf("\n");
        return 0;
}

在ASCII字符集中,大写字母和对应的小写字母作为数字值来说,具有固定的间隔,并且字母表也是连续的,因而可以直接使用类似c+’a’-‘A’的表达式。

在标准头文件<ctype.h>中,定义了一组与字符集无关的测试和转换函数,后面我们也会用到。例如:

  • tolower(c)函数可以将字符c转换为小写形式
  • isdigit(c)函数用于判断字符c是否是一个数字(类似表达式 c >= ‘0’ && c <= ‘9’)

在将字符类型(char)转换为整型(int)时,需要注意的是:由于C语言没有指定char类型的变量是有符号(signed)还是无符号(unsigned)变量,因此如果char类型值的最左一位是1,那么在某些机器中会转换成负整数(进行了“符号扩展”),而在某些机器中则总是转换为正整数(左侧补0)。

C语言的定义保证了机器的标准打印字符集中的字符不会是负值,因此,在表达式中诸如‘A’、‘a’、‘1’……等等字符总是正值。

为了保证程序的可移植性,如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。

C语言中,很多情况下会进行隐式的算术类型转换。通常如果一个二元运算符的两个操作数具有不同的类型,那么在进行运算之前会先把“较窄”的类型提升为“较宽”的类型,运算结果为较宽的类型(只要记住:程序不应丢失数据的所有信息)。

如果一个运算中没有unsigned类型的操作数,则只需参考以下5条标准即可:

  1. 如果其中一个操作数为long double,则将另一个操作数转换为该类型;
  2. 如果其中一个操作数为double,则将另一个操作数转换为该类型;
  3. 如果其中一个操作数为float,则将另一个操作数转换为该类型;
  4. char和short类型都将被转换为int类型;
  5. 如果其中一个操作数为long,则将另一个操作数转换为该类型;

要注意的是,虽然一般的数学函数(如标准头文件<math.h>中定义的函数)都使用双精度类型的变量,但float类型一来可以在使用较大数组时节省存储空间,二来也可以节省机器执行时间(双精度算术特别费时),所以float类型是不会自动转换为double类型的。

在赋值运算中,也要进行类型转换(将赋值运算符右边的值转换为左边变量的类型,而左边变量的类型便是赋值表达式结果的类型):

  1. 无论是否进行符号扩展,字符型变量都将被转换为整型变量;
  2. float类型转换为int类型时,小数部分将被截取掉;
  3. double类型转换为float类型时,是进行四舍五入还是截取则取决于具体的实现;

另外,在把参数传递给函数时,也可能进行类型转换。

在没有函数原型的情况下:

  • char和short类型都将被转换为int类型
  • float类型将被转换为double类型

而通常情况下,参数是通过函数原型来声明的。这样,当函数被调用时,声明将对参数进行自动强制转换。例如对于sqrt的函数原型

double sqrt(double);

当函数调用时temp = sqrt(2);会自动将整数2强制转换为double类型的值2.0。

关于强制类型转换

在任何表达式中,都可以使用一个称为强制类型转换的一元运算符强制进行显式的类型转换。在下面这条语句中,表达式将按照上述转换规则被转换为类型名所指定的类型:

(类型名)表达式

我们可以这样来理解这句话:表达式首先被赋值给了类型名所制定的类型的某个变量,然后再用该变量替换上面整条语句。

所以要注意,强制类型转换只是生成了一个指定类型的值,表达式本身的值并没有改变。另外,强制类型转换运算符和其他一元运算符具有相同的优先级。

标准库中包含一个可移植的实现伪随机数发生器的函数rand以及一个初始化中子数的函数srand。函数rand中就使用了强制类型转换。

#include <stdio.h>

unsigned long int next = 1;

/* rand: return pseudo-random integer on 0...32767 */
int rand(void)
{
        next = next * 1103515245 + 12345;
        return (unsigned int)(next/65536) % 32768;
}

/* srand: set seed for rand() */
void srand(unsigned int seed)
{
        next = seed;
}

int main()
{
        int i;

        srand(1000);
        for(i = 0; i < 10; ++i)
                printf("%d: random_value = %d\n", i, rand());

        return 0;
}

Leave A Reply