C语言基础之【数据类型】(下)
往期 C语言 知识回顾:
链接:
C语言基础之【C语言概述】
C语言基础之【数据类型】(上)
👨💻 博主正在持续更新C语言基础系列中。
❤️ 如果你觉得内容还不错,请多多点赞。⭐️ 如果你觉得对你有帮助,请多多收藏。(防止以后找不到了)
👨👩👧👦
C语言基础系列
持续更新中~,后续分享内容主要涉及C++全栈开发
的知识,如果你感兴趣请多多关注博主。
进制
进制也就是进位制,是人为规定的一种进位方法。
对于任何一种进制-------X进制,就表示某一位置上的数运算时是逢X进一位。
- 十进制就是逢十进一位
- 二进制就是逢二进一位
- 十六进制就是逢十六进一位
- 以此类推,X进制就是逢X进一位
进制对照表
十进制 | 二进制 | 八进制 | 十六进制 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 |
2 | 10 | 2 | 2 |
3 | 11 | 3 | 3 |
4 | 100 | 4 | 4 |
5 | 101 | 5 | 5 |
6 | 110 | 6 | 6 |
7 | 111 | 7 | 7 |
8 | 1000 | 10 | 8 |
9 | 1001 | 11 | 9 |
10 | 1010 | 12 | A |
11 | 1011 | 13 | B |
12 | 1100 | 14 | C |
13 | 1101 | 15 | D |
14 | 1110 | 16 | E |
15 | 1111 | 17 | F |
16 | 10000 | 20 | 10 |
二进制:
- 基数为2,每一位只能是
0
或1
- 例如:
10
表示十进制的2
八进制:
- 基数为8,每一位范围是
0
到7
- 例如:
10
表示十进制的8
十六进制:
- 基数为16,每一位范围是
0
到9
以及A
到F
- 例如:
10
表示十进制的16
进制转换:
- 二进制、八进制和十六进制之间可以相互转换。
- 例如:二进制
1010
= 八进制12
= 十六进制A
= 十进制10
二进制
二进制(Binary)
:一种基于 2 的数字系统,只使用两个数字:0 和 1
- 进位规则:
逢二进一
- 借位规则:
借一当二
它是计算机科学和数字电子技术中最基础的数字系统
- 因为计算机的所有数据最终都以二进制的形式存储和处理
十进制的整数转化成二进制:
- 用2数除以十进制的整数,分别取余数和商数。
- 商数为0的时候,将余数倒着数就是转化后的结果。
十进制的小数转换成二进制:
- 小数部分和2相乘,取整数,不足1取0
- 取整数的个数与小数位个数相等时,将取整后的数顺着看就是转化后的结果
八进制
八进制(Octal)
:一种基于 8 的数字系统,使用 0 到 7 这八个数字来表示数值。
- 进位规则:
逢八进一
- 借位规则:
借一当八
它在计算机科学中曾经被广泛使用,尤其是在早期的计算机系统中。
虽然现在八进制的使用不如二进制和十六进制普遍,但它仍然在某些领域(如文件权限设置)中有应用。
八进制的数可以和二进制数按位对应
- 按位对应规则:
八进制一位对应二进制三位
二进制转换成八进制:
八进制转换成二进制:
十进制的整数转化成八进制:
- 用8除以十进制的整数,分别取余数和商数。
- 商数为0的时候,将余数倒着数就是转化后的结果。
十六进制
十六进制(Hexadecimal)
:一种基于 16 的数字系统,使用 0 到 9 和 A 到 F (字母不区分大小写) 这16个符号来表示数值。
- 进位规则:
逢十六进一
- 借位规则:
借一当十六
它在计算机科学中非常常用
- 因为可以方便地与二进制进行转换
- 同时比二进制更紧凑,便于人类阅读和书写
十六进制的数可以和二进制数按位对应
- 按位对应规则:
十六进制一位对应二进制四位
二进制转换成十六进制:
十六进制转换成二进制:
十进制的整数转化成十六进制:
- 用16除以十进制的整数,分别取余数和商数。
- 商数为0的时候,将余数倒着数就是转化后的结果。
总结:
C语言如何表示相应进制数
十进制、八进制、十六进制 和 二进制的表示方法及其特点:
进制 | 前缀或规则 | 示例 | 说明 |
---|---|---|---|
十进制 | 无前缀,直接书写 1-9 数字 | 123 | 正常数字表示,如: int a = 123; |
八进制 | 以数字 0 开头 | 0123 | 以 0 开头的数字表示八进制,如 :int a = 0123; |
十六进制 | 以 0x 或 0X 开头 | 0x123 | 以 0x 开头的十六进制数,如 :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
表示负数其余位:表示数值的绝对值
十进制数及其原码表示的表格:
十进制数 原码 +15 0000 1111 -15 1000 1111 +0 0000 0000 -0 1000 0000
原码的特点:
- 优点:简单直观,易于理解。
- 缺点:
- 存在 +0 和 -0 两种表示,增加了复杂性。
- 加减运算不方便,需要额外处理符号位。(当
两个正数相减
或不同符号数相加
时,必须比较两个数哪个绝对值大,才能决定谁减谁,才能确定结果是正还是负)
反码
反码(Ones' Complement)
:计算机中表示有符号数的一种方法。
- 最高位表示符号位:
0
表示正数。1
表示负数。正数:
反码与原码相同
负数:
原码的符号位不变,数值位按位取反
十进制数及其反码表示的表格:
十进制数 反码 +15 0000 1111 -15 1111 0000 +0 0000 0000 -0 1111 1111
反码的特点:
- 优点:加减运算比原码方便。
- 缺点:
- 存在 +0 和 -0 两种表示。
- 加减运算仍需处理进位。
补码
在计算机系统中,数值一律用补码来存储
补码(Two's Complement)
:现代计算机中表示有符号数的方法。
- 正数:
原码、反码、补码相同
- 负数:
反码加1
十进制数及其补码表示的表格:
十进制数 补码 +15 0000 1111 -15 1111 0001 +0 0000 0000 -0 0000 0000
补码的特点:
- 优点:
- 解决了 +0 和 -0 的问题。
- 加减运算更方便。
- 缺点:
- 需要额外的硬件支持。
#include <stdio.h>
int main()
{
int a = -15; //-15在计算机中是以补码的形式存储的。
printf("%x\n", a);
return 0;
}
输出:
fffffff1
-15 的补码计算
:
取绝对值
15
的二进制表示:0000 0000 0000 0000 0000 0000 0000 1111
按位取反(反码):
1111 1111 1111 1111 1111 1111 1111 0000
加 1(补码):
1111 1111 1111 1111 1111 1111 1111 0001
-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
十进制数 原码 +0 0000 0000 -0 1000 0000
十进制数 反码 +0 0000 0000 -0 1111 1111 不管以原码方式存储,还是以反码方式存储,0都有两种不同的表示形式。
怎么才能解决同一个0会有两种不同的表示形式的情况呢?
补码 解决了这一问题,因此现代计算机普遍使用补码。
- +0 和 -0 在补码中的表示相同,均为
0000 0000
十进制数 补码(8位) +0 0000 0000 -0 0000 0000
-0 的补码计算:
- -0 的原码:
1000 0000
- 原码数值位取反:
1111 1111
反码加1
:1111 1111 + 1 = 10000 0000
8位截断
:由于只用8位表示,最高位的1
被丢弃,结果为0000 0000
以原码方式相加:
十进制数 原码(8位) +9 0000 1001 -6 1000 0110
解释:结果为-15,不正确。
以补码方式相加:
十进制数 补码(8位) +9 0000 1001 -6 1111 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
)的溢出。
数据类型 占用空间 取值范围 char
1字节 -128 到 127( − 2 7 -2^{7} −27 ~ 2 7 − 1 2^{7}-1 27−1) unsigned char
1字节 0 到 255( 0 0 0 ~ 2 8 − 1 2^{8}-1 28−1)
- char 用于表示有符号字符,取值范围为
-128
到127
- unsigned char 用于表示无符号字符,取值范围为
0
到255
#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);
0x7f
是127
的十六进制表示,二进制为0111 1111
127 + 1 = 128
,但char
的取值范围是-128
到127
,因此会发生溢出。
127 + 2 = 129
,但char
的取值范围是-128
到127
,因此会发生溢出。
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);
0xff
是255
的十六进制表示,二进制为1111 1111
255 + 1 = 256
,但unsigned char
的取值范围是0
到255
,因此会发生溢出。
255 + 2 = 257
,但unsigned char
的取值范围是0
到255
,因此会发生溢出。
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 + 1 | 0 | 无符号数溢出,结果超出 unsigned char 范围,最高位丢失 |
ch2 = 0xff + 2; | 255 + 2 | 1 | 无符号数溢出,结果超出 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
格式字符
及其对应数据类型
打印格式 | 对应数据类型 | 含义 |
---|---|---|
%d | int | 输出有符号的十进制整数 |
%hd | short int | 输出短整数 |
%hu | unsigned short | 输出无符号短整数 |
%o | unsigned int | 输出无符号八进制整数 |
%u | unsigned int | 输出无符号十进制整数 |
%x, %X | unsigned int | 输出无符号十六进制整数(x 对应的是 abcdef ,X 对应的是 ABCDEF ) |
%f | float | 输出单精度浮点数 |
%lf | double | 输出双精度浮点数 |
%e, %E | double | 输出科学计数法表示的浮点数,(e 的大小写决定输出时的大小写) |
%c | char | 输出字符。(可以把输入的数字按照 ASCII 码相应转换为对应的字符) |
%s | char * | 输出字符串,直到遇到空字符 \0 |
%p | void * | 输出指针的十六进制表示 |
%% | % | 输出百分号 % |
printf 附加格式:
字符 | 含义 |
---|---|
l | 用于表示长整数(附加在 d , u , x , o 前面) |
示例:%ld 表示长整型十进制数 | |
- | 用于指定左对齐 |
示例:%-10d 表示左对齐输出,宽度为 10 | |
m | 用于指定数据的最小宽度 |
示例:%5d 表示输出的整数至少占 5 个字符宽度 | |
0 | 用于在输出的前面补上 0 直到占满指定列宽 |
示例:%05d 表示输出的整数至少占 5 个字符宽度,不足部分用 0 填充。 | |
m.n | m 表示域宽,即输出的数据在输出设备上所占的字符数。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'
,其中X
是ch2
的值。
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]
表示读取除换行符以外的所有字符,直到遇到换行符为止。这样就可以读取包含空格的字符串。