2024年10月16日 11:06

This commit is contained in:
许大仙 2024-10-16 03:06:33 +00:00
parent 5c268af6ac
commit 95378bd7f9
45 changed files with 45 additions and 1407 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 KiB

View File

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 452 KiB

After

Width:  |  Height:  |  Size: 452 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

View File

Before

Width:  |  Height:  |  Size: 436 KiB

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 271 KiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,1364 +1,6 @@
# 第一章:数组的概念
# 第一章:字符串(⭐)
## 1.1 为什么需要数组?
### 1.1.1 需求分析 1
* 需要统计某公司 50 个员工的工资情况,例如:计算平均工资、最高工资等。如果使用之前的知识,我们需要声明 50 个变量来分别记录每位员工的工资,即:
```c
#include <stdio.h>
int main(){
double num1 = 0;
double num2 = 0;
double num3 = 0;
...
printf("请输入第 1 个员工的工资:");
scanf("%lf",&num1);
printf("请输入第 2 个员工的工资:");
scanf("%lf",&num2);
printf("请输入第 3 个员工的工资:");
scanf("%lf",&num3);
...
return 0;
}
```
* 这样会感觉特别机械和麻烦全是复制Ctrl + c和粘贴Ctrl + vCV 大法);此时,我们就可以将所有的`数据`全部存储到一个`容器(数组)`中进行统一管理,并进行其它的操作,如:求最值、求平均值等,如下所示:
```c
#include <stdio.h>
int main(){
// 声明数组
double nums[50];
// 数组的长度
int length = sizeof(nums) / sizeof(double);
// 使用 for 循环向数组中添加值
for(int i = 0;i < length;i++){
printf("请输入第 &d 个员工的工资:",i);
scanf("%lf",&num[i]);
}
// 其它操作,如:求最值,求平均值等
...
return 0;
}
```
### 1.1.2 需求分析 2
* 在现实生活中,我们会使用很多 APP 或微信小程序等,即:
![](./assets/1.png)
* 同样的道理,如果我们使用变量来存储每个商品信息,那么就需要非常多的变量;但是,如果我们将这些`商品信息`都存储到一个`容器(数组)`中,进行统一管理;那么,之后的数据处理将会非常方便。
### 1.1.3 容器的概念
* `生活中的容器`:水杯(装水、饮料的容器)、衣柜(装衣服等物品的容器)、集装箱(装货物等物品的容器)。
* `程序中的容器`:将多个数据存储到一起,并且每个数据称为该容器中的元素。
## 1.2 什么是数组?
* 数组Array是将多个`相同数据类型`的`数据`按照一定的顺序排序的`集合`,并使用一个`标识符`命名,以及通过`编号(索引,亦称为下标)`的方式对这些数据进行统一管理。
![](./assets/2.png)
## 1.3 数组的相关概念
* `数组名`:本质上是一个标识符常量,命名需要符合标识符规则和规范。
* `元素`:同一个数组中的元素必须是相同的数据类型。
* `索引(下标)`:从 0 开始的连续数字。
* `数组的长度`:就是元素的个数。
## 1.4 数组的特点
* ① 创建数组的时候,会在内存中开辟一整块`连续的空间`,占据空间的大小,取决于数组的长度和数组中元素的类型。
* ② 数组中的元素在内存中是依次紧密排列且有序的。
* ③ 数组一旦初始化完成,且长度就确定的,并且`数组的长度一旦确定,就不能更改`。
* ④ 我们可以直接通过索引(下标)来获取指定位置的元素,速度很快。
* ⑤ 数组名中引用的是这块连续空间的首地址。
# 第二章:数组的操作(⭐)
## 2.1 数组的定义
### 2.1.1 动态初始化
* 语法:
```c
数据类型 数组名[元素个数|长度];
```
> [!NOTE]
>
> * ① 数据类型:表示的是数组中每一个元素的数据类型。
> * ② 数组名:必须符合标识符规则和规范。
> * ③ 元素个数或长度:表示的是数组中最多可以容纳多少个元素(不能是负数、也不能是 0 )。
* 示例:
```c
#include <stdio.h>
int main() {
// 先指定元素的个数和类型,再进行初始化
// 定义数组
int arr[3];
// 给数组元素赋值
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
return 0;
}
```
### 2.1.2 静态初始化 1
* 语法:
```c
数据类型 数组名[元素个数|长度] = {元素1,元素2,...}
```
> [!NOTE]
>
> * ① 静态部分初始化:如果数组初始化的元素个数`小于`数组声明的长度,那么就会从数组开始位置依次赋值,不够的就补 0 。
> * ② 静态全部初始化:数组初始化的元素个数`等于`数组的长度。
> [!TIP]
>
> 在 CLion 中开启`嵌入提示(形参名称-->显示数组索引的提示)`功能,即:
>
> ![](./assets/3.png)
>
> 这样,在 CLion 中,将会显示数组初始化时每个元素对应的索引,即:
>
> ![](./assets/4.png)
* 示例:静态部分初始化
```c
#include <stdio.h>
int main() {
// 定义数组和部分初始化:
// 会将给定的值从数组的开始位置一个个的赋值,没有赋值的地方,用 0 填充
int arr[5] = {1, 2};
return 0;
}
```
* 示例:静态全部初始化
```c
#include <stdio.h>
int main() {
// 定义数组和全部初始化:数组初始化的元素个数等于数组的长度。
int arr[5] = {1, 2, 3, 4, 5};
return 0;
}
```
### 2.1.3 静态初始化 2
* 语法:
```c
数据类型 数组名[] = {元素1,元素2,...}
```
> [!NOTE]
>
> 没有给出数组中元素的个数,将由系统根据初始化的元素,自动推断出数组中元素的个数。
* 示例:
```c
#include <stdio.h>
int main() {
// 指定元素的类型,不指定元素个数,同时进行初始化
int arr[] = {1, 2, 3, 4, 5};
return 0;
}
```
### 2.1.4 静态初始化 3
* 在 C 语言中,也可以只给部分元素赋值。当 {} 中的值少于元素的个数的时候,只会给前面的部分元素赋值,至于剩下的元素就会自动初始化为 0 。
```c
int arr[10] = {1,2,3,4,5};
```
> [!NOTE]
>
> * ① 数组 `arr` 在内存中开辟了 `10` 个连续的内存空间,但是只会给前 `5` 个内存空间赋值初始化值,即:`arr[0] ~ arr[4]` 分别是 `1`、`2`、`3`、`4`、`5`,而 `arr[5] ~ arr[9]` 就会被自动初始化为 `0`
> * ② 当赋值的元素少于数组总体元素的时候,剩余的元素自动初始化为 `0`,其规则如下:
> * 对于 `short`、`int`、`long`,就是整数 `0`
> * 对于 `char`,就是字符 `'\0'`。需要注意的是,`'\0'` 的十进制数就是 `0`
> * 对于 `float`、`double`,就是小数 `0.0`
* 示例:
```c
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5};
printf("arr[0] = %d \n", arr[0]); // arr[0] = 1
printf("arr[1] = %d \n", arr[1]); // arr[1] = 2
printf("arr[2] = %d \n", arr[2]); // arr[2] = 3
printf("arr[3] = %d \n", arr[3]); // arr[3] = 4
printf("arr[4] = %d \n", arr[4]); // arr[4] = 5
printf("arr[5] = %d \n", arr[5]); // arr[5] = 0
printf("arr[6] = %d \n", arr[6]); // arr[6] = 0
printf("arr[7] = %d \n", arr[7]); // arr[7] = 0
printf("arr[8] = %d \n", arr[8]); // arr[8] = 0
printf("arr[9] = %d \n", arr[9]); // arr[9] = 0
return 0;
}
```
## 2.2 访问数组元素
* 语法:
```c
数组名[索引|下标];
```
> [!NOTE]
>
> 假设数组 `arr` 有 n 个元素,如果使用的数组的下标 `< 0``> n-1` ,那么将会产生数组越界访问,即超出了数组合法空间的访问;那么,数组的索引范围是 `[0,arr.length - 1]`
* 示例:
```c
#include <stdio.h>
int main() {
// 先指定元素的个数和类型,再进行初始化
// 定义数组
int arr[3];
// 给数组元素赋值
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
// 访问数组元素
printf("arr[0] = %d\n", arr[0]); // arr[0] = 10
printf("arr[1] = %d\n", arr[1]); // arr[1] = 20
printf("arr[2] = %d\n", arr[2]); // arr[2] = 30
return 0;
}
```
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组和部分初始化:
// 会将给定的值从数组的开始位置一个个的赋值,没有赋值的地方,用 0 填充
int arr[5] = {1, 2};
// 访问数组元素
printf("arr[0] = %d\n", arr[0]); // arr[0] = 1
printf("arr[1] = %d\n", arr[1]); // arr[1] = 2
printf("arr[2] = %d\n", arr[2]); // arr[2] = 0
printf("arr[3] = %d\n", arr[3]); // arr[3] = 0
printf("arr[4] = %d\n", arr[4]); // arr[4] = 0
return 0;
}
```
* 示例:
```c
#include <stdio.h>
int main() {
// 指定元素的类型,不指定元素个数,同时进行初始化
int arr[] = {1, 2, 3, 4, 5};
// 访问数组元素
printf("arr[0] = %d\n", arr[0]); // arr[0] = 1
printf("arr[1] = %d\n", arr[1]); // arr[1] = 2
printf("arr[2] = %d\n", arr[2]); // arr[2] = 3
printf("arr[3] = %d\n", arr[3]); // arr[3] = 4
printf("arr[4] = %d\n", arr[4]); // arr[4] = 5
return 0;
}
```
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组和全部初始化:数组初始化的元素个数等于数组的长度。
int arr[5] = {1, 2, 3, 4, 5};
// 访问数组元素
printf("arr[0] = %d\n", arr[0]); // arr[0] = 1
printf("arr[1] = %d\n", arr[1]); // arr[1] = 2
printf("arr[2] = %d\n", arr[2]); // arr[2] = 3
printf("arr[3] = %d\n", arr[3]); // arr[3] = 4
printf("arr[4] = %d\n", arr[4]); // arr[4] = 5
return 0;
}
```
## 2.3 数组越界
* 数组下标必须在指定范围内使用,超出范围视为越界。
![](./assets/5.png)
> [!NOTE]
>
> * ① C 语言是不会做数组下标越界的检查,并且编译器也不会报错;但是,编译器不报错,并不意味着程序就是正确!
> * ② 在其它高级编程语言Java、JavaScript、Rust 等中,如果数组越界访问,编译器是会直接报错的!!!
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组和全部初始化:数组初始化的元素个数等于数组的长度。
int arr[] = {1, 2, 3, 4, 5};
// 访问数组元素
printf("arr[0] = %d\n", arr[0]); // arr[0] = 1
printf("arr[1] = %d\n", arr[1]); // arr[1] = 2
printf("arr[2] = %d\n", arr[2]); // arr[2] = 3
printf("arr[3] = %d\n", arr[3]); // arr[3] = 4
printf("arr[4] = %d\n", arr[4]); // arr[4] = 5
printf("arr[-1] = %d\n", arr[-1]); // 得到的是不确定的结果
printf("arr[5] = %d\n", arr[5]); // 得到的是不确定的结果
return 0;
}
```
## 2.4 计算数组的长度
* 数组长度(元素个数)是在数组定义的时候明确指定且固定的,我们不能在运行的时候直接获取数组长度;但是,我们可以通过 sizeof 运算符间接计算出数组的长度。
* 计算步骤,如下所示:
* ① 使用 sizeof 运算符计算出整个数组的字节长度。
* ② 由于数组成员是同一数据类型;那么,每个元素的字节长度一定相等,那么`数组的长度 = 整个数组的字节长度 ÷ 单个元素的字节长度 `。
![](./assets/6.png)
> [!NOTE]
>
> * ① 在很多编程语言中都内置了获取数组的长度的属性或方法Java 中的 arr.length 或 Rust 的 arr.len()。
> * ② 但是C 语言没有内置的获取数组长度的属性或方法,只能通过 sizeof 运算符间接来计算得到。
> * ③ 数组一旦`声明`或`定义`,其`长度`就`固定`了,`不能动态变化`。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组和全部初始化:数组初始化的元素个数等于数组的长度。
int arr[] = {1, 2, 3, 4, 5};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(arr[0]);
// 遍历数组
for (int i = 0; i < length; i++) {
printf("%d \n", arr[i]);
}
return 0;
}
```
## 2.5 遍历数组
* 遍历数组是指按顺序访问数组中的每个元素,以便读取或修改它们,编程中一般使用循环结构对数组进行遍历。
* 示例:声明一个存储有 12、2、31、24、15、36、67、108、29、51 的数组,并遍历数组所有元素
```c
#include <stdio.h>
int main() {
// 定义数组并初始化
int arr[] = {12, 2, 31, 24, 15, 36, 67, 108, 29, 51};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(int);
// 遍历数组
for (int i = 0; i < length; i++) {
printf("%d\n", arr[i]);
}
return 0;
}
```
* 示例:声明长度为 10 的 int 类型数组,给数组元素依次赋值为 0 ~ 9 ,并遍历数组所有元素
```c
#include <stdio.h>
int main() {
// 定义数组
int arr[10];
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(int);
// 给数组的每个元素赋值
for (int i = 0; i < length; i++) {
arr[i] = i;
}
// 遍历数组
for (int i = 0; i < length; i++) {
printf("%d\n", arr[i]);
}
return 0;
}
```
## 2.6 一维数组的内存分析
### 2.6.1 数组内存图
* 假设数组是如下的定义:
```c
int arr[] = {1,2,3,4,5};
```
* 那么,对应的内存结构,如下所示:
![](./assets/7.png)
> [!NOTE]
>
> * ① 数组名 `arr` 就是记录该数组的首地址,即 `arr[0]` 的地址。
> * ② 数组中的各个元素是连续分布的,假设 `arr[0]` 的地址是 `0xdea7bff880`,则 `arr[1] 的地址 = arr[0] 的地址 + int 字节数4 = 0xdea7bff880 + 4 = 0xdea7bff884` ,依次类推...
* 在 C 语言中,我们可以通过 `&arr``&arr[0]` 等形式获取数组或数组元素的地址,即:
```c
#include <stdio.h>
int main() {
// 定义数组
int arr[10];
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(int);
// 给数组的每个元素赋值
for (int i = 0; i < length; i++) {
arr[i] = i;
}
printf("数组的地址是 = %p\n", arr);
// 遍历数组
for (int i = 0; i < length; i++) {
printf("数组元素 %d 的地址是 = %p\n", arr[i], &arr[i]);
}
return 0;
}
```
### 2.6.2 数组的注意事项
* `C 语言规定,数组一旦声明,数组名指向的地址将不可更改`。因为在声明数组的时候,编译器会自动会数组分配内存地址,这个地址和数组名是绑定的,不可更改。
> [!WARNING]
>
> 如果之后试图更改数组名对应的地址,编译器就会报错。
* 示例:错误演示
```c
int num[5]; // 声明数组
// 使用大括号重新赋值是不允许的,必须在数组声明的时候赋值,否则编译将会报错
num = {1,2,3,4,5} ; // [!code error]
```
* 示例:错误演示
```c
int num[] = {1,2,3,4,5};
// 使用大括号重新赋值是不允许的,必须在数组声明的时候赋值,否则编译将会报错
num = {2,3,4,5,6}; // [!code error]
```
* 示例:错误演示
```c
int num[5];
// 报错,需要和 Java 区别一下,在 C 中不可以
num = NULL; // [!code error]
```
* 示例:错误演示
```c
int a[] = {1,2,3,4,5}
// 报错,需要和 Java 区别一下,在 C 中不可以
int b[5] = a ; // [!code error]
```
## 2.7 数组应用案例
### 2.7.1 应用示例
* 需求:计算数组中所有元素的和以及平均数。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组并初始化
int arr[] = {12, 2, 31, 24, 15, 36, 67, 108, 29, 51};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(int);
// 变量保存总和
int sum = 0;
// 遍历数组
for (int i = 0; i < length; i++) {
sum += arr[i];
}
double avg = (double)sum / length;
printf("数组的和为:%d\n", sum); // 数组的和为375
printf("数组的平均值为:%.2lf\n", avg); //数组的平均值为37.50
return 0;
}
```
### 2.7.2 应用示例
* 需求:计算数组的最值(最大值和最小值)。
> [!NOTE]
>
> 思路:
>
> * ① 假设数组中的第一个元素是最大值或最小值,并使用变量 max 或 min 保存。
> * ② 遍历数组中的每个元素:
> * 如果有元素比最大值还要大,就让变量 max 保存最大值。
> * 如果有元素比最小值还要小,就让变量 min 保存最小值。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组并初始化
int arr[] = {12, 2, 31, 24, 15, -36, 67, 108, 29, 51};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(int);
// 定义最大值
int max = arr[0];
// 定义最小值
int min = arr[0];
// 遍历数组
for (int i = 0; i < length; i++) {
if (arr[i] >= max) {
max = arr[i];
}
if (arr[i] <= min) {
min = arr[i];
}
}
printf("数组的最大值为:%d\n", max); // 数组的最大值为108
printf("数组的最小值为:%d\n", min); // 数组的最小值为:-36
return 0;
}
```
### 2.7.3 应用示例
* 需求:统计数组中某个元素出现的次数,要求:使用无限循环,如果输入的数字是 0 ,就退出。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义数组并初始化
int arr[] = {12, 2, 31, 24, 2, -36, 67, 108, 29, 51};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(int);
// 遍历数组
printf("当前数组中的元素是:");
for (int i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 无限循环
while (true) {
// 统计的数字
int num;
// 统计数字出现的次数
int count = 0;
// 输入数字
printf("请输入要统计的数字:");
scanf("%d", &num);
// 0 作为结束条件
if (num == 0) {
break;
}
// 遍历数组,并计数
for (int i = 0; i < length; i++) {
if (arr[i] == num) {
count++;
}
}
printf("您输入的数字 %d 在数组中出现了 %d 次\n", num, count);
}
return 0;
}
```
### 2.7.4 应用示例
* 需求:将数组 a 中的全部元素复制到数组 b 中。
* 示例:
```c
#include <stdio.h>
#define SIZE 10
int main() {
// 定义数组并初始化
int a[] = {12, 2, 31, 24, 15, -36, 67, 108, 29, 51};
int b[SIZE];
// 复制数组
for (int i = 0; i < SIZE; i++) {
b[i] = a[i];
}
// 打印数组 b 中的全部元素
for (int i = 0; i < SIZE; i++) {
printf("%d ", b[i]);
}
return 0;
}
```
### 2.7.5 应用示例
* 需求:数组对称位置的元素互换。
> [!NOTE]
>
> 思路:假设数组一共有 10 个元素,那么:
>
> * a[0] 和 a[9] 互换。
> * a[1] 和 a[8] 互换。
> * ...
>
> 规律就是 `a[i] <--互换--> arr[arr.length -1 -i]`
* 示例:
```c
#include <stdio.h>
int main() {
// 原始数组
int arr[] = {12, 2, 31, 24, 15, -36, 67, 108, 29, 51};
// 计算数组的长度
size_t SIZE = sizeof(arr) / sizeof(arr[0]);
// 打印原始数组中的全部元素
printf("原始数组:");
for (int i = 0; i < SIZE; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 交换数组
for (int i = 0; i < SIZE / 2; i++) {
int temp = arr[i];
arr[i] = arr[SIZE - 1 - i];
arr[SIZE - 1 - i] = temp;
}
// 打印交换后的数组
printf("交换后数组:");
for (int i = 0; i < SIZE; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
```
* 示例:
```c
#include <stdio.h>
int main() {
// 原始数组
int arr[] = {12, 2, 31, 24, 15, -36, 67, 108, 29, 51};
// 计算数组的长度
size_t SIZE = sizeof(arr) / sizeof(arr[0]);
// 打印原始数组中的全部元素
printf("原始数组:");
for (int i = 0; i < SIZE; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 交换数组
for (int i = 0, j = SIZE - 1 - i; i < SIZE / 2; i++, j--) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 打印交换后的数组
printf("交换后数组:");
for (int i = 0; i < SIZE; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
```
### 2.7.6 应用示例
* 需求:将数组中的最大值移动到数组的最末尾。
> [!NOTE]
>
> 思路:从数组的下标 `0` 开始依次遍历到 `length - 1` ,如果 `i` 下标当前的值比 `i+1` 下标的值大,则交换;否则,就不交换。
* 示例:
```c
#include <stdio.h>
int main() {
// 原始数组
int arr[] = {12, 2, 31, -24, 15, -36, 67, 891, 29, 51};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(arr[0]);
// 打印原始数组中的全部元素
printf("原始数组:");
for (int i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 移动最大值到数组的最后一个位置
for (int i = 0; i < length - 1; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
// 打印移动之后的数组
printf("移动之后的数组:");
for (int i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
```
### 2.7.7 应用示例
* 需求:实现冒泡排序,即将数组的元素从小到大排列。
> [!NOTE]
>
> 思路:一层循环,能实现最大值移动到数组的最后;那么,二层循环(控制内部循环数组的长度)就能实现将数组的元素从小到大排序。
* 示例:
```c
#include <stdio.h>
int main() {
// 原始数组
int arr[] = {12, 2, 31, -24, 15, -36, 67, 891, 29, 51};
// 计算数组的长度
size_t length = sizeof(arr) / sizeof(arr[0]);
// 打印原始数组中的全部元素
printf("原始数组:");
for (int i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
for (int j = 0; j < length - 1; j++) {
for (int i = 0; i < length - 1 - j; i++) {
if (arr[i] > arr[i + 1]) {
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
// 打印移动之后的数组
printf("移动之后的数组:");
for (int i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
```
### 2.7.8 应用示例
* 需求:数组中的元素是从小到大排列的,现在要求根据指定的元素获取其在数组中的位置。
> [!NOTE]
>
> 二分查找(折半查找)的前提条件是:数组中的元素必须是`有序`的(从小到大或从大到小)。其基本步骤,如下所示:
>
> * ① 确定初始范围:定义数组的起始索引 `min = 0` 和结束索引 `max = len - 1`
> * ② 计算中间索引:在每次迭代中,计算中间位置 `mid = (min + right) / 2`
> * ③ 比较中间值:
> * 如果`目标值`比 `arr[mid]` 小,则继续在`左`半部分查找,那么 `min` 不变,而`max = mid - 1` 。
> * 如果`目标值`比 `arr[mid]` 大,则继续在`右`半部分查找,那么 `max` 不变,而`min = mid + 1` 。
> * 如果`目标值`和 `arr[mid]` 相等,则找到了目标,返回该索引。
> * ④ 结束条件:当 `min > max` 的时候,表示查找范围为空,即:元素不存在,返回 `-1`
* 示例:
```c
#include <stdio.h>
/**
* 二分查找
*
* @param arr 数组
* @param len 数组长度
* @param num 要查找的数据
* @return 返回数据的下标,没有找到返回-1
*/
int search(int arr[], int len, int num) {
int min = 0;
int max = len - 1;
while (min <= max) {
int mid = (min + max) / 2;
if (num < arr[mid]) { // 说明要查找的数据在左半边
max = mid - 1;
} else if (num > arr[mid]) { // 说明要查找的数据在右半边
min = mid + 1;
} else { // 说明找到了
return mid;
}
}
return -1;
}
int main() {
int arr[] = {1, 2, 3, 4, 5, 6};
int len = sizeof(arr) / sizeof(arr[0]);
int index = search(arr, len, -1);
printf("index = %d\n", index);
return 0;
}
```
# 第三章:多维数组(⭐)
## 3.1 概述
### 3.1.1 引入
* 我们在数学、物理和计算机科学等学科中学习过`一维坐标`、`二维坐标`以及`三维坐标`。
* 其中,`一维坐标`通常用于描述在线段或直线上的点的位置,主要应用有:
* **数轴**:一维坐标可以用来表示数轴上的数值位置,这在基础数学和初等代数中非常常见。
![](./assets/8.png)
* **时间轴**:时间可以看作是一维的,它可以用一维坐标表示,例如:秒、分钟、小时等。
![](./assets/9.png)
* **统计数据**:一维坐标常用于表示单变量的数据集,如:测量身高、体重、温度等。
![](./assets/10.jpg)
* 其中,`二维坐标`用于描述平面上的点的位置。主要应用包括:
* **几何学**:在几何学中,二维坐标用于表示平面图形的顶点、边和面积等。
![](./assets/11.png)
* **地图和导航**:地理坐标系统(经纬度)使用二维坐标来表示地球表面的任意位置。
![image-20240724112326592](./assets/12.png)
* **图形设计和计算机图形学**:二维坐标在绘制图形、设计图案和用户界面中非常重要。
![](./assets/13.png)
* **物理学**:二维运动和场,例如:在描述物体在平面上的运动轨迹时使用二维坐标。
![](./assets/14.jpg)
* 其中,三维坐标用于描述空间中点的位置。主要应用包括:
* **几何学**:三维坐标在空间几何中用于表示立体图形的顶点、边、面和体积。
![](./assets/15.png)
* **计算机图形学**:三维建模和动画需要使用三维坐标来创建和操控虚拟对象。
![](./assets/16.png)
* **工程和建筑设计**:在设计建筑物、机械部件和其他工程项目时,使用三维坐标来精确定位和规划。
![](./assets/17.png)
* **物理学**:三维空间中的力、运动和场,例如:描述物体在空间中的位置和运动轨迹。
![](./assets/18.png)
* 总而言之,一维、二维和三维坐标系统在不同的领域中各有其重要的应用,从基础数学到高级科学和工程技术,它们帮助我们更好地理解和描述世界的结构和行为。
### 3.1.2 多维数组
* 在 C 语言中,多维数组就是数组嵌套,即:在数组中包含数组,数组中的每一个元素还是一个数组类型,如下所示:
![](./assets/19.png)
> [!NOTE]
>
> * ① 如果数组中嵌套的每一个元素是一个常量值,那么该数组就是一维数组。
> * ② 如果数组中嵌套的每一个元素是一个一维数组,那么该数组就是二维数组。
> * ③ 如果数组中嵌套的每一个元素是一个二维数组,那么该数组就是三维数组.
> * ④ 依次类推...
* 一维数组和多维数组的理解:
* 从内存角度看:一维数组或多维数组都是占用的一整块连续的内存空间。
* 从数据操作角度看:
* 一维数组可以直接通过`下标`访问到数组中的某个元素0、1、...
* 二维数组要想访问某个元素,先要获取某个一维数组,然后在一维数组中获取对应的数据。
> [!NOTE]
>
> * ① C 语言中的一维数组或多维数组都是占用的一整块连续的内存空间其它编程语言可不是这样的Java 等。
> * ② 在实际开发中,最为常用的就是二维数组或三维数组了,以二维数组居多!!!
## 3.2 二维数组的定义
### 3.2.1 动态初始化
* 语法:
```c
数据类型 数组名[几个⼀维数组元素][每个⼀维数组中有几个具体的数据元素];
```
> [!NOTE]
>
> * ① 二维数组在实际开发中,最为常见的应用场景就是表格或矩阵了。
> * ② 几个一维数组元素 = 行数。
> * ③ 每个⼀维数组中有几个具体的数据元素 = 列数。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义二维数组并初始化
int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
// 输出二维数组中的元素
printf("%d ", arr[0][0]);
printf("%d ", arr[0][1]);
printf("%d ", arr[0][2]);
printf("%d \n", arr[0][3]);
printf("%d ", arr[1][0]);
printf("%d ", arr[1][1]);
printf("%d ", arr[1][2]);
printf("%d \n", arr[1][3]);
printf("%d ", arr[2][0]);
printf("%d ", arr[2][1]);
printf("%d ", arr[2][2]);
printf("%d ", arr[2][3]);
return 0;
}
```
### 3.2.2 静态初始化 1
* 语法:
```c
数据类型 数组名[行数][列数] = {{元素1,元素2,...},{元素3,...},...}
```
> [!NOTE]
>
> * ① 行数 = 几个一维数组元素。
> * ② 列数 = 每个⼀维数组中有几个具体的数据元素。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义二维数组并初始化
int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
// 输出二维数组中的元素
printf("%d ", arr[0][0]);
printf("%d ", arr[0][1]);
printf("%d ", arr[0][2]);
printf("%d \n", arr[0][3]);
printf("%d ", arr[1][0]);
printf("%d ", arr[1][1]);
printf("%d ", arr[1][2]);
printf("%d \n", arr[1][3]);
printf("%d ", arr[2][0]);
printf("%d ", arr[2][1]);
printf("%d ", arr[2][2]);
printf("%d ", arr[2][3]);
return 0;
}
```
### 3.2.3 静态初始化 2
* 语法:
```c
数据类型 数组名[][列数] = {{元素1,元素2,...},{元素3,...},...}
```
> [!NOTE]
>
> * ① 列数 = 每个⼀维数组中有几个具体的数据元素。
> * ② 可以`不`指定`行数``必须`指定`列`数,编译器会根据元素的个数和列的个数,自动推断出行数!!!
* 示例:
```c
#include <stdio.h>
int main() {
// 定义二维数组
int arr[][4] = {{1, 2, 3, 4}, {5, 6}, {9, 10, 11, 12}};
// 输出二维数组中的元素
printf("%d ", arr[0][0]);
printf("%d ", arr[0][1]);
printf("%d ", arr[0][2]);
printf("%d \n", arr[0][3]);
printf("%d ", arr[1][0]);
printf("%d \n", arr[1][1]);
printf("%d ", arr[2][0]);
printf("%d ", arr[2][1]);
printf("%d ", arr[2][2]);
printf("%d ", arr[2][3]);
return 0;
}
```
## 3.3 二维数组的理解
* 如果二维数组是这么定义的,即:
```c
int arr[3][4];
```
* 那么,这个二维数组 `arr` 可以看做是 `3` 个一维数组组成,它们分别是 `arr[0]`、`arr[1]`、`arr[2]`。这 `3` 个一维数组都各有 4 个元素,如:一维数组 `arr[0]` 中的元素是 `arr[0][0]`、`arr[0][1]`、`arr[0][2]`、`arr[0][3]`,即:
![](./assets/20.png)
## 3.4 二维数组的遍历
* 访问二维数组的元素,需要使用两个下标(索引),一个用于访问行(第一维),另一个用于访问列(第二维),我们通常称为行下标(行索引)或列下标(列索引)。
* 所以,遍历二维数组,需要使用双层循环结构。
> [!NOTE]
>
> 如果一个二维数组是这么定义的,即:`int arr[3][4]`,那么:
>
> * `行的长度 = sizeof(arr) / sizeof(arr[0])` ,因为 `arr` 是二维数组的`总`的内存空间;而 `arr[0]` 、`arr[1]`、`arr[2]` 是二维数组中一维数组的内存空间 。
> * `列的长度 = sizeof(arr[0]) / sizeof(arr[0][0])`,因为`arr[0]` 、`arr[1]`、`arr[2]` 是二维数组中一维数组的内存空间 ,而 `arr[0][0]`、`arr[0][1]`、... 是一维数组中元素的内存空间。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义二维数组
int arr[][4] = {{1, 2, 3, 4}, {5, 6}, {9, 10, 11, 12}};
// 获取行列数
int row = sizeof(arr) / sizeof(arr[0]);
int col = sizeof(arr[0]) / sizeof(arr[0][0]);
// 打印二维数组元素
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
```
## 3.5 二维数组的内存分析
* 用`矩阵形式`3 行 4 列形式)表示二维数组,是`逻辑`上的概念,能形象地表示出行列关系。而在`内存`中,各元素是连续存放的,不是二维的,是`线性`的。
* C 语言中,二维数组中元素排列的顺序是`按行存放`的。即:先顺序存放第一行的元素,再存放第二行的元素。例如:数组`a[3][4] `在内存中的存放,如下所示:
![](./assets/21.png)
> [!NOTE]
>
> * ① 这就是 `C` 语言的二维数组在进行静态初始化的时候,`可以`忽略`行数`的原因所在(底层的`内存结构`是`线性`的),因为可以根据 `元素的总数 ÷ 每列元素的个数 = 行数`的公式计算出`行数`。
> * ② 如果你学过 `Java` 语言可能会感觉困惑Java 语言中的二维数组在进行静态初始化,是`不能`忽略`行数`的,是因为 Java 编译器会根据`行数`去堆内存空间先开辟出一维数组,然后再继续...,所以当然`不能`忽略`行数`。
## 3.6 二维数组的应用案例
* 需求:现在有三个班,每个班五名同学,用二维数组保存他们的成绩,并求出每个班级平均分、以及所有班级平均分,数据要求从控制台输入。
* 示例:
```c
#include <stdio.h>
int main() {
// 定义二维数组,用于保存成绩
double arr[3][5];
// 获取二维数组的行数和列数
int row = sizeof(arr) / sizeof(arr[0]);
int col = sizeof(arr[0]) / sizeof(arr[0][0]);
// 从控制台输入成绩
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
printf("请输入第%d个班级的第%d个学生的成绩", i + 1, j + 1);
scanf("%lf", &arr[i][j]);
}
}
// 总分
double totalSum = 0;
// 遍历数组,求总分和各个班级的平均分
for (int i = 0; i < row; i++) {
double sum = 0;
for (int j = 0; j < col; j++) {
totalSum += arr[i][j];
sum += arr[i][j];
}
printf("第%d个班级的总分为%.2lf\n", i + 1, sum);
printf("第%d个班级的平均分为%.2lf\n", i + 1, sum / col);
}
printf("所有班级的总分为:%.2lf\n", totalSum);
printf("所有班级的平均分为:%.2lf\n", totalSum / (row * col));
return 0;
}
```
# 第四章:字符串(⭐)
## 4.1 概述
## 1.1 概述
* 在实际开发中,我们除了经常处理整数、浮点数、字符等,还经常和字符串打交道,如:`"Hello World"`、`"Hi"` 等。
@ -1380,20 +22,20 @@ char str[32];
* 其对应的图示,如下所示:
![](./assets/22.png)
![](./assets/1.png)
* `'\0'` 在 ASCII 码表中是第 `0` 个字符,用 `NUL` 表示,称为空字符,该字符既不能显示,也不是控制字符,输出该字符不会有任何效果,它在 C 语言中仅作为字符串的结束标志。
* C 语言在处理字符串时,会从前往后逐个扫描字符,一旦遇到`'\0'`就认为到达了字符串的末尾,就结束处理。`'\0'`至关重要,没有`'\0'`就意味着永远也到达不了字符串的结尾。
![](./assets/23.png)
![](./assets/2.png)
> [!NOTE]
>
> 在现代化的高级编程语言中都提供了字符串对应的类型Java 中的 StringJDK 11 之前,底层也是通过 `char[]` 数组来实现的) 。
## 4.2 字符数组(字符串)的定义
## 1.2 字符数组(字符串)的定义
### 4.2.1 标准写法
### 1.2.1 标准写法
* 手动在字符串的结尾添加 `'\0'`作为字符串的结束标识。
@ -1417,7 +59,7 @@ int main() {
}
```
### 4.2.2 简化写法(推荐)
### 1.2.2 简化写法(推荐)
* 字符串写成数组的形式非常麻烦。C 语言中提供了一种简化写法,即:双引号中的字符,会自动视为字符数组。
@ -1444,9 +86,9 @@ int main() {
}
```
## 4.3 字符串的输入和输出
## 1.3 字符串的输入和输出
### 4.3.1 字符串的输出
### 1.3.1 字符串的输出
* 在 C 语言中,有两个函数可以在控制台上输出字符串,它们分别是:
* ① `puts()`:输出字符串并自动换行,并且该函数只能输出字符串。
@ -1499,7 +141,7 @@ int main() {
}
```
### 4.3.2 字符串的输入
### 1.3.2 字符串的输入
* 在 C 语言中,有两个函数可以让用户从键盘输入字符串,它们分别是:
* ① ~~`gets()`:直接输入字符串,并且只能输入字符串~~
@ -1556,7 +198,7 @@ int main() {
}
```
## 4.4 字符串结束不是 `'\0'` 的后果
## 1.4 字符串结束不是 `'\0'` 的后果
* 有的时候,程序的逻辑要求我们必须逐个字符为数组赋值,这个时候就很容易遗忘字符串结束标识 `'\0'`,如下所示:
@ -1577,7 +219,7 @@ int main() {
* 该程序的执行结果,如下所示:
![](./assets/24.png)
![](./assets/3.png)
* 因为`大写字符`在 `ASCII` 码表是连续的,编码值从 `65` 开始,直到 `90` 结束;并且,为了方便,我们使用了循环。但是,我们却发现结果和我们想要的大不一样,为什么?
@ -1631,7 +273,7 @@ int main() {
}
```
## 4.5 字符串的长度
## 1.5 字符串的长度
* 所谓字符串的长度,就是字符串包含了多少个字符(不包括最后的结束符`'\0'`),如:`"abc"` 的长度是 `3` ,而不是 `4`
* 在 C 语言中的 `string.h` 中提供了 `strlen()` 函数,能够帮助我们获取字符串的长度,如下所示:
@ -1665,11 +307,11 @@ int main() {
# 第章:内存中的变量和数组(⭐)
# 第章:内存中的变量和数组(⭐)
## 5.1 内存和内存地址
## 2.1 内存和内存地址
### 5.1.1 内存
### 2.1.1 内存
* `内存`是一种`计算机硬件`,是`软件`在`运行过程`中,用来`临时存储数据`的。在生活中,最为常见的`内存`就是`随机存取存储器RAM内存条`),其特点如下所示:
* ① 生活中最常见的内存类型,用于存储当前运行的程序和数据。
@ -1678,7 +320,7 @@ int main() {
* 内存条的外观,如下所示:
![](./assets/25.jpeg)
![](./assets/4.jpeg)
* 像我们平常使用`记事本`软件一样,当我们输入一些文字的时候,其实是将数据`临时`保存在内存中的,如下所示:
@ -1688,7 +330,7 @@ int main() {
> * ② 但是也有很多软件提供了自动保存数据的功能其原理就是定时1s、3s、5s将内存中的数据刷新到文件中以防止数据丢失
> * ③ 将数据从内存存储到文件中,专业的说法是落盘(落在磁盘上)。
![](./assets/26.gif)
![](./assets/5.gif)
* 此时,如果我们在没有保存的过程下,将`记事本`软件关闭,那么刚才输入的文字将丢失;下次,再打开同样的文件(将数据从磁盘加载进内存,再交给 CPU之前输入的文字将不复存在如下所示
@ -1698,7 +340,7 @@ int main() {
> * ② 但是也有很多软件提供了自动保存数据的功能其原理就是定时1s、3s、5s将内存中的数据刷新到文件中以防止数据丢失
> * ③ 将数据从内存存储到文件中,专业的说法是落盘(落在磁盘上)。
![](./assets/27.gif)
![](./assets/6.gif)
> [!IMPORTANT]
>
@ -1707,19 +349,19 @@ int main() {
> * ① 将数据`保存`到内存中。
> * ② 从内存中的`对应位置`将数据`取出来`。
### 5.1.2 内存地址
### 2.1.2 内存地址
* 在这个计算机的内存条,动不动就 32GB、64GB 、128GB 或更高的年代,如下所示:
![](./assets/28.png)
![](./assets/7.png)
* 如果有一个 int 4 个字节)类型的数据 `2` ,如何将这个数据保存到内存中?(对应上述的步骤 ①)
![](./assets/29.svg)
![](./assets/8.svg)
* 就算数据 `2` 已经保存到内存中,那么内存中那么多的数据,我们又该如何取出呢?(对应上述的步骤 ②)
![](./assets/30.svg)
![](./assets/9.svg)
> [!IMPORTANT]
>
@ -1727,7 +369,7 @@ int main() {
* 操作系统为了更快的去管理内存中的数据,会将`内存条`按照`字节`划分为一个个的`单元格`,如下所示:
![](./assets/31.svg)
![](./assets/10.svg)
> [!NOTE]
>
@ -1742,27 +384,27 @@ int main() {
* 为了方便管理,每个独立的小单元格,都有自己唯一的编号(内存地址),如下所示:
![](./assets/32.svg)
![](./assets/11.svg)
* 之所以,加了`内存地址`,就能`加快`数据的存取速度,可以类比生活中的`字典`
* 如果没有使用`拼音查找法`或`部首查找法`,我们需要一页一页,一行一行的,在整个字典中去搜索我们想要了解的汉字,效率非常低(如果要搜索的汉字在最后一页,可能需要将整个字典从头到尾翻一遍,这辈子真有可能翻得完?)。
![](./assets/33.gif)
![](./assets/12.gif)
* 如果使用`拼音查找法`或`部首查找法`,我们可以很快的定位到所要了解汉字所在的页数,加快了搜索的效率。
![](./assets/34.jpg)
![](./assets/13.jpg)
![](./assets/35.jpg)
![](./assets/14.jpg)
* 同样的道理,如果`没有`内存地址,我们只能一个个的去寻找想要的数据,效率非常低下,如下所示:
![](./assets/36.gif)
![](./assets/15.gif)
* 如果`使用`内存地址,我们就可以直接定位到指定的数据,效率非常高,如下所示:
![](./assets/37.gif)
![](./assets/16.gif)
> [!IMPORTANT]
>
@ -1800,7 +442,7 @@ int main() {
> * ③ 在 32 位操作系统上,内存地址以 32 位的二进制数字表示,最大支持的的内存是 4 GB所以 32 位操作系统已经被淘汰。
> * ④ 在 64 位操作系统上,内存地址以 64 位的二进制数字表示,由于表示形式太长,我们通常会转为十六进制,以方便阅读。
## 5.2 内存中的变量
## 2.2 内存中的变量
* 在 C 语言中数据类型的种类很多short、int、long、float、double、char 等。以 int 类型为例,在 32 位或 64 位操作系统中的int 类型的变量都是占 4 个字节,当我们在代码中这么定义变量,如:
@ -1818,7 +460,7 @@ int main(){
* 那么,编译器就会这么处理,如下所示:
![](./assets/38.svg)
![](./assets/17.svg)
* 在代码中,我们可以使用 `&变量名` 来获取一个变量的内存首地址,如下所示:
@ -1842,7 +484,7 @@ int main() {
> * ① `变量`是对程序中`数据`在内存中存储空间的抽象,如果不涉及到指针变量,那我们在编码的时候,就只需要将变量等价于内存中存储空间里面存储的数据,而不需要再去考虑编译器底层是如何转换,提高了开发效率(机器语言和汇编可不是这样的,需要关注每个细节)。
> * ② 数据类型只在`定义`变量的时候`指定`,而且必须指定;`使用`变量的时候`无需`再声明,因为此时的数据类型已经确定了。
## 5.3 内存中的数组
## 2.3 内存中的数组
* 如果我们在代码中这么定义数组,如下所示:
@ -1860,7 +502,7 @@ int main(){
* 那么,编译器就会这么处理,如下所示:
![](./assets/39.svg)
![](./assets/18.svg)
* 在代码中,我们可以直接打印数组名的内存地址,如下所示:
@ -1890,9 +532,9 @@ int main() {
# 第章:数组越界和数组溢出(⭐)
# 第章:数组越界和数组溢出(⭐)
## 6.1 数组越界
## 3.1 数组越界
* C 语言的数组是静态的,当我们定义的时候,就不能自动扩容。当我们试图访问数组的`负索引`或`超出`数组长度的索引时,就会产生`数组越界`。
@ -1944,7 +586,7 @@ int main() {
* 其结果,如下所示:
![](./assets/40.png)
![](./assets/19.png)
> [!NOTE]
>
@ -1969,7 +611,7 @@ int main() {
}
```
## 6.2 数组溢出
## 3.2 数组溢出
* 数组溢出通常是指将数据存储到一个数组中,超出了数组所能容纳的空间,那么多余的元素就会被丢弃。对于一般的数组,貌似没什么问题,如下所示:
@ -1990,7 +632,7 @@ int main() {
* 其结果,如下所示:
![](./assets/41.png)
![](./assets/20.png)
* 但是,对于字符串而言,就会出现不可控的情况,如下所示:
@ -2006,7 +648,7 @@ int main()
* 其结果,如下所示:
![](./assets/42.png)
![](./assets/21.png)
* 因为字符串的长度大于数组的长度,数组只能容纳字符串前面的一部分,即使编译器在字符串最后保存了 `'\0'`,也无济于事,因为超过数组长度的元素都会被丢弃。而 `printf()` 输出字符串时,会从第 `0` 个元素开始往后检索,直到遇见`'\0'`才停止,然后把`'\0'`前面的字符全部输出,至于何时遇到 `'\0'`,就只有上帝才能知道。
@ -2017,9 +659,9 @@ int main()
# 第C 语言中的数组 VS Java 语言中的数组(⭐)
# 第C 语言中的数组 VS Java 语言中的数组(⭐)
## 7.1 Linux 下 32 位环境的用户空间内存分布情况
## 4.1 Linux 下 32 位环境的用户空间内存分布情况
* 对于 32 位的环境而言,理论上程序是可以拥有 4GB 的虚拟地址空间的,在 C 语言中使用到的变量、函数、字符串等都会对应内存中的一块区域。
* 但是,在这 4GB 的地址空间中,要拿出一部分给操作系统内核使用,应用程序无法直接访问这一段内存,这一部分内存地址被称为`内核空间`Kernel Space
@ -2032,7 +674,7 @@ int main()
* 也就是说,应用程序只能使用剩下的 2GB 或 3GB 的地址空间,称为`用户空间`User Space
* Linux 下 32 位环境的经典内存模型,如下所示:
![](./assets/43.svg)
![](./assets/22.svg)
* 各个内存分区的说明,如下所示:
@ -2051,7 +693,7 @@ int main()
> - ② 函数被调用时,会将参数、局部变量、返回地址等与函数相关的信息压入栈中,函数执行结束后,这些信息都将被销毁。所以局部变量、参数只在当前函数中有效,不能传递到函数外部,因为它们的内存不在了。
> - ③ 常量区、全局数据区、栈上的内存由系统自动分配和释放,不能由程序员控制。程序员唯一能控制的内存区域就是`堆`Heap它是一块巨大的内存空间常常占据整个虚拟空间的绝大部分在这片空间中程序可以申请一块内存并自由地使用放入任何数据。堆内存在程序主动释放之前会一直存在不随函数的结束而失效。在函数内部产生的数据只要放到堆中就可以在函数外部使用。
## 7.2 C 语言中的数组
## 4.2 C 语言中的数组
* 之前,我们都是这么使用数组的,如下所示:
@ -2108,7 +750,7 @@ int main() {
}
```
## 7.3 Java 语言中的数组
## 4.3 Java 语言中的数组
* Java 语言和 C 语言不同Java 语言从语法层面就将数组在内存中的分配放到了`堆`中。