【新书推荐】3.2 数据类型的分类

发布时间:2024年01月24日

本节必须掌握的知识点:

??基本数据类型

?? 定义变量数据类型

?? 示例七

?? 代码分析

?? 汇编解析

?????? ■C语言包含的数据类型如下图所示:

????????????????????????????????????????????????图3-1C语言数据类型

3.2.1 基本数据类型

编译器定义的基本数据类型有整型、浮点型、字符型和枚举类型。常用的四种基本数据类型为char、int、float、double。

整型

int?类型:有符号整数。VS编译器为32位有符号整数,取值范围-231~231-1。

unsigned int类型:无符号整数,VS编译器为32位无符号整数,取值范围0~232-1。

short?(int) 类型:2个字节,16位有符号整数,可以简写为short。

unsigned short (int)类型:2个字节,16位无符号整数,可以简写为unsigned short。

long?(int) 类型:4个字节,32位有符号整数,可以简写为long。

unsigned long?(int) 类型:4个字节,32位无符号整数,可以简写为unsigned long。

long long(int) 类型:8个字节,64位有符号整数,可以简写为long long。

浮点型

单精度浮点型?float?:4个字节,32位浮点数,6位有效位。

双精度浮点型?double?:8个字节,64位浮点数,15位有效位。

长双精度浮点型long?double?:10个字节,80位浮点数,19位有效位。

????????????????????????????????????????????????????????????????图3-2 ASCII码表

字符型

char?类型:8位1个字节,取值范围0~255,VS编译器取值范围-128~127

unsigned char?类型:8位1个字节,取值范围0~255,对应的整数称为字符的ASCII码值。如图2-10所示。

singned char类型:8位1个字节,取值范围-128~127。

3.2.2 定义变量数据类型

定义变量的格式:类型 变量名

举例

int a;

定义一个整型变量a,定义变量时也可以赋值:int a = 1;
char ch=‘A’; //字符要加单引号。

或者先定义然后赋值:
char ch;
ch=65; //字符类型可以直接使用ASCII码值。

3.2.3 示例七

示例代码七

/*

?? 输入输出基本数据类型

*/

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

??? /********整型*********/

??? int y;???? //准备变量

??? printf("请输入一个整型:");

??? scanf_s("%d", &y);?

??? printf("用户输入的内容是%d\n", y);

??? /********字符型********/

??? char ch;??? //准备变量

??? getchar();//取出前一个scanf_s函数遗留在stdin的回车符

??? printf("请输入一个字符:");

??? scanf_s("%c", &ch,1);?

??? printf("用户输入的内容是%c\n", ch);

??? /********单精度浮点型*********/

??? float a;???? //准备变量

??? printf("请输入一个float型:");

??? scanf_s("%f", &a);?

??? printf("用户输入的内容是%f\n", a);

??? /********双精度浮点型*********/

??? double b;???? //准备变量

??? printf("请输入一个double型:");

??? scanf_s("%lf", &b);?

??? printf("用户输入的内容是%f\n", b);

??? system("pause");

??? return 0;

}

●输出结果:

请输入一个整型:1

用户输入的内容是1

请输入一个字符:a

用户输入的内容是a

请输入一个float型:1.2

用户输入的内容是1.200000

请输入一个double型:1.3

用户输入的内容是1.300000

请按任意键继续. . .?????

3.2.4 代码分析

■scanf_s函数的格式化说明符与printf函数稍有差异:

scanf_s函数中float浮点类型对应的格式化说明符为’%f’,double浮点类型对应的格式化说明符为’%lf’,long double类型对应的格式化说明符同样是’%lf’。

printf函数中float浮点类型对应的格式化说明符为’%f’,double浮点类型对应的格式化说明符同样是’%f’,long double类型对应的格式化说明符才是’%lf’。

?????? ■scanf_s函数遗留字符:

getchar();//取出前一个scanf_s函数遗留在stdin的回车符

printf("请输入一个字符:");

??? scanf_s("%c", &ch,1);

?????? 之所以在接收键盘输入一个字符前先调用getchar()函数,是因为前一个scanf_s函数接收键盘输入一个int类型整数值时按了回车键结束,因此stdin输入流中遗留了一个“回车符0ah”。如果不添加getchar()函数,获取到的字符将是回车符,程序出现错误。如果此处scanf_s函数接收的不是字符型数据,而是整型、浮点型或者其他类型数据,则会自动过滤掉遗留的回车符,不受遗留字符的影响。

■格式化说明符对应相应的数据类型
%d输入或者输出一个int类型数据;

%c输入或者输出一个char类型数据;

%f输入float类型数据,输出float类型或double类型数据;

%lf输入或者输出一个long double类型数据。

■输出符号

空格:输出正数的时候在前面补一个空格。

在用%o输出八进制的时候,在八进制前面补一个0。

在用%x输出16进制的时候,在16进制前面补一个0x。

■其他不常格式占用符

%hd输入或者输出一个short类型数据;?

%ld 输入或者输出一个long类型数据;

%lld 输入或者输出一个long long类型数据;

%x 输入或者输出一个16进制整型数据;

%o 输入或者输出一个8进制整型数据;?

%u 输入或者输出一个无符号整型数据;

%s 输入或者输出一个字符串类型数据;

%p 输入或者输出一个地址类型数据;

对于输入输出的格式和精度,我们将在第十二章详细讲述。

实验二十一:验证scanf_s函数执行后的遗留字符

第一步:屏蔽掉//getchar();//取出前一个scanf_s函数遗留在stdin的回车符。

第二步:在scanf_s("%c", &ch,1);这行下断点。

第三步:按F5执行调试。控制台窗口提示输入整型,键盘输入整数1,然后回车结束输入。接着提示输入一个字符。

请输入一个整型:1

用户输入的内容是1

请输入一个字符:

第四步:监视1窗口输入&ch,显示内容如下所示:当前ch变量地址处为无效字符。

五步:按快捷键F10单步执行,监视1窗口显示内容如下:

此时,会发现char型变量ch地址处存储的第一个字符为’\n’回车符,即输入整数1时遗留在stdin输入流中的回车符。

?结论

?????? 当调用scanf_s接收输入一个字符时,请务必先清空stdin标准输入流,否则会造成错误!

3.2.5 汇编解析

汇编代码

?????? ;FileName:3-2-1.asm

;例7:示例代码7-1 输入输出基本数据类型

;by:bcdaren

;2023.08.27

;===============================

;C标准库头文件和导入库

include vcIO.inc

.data?????? ;全局区

y???? sdword 0

chr ?byte????? ?????? ;notepad++中ch变量名与关键词有冲突

a???? real4?????? ?

b ?? real8?????? ??????

.const???? ;常量区

iszMsg1 db "请输入一个整型:",0

iszMsg2 db "%d",0

iszMsg3 db "用户输入的内容是%d",0dh,0ah,0

cszMsg1 db "请输入一个字符:",0

cszMsg2 db "%c",0

cszMsg3 db "用户输入的内容是%c",0dh,0ah,0

fszMsg1 db "请输入一个float型:",0

fszMsg2 db "%f",0

fszMsg3 db "用户输入的内容是%f",0dh,0ah,0

dszMsg1 db "请输入一个double型:",0

dszMsg2 db "%lf",0

dszMsg3 db "用户输入的内容是%f",0dh,0ah,0

.code????? ;代码区

start:

?????? ;输入一个整数

?????? push offset iszMsg1???? ;格式化常量字符串偏移地址入栈

?????? call printf????????????? ;调用printf函数输出结果

?????? lea esi,y? ;取变量y地址

?????? push esi

?????? push offset iszMsg2

?????? call scanf

?????? push y

?????? push offset iszMsg3

?????? call printf

?????? ;输入一个字符

?????? call getchar;取出输入流中遗留的回车符,不可以用_getch()函数

?????? push offset cszMsg1

?????? call printf

?????? lea esi,chr

?????? push esi

?????? push offset cszMsg2

?????? call scanf

?????? movsx eax,chr

?????? push eax

?????? push offset cszMsg3

?????? call printf

?????? ;输入一个单精度浮点数

?????? push offset fszMsg1

?????? call printf

?????? lea esi,a

?????? push esi

?????? push offset fszMsg2

?????? call scanf

?????? invoke printf,offset fszMsg3,a ;输出结果为错误

?????? ;输入一个双精度浮点数

?????? push offset dszMsg1

?????? call printf

?????? lea esi,b

?????? push esi

?????? push offset dszMsg2

?????? call scanf

?????? invoke printf,offset dszMsg3,b

?????? ;?????

?????? invoke _getch ?????? ;等待输入单个字符

?????? ret??????????????????????? ;结束返回

end start

?????? ●输出结果:

?????? 请输入一个整型:1

用户输入的内容是1

请输入一个字符:A

用户输入的内容是A

请输入一个float型:2

用户输入的内容是0.000000

请输入一个double型:3

用户输入的内容是3.000000

?????? 上述汇编代码需要注意两个问题:

  1. 取出标准输入流stdin中遗留的回车符使用getchar()函数,而不是_getch()函数。

_getch()函数会暂停控制台输出,直到按下一个按键为止,它不使用任何缓冲区来存储输入字符,输入字符后将立即返回,而无需等待回车键,输入的字符不会显示在控制台上。

getch()函数适用于接受隐藏的输入,例如密码、ATM账号等。

getchar()函数的函数原型:int getchar(void);

函数返回值是ASCII码,所以只要是ASCII码表里有的字符它都能读取出来。在调用getchar()函数时,编译器会依次读取用户键入缓存区的一个字符(注意这里只读取一个字符,如果缓存区有多个字符,那么将会读取上一次被读取字符的下一个字符),如果缓存区没有用户按键输入的字符,那么编译器会等待用户输入并回车后再执行下一步 (注意键入后的回车键也算一个字符,输出时直接换行)。

2.movsx eax,chr语句将chr8位扩展为32位,然后入栈。这是因为32位程序的堆栈空间是32位对齐的,X86指令不支持8位入栈。

3.输入一个float型数据2时,printf输出的值为0,显示这是错误的。在汇编语言中,数据类型real4为单精度浮点型,等同于C语言中的float类型,数据类型real8等同于C语言中的double类型。在printf函数中,float double都是%f输出,但 float 32位的,double?是64位的,所以在参数传递的时候C语言的编译器统一将 float 类型数值传换为 double 类型再传入printf 函数。问题肯定出现在数据类型转换的环节,接下来我们使用调试器单步跟踪验证一下错误原因。

实验二十二:验证汇编语言中float类型输出错误原因

第一步:打开DtDebug调试器,将汇编生成的3-2-1.exe程序拖入调试器。

第二步:按Ctrl+F9,进入程序入口地址。

第三步:按F8单步执行,直到输出double型数据结束。控制台窗口显示内容如下:

请输入一个整型:1

用户输入的内容是1

请输入一个字符:a

用户输入的内容是a

请输入一个float型:2

用户输入的内容是0.000000

请输入一个double型:3

用户输入的内容是3.000000

第四步:查看下方的内存窗口,如图3-3所示。第一个输入的整数1位于0x00A53000地址处(每次运行地址不同),占用4个字节空间。接着在地址0x00A53004处存储输入的单个字符’a’(十进制数值61),占用一个字节空间。然后是输入的float型数据2,存储在地址0x00A53005处,占用4个字节空间,值为0x40000000。最后是输入的double类型数据3,存储在地址0x00A53009处,占用8个字节空间,低32位值0x00000000,高32位值为0x40080000。

????????????????????????????????图3-3 汇编代码中的float类型数据输出错误

第五步观察反汇编窗口,输出float型数据的printf函数有3条反汇编语句:

00A51078?????? PUSH DWORD PTR DS:[A53005] ; /<%f> = 2.000000

00A5107E?????? PUSH 3-2-1.00A52086??????????????? ; |format =

00A51083?????? CALL 3-2-1.00A510C6??????????????? ; \printf

第一个PUSH语句将DS数据段DS:[A53005] 4个字节数据入栈0x40000000。

第二个PUSH语句将格式化字符串入栈。

第三条CALL指令调用printf。

【注意】执行printf函数时,会自动将0x40000000转换成double类型数据,低32位为0x00000000,高32位为0x40000000。但是使用ml.exe汇编器编译后,printf输出时只输出了低32位,因此输出结果为0,出现错误。

?????? 上述汇编代码,读者可以将real4类型变量a改为real8类型,尝试一下结果是否正确。

?

结论

?????? 1.数据类型的宽度和取值范围和编译器相关。

2.数据类型的转换有自动类型转换和强制类型转换两种方式。

3.我们可以观察汇编和反汇编代码,单步跟踪来分析C语言的实现过程和产生错误的原因。

反汇编代码

?????? ??? /********整型*********/

??? int y;???? //准备变量

??? printf("请输入一个整型:");

00951952? push??????? offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd5\xfb\xd0\xcd:" (0957B30h)?

??? /********整型*********/

??? int y;???? //准备变量

??? printf("请输入一个整型:");

00951957? call??????? _printf (095104Bh)?

0095195C? add???????? esp,4?

??? scanf_s("%d", &y);?

0095195F? lea???????? eax,[y]?

00951962? push??????? eax?

00951963? push??????? offset string "%d" (0957B44h)?

00951968? call??????? _scanf_s (0951154h)?

0095196D? add???????? esp,8?

??? printf("用户输入的内容是%d\n", y);

00951970? mov???????? eax,dword ptr [y]?

00951973? push??????? eax?

00951974? push??????? offset string "\xd3\xc3\xbb\xa7\xca\xe4\xc8\xeb\xb5\xc4\xc4\xda\xc8\xdd\xca\xc7%d\n" (0957B48h)?

00951979? call??????? _printf (095104Bh)?

0095197E? add???????? esp,8?

??? /********字符型********/

??? char ch;??? //准备变量

??? getchar();//取出前一个scanf_s函数遗留在stdin的回车符

00951981? mov???????? esi,esp?

00951983? call??????? dword ptr [__imp__getchar (095B17Ch)]?

00951989? cmp???????? esi,esp?

0095198B? call??????? __RTC_CheckEsp (095122Bh)?

??? printf("请输入一个字符:");

00951990? push??????? offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6\xd7\xd6\xb7\xfb:" (0957B60h)?

00951995? call??????? _printf (095104Bh)?

0095199A? add???????? esp,4?

??? scanf_s("%c", &ch,1);?

0095199D? push??????? 1?

0095199F? lea???????? eax,[ch]?

009519A2? push??????? eax?

009519A3? push??????? offset string "%c" (0957B74h)?

009519A8? call??????? _scanf_s (0951154h)?

009519AD? add???????? esp,0Ch?

??? printf("用户输入的内容是%c\n", ch);

009519B0? movsx?????? eax,byte ptr [ch] ;8位有符号数扩展为32位有符号数

009519B4? push??????? eax?

009519B5? push??????? offset string "\xd3\xc3\xbb\xa7\xca\xe4\xc8\xeb\xb5\xc4\xc4\xda\xc8\xdd\xca\xc7%c\n" (0957B78h)?

009519BA? call??????? _printf (095104Bh)?

??? printf("用户输入的内容是%c\n", ch);

009519BF? add???????? esp,8?

??? /********单精度浮点型*********/

??? float a;???? //准备变量

??? printf("请输入一个float型:");

009519C2? push??????? offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6float\xd0\xcd:" (0957B90h)?

009519C7? call??????? _printf (095104Bh)?

009519CC? add???????? esp,4?

??? scanf_s("%f", &a); ?

009519CF? lea???????? eax,[a]?

009519D2? push??????? eax?

009519D3? push??????? offset string "%f" (0957BA8h)?

009519D8? call??????? _scanf_s (0951154h)?

009519DD? add???????? esp,8?

??? printf("用户输入的内容是%f\n", a);

009519E0? cvtss2sd??? xmm0,dword ptr [a]?

009519E5? sub???????? esp,8?

009519E8? movsd?????? mmword ptr [esp],xmm0 ?

009519ED? push??????? offset string "\xd3\xc3\xbb\xa7\xca\xe4\xc8\xeb\xb5\xc4\xc4\xda\xc8\xdd\xca\xc7%f\n" (0957BACh)?

009519F2? call?? ?????_printf (095104Bh)?

009519F7? add???????? esp,0Ch?

??? /********双精度浮点型*********/

??? double b;???? //准备变量

??? printf("请输入一个double型:");

009519FA? push??????? offset string "\xc7\xeb\xca\xe4\xc8\xeb\xd2\xbb\xb8\xf6double\xd0\xcd:" (0957BC4h)?

009519FF? call? ??????_printf (095104Bh)?

00951A04? add???????? esp,4?

??? scanf_s("%lf", &b);?

00951A07? lea???????? eax,[b]?

00951A0A? push??????? eax?

00951A0B? push??????? offset string "%lf" (0957BDCh)?

00951A10? call??????? _scanf_s (0951154h)?

00951A15? add???????? esp,8?

??? printf("用户输入的内容是%f\n", b);

00951A18? sub???????? esp,8?

00951A1B? movsd?????? xmm0,mmword ptr [b]?

00951A20? movsd?????? mmword ptr [esp],xmm0?

00951A25? push??????? offset string "\xd3\xc3\xbb\xa7\xca\xe4\xc8\xeb\xb5\xc4\xc4\xda\xc8\xdd\xca\xc7%f\n" (0957BACh)?

00951A2A? call??????? _printf (095104Bh)?

00951A2F? add???????? esp,0Ch?

?????? 注意,上述VS反汇编代码中关于浮点数的输出语句:

009519E0? cvtss2sd??? xmm0,dword ptr [a]?

009519E5? sub???????? esp,8?

009519E8? movsd?????? mmword ptr [esp],xmm0;将float类型转换为double类型入栈

009519ED? push??????? offset string "\xd3\xc3\xbb\xa7\xca\xe4\xc8\xeb\xb5\xc4\xc4\xda\xc8\xdd\xca\xc7%f\n" (0957BACh)?

009519F2? call??????? _printf (095104Bh)

?????? 反汇编代码中使用浮点指令cvtss2sd将float类型数据加载到浮点寄存器xmm0,然后再使用movsd指令将浮点寄存器xmm0中的64位值复制到堆栈中,实现了float类型到double类型的转换。而在汇编代码中直接使用高级伪指令语句:invoke printf,offset fszMsg3,a ;输出结果。在DtDebug调试器反汇编窗口使用的是PUSH DWORD PTR DS:[A53005]这样的语句,只是将32位值压入堆栈传参,没有事先实现float类型到double类型的数据转换。double类型的值通过两次PUSH分别把低32位值和高32值位入栈传参。

本文摘自编程达人系列教材《汇编的角度——C语言》。

文章来源:https://blog.csdn.net/bcdaren/article/details/135775726
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。