暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

C系列二 - 数据类型

1024星球 2020-11-27
922


C 语言的数据类型有很多,可以分为 4 大类型:

  • void 类型
  • 基本类型
    • 字符类型
    • 有符号整数类型
    • 无符号整数类型
    • 浮点类型
  • 枚举类型
  • 派生类型
    • 数组类型
    • 结构体类型
    • 联合体类型(也叫共同体类型)
    • 函数类型
    • 指针类型

一、基本类型

  • 有符号整数类型

    • char
      (等价类型:signed char
    • short
      (等价类型:signed short
      short int
      signed short int
    • int
      (等价类型:signed int
      signed
    • long
      (等价类型:signed long
      long int
      signed long int
      )(C99 起)
    • long long
      (等价类型:signed long long
      long long int
      signed long long in
      t`)(C99 起)
  • 无符号整数类型

    • unsigned char
    • unsigned short(等价类型:unsigned short int
    • unsigned int(等价类型:unsigned
    • unsigned long(等价类型:unsigned long int
      )(C99 起)
    • unsigned long long(等价类型:unsigned long long int
      )(C99 起)
  • 字符类型

    • char
  • 浮点类型

    • float
    • double
    • long double

1.1. 整数类型

1.1.1. 整数类型大小

C 标准规定

1 == sizeof(char)
  <= sizeof(short)
  <= sizeof(int)
  <= sizeof(long)
  <= sizeof(long long)

复制

1.1.2. 数据模型

关于基本类型大小的是实现方案,统称为:数据模型。有 4 种常见的数据模型

  • 32bit 系统(指针为 32 位)

    • Win32 API
    • Unix、类 Unix 系统(Linux、Mac OS X)
    • Win16 API
    • LP32
    • ILP32
  • 64bit 系统(指针为 64 位)

    • Unix、类 Unix 系统(Linux、Mac OS X)
    • Win64 API
    • LLP64
    • LP64
  • 比较少见的数据模型

    • 仅出现在早期 64 位 Unix 系统(例如 Unicos on Cray)
    • ILP64:int、long、指针均为 64 位

1.1.3. 有符号整数类型和无符号整数类型区别

整数类型可分为:有符号整数类型、无符号整数类型。

1.1.4. 有符号整数 char、无符号整数 unsigned char

// 0xBD <=> 0b10111101
char c1 = 0xBD;
unsigned char c2 = 0xBD;
printf("%d %d", c1, c2); // 输出:-67 189

复制
// 0x43 <=> 0b01000011
char c1 = 0x43;
unsigned char c2 = 0x43;
printf("%d %d", c1, c2); // 输出:67 67

复制
  • c1、c2 变量在内存中存放的二进制数据是完全一样的;
  • 对于同一份二进制数据,分别以有符号数形式、无符号数形式解读出来的含义可能是不一样的。

1.1.5. 整数的取值范围

char
unsigned char
都只占用一个字节,能够存放的二进制数据范围都是[0b0000 0000, 0b1111 1111]

  • char
    的取值范围:
  • unsigned char
    的取值范围:
  • 有 n 个二进制位的有符号数的取值范围
  • 有 n 个二进制位的无符号数的取值范围
位数符号最小值最大值
8有符号

8无符号0
16有符号

16无符号0
32有符号

32无符号0
64有符号

64无符号0

1.1.6. 溢出(Overflow)

溢出指的是内容超过了已知容器的容量。

例:short s;

  • short
    类型的变量 s 有 2 个字节的内存空间
  • 如果要将超过 2 个字节(比如 4 个字节)的数据存储到变量 s 中去,就会产生内存溢出

案例一:

int i = 16909292;
short s = 16909292;
char c = i;
printf("%d\n", i); // 输出:16909292
printf("%d\n", s); // 输出:1004
printf("%d\n", c); // 输出:-20

复制
  • 当出现溢出时,会优先保留低字节的数据,舍弃高字节的数据;
  • 所以在给取值范围小的变量赋值时要注意防止数据溢出,否则结果可能会跟预期不符合。

思考:那是不是以后都统一使用取值范围比较大的变量就好了?应该根据已知的数据范围选择合适大小的变量,不然会造成内存空间的浪费

案例二:

int i = 16909292;
char c = i;
i = c;
printf("%d\n", i); // 输出:-20
printf("%d\n", c); // 输出:-20

复制

案例三:

unsigned char c1 = 255;
char c2 = -1;

复制
c1 = c1 + 1;
c2 = c2 + 1;
printf("c1=%d, c2=%d", c1, c2); // 输出:c1=0, c2=0

复制

案例四:

unsigned char c1 = 0;
char c2 = 0;

复制
c1 = c1 - 1;
c2 = c2 - 1;
printf("c1=%d, c2=%d", c1, c2); // 输出:c1=255, c2=-1

复制

案例五:

unsigned char c1 = 127;
char c2 = 127;

复制
c1 = c1 + 1;
c2 = c2 + 1;
printf("c1=%d, c2=%d", c1, c2); // 输出:c1=128, c2=-128

复制

案例六:

unsigned char c1 = 128;
char c2 = -128;

复制
c1 = c1 - 1;
c2 = c2 - 1;
printf("c1=%d, c2=%d", c1, c2); // 输出:c1=127, c2=127

复制

案例七:

unsigned char c1 = 255;
char c2 = -1;

复制
c1 = c1 + 1;
c2 = c2 + 1;
printf("c1=%d, c2=%d", c1, c2); // 输出:c1=0, c2=0

复制

案例八:

unsigned char c1 = 0;
char c2 = 0;

复制
c1 = c1 - 1;
c2 = c2 - 1;
printf("c1=%d, c2=%d", c1, c2); // 输出:c1=255, c2=-1

复制

1.1.7. unsigned char、char 的取值范围

1.2. 浮点类型

浮点类型可以用来表示小数(比如 3.14),包括float
,double
,long double
类型

最常用的浮点类型是float
double
,一般在数值后面加上 f 或者 F 表示十 float 类型的数值

float f = 3.14F;
double d = 10.24;
printf("%f %f", f, d); // 输出:3.140000 10.240000


复制
  • float
    :单精度(Single)浮点类型,占用 32bit,可以保证精确到小数点后 6 位
    • 最小值:
    • 最大值:
  • double
    :双精度(Double)浮点类型,占用 64bit,可以保证精确到小数点后 15 位
    • 最小值:
    • 最大值:

1.2.1. 浮点类型的存储细节

思考:为什么 32bit 的unsigned int
最大值是 2^32-1,而 32bit 的float
最大值是 3.4 * 10^38?因为他们的存储方式不一样,浮点数在计算机中是按照 IEEE 754 标准存储的。

1.3. 整数、浮点数字面量

char c = 10;
unsigned char uc = 10;
short s = 10;
unsigned short us = 10;

int i = 10;
unsigned int ui1 = 10u;
unsigned int ui2 = 10U;

long l1 = 10l;
long l2 = 10L;
unsigned long ul1 = 10ul;
unsigned long ul2 = 10UL;

// 不能是lL或Ll
long long ll1 = 10ll;
long long ll2 = 10ll;

float f1 = 10.24F;
double d1 = 10.24;

// 小数部分可选:1.0
float f2 = 1.F;
double d2 = 1.;

// 整数部分可选:0.1
float f3 = .1F;
double d3 = .1;

// 十进制小数 1.2 * 10^3 = 1200.0
float f4 = 1.2e3F; // 1.2E3F
double d4 = 1.2e3; // 1.2E3

// 十六进制小数 1.125 * 2^10 = 1152.0
float f5 = 0x1.2p10F; // 0x1.2P10F
double d5 = 0x1.2p10; // 0x1.2P10

// 十进制 1.875 * 2^3 = 15.0
printf("%f", 0x1.ep+3); // 可写:0x1.EP+3 输出:15.000000

// 十进制 1.875 * 2^(-2) = 0.46875
printf("%f", 0x1.ep-2); // 可写:0x1.EP-2 输出:0.468750

int v 0xE+2; // 错误
int x = 0xe+2; // 错误
int y = 0xA+2; // OK
int z = 0xE +2; // OK
int q = (0xE)+2 // OK

复制

1.4. printf 中的转换格式指定符

1.4.1. 案例一

printf("%zd\n", sizeof(int)); // 输出:4

long long age = 10LL;
printf("%lld\n", age); // 输出:10

unsigned long no = 8UL;
printf("%lu\n", no); // 输出:8

char *name = "idbeny";
char *place = "1024星球";
printf("我是%s,欢迎来到%s\n", name, place); // 输出:我是idbeny,欢迎来到1024星球

复制

1.4.2. 案例二

// 用%%来显示一个%
printf("%d%%%d\n", 10, 20); // 输出:10%20

int i1 = 10;
// %6d表示占用6个字符位置,默认靠右对齐(左边空白)
printf("welcome%6d24星球\n", i1); // 输出:welcome    1024星球
// %-6d表示占用6个字符位置,靠左对齐(右边空白)
printf("welcome%-6d24星球\n", i1); // 输出:welcome10    24星球

int i2 = -6;
// %+6d:加号(+)表示显示正负号
printf("%+6d和%+-6d\n", i1, i2); // 输出:   +10和-6

double d = 3.1415926;
// 四舍五入保留2位小数
printf("%.2f\n", d); // 输出:3.14

/*
 * 占用10个字符位置
 * 四舍五入保留4位小数
 * 显示正负号
 * 靠左对齐
 */
printf("1%+-10.4f2\n", d); // 输出:1+3.1416          2

复制

1.5. scanf 中的转换格式指定符

// 用%%来匹配一个%
int age = 10;
scanf("%%%d", &age); // 输入:%30
printf("age is %d\n", age); // 输出:age is 30
/*
  输入:30
  输出:age is 10

  输入:%30
  输出:age is 30
  */

// 在scanf中,float类型用%f
float f;
scanf("%f", &f);

// 在scanf中,double类型用%lf
double d;
scanf("%lf", &d);

// 在printf中,float、double都可以用%f、%lf
printf("%f %lf %f %lf", f, f, d, d);
/*
  输入:10(回车)20
  输出:10.000000 10.000000 20.000000 20.000000
  */

int i1 = 1;
int i2 = 2;
// %*3d是跳过长度为3的整数
scanf("%2d%*3d%4d", &i1, &i2);
printf("i1=%d,i2=%d", i1, i2);
/*
  输入:10   20   40
  输出:i1=10,i2=40

  输入:10111140
  输出:i1=10,i2=140
  */

复制

1.6. 类型转换

格式:

类型1 v1 = xx;
类型2 v2 = (类型1)v1;

复制

案例:

char c = 'A';
int i = (int)c;
printf("%d\n", i); // 输出:65

int i2 = 200;
short s = (short)i2;
printf("%d\n", s); // 输出:200

// 会丢失精度
double d = 3.14;
int i3 = (int)d;
printf("%d\n", i3); // 输出:3

int i4 = 333;
double d2 = (double)i4;
printf("%f\n", d2); // 输出:333.000000

复制

其实在很多时候,编译器都会进行隐式类型转换:

char c = 'A';
int i = c;
printf("%d\n", i); // 输出:65

int i2 = 200L;
short s = i2;
printf("%d\n", s); // 输出:200

double d = 3.14;
int i3 = d;
printf("%d\n", i3); // 输出:3

int i4 = 333;
double d2 = i4;
printf("%f\n", d2); // 输出:333.000000

复制

大类型转小类型的时候,可能会丢失精度:

double d = 3.14;
printf("%f\n", d); // 输出:3.140000

int i = d;
printf("%d\n", i); // 输出:3

double d2 = 1.1234567890123456789123;
printf("%.20f\n", d2); // 输出:1.12345678901234569125

float f = d2;
printf("%.20f\n", f); // 输出:1.12345683574676513672

复制



文章转载自1024星球,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论