【7.基于C语言的Windows桌面应用程序--进制转换器准备工作】

思路

  • 在实现整个项目之前,先验证算法应该永远是最快的。

  • 后面再加上图形化界面

  • 最好还是跨平台 只有windows太局限了

在进制转换的过程中遇到的问题和思考的方面

计算机底层存放数据和运算

计算机内部的所有数据最终都是以二进制形式存储和处理的。无论是整数、浮点数、字符还是其他类型的数据,它们在计算机的存储器中都是以二进制的形式存在的。

在编程时,我们可能会使用不同的数据类型和操作,比如整数、浮点数、字符串等,以及各种运算符,如加法、减法、乘法和除法。但是,当这些操作被执行时,它们会被转换成二进制代码,然后由计算机的处理器按照二进制逻辑进行处理

所以说对其他各种进制的数据的运算,最终都是对二进制数进行运算

二进制的运算处理方式
针对二进制数据的底层操作,主要涉及到的是逻辑运算和位运算,这些操作直接作用于二进制位。以下是针对二进制数据的基本操作:

逻辑运算:

AND(与):对两个位进行逻辑与操作。
OR(或):对两个位进行逻辑或操作。
NOT(非):对一个位进行逻辑非操作。
XOR(异或):对两个位进行逻辑异或操作。
NAND(与非):对两个位进行逻辑与操作后取反。
NOR(或非):对两个位进行逻辑或操作后取反。
XNOR(同或):对两个位进行逻辑异或操作后取反。

位运算:

位测试(Bit Test):检查特定位是否为1。
位设置(Bit Set):将特定位设置为1。
位清除(Bit Clear):将特定位设置为0。
位翻转(Bit Flip):将特定位的值翻转(0变1,1变0)。

移位运算:

左移(Left Shift):将所有位向左移动指定数量的位置,右边空出的位通常填充0。
右移(Right Shift):
逻辑右移:将所有位向右移动指定数量的位置,左边空出的位通常填充0。
算术右移:将所有位向右移动指定数量的位置,左边空出的位通常填充最高位的值(符号位)。

算术运算:

加法(Addition):对两个二进制数进行加法运算。
减法(Subtraction):对两个二进制数进行减法运算。
乘法(Multiplication):对两个二进制数进行乘法运算。
除法(Division):对两个二进制数进行除法运算。
这些操作是计算机处理二进制数据时最基本的操作,它们构成了计算机指令集的基础,并且在硬件级别上由处理器的逻辑电路实现。

printf格式控制符说明进制转换底层有实现

通过观察我们发现格式控制符有控制输出八进制、十进制、十六进制。

那么我们可以通过格式控制符的存在知道一些事情

  1. 我们在编程过程中定义了一个八进制、十进制、十六进制的数
  2. 编译器底层会把它们这些进制的数转换为二进制存放到计算机内存中(说明编译器底层有实现这些进制转为二进制的功能
  3. 用格式控制符打印出这些数(说明编译器底层也有实现二进制转换为其他这些进制的功能)

更底层来说:

GCC 编译器在底层确实涉及到将不同进制的数值转换为二进制的过程。在编译过程中,源代码会经过预处理、编译、汇编和链接四个阶段。在编译阶段,编译器(CC1)将预处理后的代码转换成汇编语言,这个过程包括将高级语言中的数值常量转换为二进制形式的机器指令。汇编器(AS)接着将这些汇编语言代码转换为机器码,即二进制形式的目标代码。这些目标代码最终由链接器(LD)链接成可执行文件。

所以我们其实可以观察一下底层printf是怎么实现这些功能的

学习printf的底层实现

我们假设一个过程,来了解printf的底层实现。

在C语言中,当你定义一个变量 int a = 10; 时,变量 a 的值确实是以二进制形式存储在计算机内存中的。但是,当你使用 printf 函数打印这个值时,情况并不是直接将十进制转换为二进制,而是执行了以下步骤:

  • 值的存储:变量 a 的值 10 在内存中以二进制形式存储,这通常是使用二进制补码形式

  • 调用 printf:当你调用 printf(“%d\n”, a); 时,你实际上是在请求标准输出函数将 a 的值转换为字符串形式,并按照十进制形式打印出来。

  • 格式化输出:printf 函数使用格式化字符串 “%d\n” 来确定如何解释和转换变量 a 的值。%d 指定了要打印的是一个十进制整数。

  • 内部转换:printf 函数内部会将 a 的二进制表示转换为十进制形式的字符串。这是通过计算二进制数的权值和进行必要的算术运算来完成的。

  • 输出到控制台:转换完成后,printf 将生成的十进制字符串发送到标准输出(通常是控制台或终端),并添加换行符 \n。

printf 函数是 C 语言标准库中的一部分,其内部实现通常是由编译器提供的标准库实现,而不是由用户编写的代码。由于 printf 是一个复杂且高度优化的函数,其内部实现细节可能涉及多种低级操作和特定于平台的优化,因此查看其内部实现并不是一个简单的过程。可能要去找到编译器文档和库
在这里插入图片描述

GNU C库
https://www.gnu.org/software/libc/

由此引出的关于进制转换的问题

但是可以观察到,并不存在二进制格式控制符

找到以下原因:

  • 可读性:二进制表示对于人类来说通常不如十进制、八进制或十六进制直观。十进制是我们日常生活中最常用的计数系统,而八进制和十六进制在某些领域(如计算机科学)中使用广泛,因为它们可以更简洁地表示二进制数。例如,每四位二进制数可以对应一个十六进制数字。

  • 简洁性:二进制数通常很长,尤其是对于较大的数值。例如,一个 32 位的整数在二进制中需要 32 位来表示,这在打印输出时可能会占用很大的空间。

  • 实用性:在大多数情况下,程序员不需要以二进制形式查看数值。十六进制通常足够用于调试目的,因为它提供了一个更紧凑的表示形式,并且每四位二进制数可以直接映射到一个十六进制数字。

  • 实现复杂性:虽然对于编译器来说,添加一个二进制格式控制符并不是技术上的挑战,但考虑到上述的可读性和实用性,可能没有必要增加这个功能。

尽管如此,如果程序员需要以二进制形式打印数值,可以使用位操作和循环来手动实现这一功能。例如,可以编写一个函数来遍历整数的每一位,并打印出相应的二进制数字。这种方法虽然不如直接使用格式控制符那样方便,但它允许程序员根据需要自定义输出格式。

在某些特定的应用场景中,比如底层硬件操作或者加密算法的实现,程序员可能会需要以二进制形式查看数据,这时候手动实现二进制打印功能就显得很有用了。

尽管没有实现打印二进制,但是实际调试的时候要是能实时查看数据的十六进制形式或者二进制的时候就会很方便,这样我们就能直观知道是否给寄存器的各个位赋值是否正确,从而能判断其是否配置成功。

所以寻找调试时能查看十六进制或者二进制的方法

调试时查看十六进制或者二进制

  • 方式一

在监视窗口输入 variable,h 或 variable,x

对于八进制显示,可以使用 ,o 后缀。例如,variable,o 会显示变量 variable 的八进制形式。

对于二进制显示,可以在“监视”窗口中对变量名后附加 ,b 来显示二进制值。例如,variable,b 会显示变量 variable 的二进制形式。
在这里插入图片描述

  • 方式二
    使用插件
    比如Debug visualizer和hex editor

考虑情况后要实现的功能

现在考虑到其他的进制都是以二进制保存在内存中的,所以是否意味着将他们转换为二进制没有意义,可以直接移位、打印其二进制值

目前库函数没有这样的实现,需要自己做

后续其他进制的转换再慢慢先自己实现,再看库实现,目前先做到能把其他进制以二进制的形式打印出来。

实现进制转换代码

高进制转低进制

高进制数转换为低进制数可以通用地使用“除低进制取余法”。
以下是转换过程的一般步骤:

  • 除以目标进制的基数:将高进制数除以目标低进制数的基数。
  • 取余数:记录除法的余数,这个余数就是低进制表示中的一位。
  • 重复:将上一步的商继续除以目标进制的基数,重复取余数,直到商为0。
  • 逆序排列:将记录的余数逆序排列,就得到了该数的低进制表示。

举个例子,不论是十进制、八进制、十六进制都可以采用除二取余法来得到二进制,把低进制的二进制作为除数取余再将得到的余数逆序排列就可以得到二进制了。

十进制转二进制

假设我们有一个十进制数 29,转换为二进制的过程如下:

29 ÷ 2 = 14 余 1
14 ÷ 2 = 7 余 0
7 ÷ 2 = 3 余 1
3 ÷ 2 = 1 余 1
1 ÷ 2 = 0 余 1
将余数逆序排列,得到二进制表示:11101。
十进制转八进制
100(10) ÷ 8 = 12(10) 余 4(8)
12(10) ÷ 8 = 1(10) 余 4(8)
1(10) ÷ 8 = 0(10) 余 1(8)

将余数逆序排列,得到八进制表示:144。

代码实现

这里暂时不考虑浮点数和负数的情况,而且传入的整数范围要在unsigned int的范围内
微软自带的程序员计算器的十进制最大值是十八位数,应该是用unsigned long long int存储的

以下的代码只要传入数据的大小符合unsigned int 的范围,应该无论什么进制都可以转换为二进制的,因为底层存放的形式都是二进制,所以用移位和除2取余都是可以转换为二进制的。

  • 根据位的大小循环移位
void printBinary(unsigned int num) {
    for (int i = sizeof(num) * 8 - 1; i >= 0; i--) {
        printf("%d", (num >> i) & 1);
    }
}

这样有一个缺点,那就是很小的值它的高位可能都是0,但是还是会打印出来,很浪费时间。

  • 递归 根据传入值的大小判断循环移位
void printBinary(unsigned int num) {
	 if (num == 0) {
        printf("0");
        return ;
        
    }
    if (num > 0) {
        printBinary(num >> 1);
        printf("%d", num & 1);
    }
}


void printBinary(unsigned int num) {

	 if (num == 0) {
        printf("0");
        return ;
        
    }
    if (num > 0) {
        printBinary(num / 2);
        printf("%d", num % 2);
    }
}

上面两段代码本质上是一样的,这样就不会每次都移那么多位了
需要注意的是,递归函数在处理非常大的数值时可能会遇到栈溢出的问题,因为每次递归调用都会占用一定的栈空间。

程序运行的时间

这又要考虑一个问题,移位循环操作和递归的话,谁的执行时间和次数更短呢?也就是涉及到一个时间复杂度的问题,还要考虑程序占用的空间,也就是空间复杂度

这次我们先不考虑,只考虑程序运行的时间问题

这里先留个坑 为什么呢 因为就是c/c++ complier run
和code run搞得总有时候编译调试不过
这些鬼插件会改那个task.json的配置,所以多头文件的时候编译就会报错了,翻白眼。日后再来解决

低进制转高进制

低进制数转换为高进制数的通用方法是使用“乘高进制基数加权法”。这种方法适用于将任何低进制数(如十进制、八进制、十六进制等)转换为高进制数。以下是转换过程的一般步骤:

  • 乘以高进制基数:将每一位低进制数乘以高进制基数的相应权重(即该位的位数)。
  • 求和:将所有乘积相加,得到的和就是高进制数。

数据类型大小

我在学习的时候,数据类型的大小范围一般都是书上或者网上告诉我们,然后我们就记住它的范围,我感觉这样只是知其然不知其所以然。在搜集资料之前我有一个猜想,数据类型的确定可能是有两种可能,一种是由创建C语言的人确定的,一种是由创建编译器平台的人确定的

在做出假设之后我就去查找资料,基本上得到的答案都是类似的,即数据类型大小取决于编译器和平台

接下来我就去搜具体的历史细节:

  • c语言的发明时间

C语言是由丹尼斯·里奇(Dennis Ritchie)在1972年左右于美国新泽西州的贝尔实验室(Bell Labs)发明的。C语言的设计初衷是为了重新编写Unix操作系统,以提高系统的效率和可移植性。丹尼斯·里奇与肯·汤普逊(Ken Thompson)合作,将Unix从汇编语言移植到了C语言,这一举措极大地推动了Unix系统的发展和普及。

  • gcc编译器的发明时间

GCC编译器最初是由理查德·斯托曼(Richard Stallman)发起并主导开发的,它是GNU计划的一部分,旨在创建一个完全自由的操作系统。GCC的全称是GNU C Compiler,后来随着支持的语言增多,扩展为GNU Compiler Collection。GCC的第一个beta版本发布于1987年3月22日,它是基于一个现有的Pastel编译器,并扩展支持编译C语言,后用C进行重写。GCC的主要贡献者包括理查德·斯托曼和迈克尔·蒂曼(Michael Tiemann),后者后来成为GCC代码的主要贡献者之一,并参与创建了Cygnus Solutions公司。GCC现在已经发展到支持多种编程语言和多种计算机体系结构,是开源软件和自由软件中广泛使用的编译器。

丹尼斯·里奇作为C语言的发明者,他定义了C语言中基本数据类型的抽象概念,但是具体的实现细节(比如数据类型的大小)则留给了编译器的实现者和硬件平台。

  • 具体确定过程

c语言标准

在C语言的早期标准中(比如C89标准),基本数据类型的确切大小并没有严格规定,这导致在不同的平台上,int、short、long等类型的大小可能会有所不同。这种灵活性允许程序员编写能够在多种硬件上运行的代码,但同时也带来了移植性问题。

为了解决这些问题,C89/C90标准(由ANSI在1989年发布,随后ISO在1990年发布)开始对数据类型的大小做了一些规定,以确保基本的数据类型在不同平台上具有一致的行为。例如,C89/C90标准规定了int至少必须是16位的

随着C语言标准的不断发展,后续的标准如C99进一步明确了一些数据类型的最小大小要求,并且引入了固定宽度的整数类型(如int32_t),此外,C11标准进一步定义了更多的数据类型大小和对齐要求,这些类型在标准中规定了确切的大小,从而提高了程序的可移植性。

编译器和硬件系统

编译器的实现者需要根据C语言标准和目标硬件平台的特性来确定数据类型的大小。例如,在32位系统上,一个int可能是32位(4字节)的,而在64位系统上,int可能仍然是32位,但long可能是64位(8字节)的。

总的来说,C语言的数据类型大小是由C语言标准、编译器实现和硬件架构共同决定的,而C语言标准的制定则是由国际标准化组织(ISO)和美国国家标准学会(ANSI)等机构负责。随着标准的不断发展和完善,C语言的数据类型大小在不同平台上的一致性得到了显著提高。

总结

综上所述,C语言中数据类型的大小是由硬件架构、编译器实现和C语言标准共同决定的


然后我就在想,使用keil的时候,可以F12直接看到标准库基本上是对c语言的数据结构进行了一次重命名和定义,那么再底层一点来说归根结底到底在哪里可以看到编译器对数据类型大小范围的定义呢?

经过搜索发现了几种方式

  1. 看头文件的宏定义
  2. 看编译器平台文档

头文件的宏定义查看方式

我使用的是VSCode代码编辑器,经过搜索发现

  • limits.h定义了数据类型的最大最小值
  • float.h也有
  • stddef.h定义了格式输出符

再通过F12转到stdio.h的定义可以看到路径,也就是哪里存放了这些头文件的源码
在这里插入图片描述

limits.h

C:\Program Files\mingw64\x86_64-w64-mingw32\include\limits.h

在打开这个路径下的文件后,比较关键的信息如下:


#include <crtdefs.h>

#ifndef _INC_LIMITS
#define _INC_LIMITS

#define PATH_MAX	260

#define CHAR_BIT 8 //每个字符的位数 8位
#define SCHAR_MIN (-128)
#define SCHAR_MAX 127
#define UCHAR_MAX 0xff

#ifdef __CHAR_UNSIGNED__//这里这一行不执行 因为没有定义__CHAR_UNSIGNED__
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#else
#define CHAR_MIN SCHAR_MIN//所以这里才是有用的 也就是说写代码的char是有符号型 
#define CHAR_MAX SCHAR_MAX
#endif

#define MB_LEN_MAX 5
#define SHRT_MIN (-32768)
#define SHRT_MAX 32767
#define USHRT_MAX 0xffffU
#define INT_MIN (-2147483647 - 1)
#define INT_MAX 2147483647
#define UINT_MAX 0xffffffffU
#define LONG_MIN (-2147483647L - 1)
#define LONG_MAX 2147483647L
#define ULONG_MAX 0xffffffffUL
#define LLONG_MAX 9223372036854775807ll
#define LLONG_MIN (-9223372036854775807ll - 1)
#define ULLONG_MAX 0xffffffffffffffffull

#define _I8_MIN (-127 - 1)
#define _I8_MAX 127
#define _UI8_MAX 0xffu

#define _I16_MIN (-32767 - 1)
#define _I16_MAX 32767
#define _UI16_MAX 0xffffu

#define _I32_MIN (-2147483647 - 1)
#define _I32_MAX 2147483647
#define _UI32_MAX 0xffffffffu

#endif

#endif /* _INC_LIMITS */


另一个文件,虽然也叫limits.h,但是内容和路径有所不一样
它的存放路径如下:

C:\Program Files\mingw64\lib\gcc\x86_64-w64-mingw32\8.1.0\include-fixed\limits.h

它是可以通过右键转到定义查看的,这个文件更直观地告诉我们各种数据类型的大小

#ifndef _LIMITS_H___
#define _LIMITS_H___

/* Number of bits in a `char'.  */
#undef CHAR_BIT
#define CHAR_BIT __CHAR_BIT__

/* Maximum length of a multibyte character.  */
#ifndef MB_LEN_MAX
#define MB_LEN_MAX 1
#endif

/* Minimum and maximum values a `signed char' can hold.  */
#undef SCHAR_MIN
#define SCHAR_MIN (-SCHAR_MAX - 1)
#undef SCHAR_MAX
#define SCHAR_MAX __SCHAR_MAX__

/* Maximum value an `unsigned char' can hold.  (Minimum is 0).  */
#undef UCHAR_MAX
#if __SCHAR_MAX__ == __INT_MAX__
# define UCHAR_MAX (SCHAR_MAX * 2U + 1U)
#else
# define UCHAR_MAX (SCHAR_MAX * 2 + 1)
#endif

/* Minimum and maximum values a `char' can hold.  */
#ifdef __CHAR_UNSIGNED__
# undef CHAR_MIN
# if __SCHAR_MAX__ == __INT_MAX__
#  define CHAR_MIN 0U
# else
#  define CHAR_MIN 0
# endif
# undef CHAR_MAX
# define CHAR_MAX UCHAR_MAX
#else
# undef CHAR_MIN
# define CHAR_MIN SCHAR_MIN
# undef CHAR_MAX
# define CHAR_MAX SCHAR_MAX
#endif

/* Minimum and maximum values a `signed short int' can hold.  */
#undef SHRT_MIN
#define SHRT_MIN (-SHRT_MAX - 1)
#undef SHRT_MAX
#define SHRT_MAX __SHRT_MAX__

/* Maximum value an `unsigned short int' can hold.  (Minimum is 0).  */
#undef USHRT_MAX
#if __SHRT_MAX__ == __INT_MAX__
# define USHRT_MAX (SHRT_MAX * 2U + 1U)
#else
# define USHRT_MAX (SHRT_MAX * 2 + 1)
#endif

/* Minimum and maximum values a `signed int' can hold.  */
#undef INT_MIN
#define INT_MIN (-INT_MAX - 1)
#undef INT_MAX
#define INT_MAX __INT_MAX__

/* Maximum value an `unsigned int' can hold.  (Minimum is 0).  */
#undef UINT_MAX
#define UINT_MAX (INT_MAX * 2U + 1U)

/* Minimum and maximum values a `signed long int' can hold.
   (Same as `int').  */
#undef LONG_MIN
#define LONG_MIN (-LONG_MAX - 1L)
#undef LONG_MAX
#define LONG_MAX __LONG_MAX__

/* Maximum value an `unsigned long int' can hold.  (Minimum is 0).  */
#undef ULONG_MAX
#define ULONG_MAX (LONG_MAX * 2UL + 1UL)

#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
/* Minimum and maximum values a `signed long long int' can hold.  */
# undef LLONG_MIN
# define LLONG_MIN (-LLONG_MAX - 1LL)
# undef LLONG_MAX
# define LLONG_MAX __LONG_LONG_MAX__

/* Maximum value an `unsigned long long int' can hold.  (Minimum is 0).  */
# undef ULLONG_MAX
# define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL)
#endif


#endif /* _LIMITS_H___ */

两者一对比,可以发现常用的数据类型大小。

整型数据类型

在这里插入图片描述

在这里插入图片描述

char类型
数据类型(实际写代码使用的类型)位数大小
signed char(有符号型)8(1字节)- 27 ~ 27 -1 (-128~127)
unsigned char(无符号型)8 (1字节)0~ 28- 1 (0~255)
char(实际是有符号型)8 (1字节)- 27~ 27- 1 (-128~127)

有关值:SCHAR_MIN 、SCHAR_MAX、UCHAR_MAX 、CHAR_MIN 、CHAR_MAX

/* Minimum and maximum values a `signed char' can hold.  */
#undef SCHAR_MIN
#define SCHAR_MIN (-SCHAR_MAX - 1)
#undef SCHAR_MAX
#define SCHAR_MAX __SCHAR_MAX__

/* Maximum value an `unsigned char' can hold.  (Minimum is 0).  */
#undef UCHAR_MAX
#if __SCHAR_MAX__ == __INT_MAX__
# define UCHAR_MAX (SCHAR_MAX * 2U + 1U)
#else
# define UCHAR_MAX (SCHAR_MAX * 2 + 1)
#endif
/* Minimum and maximum values a `char' can hold.  */
#ifdef __CHAR_UNSIGNED__
# undef CHAR_MIN
# if __SCHAR_MAX__ == __INT_MAX__
#  define CHAR_MIN 0U
# else
#  define CHAR_MIN 0
# endif
# undef CHAR_MAX
# define CHAR_MAX UCHAR_MAX
#else
# undef CHAR_MIN
# define CHAR_MIN SCHAR_MIN
# undef CHAR_MAX
# define CHAR_MAX SCHAR_MAX
#endif

关于U、L、UL、ULL、LL
  • 0U:这是一个无符号整数常量,U 表示无符号(unsigned),通常用于定义无符号的字面量。这里的 0 表示数值为零。

  • 1L:这是一个长整型(long int)常量,L 表示长整型。在数值后加上 L 可以确保数值被解释为长整型,而不是默认的整型。

  • 1UL:这是一个无符号长整型(unsigned long int)常量,UL 表示无符号长整型。这同样确保数值被解释为无符号长整型。

  • 2ULL:这是一个无符号长长整型(unsigned long long int)常量,ULL 表示无符号长长整型。在数值后加上 ULL 可以确保数值被解释为无符号长长整型。

  • 1LL:这是一个长长整型(long long int)常量,LL 表示长长整型。在数值后加上 LL 可以确保数值被解释为长长整型。

这些后缀通常用于确保数值的类型与预期一致,特别是在进行数值比较或者赋值操作时,避免由于整数类型不匹配导致的潜在问题。例如,如果一个函数期望一个长整型参数,而你传递了一个默认整型(可能是 int),那么在没有这些后缀的情况下,可能会发生类型不匹配的问题。使用这些后缀可以显式地指定数值的类型,从而避免这些问题。

short int(short)类型
数据类型(实际写代码使用的类型)位数大小
signed short int (有符号型)16(2字节)- 215~ 215- 1 (-32768~32767)
unsigned short int (无符号型)16 (2字节)0~ 216- 1 (0~65535)
short int(实际是有符号型)16 (2字节)- 215~ 215- 1 (-32768~32767)

有关值:SHRT_MIN、SHRT_MAX、USHRT_MAX

/* Minimum and maximum values a `signed short int' can hold.  */
#undef SHRT_MIN
#define SHRT_MIN (-SHRT_MAX - 1)
#undef SHRT_MAX
#define SHRT_MAX __SHRT_MAX__

/* Maximum value an `unsigned short int' can hold.  (Minimum is 0).  */
#undef USHRT_MAX
#if __SHRT_MAX__ == __INT_MAX__
# define USHRT_MAX (SHRT_MAX * 2U + 1U)
#else
# define USHRT_MAX (SHRT_MAX * 2 + 1)
#endif
int 数据类型
数据类型(实际写代码使用的类型)位数大小
signed int (有符号型)32(4字节)- 231~ 231- 1 (-2147483648~2147483647)
unsigned int (无符号型)32(4字节)0~ 232- 1 (0~4,294,967,295)
int(实际是有符号型)32(4字节)- 231~ 231- 1 (-2147483648~2147483647)

有关的值:INT_MIN、INT_MAX、UINT_MAX

/* Minimum and maximum values a `signed int' can hold.  */
#undef INT_MIN
#define INT_MIN (-INT_MAX - 1)
#undef INT_MAX
#define INT_MAX __INT_MAX__

/* Maximum value an `unsigned int' can hold.  (Minimum is 0).  */
#undef UINT_MAX
#define UINT_MAX (INT_MAX * 2U + 1U)
long int 数据类型

和int范围一致

long long int 数据类型
数据类型(实际写代码使用的类型)位数大小
signed long long int (有符号型)64(8字节)- 263~ 263- 1
unsigned long long int (无符号型)64(8字节)0~ 264- 1
long long int(实际是有符号型)64(8字节)- 263~ 263- 1

有关的值:LLONG_MIN、LLONG_MAX

总结

可以总结规律:

  • 同一种数据类型,比如char、signed char、unsigned char,他们的字节数是相同的
  • 如果不明确表明有无符号时,默认有符号。也就是说char 和 signed char的范围是相同的
  • 数据类型的大小,可以由一个基准值X表达,比如char是八位,那么X就是28-1-1 ,signed char大小就是 -X-1 ~ X。
    unsigned char的大小就是0 ~ 2X+1
  • short 就是 short int 、long就是long int,而且long int 和 int本身范围一样大
浮点型数据类型

字节大小

通过sizeof的方式得到各个浮点型数据的字节大小
在这里插入图片描述


取值范围

FLT_MAX、DBL_MAX、LDBL_MAX:表示可表示的最大有限浮点数。
FLT_MIN、DBL_MIN、LDBL_MIN:表示可表示的最小规范化正浮点数。

请注意,对于浮点数类型,FLT_MIN、DBL_MIN 和 LDBL_MIN 表示可以表示的最小正数,而不是最小值。浮点数的最小值是负无穷大。
对于最大值,FLT_MAX、DBL_MAX 和 LDBL_MAX 表示可以表示的最大值,但不是正无穷大。
在这里插入图片描述
在这里插入图片描述

long double这里打印的是错的,暂时还不知道为什么
在这里插入图片描述

在这里插入图片描述

编译器平台文档查看方式

这些全是英文,有点看不进去,秉承着先完成项目后研究细节的原则,先跳过。但是可以看出这些就是比较底层的手册了,还是挺有研究价值的

GNU C Language Manual

https://www.gnu.org/software/c-intro-and-ref/manual/html_node/index.html#SEC_Contents
GNU C语言手册
在这里插入图片描述

The GNU C Reference Manual

https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.html#Real-Number-Constants
GNU C参考手册

在这里插入图片描述

在这里插入图片描述
感觉还是参考手册对我们用户比较有用

格式控制符

在C语言中,printf 函数用于格式化输出,其中 %d 是一种格式控制符,用来指定输出一个整数(decimal)。当你在 printf 函数中使用 %d,它告诉 printf 函数接下来的参数是一个整数,并且应该以十进制(decimal)形式输出。

除了 %d 之外,printf 函数还支持许多其他的格式控制符,用于不同类型的数据输出,以下是一些常见的格式控制符:

%c:用于输出单个字符。
%s:用于输出字符串。
%f:用于输出浮点数(以小数形式)。
%e:用于输出科学计数法形式的浮点数。
%g:用于输出一般形式的浮点数,根据数值的大小自动选择 %f 或 %e。
%x 或 %X:用于输出无符号整数的十六进制形式,%x 输出小写字母,%X 输出大写字母。
%o:用于输出无符号整数的八进制形式。
%u:用于输出无符号整数的十进制形式。
%ld:用于输出长整型(long int)的十进制形式。
%lf:用于输出双精度浮点数(double)。
%lu:用于输出无符号长整型(unsigned long int)的十进制形式。
%%:用于输出百分号(%)字符。

076 表示八进制
0b10 二进制数
010 一般指八进制数 也可以指二进制 所以我们使用的时候尽量加前缀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值