import{_ as s,c as i,o as a,a6 as n}from"./chunks/framework.CZKtKhAm.js";const l="/c/assets/1.CXNJqOOc.png",e="/c/assets/2.E0LS08Y5.png",t="/c/assets/3.6recRAvz.jpeg",p="/c/assets/4.DcyDw4rB.jpg",h="/c/assets/5.q20QOAIA.png",d="/c/assets/6.CmrWpBzQ.png",k="/c/assets/7.CocAjZjO.png",r="/c/assets/8.CHZSlb-7.png",c="/c/assets/9.RD2M_pYn.png",o="/c/assets/10.CmNKK_Ug.png",u="/c/assets/11.CbGZ55Dj.png",E="/c/assets/12.DpTBR420.png",b="/c/assets/13.XcPl7d9s.png",g="/c/assets/14.DL02VQMp.png",F="/c/assets/15.Dr67r_Ws.png",y="/c/assets/16.C5XiXNVN.png",m="/c/assets/17.DO8XxSV6.jpg",C="/c/assets/18.CUXrdefp.jpeg",B="/c/assets/19.uqLiL_yu.png",v="/c/assets/20.CkykpHY2.png",A="/c/assets/21.DV1YbrOP.png",D="/c/assets/22.AHNJT9TV.png",_="/c/assets/23.Bs-MOwx2.png",q="/c/assets/24.StzjmBz-.png",f="/c/assets/25.C0wVWaxD.png",x="/c/assets/26.LXJMAihe.png",P="/c/assets/27._UTCq3PD.png",O="/c/assets/28.BjQ5kBL-.png",T="/c/assets/29.COIOzcmT.png",N="/c/assets/30.Cu__mjav.png",M="/c/assets/31.BX_KzkHt.png",w="/c/assets/32.COt_QxSP.png",K=JSON.parse('{"title":"第一章:变量(⭐)","description":"","frontmatter":{},"headers":[],"relativePath":"notes/01_c-basic/02_xdx/index.md","filePath":"notes/01_c-basic/02_xdx/index.md","lastUpdated":1722740854000}'),I={name:"notes/01_c-basic/02_xdx/index.md"},U=n('
数量
、价格
等。播放的时间
、进度条
、歌词的展示
等。消息条数
、时间
、语音的长度
、头像
、名称
等。冷却时间
、血量
、蓝量
、buff 时间
、金币的数量
等。购物车
中变化
的数据
,即:变量
来保存
和操作
这些变化
的数据
。NOTE
变量
:用来存储数据
的容器
。数据
:可以是一个用来计算的数字
,如:上文购物车中的价格
等;也可以是一句话中的关键词
或其它任意格式的数据
。特别
之处就在于它存放的数据是可以改变
的。变量
想象为一个容器
,盒子中装的
就是我们想要的数据
,并且我们需要给
盒子取
一个特别的名称
;通过这个特别的名称
,我们可以给
盒子添加数据
或移除数据
,这个特别的名称
就是变量名
。NOTE
变量
是内存中的一个存储区域
,该区域的数据可以在同一类型
范围内不断变化
。变量名
,可以操作
这块内存区域,向其中存储数据
或获取数据
以及移除数据
。数据类型
、变量名
、需要存储的数据
。100(整型)
元,这双鞋子的价格是 250.5(小数,浮点类型)
元,今天天气真好(字符串类型)
之类的话;在计算机科学中,这些都是数据,并且它们是有类型,即:数据类型。(数据类型用于定义变量所能存储的数据的种类以及可以对这些数据进行的操作的一种分类,每种数据类型都有特定的属性和用途,它们决定了变量在内存中如何表示和存储,以及变量可以执行哪些操作)IMPORTANT
在实际开发中,我们通常都会在声明变量的同时,给其赋值,这被称为初始化。
#include <stdio.h>
int main() {
// 声明一个整型变量,取名为 a
int a;
// 给变量赋值
a = 10;
printf("a = %d\\n", a);
return 0;
}
#include <stdio.h>
int main() {
// 声明一个整型变量,取名为 b ,并直接赋值(初始化,实际开发中最为常用)
int b = 200;
// 修改变量 b 的值,将变量 a 的值赋值给变量 b
b = 300;
printf("b= %d\\n", b);
return 0;
}
#include <stdio.h>
int main() {
// 同时声明多个整型的变量并赋值
int c1 = 10, c2 = 20, c3 = 30;
printf("c1 = %d\\n", c1);
printf("c2 = %d\\n", c2);
printf("c3 = %d\\n", c3);
return 0;
}
NOTE
NOTE
channel
在计算机
中可以翻译
为信道
或通道
。槽1
和 槽2
是一个通道,槽3
和 槽4
是一个通道;所以,通常是这么建议的: 槽2
中。槽2
和 槽4
中。NOTE
组成双通道配置的内存条需要遵循一些基本要求来确保它们能够正常以双通道模式运行:
NOTE
上图中的内存条有 8 个内存颗粒;但是,高端服务器上的内存条通常会存在 9 个内存颗粒,最后 1 个内存颗粒专门用来做 ECC 校验。
NOTE
Channel > DIMM > Rank -> Chip -> Bank -> Row/Column
。如果我们希望计算 10 和 20 的和;那么,在计算机中需要怎么做?
NOTE
即使 10 和 20 是存储在文件中的,也需要先加载进内存,然后再交给 CPU 进行运算。
其中,最为重要的问题就是如何将数据存储到内存中?答案就是通过变量
。
0000,0000,000000010000 代表 LOAD A, 16
0000,0001,000000000001 代表 LOAD B, 1
0001,0001,000000010000 代表 STORE B, 16
内存地址
来编写代码(机器语言)实现是太难阅读、修改和维护了;于是,我们就使用了汇编语言来编写代码,并通过编译器来将汇编语言翻译为机器语言,即:LOAD A, 16 -- 编译 --> 0000,0000,000000010000
LOAD B, 1 -- 编译 --> 0000,0001,000000000001
STORE B, 16 -- 编译 --> 0001,0001,000000010000
int num = 10;
我们使用变量名
来关联
内存地址
,这样我们在编写代码的时候,就可以不用直接操作内存地址,极大地提高了代码的可读性和开发效率。并且,当程序运行完毕之后,程序所占用的内存还会交还给操作系统,以便其它程序使用。
综上所述,高级语言编译器的作用就是:
此时,我们就可以知道,变量
就是内存中用于存储数据
的临时空间
,并且变量中的值是可以变化的。
内存
中空间的最小单位
是字节
(Bytes),即 8 个 0 或 1 ,如下所示:
00011001 00100110 00100110 00100110 00100110 ...
NOTE
计算机中存储单位的换算,如下所示:
NOTE
变量就是保存程序运行过程中临时产生的值。
其实,到这里还是有疑惑的?我们说过,一个变量至少会占用 1 个字节,如果一个变量占用了 4 个字节,而 CPU 只会通过变量的地址(首地址)获取数据,那么 CPU 是如何获取完整的数据的?答案就是通过数据类型
,数据类型除了限制数据的种类,还限制了数据在内存中所占空间的大小,如上图所示:
a
的首地址是 01
,变量的数据类型是 4
个字节。01 ~ 04
中获取数据。再次,剖析下变量的语法格式:
数据类型 变量名 = 值;
变量名
的作用
,如下所示: 编写
代码的时候,使用变量名
来关联
某块内存的地址
。执行
的时候,会将变量名替换
为具体的地址,再进行具体的操作。CAUTION
变量名(标识符)需要符合命名规则和命名规范!!!
数据类型
的作用
,如下所示:
决定了
变量所占空间的大小。当我们在声明变量的时候写了数据数据类型,CPU 就知道从变量的首地址位置开始取多少字节。决定了
两个变量是否能够运行,以及能够做何种运算。例如:JavaScript 就没有 char 类型的变量,都是 string 类型,可以和任意数据类型的数据拼接,并转换为 string 类型;Java 中有 char 类型的变量,底层都会转换 unicode 编码,然后再计算。值
的作用
,如下所示:
值
就是内存
中实际存储
的数据
。=
是赋值操作符,就是将等号右侧的数据存储到等号左侧的变量名所代表的内存空间。那么,如下代码的含义就是:
// int 数据类型,4 个字节
// num 变量名 -- 关联内存中的一块存储空间
// = 10 将 10 存储到 num 所代表的 4 个字节的存储空间中
int num = 10;
输入
和输出
都是以计算机(CPU 和内存)为主体而言的,即:NOTE
输入:从输入设备(键盘、鼠标、扫描仪)向计算机输入数据。
输出:从计算机向外部输出设备(显示器、打印机)输出数据。
printf()
函数用于输出信息,其函数声明是:int printf (const char *__format, ...) {
...
}
printf
的标准含义是格式化输出文本,来源于 print formatted(格式化打印)
的缩写,其语法规则,如下所示:NOTE
%
和格式字符
组成,作用是将输出的数据转换为指定的格式后输出,这里的 %d
表示整数。在计算机中,二进制、八进制、十进制以及十六进制的英文名称和缩写,如下所示:
其实,我们也可以在 Windows 系统中的计算器中来看到,即:
IMPORTANT
在生活中的 decimal 是小数的意思;但是,在计算机中,decimal 的完整含义是 decimal integer ,即十进制整数。
#include <stdio.h>
int main() {
// 声明变量并赋值
int num = 18;
// 使用输出语句,将变量 num 的值输出,其中 %d 表示输出的是整数
printf("我今年%d岁\\n", num);
return 0;
}
我们可以使用 sizeof
关键字(运算符)来计算变量或类型所占内存空间的大小。
示例:
#include <stdio.h>
int main() {
int num = 10;
printf("变量所占内存空间的大小:%zd字节\\n", sizeof(num));
// 数据类型所占内存空间的大小
printf("数据类型所占内存空间的大小:%zd字节\\n", sizeof(int));
return 0;
}
在 C 语言中,我们可以使用取地址运算符 &
来获取变量的地址。
示例:
#include <stdio.h>
int main() {
int num = 10;
printf("变量 num 的值是:%d\\n", num);
printf("变量 num 的地址(指针)是:%#p\\n", &num);
return 0;
}
scanf()
函数用于从标准输入(通常是键盘)中读取数据并根据变量的地址赋值给变量(变量需要提前声明),其函数声明是:int scanf(const char *__format, ...) {
...
}
NOTE
&age
、&num
中的 &
是寻址操作符,&age
表示变量 age
在内存中的地址。
CAUTION
① scanf() 函数中的 %d
,如果是连着写,即:%d%d
,那么在输入数据的时候,数据之间不可以使用逗号,
分隔,只能使用空白字符(空格、tab 键或回车键),即:2空格3tab
或2tab3回车
等。
② 如果是 %d,%d
,则输入的时候需要加上逗号,
,即:2,3
。
③ 如果是 %d %d
,则输入的时候需要加上空格,即:2空格3
。
#include <stdio.h>
int main() {
// 禁用 stdout 缓冲区
// CLion debug 独有,后文不再提及,如果 debug 有问题,就添加如下代码
setbuf(stdout, NULL);
float radius;
printf("请输入一个半径:");
scanf("%f", &radius);
double area = 3.1415926 * radius * radius;
printf("半径是%f的圆的面积是%.2lf", radius, area);
return 0;
}
#include <stdio.h>
int main() {
int num;
printf("请输入一个整数:");
scanf("%d", &num);
int absNum;
if (num < 0) {
absNum = -num;
} else {
absNum = num;
}
printf("%d的绝对值是:%d", num, absNum);
return 0;
}
#include <stdio.h>
int main() {
int a, b, c;
printf("请输入整数 a 、b 和 c 的值:");
scanf("%d %d %d", &a, &b, &c);
int result = a * b * c;
printf("%d × %d × %d = %d", a, b, c, result);
return 0;
}
NOTE
在上世纪 60 - 70 年代的时候,因为国家贫穷,人民生活不富裕等原因,家长虽然会给孩子取名为:张建国
、李华强
等;但是,也会取小名为二狗子
、狗剩
等,目的是希望孩子能健康成长(养活),像 张建国
、李华强
、二狗子
、狗剩
都是名字(标识符),伴随人的一生。
强制规范:
小写
或大写英文字母
,0-9
或 _
组成。数字
开头。关键字
。长度
限制,不同编译器和平台会有所不同,一般限制在 63 个字符内。区分大小写字母
,如:Hello、hello 是不同的标识符。建议规范:
_Bool
,为防止冲突,建议开发者尽量避免使用下划线开头的标识符。示例:合法(不一定建议)的标识符
a、BOOK_sun、MAX_SIZE、Mouse、student23、Football、FOOTBALL、max、_add、num_1、sum_of_numbers
$zj、3sum、ab#cd、23student、Foot-baii、s.com、b&c、j**p、book-1、tax rate、don't
预定义
的保留字
,它们有特定
的含义
和用途
,用于控制程序的结构和执行。类型(功能) | 具体关键字 |
---|---|
数据类型关键字 | char 、double 、float 、int 、long 、short 、signed 、unsigned 、void |
存储类说明符关键字 | auto 、extern 、register 、static 、typedef 、volatile 、const |
控制语句关键字 | break 、case 、continue 、default 、do 、else 、for 、goto 、if 、return 、switch 、while |
结构体、联合体和枚举关键字 | enum 、struct 、union |
其他关键字 | sizeof |
类型(功能) | 具体关键字 |
---|---|
数据类型关键字 | _Bool 、_Complex 、_Imaginary |
存储类说明符关键字 | inline 、restrict |
其他关键字 | _Complex 、 _Imaginary |
类型(功能) | 具体关键字 |
---|---|
存储类说明符关键字 | _Atomic |
其他关键字 | _Alignas 、 _Alignof 、 _Generic 、 _Noreturn 、 _Static_assert 、 _Thread_local |
IMPORTANT
NOTE
π
,就是一个常量,其值为 3.1415926 。男
和女
;其中,男
和女
也是常量。#define
宏定义的标识符常量。const
关键字修饰的标识符常量。NOTE
字面量常量
,就是可以直接使用的常量,不需要声明或定义,包括:整数常量、浮点数常量以及字符常量。标识符常量
,就是使用标识符来作为常量名,包括: #define
宏定义的标识符常量、const
关键字修饰的标识符常量、枚举常量。#include <stdio.h>
int main() {
1;
'A';
12.3;
"你好";
return 0;
}
#include <stdio.h>
int main() {
printf("整数常量 =》%d\\n", 1);
printf("字符常量 =》%c\\n", 'A');
printf("浮点数常量 =》%f\\n", 12.3);
printf("字符串常量 =》%s\\n", "你好");
return 0;
}
#define
来定义常量,也叫作宏定义,就是用一个标识符来表示一个常量值,如果在后面的代码中出现了该标识符,那么编译时就全部替换成指定的常量值,即用宏体替换所有宏名,简称宏替换
。#define 常量名 常量值
CAUTION
宏定义
的常量的执行时机
是在预处理
阶段,将所有宏常量
替换完毕,才会继续编译代码。;
结尾,如果有 ;
,分号也会成为常量值的一部分。# define
必须写在 main
函数的外面!!!常量名
习惯用大写字母
表示,如果多个单词,使用 _
来分隔,以便和变量区分。#include <stdio.h>
#define PI 3.1415926
int main() {
double radius = 2.5;
double area = PI * radius * radius;
printf("半径为%lf的圆的面积是%.2lf", radius, area);
return 0;
}
const
关键字。#define定义宏常量
相比,const 定义的常量有详细的数据类型,而且会在编译阶段进行安全检查,在运行时才完成替换,所以会更加安全和方便。const 数据类型 常量名 = 常量值;
#include <stdio.h>
const int PI = 3.1415926;
int main() {
double radius = 2.5;
double area = PI * radius * radius;
printf("半径为%lf的圆的面积是%.2lf", radius, area);
return 0;
}
enum 枚举常量 {
xxx = 1;
yyy;
...
}
NOTE
#include <stdio.h>
enum sex {
MALE = 1,
FEMALE = 2,
};
int main() {
printf("%d\\n", MALE);
printf("%d\\n", FEMALE);
return 0;
}
#include <stdio.h>
enum Sex {
MALE = 1,
FEMALE = 2,
};
int main() {
enum Sex sex;
printf("请输入性别(1 表示男性, 2 表示女性):");
scanf("%d", &sex);
printf("您的性别是:%d\\n", sex);
return 0;
}
#define
是预处理指令,在编译之前
执行;const
是关键字,在编译过程
中执行。#define
定义常量不用指定类型
,不进行类型检查
,只是简单地文本替换;const
定义常量需要指定数据类型
,会进行类型检查
,类型安全性更强。二进制
,即计算机中运算
和存储
的所有数据
都需要转换为二进制
,包括:数字、字符、图片、视频等。冯·诺依曼
体系结构,其理论要点如下: 程序指令
和数据
都存储在计算机的内存中,这使得程序可以在运行时修改。二进制
形式表示。运算器
、控制器
、存储器
、输入设备
和输出设备
组成。十进制
,其规则是满 10 进 1
,即:二进制
、八进制
和十六进制
,即: NOTE
在十六进制中,除了 0 到 9 这十个数字之外,还引入了字母,以便表示超过 9 的值。字母 A 对应十进制的 10 ,字母 B 对应十进制的 11 ,以此类推,字母 F 对应十进制的 15。
十进制 | 二进制 | 八进制 | 十六进制 |
---|---|---|---|
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 或 A |
11 | 1011 | 13 | b 或 B |
12 | 1100 | 14 | c 或 C |
13 | 1101 | 15 | d 或 D |
14 | 1110 | 16 | e 或 E |
15 | 1111 | 17 | f 或 F |
16 | 10000 | 20 | 10 |
... | ... | ... | ... |
NOTE
十六进制的范围是:0 ~ F (0 ~ 15)对应的二进制数的范围是:0000 ~ 1111 (0 ~ 15)。
十六进制 | 二进制 |
---|---|
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
A | 1010 |
B | 1011 |
C | 1100 |
D | 1101 |
E | 1110 |
F | 1111 |
NOTE
由此可见,每个十六进制数字确实由 4 位二进制数表示。
NOTE
八进制的范围是:0 ~ 7 对应的二进制数的范围是:000 ~ 111。
八进制 | 二进制 |
---|---|
0 | 000 |
1 | 001 |
2 | 010 |
3 | 011 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
NOTE
由此可见,每个八进制数字确实由 3 位二进制数表示。
规则如下:
二进制
(字面常量),则需要在二进制整数前加上 0b
或 0B
。八进制
(字面常量),则需要在八进制整数前加上 0
。十进制
(字面常量),正常数字表示即可。十六进制
(字面常量),则需要在十六进制整数前加上 0x
或0X
。示例:
#include <stdio.h>
int main() {
int num1 = 0b10100110; // 二进制
int num2 = 0717563; // 八进制
int num3 = 1000; // 十进制
int num4 = 0xaf72; // 十六进制
printf("num1 = %d\\n", num1); // num1 = 166
printf("num2 = %d\\n", num2); // num2 = 237427
printf("num3 = %d\\n", num3); // num3 = 1000
printf("num4 = %d\\n", num4); // num4 = 44914
return 0;
}
格式占位符
来输出
不同进制
的整数,如下所示: %d
:十进制整数。%o
:八进制整数。%x
:十六进制整数。%#o
:显示前缀 0
的八进制整数。%#x
:显示前缀 0x
的十六进制整数。%#X
:显示前缀 0X
的十六进制整数。CAUTION
C 语言中没有输出二进制数的格式占位符!!!
#include <stdio.h>
int main() {
int num = 100;
printf("%d 的十进制整数: %d\\n", num, num); // 100 的十进制整数: 100
printf("%d 的八进制整数: %o\\n", num, num); // 100 的八进制整数: 144
printf("%d 的十六进制整数: %x\\n", num, num); // 100 的十六进制整数: 64
printf("%d 的八进制(前缀)整数: %#o\\n", num, num); // 100 的八进制(前缀)整数: 0144
printf("%d 的十六进制(前缀)整数: %#x\\n", num, num); // 100 的十六进制(前缀)整数: 0x64
printf("%d 的十六进制(前缀)整数: %#X\\n", num, num); // 100 的十六进制(前缀)整数: 0X64
return 0;
}
十进制
的运算规则,如下所示: 十
进一
(针对加法而言)。一
当十
(针对减法而言)。二进制
的运算规则,如下所示: 二
进一
(针对加法而言)。一
当二
(针对减法而言)。八进制
的运算规则,如下所示: 八
进一
(针对加法而言)。一
当八
(针对减法而言)。十六进制
的运算规则,如下所示: 十六
进一
(针对加法而言)。一
当十六
(针对减法而言)。NOTE
位权相加法
。八进制转换为十进制
、十六进制转换为十进制
和二进制转换为十进制
的算法相同!!!NOTE
短除法
或连续除2取余法
。八进制转换为二进制
、十六进制转换为二进制
和十进制转换为二进制
的算法相同!!!规则:每 3 位二进制就是一个八进制。
示例:011 101 001 -> 351
规则:每 4 位二进制就是一个十六进制。
示例:1110 1001 -> 0xE9
原码
是它本身对应的二进制数,符号位是 0 。原码
是它本身绝对值对应的二进制数,但是符号位是 1 。十进制数 | 原码(8位二进制数) |
---|---|
+1 | 0 000 0001 |
十进制数 | 原码(8位二进制数) |
---|---|
-1 | 1 000 0001 |
IMPORTANT
按照原码的规则,会出现 +0
和 -0
的情况,即:0
000 0000(+0)、1
000 0000(-0),显然不符合实际情况;所以,计算机底层虽然存储和计算的都是二进数,但显然不是原码。
规则:
+1 的反码,使用 8 位二进数来表示,就是:
十进制数 | 原码(8位二进制数) | 反码(8位二进制数) |
---|---|---|
+1 | 0 000 0001 | 0 000 0001 |
十进制数 | 原码(8位二进制数) | 反码(8位二进制数) |
---|---|---|
-1 | 1 000 0001 | 1 111 1110 |
IMPORTANT
按照反码的规则,如果是 +0
,对应的原码是 0
000 0000;那么,其反码还是 0
000 0000 ;如果是 -0
,对应的原码是 1
000 0000,其反码是 1
111 1111,显然不符合实际情况;所以,计算机底层虽然存储和计算的都是二进数,但显然不是反码。
规则:
+1 的补码,使用 8 位二进数来表示,就是:
十进制数 | 原码(8位二进制数) | 反码(8位二进制数) | 补码(8位二进制数) |
---|---|---|---|
+1 | 0 000 0001 | 0 000 0001 | 0 000 0001 |
十进制数 | 原码(8位二进制数) | 反码(8位二进制数) | 补码(8位二进制数) |
---|---|---|---|
-1 | 1 000 0001 | 1 111 1110 | 1 111 1111 |
+0
的情况进行处理,即:-0
的情况进行处理,即:IMPORTANT
原码
和反码
存在的两种
零(+0
和 -0
)的问题,即:在补码表示法中,只有一个
零,即 0000 0000。加法运算
和减法运算
可以统一处理,通过将减法运算转换
为加法运算,可以简化硬件设计,提高了运算效率。存储
和计算
的都是二进数的补码
。2 - 2
,那么可以转换为 2 + (-2)
,这样计算机内部在处理减法计算
的时候,就会将其转换为加法计算
的形式,以简化硬件设计和提高计算效率。最高位
表示符号位
,由于符号位的存在,如果使用原码
来计算,就会导致计算结果不正确
,即:补码
的设计可以巧妙的让符号位
也参与计算,并且可以得到正确的计算结果
,即: