This commit is contained in:
许大仙 2024-08-08 17:21:30 +08:00
parent 78dd9b9355
commit 50a8b60bc5
2 changed files with 110 additions and 49 deletions

View File

@ -2110,3 +2110,33 @@ cp perf /usr/bin/
![](./assets/196.png)
![](./assets/197.png)
## 9.7 C 语言的学习技巧
* 对于大部分的初学者, 学习 C 语言的目的可能是为了成为一名合格的程序员,开发出优秀的软件。但是,在学习了 C 语言的基本语法后,却发现只能在`控制台``黑底白字`)上玩玩,没有漂亮的用户界面以及优化、人性化的交互。于是,开始学习数据结构、算法、数据库、操作系统,越陷越深,越来越迷茫,不知道学习 C 语言能做什么,认为学习编程很难,开始怀疑自己,直到放弃!!!
* 其实C 语言本身就是一门非常简单的语言,提供的实用功能不多,大部分的时候需要`借助`操作系统、第三方库以及以及一些硬件才能发挥它的威力!!!
> [!IMPORTANT]
>
> * ① 学习 C 语言仅仅是让你踏上程序员之路的第一步而已,只学习 C 语言也做不了什么。
> * ② 系统、扎实的学习 C 语言可以让你了解底层硬件、一些简单的数据结构和算法,并培养计算机思维。
* C 语言是一门通用性的语言并没有针对某个领域进行优化在实际项目中C 语言主要用于较底层的开发,例如:
* Windows、Linux、Unix 等操作系统的内核 90% 以上都使用 C 语言开发。
* 开发硬件驱动,让硬件和操作系统连接起来,这样用户才能更有效的使用硬件。
* 单片机和嵌入式属于软硬件的结合,是使用 C 语言最多的地方。
* 开发系统组件或服务,用于支撑上层应用。
* ……
* 既然 C 语言的应用很多,为什么感觉学习它还是做不了什么?答案就是`生态`。
> [!IMPORTANT]
>
> 现代化的高级编程语言的流行程度,除了和编程语言的设计是否优秀有关,最主要的原因就是`生态`。
>
> * ① 很多编程语言都自带`标准库`语言本身提供的开箱即用Java、Go 等。
> * ② 很多编程语言都有自己的`包管理器`用于管理第三方库解决方案Java 中的 Maven、Gradle、Go 中的 go modules JavaScript 的 npm 等。
>
> 遗憾的是C 语言的`标准库`非常简单输入输出、文件操作、日期时间、字符串处理、内存管理等。C 语言的`第三方库`也非常稀少,更别提缺少自己的包管理器。
>
> 不过,现在 C 语言社区也开始诞生了一些包管理器Conan 和 vcpkg 也有自己的项目构建工具cmake 、xmake 等。

View File

@ -300,74 +300,105 @@ int main() {
>
> 下文中提及的`指针`都是`指针变量`,不再阐述!!!
## 3.3 指针变量的定义
## 3.2 普通变量和指针变量的区别
在 CLion 中使用 GDB 调试时,可以通过反编译代码来查看指针变量和普通变量的区别。下面是具体的步骤:
### 设置 GDB 调试器
1. 打开 CLion并加载你的项目。
2. 确保你使用的是带有 GDB 支持的工具链(如 GCC 工具链)。
3. 在 CLion 的设置中,确保调试器设置为 GDB。
### 编译你的代码
确保在编译你的代码时使用了调试信息生成选项(如 `-g`)。你可以在 CMakeLists.txt 文件中添加以下行:
## 3.4 指针的作用
```cmake
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
* 查询数据。
* 存储数据。
* 参数传递。
* 内存管理。
在 Java 中引用数据类型的向上类型转换upcasting和向下类型转换downcasting是面向对象编程中常见的操作。这些转换是 Java 继承体系和多态性的重要部分。我们先分别介绍向上类型转换和向下类型转换,然后讨论它们在 C 语言中指针的类似操作。
### 向上类型转换Upcasting
向上类型转换是将一个子类对象引用转换为父类对象引用。由于子类继承了父类的所有方法和属性,子类对象也包含父类对象的所有部分,因此这种转换是安全且隐式的。
**例子:**
```java
class Animal {
void makeSound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
Animal animal = dog; // Upcasting, 隐式转换
animal.makeSound(); // 输出Bark
}
}
```
在这个例子中,`Dog` 类型的对象 `dog` 被转换为 `Animal` 类型。尽管 `animal` 引用的实际对象是 `Dog`,但在编译时,`animal` 被视为 `Animal` 类型。
### 开始调试
1. 设置断点:在代码中你想要查看变量的地方设置一个断点。
2. 启动调试:点击调试按钮启动调试会话。
### 向下类型转换Downcasting
### 查看变量
当调试器在断点处暂停时,你可以在调试控制台中使用 GDB 命令来查看变量。以下是一些常用的 GDB 命令:
向下类型转换是将一个父类对象引用转换为子类对象引用。由于父类对象不一定具有子类的所有方法和属性,因此这种转换需要显式进行,并且在运行时进行类型检查(使用 `instanceof` 关键字来确保安全)。
- `print variable_name`:打印变量的值。
- `info locals`:打印当前作用域中的所有局部变量。
- `whatis variable_name`:显示变量的类型。
**例子:**
```java
public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // Upcasting
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // Downcasting, 显式转换
dog.makeSound(); // 输出Bark
}
}
}
```
在这个例子中,`animal` 引用的对象实际是 `Dog` 类型,在向下转换之前使用 `instanceof` 检查以确保安全。
### 区分指针变量和普通变量
指针变量和普通变量的主要区别在于它们的类型和存储的内容。指针变量存储的是地址,而普通变量存储的是实际的值。通过 GDB 命令可以很容易地看到这种区别。
### 区别
#### 示例
假设有如下代码:
- **向上类型转换**:隐式的,安全的,因为子类是父类的扩展。
- **向下类型转换**:显式的,可能不安全,需要运行时检查,因为父类不一定具有子类的特性。
```cpp
#include <iostream>
### C 语言中的指针转换
在 C 语言中,指针的转换类似于引用类型的转换,但由于 C 语言没有继承和多态的概念,其转换更多是基于内存布局。
**例子:**
```c
#include <stdio.h>
void printInt(void* ptr) {
printf("%d\n", *(int*)ptr);
}
int main() {
int a = 10;
int *p = &a;
std::cout << "a: " << a << ", p: " << p << std::endl;
int x = 10;
void* voidPtr = &x; // Upcasting, 隐式转换
printInt(voidPtr); // 输出10
int* intPtr = (int*)voidPtr; // Downcasting, 显式转换
printf("%d\n", *intPtr); // 输出10
return 0;
}
```
在这个例子中,`void*` 是通用指针类型,可以指向任何类型的数据。将 `int*` 转换为 `void*` 是隐式的,而将 `void*` 转换为 `int*` 是显式的。由于 C 没有类型检查,因此这种转换需要程序员自己确保安全。
在 CLion 中设置断点并开始调试,程序将在 `std::cout` 行暂停。此时在调试控制台中输入以下命令:
- `print a`:输出变量 `a` 的值,应该是 `10`
- `print p`:输出指针变量 `p` 的值,即 `a` 的地址。
- `print *p`:输出指针 `p` 指向的值,即 `10`
通过这些命令,你可以看到指针变量 `p` 实际上存储的是一个地址,而普通变量 `a` 存储的是一个整数值。
#### 使用反汇编
在某些情况下,你可能需要查看反汇编代码来更深入地理解变量的存储方式。使用以下命令可以查看当前函数的反汇编代码:
- `disassemble`:反汇编当前函数的代码。
- `x/4wx &a`:查看变量 `a` 的内存内容。
### 总结
在 CLion 中使用 GDB 调试时,通过查看变量值和反汇编代码,可以清楚地区分指针变量和普通变量。指针变量存储地址,而普通变量存储实际的值,通过适当的 GDB 命令可以轻松辨别两者的区别。
总结:
- Java 中的向上类型转换和向下类型转换是为了支持多态性和继承,向上转换是安全的,向下转换需要显式进行并且进行运行时检查。
- C 语言中的指针转换没有多态性和继承的概念,但有类似的指针类型转换操作,程序员需要确保转换的安全性。