Skip to content

第一章:变量(⭐)

1.1 程序中变化的数据

  • 在生活中,我们使用最多的不是固定的数据,而是会变化的数据:
    • ① 购物车商品的数量价格等。
    • ② 一首歌播放的时间进度条歌词的展示等。
    • ③ 微信聊天中消息条数时间语音的长度头像名称等。
    • ④ 游戏中技能的冷却时间血量蓝量buff 时间金币的数量等。
    • ……
  • 下图是一个购物车变化数据,即:

  • 那么,在实际开发中,我们就会使用变量保存操作这些变化数据

1.2 变量

  • 变量的定义:变量是程序中不可或缺的组成单位,最基本的存储单元。其实,变量就是一个存储数据的临时空间,可以向其中存储不同类型的数据,如:整数、小数、字符、字符串等,并且变量中的数据在程序运行的时候可以动态改变。

NOTE

  • 变量:用来存储数据容器
  • 数据:可以是一个用来计算的数字,如:上文购物车中的价格等;也可以是一句话中的关键词其它任意格式的数据
  • 变量的特别之处就在于它存放的数据是可以改变的。
  • 我们可以将变量想象为一个容器,盒子中装的就是我们想要的数据,并且我们需要盒子一个特别的名称;通过这个特别的名称,我们可以盒子添加数据移除数据,这个特别的名称就是变量名

NOTE

  • 变量是内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化
  • ② 通过变量名,可以操作这块内存区域,向其中存储数据获取数据以及移除数据
  • ③ 变量的构成包含三个要素:数据类型变量名需要存储的数据
  • ④ 在生活中,我们会经常说:这件衣服的价格是 100(整型) 元,这双鞋子的价格是 250.5(小数,浮点类型) 元,今天天气真好(字符串类型)之类的话;在计算机科学中,这些都是数据,并且它们是有类型,即:数据类型。(数据类型用于定义变量所能存储的数据的种类以及可以对这些数据进行的操作的一种分类,每种数据类型都有特定的属性和用途,它们决定了变量在内存中如何表示和存储,以及变量可以执行哪些操作)

1.3 变量的声明和使用

  • ① 变量必须先声明,后使用。
  • ② 可以先声明变量再赋值,也可以在声明变量的同时进行赋值。
  • ③ 变量的值可以在同一类型范围内不断变化。

IMPORTANT

在实际开发中,我们通常都会在声明变量的同时,给其赋值,这被称为初始化。

  • 示例:先声明,再使用
c
#include <stdio.h>

int main() {

    // 声明一个整型变量,取名为 a
    int a;

    // 给变量赋值
    a = 10;

    printf("a = %d\n", a);

    return 0;
}
  • 示例:初始化(声明变量的同时给其赋值)
c
#include <stdio.h>

int main() {

    // 声明一个整型变量,取名为 b ,并直接赋值(初始化,实际开发中最为常用)
    int b = 200;

    // 修改变量 b 的值,将变量 a 的值赋值给变量 b
    b = 300;

    printf("b= %d\n", b);

    return 0;
}
  • 示例:同时声明多个变量并赋值
c
#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;
}

1.4 输出变量

  • 在计算机中,所谓的输入输出都是以计算机(CPU 和内存)为主体而言的,即:

NOTE

输入:从输入设备(键盘、鼠标、扫描仪)向计算机输入数据。

输出:从计算机向外部输出设备(显示器、打印机)输出数据。

  • 在 C 语言中,提供了 printf() 函数用于输出信息,其函数声明是:
c
int printf (const char *__format, ...) {
    ...
}
  • printf 的标准含义是格式化输出文本,来源于 print formatted(格式化打印)的缩写,其语法规则,如下所示:

NOTE

  • 格式化字符串:是使用双引号括起来的字符串,里面包含了普通的字符串和格式占位符。
  • 格式占位符(格式声明符):由 %格式字符组成,作用是将输出的数据转换为指定的格式后输出,这里的 %d 表示整数。
  • 输出列表:是程序要输出的一些数据,可以是常量、变量或表达式,需要和格式占位符一一对应。
  • 在计算机中,二进制、八进制、十进制以及十六进制的英文名称和缩写,如下所示:

    • 二进制(binary),缩写是 bin。
    • 八进制(octal),缩写是 oct。
    • 十进制(decimal),缩写是 dec。
    • 十六进制(Hexadecimal),缩写是 hex。
  • 其实,我们也可以在 Windows 系统中的计算器中来看到,即:

IMPORTANT

在生活中的 decimal 是小数的意思;但是,在 C 语言中,decimal 的完整含义是 decimal integer ,即十进制整数。

  • 示例:
c
#include <stdio.h>

int main() {

    // 声明变量并赋值
    int num = 18;

    // 使用输出语句,将变量 num 的值输出,其中 %d 表示输出的是整数
    printf("我今年%d\n", num);

    return 0;
}

1.5 输入数据赋值给变量

  • 在 C 语言中,提供了 scanf() 函数用于从标准输入(通常是键盘)中读取数据并根据变量的地址赋值给变量(变量需要提前声明),其函数声明是:
c
int scanf(const char *__format, ...) {
    ...
}
  • 其语法规则,如下所示:

NOTE

&age&num 中的 &是寻址操作符,&age 表示变量 age 在内存中的地址。

CAUTION

  • ① scanf() 函数中的 %d,如果是连着写,即:%d%d,那么在输入数据的时候,数据之间不可以使用逗号,分隔,只能使用空白字符(空格、tab 键或回车键),即:2空格3tab2tab3回车等。

  • ② 如果是 %d,%d,则输入的时候需要加上逗号,,即:2,3

  • ③ 如果是 %d %d,则输入的时候需要加上空格,即:2空格3

  • 示例:计算圆的面积,半径由用户指定
c
#include <stdio.h>

int main() {

    float radius;

    printf("请输入一个半径:");
    scanf("%f", &radius);

    double area = 3.1415926 * radius * radius;

    printf("半径是%f的圆的面积是%.2lf", radius, area);

    return 0;
}
  • 示例:输入一个整数值,求其绝对值
c
#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;
}
  • 示例:输入多个变量的值,求其乘积
c
#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;
}

1.6 标识符

1.6.1 概述

  • 在 C 语言中,变量、函数、数组名、结构体等要素命名的时候使用的字符序列,称为标识符。

NOTE

在上世纪 60 - 70 年代的时候,因为国家贫穷,人民生活不富裕等原因,家长虽然会给孩子取名为:张建国李华强等;但是,也会取小名为二狗子狗剩等,目的是希望孩子能健康成长(养活),像 张建国李华强二狗子狗剩都是名字(标识符),伴随人的一生。

1.6.2 标识符的命名规范

  • 强制规范:

    • ① 只能由小写大写英文字母0-9_ 组成。
    • ② 不能以数字开头。
    • ③ 不可以是关键字
    • ④ 标识符具有长度限制,不同编译器和平台会有所不同,一般限制在 63 个字符内。
    • ⑤ 严格区分大小写字母,如:Hello、hello 是不同的标识符。
  • 建议规范:

    • ① 为了提高阅读性,使用有意义的单词,见名知意,如:sum,name,max,year 等。
    • ② 使用下划线连接多个单词组成的标识符,如:max_classes_per_student 等。
    • ③ 多个单词组成的标识符,除了使用下划线连接,也可以使用小驼峰命名法,除第一个单词外,后续单词的首字母大写,如: studentId、student_name 等。
    • ④ 不要出现仅靠大小写区分不同的标识符,如:name、Name 容易混淆。
    • ⑤ 系统内部使用了一些下划线开头的标识符,如:C99 标准添加的类型 _Bool,为防止冲突,建议开发者尽量避免使用下划线开头的标识符。
  • 示例:合法(不一定建议)的标识符

txt
a、BOOK_sun、MAX_SIZE、Mouse、student23、Football、FOOTBALL、max、_add、num_1、sum_of_numbers
  • 示例:非法的标识符
txt
$zj、3sum、ab#cd、23student、Foot-baii、s.com、b&c、j**p、book-1、tax rate、don't

1.6.3 关键字

  • C 语言中的关键字是编译器预定义保留字,它们有特定含义用途,用于控制程序的结构和执行。
  • C80 和 C90 (ANSI C)定义的关键字,如下所示:
类型(功能)具体关键字
数据类型关键字chardoublefloatintlongshortsignedunsignedvoid
存储类说明符关键字autoexternregisterstatictypedefvolatileconst
控制语句关键字breakcasecontinuedefaultdoelseforgotoifreturnswitchwhile
结构体、联合体和枚举关键字enumstructunion
其他关键字sizeof
  • C99 新增的关键字,如下所示:
类型(功能)具体关键字
数据类型关键字_Bool_Complex_Imaginary
存储类说明符关键字inlinerestrict
其他关键字_Complex_Imaginary
  • C11 新增的关键字,如下所示:
类型(功能)具体关键字
存储类说明符关键字_Atomic
其他关键字_Alignas_Alignof_Generic_Noreturn_Static_assert_Thread_local

IMPORTANT

  • ① 关键字不能用作标识符(如:变量名、函数名等)。
  • ② 不要死记硬背这些关键字,在实际开发中,并不一定全部使用到;而且,在学到后面的时候,会自动记住这些关键字以及对应的含义。

第二章:常量(⭐)

2.1 概述

  • 在程序运行过程中,不能改变的量就是常量。

NOTE

  • ① 在数学中的 π,就是一个常量,其值为 3.1415926 。
  • ② 在生活中,人类的性别只有;其中,也是常量。
  • ③ ...

2.2 常量的分类

  • 在 C 语言中的变量的分类,如下所示:
    • ① 字面量常量。
    • ② 标识符常量:
      • #define 宏定义的标识符常量。
      • const 关键字修饰的标识符常量。
      • 枚举常量。

NOTE

  • 所谓的字面量常量,就是可以直接使用的常量,不需要声明或定义,包括:整数常量、浮点数常量以及字符常量。
  • 所谓的标识符常量,就是使用标识符来作为常量名,包括: #define 宏定义的标识符常量、const 关键字修饰的标识符常量、枚举常量。
  • 示例:字面量常量
c
#include <stdio.h>

int main() {

    1;
    'A';
    12.3;
    "你好";

    return 0;
}
  • 示例:字面量常量
c
#include <stdio.h>

int main() {

    printf("整数常量 =》%d\n", 1);
    printf("字符常量 =》%c\n", 'A');
    printf("浮点数常量 =》%f\n", 12.3);
    printf("字符串常量 =》%s\n", "你好");

    return 0;
}

2.3 使用 #define 定义常量

  • #define 来定义常量,也叫作宏定义,就是用一个标识符来表示一个常量值,如果在后面的代码中出现了该标识符,那么编译时就全部替换成指定的常量值,即用宏体替换所有宏名,简称宏替换
  • 格式是:
c
#define 常量名 常量值

CAUTION

  • ① 其实宏定义的常量的执行时机是在预处理阶段,将所有宏常量替换完毕,才会继续编译代码。
  • ② 不要以 ; 结尾,如果有 ; ,分号也会成为常量值的一部分。
  • # define 必须写在 main 函数的外面!!!
  • 常量名习惯用大写字母表示,如果多个单词,使用 _ 来分隔,以便和变量区分。
  • 示例:
c
#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;
}

2.4 const 关键字

  • C99 标准新增,这种方式跟定义一个变量是类似的;只不过,需要在变量的数据类型前加上 const 关键字。
  • 和使用 #define定义宏常量相比,const 定义的常量有详细的数据类型,而且会在编译阶段进行安全检查,在运行时才完成替换,所以会更加安全和方便。
  • 格式是:
c
const 数据类型 常量名 = 常量值;
  • 示例:
c
#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;
}

2.5 枚举常量

  • 格式:
c
enum 枚举常量 {
    xxx = 1;
    yyy;
    ...
}

NOTE

  • ① 默认情况下,枚举常量是从 0 开始递增的。
  • ② 也可以在定义枚举常量的时候,自定义它们的值。
  • 示例:
c
#include <stdio.h>

enum sex {
    MALE = 1,
    FEMALE = 2,
};

int main() {

    printf("%d\n", MALE);
    printf("%d\n", FEMALE);

    return 0;
}
  • 示例:
c
#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;
}

2.6 #defind 定义常量 VS const 定义常量

  • ① 执行时机:#define 是预处理指令,在编译之前执行;const 是关键字,在编译过程中执行。
  • ② 类型检查:#define 定义常量不用指定类型不进行类型检查,只是简单地文本替换;const 定义常量需要指定数据类型会进行类型检查,类型安全性更强。

第三章:二进制

3.1 概述

  • 计算机的底层只有二进制,即计算机中运算存储所有数据都需要转换为二进制,包括:数字、字符、图片、视频等。

  • 之前,我们也提到现代的计算机(量子计算机除外)几乎都遵循冯·诺依曼体系结构,其理论要点如下:
    • 存储程序程序指令数据都存储在计算机的内存中,这使得程序可以在运行时修改。
    • 二进制逻辑:所有数据和指令都以二进制形式表示。
    • 顺序执行:指令按照它们在内存中的顺序执行,但可以有条件地改变执行顺序。
    • 五大部件:计算机由运算器控制器存储器输入设备输出设备组成。
    • 指令结构:指令由操作码和地址码组成,操作码指示要执行的操作,地址码指示操作数的位置。
    • 中心化控制:计算机的控制单元(CPU)负责解释和执行指令,控制数据流。
  • 所以,再次论证了为什么计算机只能识别二进制。

3.2 进制

3.2.1 常见的进制

  • 在生活中,我们最为常用的进制就是十进制,其规则是满 10 进 1 ,即:

  • 在计算机中,常见的进制有二进制八进制十六进制,即:
    • 二进制:只能 0 和 1 ,满 2 进 1 。
    • 八进制:0 ~ 7 ,满 8 进 1 。
    • 十六进制:0 ~ 9 以及 A ~ F ,满 16 进 1 。

NOTE

在十六进制中,除了 0 到 9 这十个数字之外,还引入了字母,以便表示超过 9 的值。字母 A 对应十进制的 10 ,字母 B 对应十进制的 11 ,以此类推,字母 F 对应十进制的 15。

  • 进制的换算举例,如下所示:
十进制二进制八进制十六进制
0000
1111
21022
31133
410044
510155
611066
711177
81000108
91001119
10101012a 或 A
11101113b 或 B
12110014c 或 C
13110115d 或 D
14111016e 或 E
15111117f 或 F
16100002010
............
  • 二进制和十六进制的关系:十六进制是以 16 为基数的进制系统,16 在二进制中表示为 ( 2^4 ),即:一个十六进制可以表示 4 位二进制。

NOTE

十六进制的范围是:0 ~ F (0 ~ 15)对应的二进制数的范围是:0000 ~ 1111 (0 ~ 15)。

  • 每个十六进制数都可以映射到一个唯一的 4 位二进制数,即:
十六进制二进制
00000
10001
20010
30011
40100
50101
60110
70111
81000
91001
A1010
B1011
C1100
D1101
E1110
F1111

NOTE

由此可见,每个十六进制数字确实由 4 位二进制数表示。

  • 二进制和八进制的关系:八进制是以 8 为基数的进制系统,8 在二进制中表示为 ( 2^3 );即:一个八进制位可以表示 3 个二进制位。

NOTE

八进制的范围是:0 ~ 7 对应的二进制数的范围是:000 ~ 111。

  • 每个八进制数位都可以映射到一个唯一的 3 位二进制数,即:
八进制二进制
0000
1001
2010
3011
4100
5101
6110
7111

NOTE

由此可见,每个八进制数字确实由 3 位二进制数表示。

3.2.2 C 语言中如何表示不同进制的整数?

  • 规则如下:

    • 在 C 语言中,如果是二进制(字面常量),则需要在二进制整数前加上 0b0B
    • 在 C 语言中,如果是八进制(字面常量),则需要在八进制整数前加上 0
    • 在 C 语言中,如果是十进制(字面常量),正常数字表示即可。
    • 在 C 语言中,如果是十六进制(字面常量),则需要在十六进制整数前加上 0x0X
  • 示例:

c
#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;
}

3.2.3 输出格式

  • 在 C 语言中,可以使用不同的格式占位符输出不同进制的整数,如下所示:
    • %d:十进制整数。
    • %o :八进制整数。
    • %x:十六进制整数。
    • %#o :显示前缀 0 的八进制整数。
    • %#x :显示前缀 0x 的十六进制整数。
    • %#X :显示前缀 0X 的十六进制整数。

CAUTION

C 语言中没有输出二进制数的格式占位符!!!

  • 示例:
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;
}

3.3 进制的转换

3.3.1 概述

  • 不同进制的转换,如下所示:

  • 在计算机中,数据是从右往左的方式排列的;其中,最右边的是低位,最左边的是高位,即:

3.3.2 二进制和十进制的转换

3.3.2.1 二进制转换为十进制

  • 规则:从最低位开始,将每个位上的数提取出来,乘以 2 的 (位数 - 1 )次方,然后求和。

NOTE

  • ① 在学术界,将这种计算规则,称为位权相加法
  • 八进制转换为十进制十六进制转换为十进制二进制转换为十进制的算法相同!!!
  • 示例:十进制转十进制

  • 示例:二进制转十进制

3.3.2.2 十进制转换二进制

  • 规则:将该数不断除以 2 ,直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的二进制。

NOTE

  • ① 在学术界,将这种计算规则,称为短除法连续除2取余法
  • ② 很好理解,只有不断地除以 2 ,就能保证最大的数字不超过 2 ,这不就是二进制(只能有 0 或 1)吗?
  • 八进制转换为二进制十六进制转换为二进制十进制转换为二进制的算法相同!!!
  • 示例:十进制转十进制

  • 示例:十进制转二进制

3.3.3 二进制转八进制

  • 规则:每 3 位二进制就是一个八进制。

  • 示例:011 101 001 -> 351

3.3.4 二进制转十六进制

  • 规则:每 4 位二进制就是一个八进制。

  • 示例:1110 1001 -> 0xE9

3.4 原码、反码和补码

3.4.1 概述

  • 机器数:一个数在计算机的存储形式是二进制,我们称这些二进制数为机器数。机器数可以是有符号的,用机器数的最高位来存放符号位,0 表示正数,1 表示负数。

  • 真值:因为机器数带有符号位,所以机器数的形式值不等于其真实表示的值(真值),以机器数 1000 0001 为例,其真正表示的值(首位是符号位)为 -1,而形式值却是 129 ,因此将带有符号位的机器数的真正表示的值称为机器数的真值。

3.4.2 原码

  • 原码的表示与机器数真值表示的一样,即用第一位表示符号,其余位表示数值。
  • 规则:
    • 正数的原码是它本身对应的二进制数,符号位是 0 。
    • 负数的原码是它本身绝对值对应的二进制数,但是符号位是 1 。
  • +1 的原码,使用 8 位二进数来表示,就是:
十进制数原码(8位二进制数)
+10000 0001
  • -1 的原码,使用 8 位二进数来表示,就是:
十进制数原码(8位二进制数)
-11000 0001

IMPORTANT

按照原码的规则,会出现 +0-0 的情况,即:0000 0000(+0)、1000 0000(-0),显然不符合实际情况;所以,计算机底层虽然存储和计算的都是二进数,但显然不是原码。

3.4.3 反码

  • 规则:

    • 正数的反码和它的原码相同。
    • 负数的反码是在其原码的基础上,符号位不变,其余各位取反。
  • +1 的反码,使用 8 位二进数来表示,就是:

十进制数原码(8位二进制数)反码(8位二进制数)
+10000 00010000 0001
  • -1 的反码,使用 8 位二进数来表示,就是:
十进制数原码(8位二进制数)反码(8位二进制数)
-11000 00011111 1110

IMPORTANT

按照反码的规则,如果是 +0,对应的原码是 0000 0000;那么,其反码还是 0000 0000 ;如果是 -0,对应的原码是 1000 0000,其反码是 1111 1111,显然不符合实际情况;所以,计算机底层虽然存储和计算的都是二进数,但显然不是反码。

3.4.4 补码

  • 规则:

    • 正数的补码和它的原码相同。
    • 负数的补码是在其反码的基础上 + 1 。
  • +1 的补码,使用 8 位二进数来表示,就是:

十进制数原码(8位二进制数)反码(8位二进制数)补码(8位二进制数)
+10000 00010000 00010000 0001
  • -1 的补码,使用 8 位二进数来表示,就是:
十进制数原码(8位二进制数)反码(8位二进制数)补码(8位二进制数)
-11000 00011111 11101111 1111
  • 如果 0 ,按照 +0 的情况进行处理,即:

  • 如果 0 ,按照 -0 的情况进行处理,即:

IMPORTANT

  • ① 补码表示法解决了原码反码存在的两种零(+0-0)的问题,即:在补码表示法中,只有一个零,即 0000 0000。
  • ② 补码使得加法运算减法运算可以统一处理,通过将减法运算转换为加法运算,可以简化硬件设计,提高了运算效率。
  • ③ 计算机底层存储计算的都是二进数的补码

3.4.5 总结

  • ① 正数的原码、反码和补码都是一样的,三码合一。
  • ② 负数的反码是在其原码的基础上,按位取反(0 变 1 ,1 变 0 ),符号位不变;负数的补码是其反码 + 1 。
  • ③ 0 的补码是 0 。

3.5 计算机底层为什么使用补码?

  • 如果计算是 2 - 2 ,那么可以转换为 2 + (-2),这样计算机内部在处理减法计算的时候,就会将其转换为加法计算的形式,以简化硬件设计和提高计算效率。
  • 最高位表示符号位,由于符号位的存在,如果使用原码来计算,就会导致计算结果不正确,即:

  • 补码的设计可以巧妙的让符号位也参与计算,并且可以得到正确的计算结果,即:

Released under the MIT License.