C语言基础之【数据类型】(下)

往期 C语言 知识回顾:
链接:
C语言基础之【C语言概述】
C语言基础之【数据类型】(上)


👨‍💻 博主正在持续更新C语言基础系列中。
❤️ 如果你觉得内容还不错,请多多点赞。

⭐️ 如果你觉得对你有帮助,请多多收藏。(防止以后找不到了)

👨‍👩‍👧‍👦 C语言基础系列 持续更新中~,后续分享内容主要涉及 C++全栈开发 的知识,如果你感兴趣请多多关注博主。


进制

进制也就是进位制,是人为规定的一种进位方法。

对于任何一种进制-------X进制,就表示某一位置上的数运算时是逢X进一位。

  • 十进制就是逢十进一位
  • 二进制就是逢二进一位
  • 十六进制就是逢十六进一位
  • 以此类推,X进制就是逢X进一位

进制对照表

十进制二进制八进制十六进制
0000
1111
21022
31133
410044
510155
611066
711177
81000108
91001119
10101012A
11101113B
12110014C
13110115D
14111016E
15111117F
16100002010

二进制

  • 基数为2,每一位只能是 01
    • 例如:10 表示十进制的 2

八进制

  • 基数为8,每一位范围是 07
    • 例如:10 表示十进制的 8

十六进制

  • 基数为16,每一位范围是 09 以及 AF
    • 例如:10 表示十进制的 16

进制转换

  • 二进制、八进制和十六进制之间可以相互转换。
    • 例如:二进制 1010 = 八进制 12 = 十六进制 A = 十进制 10

二进制

二进制(Binary):一种基于 2 的数字系统,只使用两个数字:01

  • 进位规则逢二进一
  • 借位规则借一当二

它是计算机科学和数字电子技术中最基础的数字系统

  • 因为计算机的所有数据最终都以二进制的形式存储和处理

十进制的整数转化成二进制:

  • 用2数除以十进制的整数,分别取余数和商数。
  • 商数为0的时候,将余数倒着数就是转化后的结果。

在这里插入图片描述


十进制的小数转换成二进制:

  • 小数部分和2相乘,取整数,不足1取0
  • 取整数的个数与小数位个数相等时,将取整后的数顺着看就是转化后的结果

在这里插入图片描述

八进制

八进制(Octal):一种基于 8 的数字系统,使用 0 到 7 这八个数字来表示数值。

  • 进位规则逢八进一
  • 借位规则借一当八

它在计算机科学中曾经被广泛使用,尤其是在早期的计算机系统中。

虽然现在八进制的使用不如二进制和十六进制普遍,但它仍然在某些领域(如文件权限设置)中有应用。

八进制的数可以和二进制数按位对应

  • 按位对应规则八进制一位对应二进制三位

二进制转换成八进制:

在这里插入图片描述


八进制转换成二进制:

在这里插入图片描述

十进制的整数转化成八进制:

  • 用8除以十进制的整数,分别取余数和商数。
  • 商数为0的时候,将余数倒着数就是转化后的结果。

在这里插入图片描述

十六进制

十六进制(Hexadecimal):一种基于 16 的数字系统,使用 0 到 9A 到 F (字母不区分大小写) 这16个符号来表示数值。

  • 进位规则逢十六进一
  • 借位规则借一当十六

它在计算机科学中非常常用

  • 因为可以方便地与二进制进行转换
  • 同时比二进制更紧凑,便于人类阅读和书写

十六进制的数可以和二进制数按位对应

  • 按位对应规则十六进制一位对应二进制四位

二进制转换成十六进制:

在这里插入图片描述

十六进制转换成二进制:

在这里插入图片描述

十进制的整数转化成十六进制:

  • 用16除以十进制的整数,分别取余数和商数。
  • 商数为0的时候,将余数倒着数就是转化后的结果。

在这里插入图片描述

总结:

在这里插入图片描述

C语言如何表示相应进制数

十进制、八进制、十六进制 和 二进制的表示方法及其特点:

进制前缀或规则示例说明
十进制无前缀,直接书写 1-9 数字123正常数字表示,如: int a = 123;
八进制以数字 0 开头01230 开头的数字表示八进制,如 :int a = 0123;
十六进制0x0X 开头0x1230x 开头的十六进制数,如 :int a = 0x123;
二进制C 语言不支持直接书写二进制无直接表示法C 语言中没有直接表示二进制的语法,通常通过十六进制或位运算间接表示
int var = 010011; //不允许。

因为在C语言中,不能给变量直接赋值二进制数据。

#include <stdio.h>

int main()
{
	int a = 123;	   //十进制方式赋值
	int b = 0123;	   //八进制方式赋值
	int c = 0xABC;	   //十六进制方式赋值

	//如果在printf中输出一个十进制数那么用%d,八进制用%o,十六进制是%x
	printf("十进制:%d\n",a );
	printf("八进制:%o\n", b);	//%o,为字母o,不是数字
	printf("十六进制:%x\n", c);

	return 0;
}

输出:

十进制:123
八进制:123
十六进制:abc


计算机内存数值存储方式

原码

原码(Sign-Magnitude):计算机中表示有符号数的一种方法。

  • 最高位:表示符号位

    • 0 表示正数
    • 1 表示负数
  • 其余位:表示数值的绝对值


十进制数及其原码表示的表格:

十进制数原码
+150000 1111
-151000 1111
+00000 0000
-01000 0000

原码的特点

  • 优点:简单直观,易于理解。
  • 缺点
    • 存在 +0-0 两种表示,增加了复杂性。
    • 加减运算不方便,需要额外处理符号位。(当两个正数相减不同符号数相加时,必须比较两个数哪个绝对值大,才能决定谁减谁,才能确定结果是正还是负)

反码

反码(Ones' Complement):计算机中表示有符号数的一种方法。

  • 最高位表示符号位:
    • 0 表示正数。
    • 1 表示负数。

正数反码与原码相同

负数原码的符号位不变,数值位按位取反


十进制数及其反码表示的表格:

十进制数反码
+150000 1111
-151111 0000
+00000 0000
-01111 1111

反码的特点

  • 优点:加减运算比原码方便。
  • 缺点
    • 存在 +0-0 两种表示。
    • 加减运算仍需处理进位。

补码

在计算机系统中,数值一律用补码来存储

补码(Two's Complement):现代计算机中表示有符号数的方法。

  • 正数原码、反码、补码相同
  • 负数反码加1

十进制数及其补码表示的表格:

十进制数补码
+150000 1111
-151111 0001
+00000 0000
-00000 0000

补码的特点

  • 优点
    • 解决了 +0-0 的问题。
    • 加减运算更方便。
  • 缺点
    • 需要额外的硬件支持。
#include <stdio.h>

int main()
{
	int  a = -15;  //-15在计算机中是以补码的形式存储的。

	printf("%x\n", a);  
	return 0;
}

输出:

fffffff1


-15 的补码计算

  1. 取绝对值 15 的二进制表示:

     0000 0000 0000 0000 0000 0000 0000 1111
    
  2. 按位取反(反码):

     1111 1111 1111 1111 1111 1111 1111 0000
    
  3. 加 1(补码):

     1111 1111 1111 1111 1111 1111 1111 0001
    
  4. -15 的补码:

     1111 1111 1111 1111 1111 1111 1111 0001
    

  • -15 的补码 在 32 位系统中表示为 0xfffffff1
  • %x 格式符将补码直接以十六进制形式输出。

补码的十六进制表示

1111 1111 1111 1111 1111 1111 1111 0001 转换为十六进制:

//每 4 位二进制对应 1 位十六进制
1111 -> F
1111 -> F
1111 -> F
1111 -> F
1111 -> F
1111 -> F
1111 -> F
0001 -> 1

补码的意义

用8位二进制数分别表示+0-0

十进制数原码
+00000 0000
-01000 0000
十进制数反码
+00000 0000
-01111 1111

不管以原码方式存储,还是以反码方式存储,0都有两种不同的表示形式。

怎么才能解决同一个0会有两种不同的表示形式的情况呢?


补码 解决了这一问题,因此现代计算机普遍使用补码。

  • +0-0 在补码中的表示相同,均为 0000 0000
十进制数补码(8位)
+00000 0000
-00000 0000

-0 的补码计算

  • -0 的原码1000 0000
  • 原码数值位取反1111 1111
  • 反码加11111 1111 + 1 = 10000 0000
  • 8位截断:由于只用8位表示,最高位的 1 被丢弃,结果为 0000 0000

以原码方式相加:

十进制数原码(8位)
+90000 1001
-61000 0110

在这里插入图片描述

解释:结果为-15,不正确。


以补码方式相加:

十进制数补码(8位)
+90000 1001
-61111 1010

在这里插入图片描述

解释:最高位的1溢出,剩余8位二进制表示的是3,正确。

在计算机系统中,数值一律用补码来存储,主要原因是:

  • 统一 +0 和 -0 的表示

    • +0 和 -0 的表示相同,均为 0000 0000
  • 统一加减法运算

  • 符号位与数值位统一处理

    • 符号位和数值位可以统一处理,符号位直接参与运算。
  • 表示范围更合理

    • 8位补码:表示范围为 -128+127
    • 8位原码:表示范围为 -127+127
    • 补码可以多表示一个负数(-128),提高了数值的表示效率。
  • 兼容性

    • 与无符号数的表示兼容。(例如:1111 1111 在无符号数中表示 255,在补码中表示 -1)

数值溢出

数值溢出(Overflow):指计算机在进行数值运算时,结果超出了该数据类型所能表示的范围,导致结果不准确或异常的现象。

溢出的种类:

  • 有符号字符(char)的溢出。
  • 无符号字符(unsigned char)的溢出。

数据类型占用空间取值范围
char1字节-128 到 127( − 2 7 -2^{7} 27 ~ 2 7 − 1 2^{7}-1 271
unsigned char1字节0 到 255( 0 0 0 ~ 2 8 − 1 2^{8}-1 281
  • char 用于表示有符号字符,取值范围为 -128127
  • unsigned char 用于表示无符号字符,取值范围为 0255
#include <stdio.h>

int main()
{
	//有符号字符(char)的溢出
    char ch1;
	ch1 = 0x7f + 1; //127+1
	printf("%d\n", ch1);
    
    ch1 = 0x7f + 2; //127+2
	printf("%d\n", ch1);
    
    
    
	//无符号字符(unsigned char)的溢出
	unsigned char ch2;
	ch2 = 0xff + 1; //255+1
	printf("%u\n", ch2);

	ch2 = 0xff + 2; //255+2
	printf("%u\n", ch2);

	return 0;
}

输出:

-128
-127
0
1


有符号字符(char)的溢出

char ch1;
ch1 = 0x7f + 1; // 127 + 1
printf("%d\n", ch1);

ch1 = 0x7f + 2; // 127 + 2
printf("%d\n", ch1);
  • 0x7f127 的十六进制表示,二进制为 0111 1111

  • 127 + 1 = 128,但 char 的取值范围是 -128127,因此会发生溢出。

  • 127 + 2 = 129,但 char 的取值范围是 -128127,因此会发生溢出。

      0111 1111 (127 的补码)
    + 0000 0001 (1 的补码)
    -----------------------
      1000 0000 (-128 的补码)
    
    • 结果 1000 0000-128 的补码。
      • 输出:-128
      0111 1111 (127 的补码)
    + 0000 0010 (2 的补码)
     -------------------------
      1000 0001 (-127 的补码)
    
    • 结果 1000 0001-127 的补码。
    • 输出:-127

无符号字符(unsigned char)的溢出

unsigned char ch2;
ch2 = 0xff + 1; // 255 + 1
printf("%u\n", ch2);

ch2 = 0xff + 2; // 255 + 2
printf("%u\n", ch2);
  • 0xff255 的十六进制表示,二进制为 1111 1111

  • 255 + 1 = 256,但 unsigned char 的取值范围是 0255,因此会发生溢出。

  • 255 + 2 = 257,但 unsigned char 的取值范围是 0255,因此会发生溢出。
    1.

      1111 1111 (255 的补码)
    + 0000 0001 (1 的补码)
     ---------------------
      10000 0000 (256)
    
    • 由于 unsigned char 只有 8 位,最高位的 1 被丢弃,结果为 0000 0000(即 0
    • 输出0
      1111 1111 (255 的补码)
    + 0000 0010 (2 的补码)
     ----------------------
     10000 0001 (257)
    
    • 由于 unsigned char 只有 8 位,最高位的 1 被丢弃,结果为 0000 0001(即 1
    • 输出1
代码片段运 算结果(十进制)原因分析
ch1 = 0x7f + 1;127 + 1-128有符号数溢出,结果超出 char 范围,变为负数补码
ch1 = 0x7f + 2;127 + 2-127有符号数溢出,结果超出 char 范围,变为负数补码
ch2 = 0xff + 1;255 + 10无符号数溢出,结果超出 unsigned char 范围,最高位丢失
ch2 = 0xff + 2;255 + 21无符号数溢出,结果超出 unsigned char 范围,最高位丢失
  • 有符号数溢出:结果会“绕回”到负数范围

  • 无符号数溢出:结果会“绕回”到最小值(0)

类型限定符

限定符含义
extern声明一个变量,但不分配存储空间。
示例:extern int a;
const定义一个常量,常量的值在程序运行期间不能被修改。
示例:const int b = 10;a 的值不能被修改)
volatile告诉编译器不要优化变量,通常用于多线程或硬件寄存器访问。
示例:volatile int c;c 的值可能会被外部因素改变)
register建议编译器将变量存储在寄存器中,以提高访问速度。
示例:register int d;d 可能会被存储在寄存器中)

register

  • register 是建议型的指令,而不是命令型的指令。
    • 如果 CPU 有空闲寄存器,那么 register 生效
    • 如果 CPU 没有空闲寄存器,那么 register 无效

字符串格式化输出和输入

字符串常量

字符串是内存中一段连续的char空间,以'\0'(数字0)结尾

字符串常量 :由双引号括起来的字符序列。

  • 如:“china”、“C program”,“$12.5” 等都是合法的字符串常量。

字符常量与字符串常量的不同:

在这里插入图片描述

每个字符串的结尾,编译器会自动的添加一个结束标志位 '\0',即 "a" 包含两个字符 'a'’\0’

printf函数和putchar函数

printf 函数与 putchar 函数比较:

特性printf 函数putchar 函数
功能向标准输出打印格式化数据向标准输出打印单个字符
返回值返回成功输出的字符数返回输出的字符(以 int 类型返回)
输出类型可以输出多种类型的数据(如整数、浮点数、字符串等)只能输出单个字符
使用场景适用于需要输出格式化数据的场景适用于需要逐个输出字符的场景
示例printf("%d", num);putchar('A');
优点可以一次性输出多个数据,支持多种数据类型简单易用,适合处理字符输出
缺点格式化字符串复杂,容易出错只能输出单个字符,功能有限
  • printf 输出一个字符串
  • putchar 输出一个字符

printf 格式字符 及其对应 数据类型

打印格式对应数据类型含义
%dint输出有符号的十进制整数
%hdshort int输出短整数
%huunsigned short输出无符号短整数
%ounsigned int输出无符号八进制整数
%uunsigned int输出无符号十进制整数
%x, %Xunsigned int输出无符号十六进制整数(x 对应的是 abcdefX 对应的是 ABCDEF
%ffloat输出单精度浮点数
%lfdouble输出双精度浮点数
%e, %Edouble输出科学计数法表示的浮点数,(e 的大小写决定输出时的大小写)
%cchar输出字符。(可以把输入的数字按照 ASCII 码相应转换为对应的字符)
%schar *输出字符串,直到遇到空字符 \0
%pvoid *输出指针的十六进制表示
%%%输出百分号 %

printf 附加格式:

字符含义
l用于表示长整数(附加在 d, u, x, o 前面)
示例:%ld 表示长整型十进制数
-用于指定左对齐
示例:%-10d 表示左对齐输出,宽度为 10
m用于指定数据的最小宽度
示例:%5d 表示输出的整数至少占 5 个字符宽度
0用于在输出的前面补上 0 直到占满指定列宽
示例:%05d 表示输出的整数至少占 5 个字符宽度,不足部分用 0 填充。
m.nm 表示域宽,即输出的数据在输出设备上所占的字符数。n 表示精度,用于说明输出的实型数的小数位数
示例: %8.2f 表示输出的浮点数占 8 个字符宽度,其中小数部分占 2 位

注意事项:

  • 0:不可以搭配使用 -

  • m.n:对实型来说,未指定 n 时,隐含的精度为 n=6 位。

#include <stdio.h>
int main()
{
	int a = 100;
	printf("a = %d\n", a);//格式化输出一个字符串
	printf("%p\n", &a);//输出变量a在内存中的地址编号
	printf("%%d\n");//两个%号只会输出一个%号

	char c = 'a';
	putchar(c);//putchar只有一个参数,就是要输出的char
	long a2 = 100;
	printf("%ld, %lx, %lo\n", a2, a2, a2);

	long long a3 = 1000;
	printf("%lld, %llx, %llo\n", a3, a3, a3);

	int abc = 10;
	printf("abc = '%6d'\n", abc);
	printf("abc = '%-6d'\n", abc);
	printf("abc = '%06d'\n", abc); 
	printf("abc = '%-06d'\n", abc); //0 不可以搭配使用 - ,否则0的功能会失效

	double d = 12.3;
	printf("d = \' %-10.3lf \'\n", d);

	return 0;
}

输出:

a = 100
000000BAC40FF5D4
%d   
a100, 64, 144   
1000, 3e8, 1750
abc = '    10'
abc = '10    '
abc = '000010'
abc = '10    '  
d = ' 12.300     '

scanf函数与getchar函数

scanf 函数与 getchar 函数比较:

特性scanf 函数getchar 函数
功能从标准输入读取格式化数据从标准输入读取单个字符
返回值返回成功读取的输入项数返回读取的字符(以 int 类型返回)
输入类型可以读取多种类型的数据(如整数、浮点数、字符串等)只能读取单个字符
缓冲区处理读取数据后,缓冲区中的换行符可能会残留读取字符后,缓冲区中的换行符可能会残留
使用场景适用于需要读取格式化数据的场景适用于需要逐个读取字符的场景
示例scanf("%d", &num);char ch = getchar();
优点可以一次性读取多个数据,支持多种数据类型简单易用,适合处理字符输入
缺点对输入格式要求严格,容易出错只能读取单个字符,功能有限

使用scanf函数的注意事项:

  • 存储空间不足时使用scanf 函数接受数据存在安全隐患。
    • scanf 函数接收数据时,如果存储空间不足,数据能存储到内存中,但不被保护。
  • 不能使用scanf 函数接收带有空格的字符串。
    • scanf 函数接收字符串时,如果碰到 空格换行 会自动终止。
#include <stdio.h>

int main()
{
	char ch1;
	char ch2;
	char ch3;
	int a;
	int b;

	printf("请输入ch1的字符:");
	ch1 = getchar();
	printf("ch1 = %c\n", ch1);

	getchar(); //测试此处getchar()的作用

	printf("请输入ch2的字符:");
	ch2 = getchar();
	printf("\'ch2 = %ctest\'\n", ch2);

	getchar(); //测试此处getchar()的作用
	printf("请输入ch3的字符:");
	scanf("%c", &ch3);//这里第二个参数一定是变量的地址,而不是变量名
	printf("ch3 = %c\n", ch3);

	printf("请输入a的值:");
	scanf("%d", &a);
	printf("a = %d\n", a);
	printf("请输入b的值:");
	scanf("%d", &b);
	printf("b = %d\n", b);

	return 0;
}

输出:

请输入ch1的字符:a
ch1 = a
请输入ch2的字符:b
'ch2 = btest'
请输入ch3的字符:c
ch3 = c
请输入a的值:1
a = 1
请输入b的值:2
b = 2

分析:

1.变量声明

char ch1;
char ch2;
char ch3;
int a;
int b;

声明了三个字符变量 ch1, ch2, ch3 和两个整数变量 a, b


2.读取并输出 ch1

printf("请输入ch1的字符:");
ch1 = getchar();
 printf("ch1 = %c\n", ch1);

getchar():从标准输入读取一个字符,并将其赋值给 ch1

  • 输出:打印 ch1 的值。
  • 注意getchar() 会读取输入缓冲区中的第一个字符(包括换行符 \n

3.处理输入缓冲区

 getchar(); //测试此处getchar()的作用
  • 作用:清除输入缓冲区中的换行符 \n
  • 原因:在输入 ch1 后,用户按下回车键,输入缓冲区中会留下一个换行符 \n。如果不处理,下一个 getchar() 会直接读取这个换行符。

4.读取并输出 ch2

printf("请输入ch2的字符:");
ch2 = getchar();
 printf("\'ch2 = %ctest\'\n", ch2);

getchar():从标准输入读取一个字符,并将其赋值给 ch2

  • 输出:打印 ch2 的值,格式为 'ch2 = Xtest',其中 Xch2 的值。

5.再次处理输入缓冲区

 getchar(); //测试此处getchar()的作用
  • 作用:清除输入缓冲区中的换行符 \n
  • 原因:在输入 ch2 后,用户按下回车键,输入缓冲区中会留下一个换行符 \n。如果不处理,下一个 scanf("%c", &ch3) 会直接读取这个换行符。

6.读取并输出 ch3

printf("请输入ch3的字符:");
scanf("%c", &ch3); //这里第二个参数一定是变量的地址,而不是变量名
 printf("ch3 = %c\n", ch3);

scanf("%c", &ch3):从标准输入读取一个字符,并将其赋值给 ch3

  • 输出:打印 ch3 的值。
  • 注意scanf 的第二个参数是变量的地址(&ch3),而不是变量名。

7.读取并输出 a

printf("请输入a的值:");
scanf("%d", &a);
 printf("a = %d\n", a);

scanf("%d", &a):从标准输入读取一个整数,并将其赋值给 a

  • 输出:打印 a 的值。

8.读取并输出 b

printf("请输入b的值:");
scanf("%d", &b);
 printf("b = %d\n", b);

scanf("%d", &b):从标准输入读取一个整数,并将其赋值给 b

  • 输出:打印 b 的值。

关键点:

1.getchar()

  • 用于读取单个字符。
  • 会读取输入缓冲区中的换行符 \n,因此需要额外调用 getchar() 清除缓冲区。

2.scanf("%c", &ch3)

  • 用于读取单个字符。
  • 同样会读取输入缓冲区中的换行符 \n,因此需要额外调用 getchar() 清除缓冲区。

3.scanf("%d", &a)

  • 用于读取整数。
  • 不会读取输入缓冲区中的换行符 \n,因此不需要额外处理。

scanf函数默认会以空格制表符换行符作为输入的结束,因此其无法直接读取包含空格的字符串。
为了读取包含空格的字符串,可以通过 正则表达式 结合 scanf 来实现。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char str[100]; // 定义一个足够大的字符数组来存储输入

    printf("请输入一个字符串(可以包含空格):\n");
    scanf(" %[^\n]", str); // 使用 %[^\n] 读取包含空格的字符串

    printf("你输入的字符串是:%s\n", str);

    return 0;
}

输出:

请输入一个字符串(可以包含空格):
Hello World
你输入的字符串是:Hello World


分析:

scanf("%[^\n]", str); // 使用 %[^\n] 读取包含空格的字符串
  • [ ]:是扫描集合。
  • ^:是取反运算符。
  • \n:是换行符。
  • str:是字符数组的首地址。
    • scanf函数会将读取到的字符串存储到这个数组中,并自动在字符串末尾添加结束符'\0'

所以%[^\n]表示读取除换行符以外的所有字符,直到遇到换行符为止。这样就可以读取包含空格的字符串。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

序属秋秋秋

😘

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值