zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

C语言:数据类型与强制类型转换

2023-02-18 16:40:17 时间

补充知识

bit (位):最小存储单位,可以存储0或1,不能再分割;1bit 等于一个二进制位;01011110 = 表示一个8位(bit)的二进制数

byte (字节):1个字节等于8个二进制位 (bit),可以表达$2^8$种组合。

二进制:0 | 1

16进制:0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A| B | C | D | E | F

 

1个16进制数 = 4位二进制位

2个16进制数= 一个字节(8位) 

在线进制转换工具:链接

进制数的前缀

  • 二进制:0b
  • 八进制:0;输出格式化%o,显示进制数的前缀%#o
  • 十进制:无任何前缀;输出格式化%d,显示进制数的前缀%#d
  • 十六进制:0x或0X;输出格式化%x,显示进制数的前缀%#x 或 %#X

基本类型

  • 整型
  • 字符型
  • 浮点型
    • 单精度浮点型
    • 双精度浮点型
  • 枚举类型

构造类型

  • 数组类型 []
  • 结构体类型 struct
  • 共用体类型 union

指针类型 *

空类型


基本数据类型

数据类型占用的字节大小和编译器的位数有很大关系。见:链接。下面以64位编译器为例

  类型 字节 值范围 格式化输出
字符型 char 1 C字符集,-128 到 127 %c
整型 int 4 -2,147,483,648 到 2,147,483,647 %d
无符号整型 unsigned int 4 0 到 4,294,967,296 %u
短整型 short 2 -32,768 到 32,767 %h
长整型 long 4 -2,147,483,648 到 2,147,483,647 %l
长长整型 long long 8 0~18,446,744,073,709,551,616 %ll
无符号长整型 unsigned long 4 0 到 4,294,967,295 %ul
单精度型 float 4 $1.2e^{−38}$ 到 $3.4e^{38}$;6位小数 %f
双精度型 double 8 $2.3e^{−308}$ 到 $1.7e^{308}$;15位小数  

我们还可以再l(长整型)或d(整型)后面使用x(十六进制)或o(八进制),

  • %lx:以十六进制格式打印long类型整数
  • %lo:以八进制格式打印long类型整数

补充:我们可以用 sizeof 查看指定类型的大小。

整数溢出

在超过最大值时,unsigned int类型的变量从0开始;而int类型的变量则从−2147483648开始。注意,当超出其相应类型的最大值时,系统并未通知用户。因此,在编程时必须自己注意这类问题。

#include <stdio.h>

int main(void) {
    int i = 2147483647;
    unsigned int j = 4294967295;

    printf("%d %d %d\n", i, i + 1, i + 2);  // 2147483647 -2147483648 -2147483647
    printf("%u %u %u\n", j, j + 1, j + 2);  // 4294967295 0           1
}

字符型 (char)

char类型用于存储字符(如,字母或标点符号),但是从技术层面看,char是整数类型实际上存储的是整数而不是字符。参见 ASCII码对照表

注意:字符变量就是一个字符,并且只能由单引号  ‘ ’ 或者ASCII码中的数字定义。不能是双引号哈,双引号是字符串。

关于有符号还是无符号:有些C编译器把char实现为有符号类型,这意味着char可表示的范围是-128~127。而有些C编译器把char实现为无符号类型,那么char可表示的范围是0~255,可以查阅limits.h头文件得知。或者关键字char前面使用 signed char 或  unsigned char 。

浮点型

  C语言中的浮点类型有float、double和long double类型。有符号的数字(包括小数点),或者后面紧跟e或E,表示10的指数。例如: 3.1415 、 .2 、 4e16 、 .8E-5 、 100. 。

小知识:默认情况下浮点型常量是double类型的精度

float i;    // float类型 变量声明
i = 4.0 * 2.0;

 4.0和2.0默认是double类型,但是i是float类型,这样计算的结果会被截断到float类型的宽度。这样做虽然计算精度更高,但是会减慢程序的运行速度。

i = 4.0f * 2.0f;

在浮点数后面加上fF后缀可覆盖默认设置,编译器会将浮点型常量看作float类型,如 2.3f 和 9.11E9F 。使用lL后缀使得数字成为long double类型,如 54.3l 和 4.32L 。注意,建议使用L后缀,因为字母l和数字1很容易混淆。

空类型(void)

通常用于以下三种情况

  • 函数返回空:函数不返回值,或者返回空,例如 void exit (int status);
  • 函数参数为空:函数不接受任何参数,不带参数的函数可以接受一个 void。例如 int rand(void);
  • 指针指向void:类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型
#include<stdio.h>
#include<cfloat>

int main() {
    printf("int占%d字节\n",sizeof(int));    // 4
    printf("unsigned int占%d字节\n", sizeof(unsigned int));    // 4
    printf("short int占%d字节\n", sizeof(short int));    //2
    printf("long int占%d字节\n", sizeof(long int));    // 4

    printf("float占%d字节\n", sizeof(float));    // 4
    printf("double占%d字节\n", sizeof(double));    // 8

    printf("浮点数的最大值:%E\n", FLT_MIN);    // 1.175494E-38
    printf("浮点数的最小值:%E\n", FLT_MAX);    // 3.402823E+38
}

可移植类型:stdint.h和inttypes.h

在不同的系统(32位机、64位机)中,相同的数据类型代表的存储大小不一样,因此C99中的头文件stdint.h,使用 typedef 创建了很多的类型名,以确保C语言的类型在各系统中的功能相同。

比如 uint8_t 、 int32_t 等。其中u代表 unsigned char ,_t 代表  typedef 。

  • uint8_t:无符号8位整型
  • int32_t:有符号32位的整型,就是int的别名

最小整数类型,例如:int_least8_t是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。

最大有符号整数类型,可存储任何有效的有符号整数值,uintmax_t表示最大的无符号整数类型。这些类型有可能比long long和unsigned long类型更大,

最快类型集合,这组类型集合被称为最快最小宽度类型,例如:int_fast8_t被定义为系统中对8位有符号值而言运算最快的整数类型的别名。

  所以uint8_t,uint16_t,uint32_t等都不是什么新的数据类型,它们只是使用typedef给类型起的别名,新瓶装老酒的把戏。不过,不要小看了typedef,它对于你代码的维护会有很好的作用。比如C中没有bool,于是在一个软件中,一些程序员使用int,一些程序员使用short,会比较混乱,最好就是用一个typedef来定义,如: typedef char bool; 

附:C99标准中stdint.h的内容

#ifndef __INTTYPES_H_
#define __INTTYPES_H_

/* Use [u]intN_t if you need exactly N bits  */
typedef signed char int8_t;
typedef unsigned char uint8_t;

typedef int int16_t;
typedef unsigned int uint16_t;

typedef long int32_t;
typedef unsigned long uint32_t;

typedef long long int64_t;
typedef unsigned long long uint64_t;

typedef int16_t intptr_t;
typedef uint16_t uintptr_t;

#endif
View Code

C语言中的正负数

在C语言中 short、int、long都是带有正负号的,C语言规定,把内存的最高位作为符号位,在符号位中,用 0 表示正数,用 1 表示负数

以 int 为例,它占用 32 位的内存,0~30 位表示数值,31 位表示正负号。如下图所示:

int a = 0b00000000000000000000000000000001;
int b = 0b10000000000000000000000000000001;
printf("a: %d\n",a);    // a: 1
printf("b: %d\n",b);    // b: -2147483647

如果不希望设置符号位,可以在数据类型前面加上 unsigned 关键字,这样,short、int、long 中就没有符号位了,所有的位都用来表示数值,正数的取值范围更大了。这也意味着,使用了 unsigned 后只能表示正数,不能再表示负数了。

枚举类型 enum

被用来定义在程序中只能赋予其一定的离散整数值的变量。

声明枚举类型

enum 枚举名 {枚举元素1,枚举元素2,……};

案例:一星期有 7 天,为每一天定义一个整数作为别名

常规方法,#define 预定义方式:

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

enum 枚举方式(更加简洁)

enum DAY {
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

  一般情况下第一个枚举成员的默认值为 0,后续枚举成员的值在前一个成员上加 1。在这个实例中我们把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

  当然我们也可以在定义枚举类型时改变枚举元素的值 enum season {spring, summer=3, autumn, winter}; 

定义枚举变量

我们可以通过以下三种方式来定义枚举变量

1、先声明枚举类型,再定义枚举变量

// 声明枚举类型
enum DAY{
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
// 定义枚举变量
enum DAY day;

2、定义枚举类型的同时定义枚举变量

enum DAY {
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

3、省略枚举名称,直接定义枚举变量

enum {
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

实例

#include<stdio.h>

// 声明枚举类型
enum DAY {
    MON=1,TUE,WED,THU,FRI,SAT,SUM
};
int main() {
    enum DAY day;    // 定义枚举变量
    day = WED;        // 将WED复制给day
    printf("结果%d", WED);    // 3
    return 0;
}

如果枚举类型是连续的则可以实现有条件的遍历。不连续的枚举类型无法遍历。

#include<stdio.h>

// 声明枚举类型,定义枚举变量
enum DAY {
    MON=1,TUE,WED,THU,FRI,SAT,SUM
} day;
int main() {
    for (day = MON; day < SUM; day++) {
        printf("枚举类型:%d\n", day);
    }

    return 0;
}
//枚举类型:1
//枚举类型:2
//枚举类型:3
//枚举类型:4
//枚举类型:5
//枚举类型:6
结果

枚举在 switch 中的使用:

#include <stdio.h>

int main()
{
    enum color { red = 1, green, blue };  // 枚举类型声明
    enum  color favorite_color;     // 定义枚举变量

    /* 用户输入数字来选择颜色 */
    printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
    scanf_s("%u", &favorite_color);

    /* 输出结果 */
    switch (favorite_color)
    {
    case red:
        printf("你喜欢的颜色是红色");
        break;
    case green:
        printf("你喜欢的颜色是绿色");
        break;
    case blue:
        printf("你喜欢的颜色是蓝色");
        break;
    default:
        printf("你没有选择你喜欢的颜色");
    }

    return 0;
}
View Code
请输入你喜欢的颜色: (1. red, 2. green, 3. blue): 1
你喜欢的颜色是红色
结果

类型转换

强制类型转换(显式)是把变量从一种类型转换为另一种数据类型。

(类型名) 变量名

案例:

#include <stdio.h>

int main() {
    int sum = 17, count = 5;
    double mean;

    mean = (double)sum / count;        // 将sum强制类型转换为 double
    printf("Value of mean : %f\n", mean);
}

强制类型转换运算符的优先级大于除法,因此 sum 的值首先被转换为 double 型,然后除以 count,得到一个类型为 double 的值。

类型转换可以是隐式的,由编译器自动执行,也可以是显式的,通过使用强制类型转换运算符来指定。

整数提升

整数提升是指把小于 int 或 unsigned int 的整数类型转换为 int 或 unsigned int 的过程。请看下面的实例,在 int 中添加一个字符:

#include <stdio.h>

int main(){
    int  i = 17;
    char c = 'c'; /* ascii 值是 99 */
    int sum;

    sum = i + c;
    printf("Value of sum : %d\n", sum);    //116

}

在这里,sum 的值为 116,因为编译器进行了整数提升,在执行实际加法运算时,把 'c' 的值转换为对应的 ascii 值。

常用的算术转换

常用的算术转换 是隐式地把值强制转换为相同的类型。编译器首先执行整数提升,如果操作数类型不同,则它们会被转换为下列层次中出现的最高层次的类型:

优先级:long double --> double --> float --> unsigned long long --> long long --> unsigned long --> long --> unsigned int --> int

#include <stdio.h>

int main(){
    int  i = 17;
    char c = 'c'; /* ascii 值是 99 */
    float sum;

    sum = i + c;
    printf("Value of sum : %f\n", sum);    // 116.000000

}

C 首先被转换为整数,但是由于最后的值是 float 型的,所以会应用常用的算术转换,编译器会把 i 和 c 转换为浮点型,并把它们相加得到一个浮点数。

整数转换枚举

#include<stdio.h>

int main() {
    // 枚举类型的声明和定义
    enum day
    {
        saturday,
        sunday,
        monday,
        tuesday,
        wednesday,
        thursday,
        friday
    } weekend;
    int a = 1;  //变量的声明和初始化
    weekend = (enum day) a;  // 类型转换
    //weekend = a; //错误
    printf("weekend:%d", weekend);      //1

    return 0;
}

 

参考

菜鸟教程