《 C++ 点滴漫谈: 二十五 》空指针,隐秘而危险的杀手:程序崩溃的真凶就在你眼前!
摘要
本博客全面解析了 C++ 中指针与空值的相关知识,从基础概念到现代 C++ 的改进展开,涵盖了空指针的定义、表示方式、使用场景以及常见注意事项。同时,深入探讨了 nullptr
的引入及智能指针在提升代码安全性和简化内存管理方面的优势。通过实际案例剖析,展示了空指针在程序设计中的常见应用与潜在陷阱,并结合最佳实践提出了有效避免空指针错误的方法。无论是初学者还是经验丰富的开发者,本篇博客都将帮助你全面掌握 C++ 中空指针的核心知识与高级用法,提高代码的安全性与健壮性。
1、引言
在 C++ 编程中,指针是一个极其重要且强大的工具,它允许程序员直接操作内存,从而实现高效的数据访问和灵活的程序设计。然而,指针的使用也伴随着高风险,尤其是在处理未初始化指针或空指针时,可能导致程序崩溃或引发难以排查的错误。因此,理解并正确使用空指针是每一个 C++ 开发者必须掌握的基本技能。
什么是空指针?
空指针(Null Pointer)是指不指向任何有效内存地址的指针。在 C++ 中,空指针主要用于指针的初始化、指针的有效性检查以及表示特殊状态(如函数的失败返回值)。空指针的存在使得程序能够在指针未被赋值时明确表达其状态,而不是留作未定义的悬挂状态(dangling)。
空指针的演变
在早期的 C 和 C++ 语言中,程序员通常使用宏定义的 NULL
来表示空指针。然而,由于 NULL
本质上是一个整型常量,它在某些情况下可能导致歧义或错误。为了解决这一问题,C++11 引入了 nullptr
关键字,这是一个类型安全的空指针,能够显著提高代码的可读性和可靠性。
空指针的意义
空指针不仅在传统编程中发挥重要作用,在现代 C++ 的许多特性中也占据了不可或缺的地位。例如,空指针常用于动态内存管理、智能指针、函数的默认参数值等场景。理解空指针的作用,不仅能够帮助开发者避免常见的空指针异常(如空指针解引用),还可以提升代码的健壮性和维护性。
本文目标
本博客将全面解析 C++ 中空指针的方方面面。从空指针的基本概念到现代 C++ 的改进,从实际应用场景到最佳实践,本文力图通过详实的解释和案例分析,帮助读者深入理解空指针的内涵,避免开发中因空指针引发的问题。无论是刚入门的 C++ 学习者,还是经验丰富的开发者,相信您都能在本博客中找到实用的指导和启发。
希望通过这篇文章,您不仅能够掌握空指针的基础知识,还能深刻理解空指针在实际开发中的重要性,从而写出更安全、更高效的 C++ 代码。
2、指针与空值的基础知识
在 C++ 编程中,指针是一种强大而灵活的工具,能够直接操控内存并实现动态数据结构等高级功能。然而,指针的灵活性也带来了许多潜在风险,特别是在处理空值或未初始化的指针时。因此,理解指针和空值的基础知识是编写健壮 C++ 程序的关键。
2.1、指针的基本概念
2.1.1、什么是指针?
指针是 C++ 中的一种特殊变量,它存储的是另一个变量的内存地址,而不是具体的数据值。通过指针,可以间接访问或修改存储在内存中的数据。指针的基本声明和使用如下:
int a = 42; // 普通变量
int* ptr = &a; // 指针变量, 存储变量 a 的地址
在这段代码中:
int*
表示一个指向int
类型数据的指针。&a
是取地址符,返回变量a
的内存地址。*ptr
是解引用操作,访问指针所指向的内存地址上的值。
2.1.2、指针的用途
- 动态内存分配:通过指针分配和释放内存,例如使用
new
和delete
。 - 参数传递:指针用于函数参数,以实现按地址传递(call by reference)。
- 实现复杂数据结构:如链表、树和图等。
2.1.3、指针的注意事项
指针的强大功能伴随着潜在问题:
- 未初始化指针:可能指向未知的内存地址,导致不可预知的行为。
- 悬挂指针:指针指向已释放的内存区域,可能导致崩溃或数据泄露。
2.2、空指针的概念
2.2.1、什么是空指针?
空指针(Null Pointer)是指一个指针变量不指向任何有效的内存地址。它通常用于指针初始化或作为特殊状态的标志。空指针在 C++ 中的定义可以是:
int* ptr = nullptr; // 定义一个空指针
在上面的代码中:
ptr
是一个指向int
的指针,但未指向任何内存地址。nullptr
是一种类型安全的空指针常量,从 C++11 开始引入。
2.2.2、空指针的意义
- 避免未初始化指针问题:指针在声明时初始化为空,可以明确表示 “未使用” 状态。
- 指针有效性检查:通过检查指针是否为空,避免解引用无效地址。
- 特殊状态表示:在函数中,空指针可以表示 “无返回值” 或 “无效输入”。
2.2.3、空指针的表示方式
C++ 提供了多种方式表示空指针,具体如下:
- NULL:传统的空指针表示方式,在 C 和 C++ 中被广泛使用。
- 0:C++ 中允许用整数
0
表示空指针,但可能引发歧义。 - nullptr:C++11 引入的新关键字,推荐使用的空指针表示方式。
2.3、空指针的作用
2.3.1、初始化指针时避免悬挂指针
空指针可以防止指针变量在声明后指向随机地址。例如:
int* ptr = nullptr; // 初始化为空指针
2.3.2、用于指针有效性检查
通过空指针判断,可以避免程序尝试解引用无效的地址。例如:
if (ptr != nullptr) {// 指针有效时才访问std::cout << *ptr << std::endl;
}
2.3.3、数据结构中的应用
在链表或树等数据结构中,空指针通常表示节点的结束。例如,链表节点可以定义为:
struct Node {int data;Node* next; // 初始为 nullptr 表示链表结束
};
2.4、nullptr
的引入及其重要性
2.4.1、为什么引入 nullptr
?
在 C++11 之前,NULL
被用作空指针的标准表示,但其本质是一个整型常量 0
。在某些情况下,NULL
的使用可能引发歧义。例如:
void func(int);
void func(int*);func(NULL); // 不明确调用哪个重载版本
为了解决这一问题,C++11 引入了 nullptr
。nullptr
是一个专门的空指针常量,其类型为 std::nullptr_t
,避免了 NULL
的不安全性。
2.4.2、nullptr
的优势
- 类型安全:
nullptr
不会与整数混淆。 - 可读性强:明确表示 “空指针” 意图。
- 兼容性好:支持与传统代码的兼容。
2.5、空指针与零地址的区别
空指针表示指针变量不指向任何有效的内存地址,但这并不意味着其地址为 “零地址”。在实际运行时,空指针的值依赖于编译器和操作系统,但逻辑上它表示 “未指向任何内存” 的状态。
通过以上内容,我们可以看出,理解指针和空指针的基础知识是掌握 C++ 编程的关键一步。在接下来的章节中,我们将深入探索空指针的使用场景、注意事项以及最佳实践。
3、C++ 中的空指针表示方式
在 C++ 中,空指针是一种特殊的指针值,表示指针未指向任何有效的内存地址。正确地表示和处理空指针对于避免未定义行为和保证程序的稳定性至关重要。C++ 提供了多种方式表示空指针,这些表示方式随着语言的发展也经历了演进。以下将全面介绍 C++ 中空指针的主要表示方式及其适用场景。
3.1、使用 NULL
表示空指针
3.1.1、NULL
的定义
NULL
是 C 和早期 C++ 中广泛使用的空指针常量,通常在头文件 <cstddef>
或 <stddef.h>
中定义。它的定义通常是:
#define NULL 0
因此,在代码中可以通过 NULL
来初始化或检查空指针。例如:
int* ptr = NULL; // 使用 NULL 初始化空指针
if (ptr == NULL) {std::cout << "ptr 是空指针" << std::endl;
}
3.1.2、使用 NULL
的问题
尽管 NULL
具有语义上的直观性,但它的本质是整型常量 0
,在某些情况下可能导致歧义。例如:
void func(int);
void func(int*);func(NULL); // 不明确调用哪个重载版本
在上述代码中,NULL
的整型特性可能导致编译器选择错误的重载版本,进而引发潜在问题。
3.1.3、适用场景
NULL
主要用于 C 和早期的 C++ 项目中。随着 C++11 的推出,nullptr
被引入,逐渐取代了 NULL
。
3.2、使用整数 0
表示空指针
3.2.1、整数 0
的使用
在 C 和 C++ 中,整数 0
被定义为指针的空值常量。这种用法可以追溯到 C 语言的设计初期。例如:
int* ptr = 0; // 使用整数 0 初始化空指针
if (ptr == 0) {std::cout << "ptr 是空指针" << std::endl;
}
3.2.2、整数 0
的问题
与 NULL
类似,整数 0
的使用也可能导致歧义。例如:
void func(int);
void func(int*);func(0); // 编译器选择 func(int) 而非 func(int*)
此外,直接使用 0
可能会降低代码的可读性,因为它没有明确的语义表达。
3.2.3、适用场景
虽然整数 0
是空指针的最早表示方式,但其使用场景已经被 NULL
和 nullptr
所取代,现代 C++ 中不推荐使用。
3.3、使用 nullptr
表示空指针
3.3.1、nullptr
的引入
为了解决 NULL
和整数 0
的歧义问题,C++11 引入了关键字 nullptr
。nullptr
是一种类型安全的空指针常量,其类型为 std::nullptr_t
。
int* ptr = nullptr; // 使用 nullptr 初始化空指针
if (ptr == nullptr) {std::cout << "ptr 是空指针" << std::endl;
}
3.3.2、nullptr
的优点
- 类型安全:
nullptr
是std::nullptr_t
类型,与整数0
或NULL
明确区分。 - 避免歧义:
nullptr
不会与整数混淆,从而消除了函数重载选择中的问题。 - 语义明确:
nullptr
表达了指针未指向任何有效地址的含义,增强了代码的可读性。
3.3.3、使用场景
nullptr
是现代 C++ 项目中表示空指针的推荐方式,适用于所有需要空指针的场景。它是 C++11 及更高版本的最佳实践。
3.4、不同空指针表示方式的比较
以下是 NULL
、0
和 nullptr
的特性对比:
特性 | NULL | 0 | nullptr |
---|---|---|---|
本质 | 宏定义为 0 | 整数常量 | 类型为 std::nullptr_t |
类型安全 | 否 | 否 | 是 |
易读性 | 一般 | 较差 | 较高 |
函数重载歧义 | 有可能 | 有可能 | 无 |
适用场景 | C 或早期 C++ | 早期 C 或 C++ | 现代 C++ |
3.5、示例代码:从传统到现代的空指针使用
以下示例展示了从传统的 NULL
和整数 0
到现代 nullptr
的演进:
#include <iostream>void func(int* ptr) {if (ptr == nullptr) {std::cout << "空指针" << std::endl;} else {std::cout << "指针指向有效地址" << std::endl;}
}int main() {int* ptr1 = NULL; // 传统的空指针表示方式int* ptr2 = 0; // 使用整数 0 表示空指针int* ptr3 = nullptr; // 现代 C++ 推荐的空指针表示方式func(ptr1);func(ptr2);func(ptr3);return 0;
}
运行结果:
空指针
空指针
空指针
3.6、小结
C++ 提供了多种表示空指针的方式,从早期的整数 0
和 NULL
到现代的 nullptr
,它们在功能上类似,但安全性和可读性上有显著差异。在现代 C++ 编程中,应尽量使用 nullptr
表示空指针,因为它具有类型安全性和语义明确的优势,是当前的最佳实践。理解并正确使用空指针表示方式,不仅可以减少程序中的潜在错误,还能提升代码质量。
4、空指针的典型使用场景
空指针在 C++ 中有广泛的应用,其使用贯穿于程序的设计、实现和运行的各个阶段。以下将详细介绍空指针在实际编程中的一些典型使用场景,帮助读者深入理解其重要性及正确用法。
4.1、用于初始化指针
在 C++ 中,指针未初始化时会指向一个未知地址,使用这样的指针会导致未定义行为。因此,在声明指针变量时,将其初始化为空指针是一种良好的编程习惯。
示例代码:
#include <iostream>int main() {int* ptr = nullptr; // 初始化为 nullptrif (ptr == nullptr) {std::cout << "指针未指向任何有效地址" << std::endl;}return 0;
}
场景说明:
- 空指针初始化可以避免指针悬挂或误用无效指针。
- 在调试时,也更容易发现指针未被正确赋值的问题。
4.2、用于函数参数的默认值
空指针经常用作函数参数的默认值,用于表示参数可以为空或者使用默认行为。
示例代码:
#include <iostream>void processData(int* data = nullptr) {if (data == nullptr) {std::cout << "未提供数据, 使用默认处理逻辑" << std::endl;} else {std::cout << "处理提供的数据: " << *data << std::endl;}
}int main() {processData(); // 未传递数据int value = 42;processData(&value); // 传递有效数据return 0;
}
场景说明:
- 空指针表示未传递参数或使用默认行为。
- 提高函数的灵活性和可扩展性。
4.3、用于指针生命周期管理
空指针常用于指针生命周期管理中的清理阶段。在动态内存分配中,释放内存后将指针设置为 nullptr
可以防止悬挂指针问题。
示例代码:
#include <iostream>int main() {int* ptr = new int(42); // 动态分配内存std::cout << "指针值:" << *ptr << std::endl;delete ptr; // 释放内存ptr = nullptr; // 避免悬挂指针if (ptr == nullptr) {std::cout << "指针已被释放并设置为 nullptr" << std::endl;}return 0;
}
场景说明:
- 设置为空指针可以明确表示指针不再指向有效的内存。
- 避免重复释放内存或访问已释放的内存。
4.4、用于链表和树等数据结构
在链表、树等数据结构中,空指针通常表示结点的终止或叶子结点。
链表示例代码:
#include <iostream>struct Node {int data;Node* next;Node(int value) : data(value), next(nullptr) {}
};void printList(Node* head) {Node* current = head;while (current != nullptr) {std::cout << current->data << " -> ";current = current->next;}std::cout << "nullptr" << std::endl;
}int main() {Node* head = new Node(1);head->next = new Node(2);head->next->next = new Node(3);printList(head);// 清理内存while (head != nullptr) {Node* temp = head;head = head->next;delete temp;}return 0;
}
场景说明:
- 空指针用于表示链表的结束或空链表。
- 增强了代码的可读性和逻辑清晰度。
4.5、用于异常状态或特殊值表示
在某些场景下,空指针可以用来表示函数的特殊返回值,例如在查找操作中返回空指针表示未找到目标。
示例代码:
#include <iostream>
#include <string>struct Node {std::string data;Node* next;Node(std::string value) : data(value), next(nullptr) {}
};Node* findNode(Node* head, const std::string& value) {Node* current = head;while (current != nullptr) {if (current->data == value) {return current;}current = current->next;}return nullptr; // 未找到, 返回空指针
}int main() {Node* head = new Node("Alice");head->next = new Node("Bob");head->next->next = new Node("Charlie");Node* result = findNode(head, "Bob");if (result != nullptr) {std::cout << "找到结点: " << result->data << std::endl;} else {std::cout << "未找到目标结点" << std::endl;}// 清理内存while (head != nullptr) {Node* temp = head;head = head->next;delete temp;}return 0;
}
场景说明:
- 空指针表示查找失败或目标不存在的状态。
- 提供了一种直观的错误处理方式。
4.6、用于多线程或并发编程
在多线程程序中,空指针可以用于线程间的通信或同步。例如,使用空指针表示没有新任务需要处理。
示例代码:
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>std::queue<int*> taskQueue;
std::mutex mtx;
std::condition_variable cv;void worker() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return !taskQueue.empty(); });int* task = taskQueue.front();taskQueue.pop();if (task == nullptr) { // 任务队列结束信号break;}std::cout << "处理任务: " << *task << std::endl;delete task;}
}int main() {std::thread t(worker);// 提交任务for (int i = 0; i < 5; ++i) {std::unique_lock<std::mutex> lock(mtx);taskQueue.push(new int(i));cv.notify_one();}// 添加结束信号{std::unique_lock<std::mutex> lock(mtx);taskQueue.push(nullptr);cv.notify_one();}t.join();return 0;
}
场景说明:
- 空指针用作多线程任务队列的结束信号。
- 通过空指针传递特殊含义,减少额外标志变量的使用。
4.7、小结
空指针在 C++ 中有着丰富的应用场景,无论是基础的数据结构操作,还是高级的多线程编程,其语义明确且实用。合理使用空指针不仅能提高代码的可读性和逻辑性,还能有效避免错误的发生。在现代 C++ 中,推荐优先使用 nullptr
作为空指针表示方式,以充分发挥其类型安全和语义明确的优势。
5、空指针的注意事项
在 C++ 编程中,空指针虽然有广泛的应用场景,但若使用不当,也可能引发严重的问题。以下是关于空指针的一些重要注意事项和最佳实践,帮助开发者规避常见陷阱,编写更加安全可靠的代码。
5.1、避免对空指针的解引用
空指针解引用是一个严重的编程错误,它通常会导致程序崩溃或未定义行为。解引用空指针意味着尝试访问一块不存在的内存地址,这在大多数系统中是非法的。
示例代码(错误案例):
int* ptr = nullptr;
std::cout << *ptr << std::endl; // 错误: 尝试解引用空指针
解决方法: 在解引用指针前,始终检查指针是否为空。
if (ptr != nullptr) {std::cout << *ptr << std::endl;
} else {std::cout << "指针为空, 无法解引用" << std::endl;
}
建议:
- 对指针进行解引用操作时,务必确认其指向了有效的内存地址。
- 使用智能指针(如
std::shared_ptr
和std::unique_ptr
)替代原始指针,减少空指针相关问题。
5.2、使用 nullptr
而非 NULL
或 0
在 C++ 中,空指针传统上可以用 NULL
或 0
表示,但它们都存在潜在问题。C++11 引入了关键字 nullptr
,它是一种类型安全的空指针常量,推荐在现代 C++ 中使用。
问题分析:
NULL
通常被定义为宏,可能引发类型歧义。- 使用
0
表示空指针容易混淆整型值和指针。
示例代码:
int* ptr = nullptr; // 推荐
int* ptr2 = NULL; // 不推荐
int* ptr3 = 0; // 不推荐
优点:
nullptr
的类型是std::nullptr_t
,可以避免与其他类型混淆。- 提高代码的可读性和可维护性。
5.3、动态内存分配后释放指针并设置为空
在动态内存管理中,指针释放后如果不设置为空,可能会导致悬挂指针问题(dangling pointer)。访问悬挂指针会导致未定义行为。
示例代码(问题案例):
int* ptr = new int(42);
delete ptr;
// 此时 ptr 是悬挂指针, 继续使用会导致未定义行为
std::cout << *ptr << std::endl;
正确做法:
int* ptr = new int(42);
delete ptr;
ptr = nullptr; // 设置为空, 防止悬挂指针
建议:
- 始终在释放内存后将指针设置为
nullptr
。 - 考虑使用智能指针自动管理内存,避免手动释放。
5.4、防止空指针作为有效参数传递
在函数调用中,传递空指针可能会导致程序行为异常。函数设计时应明确指出参数是否允许为空,并在函数内部进行校验。
示例代码(错误案例):
void processData(int* data) {std::cout << *data << std::endl; // 如果 data 为空, 解引用将导致崩溃
}int main() {int* ptr = nullptr;processData(ptr); // 错误: 传递空指针return 0;
}
改进方法:
void processData(int* data) {if (data == nullptr) {std::cerr << "错误: 参数为空" << std::endl;return;}std::cout << *data << std::endl;
}
建议:
- 明确参数是否允许为空,如果允许,必须在函数内部进行检查。
- 为函数提供默认行为,避免依赖外部传递空指针。
5.5、警惕空指针与非空指针的混用
在复杂的程序逻辑中,如果空指针和非空指针混用,可能导致逻辑错误。例如,在链表、树等数据结构操作中,忘记检查指针是否为空,可能会导致程序崩溃。
示例代码(问题案例):
struct Node {int data;Node* next;
};void printList(Node* head) {while (head->next != nullptr) { // 未检查 head 是否为空std::cout << head->data << " ";head = head->next;}
}
正确做法:
void printList(Node* head) {while (head != nullptr) {std::cout << head->data << " ";head = head->next;}
}
建议:
- 操作指针前始终确认其有效性。
- 避免在一个代码块中频繁对同一个指针进行多种操作。
5.6、使用空指针作为结束信号需谨慎
空指针有时被用作数据结构或线程间通信的结束信号,但必须确保其语义清晰,且不会与其他逻辑冲突。
示例代码:
#include <queue>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <iostream>std::queue<int*> taskQueue;
std::mutex mtx;
std::condition_variable cv;void worker() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return !taskQueue.empty(); });int* task = taskQueue.front();taskQueue.pop();if (task == nullptr) break; // 空指针表示结束信号std::cout << "处理任务: " << *task << std::endl;delete task;}
}
注意事项:
- 使用空指针作为信号时,必须明确其语义,确保队列中的其他元素不会被误认为是空指针。
- 定义常量或宏来表示结束信号,提高代码可读性。
5.7、小结
空指针在 C++ 编程中既是一个基础概念,也是一个潜在的陷阱。通过养成良好的编程习惯(如初始化指针、避免空指针解引用),结合现代 C++ 特性(如 nullptr
和智能指针),可以有效降低空指针带来的风险。此外,借助静态和动态分析工具,程序员能够更加自信地处理与空指针相关的问题,从而编写更安全和健壮的代码。
6、现代 C++ 对空指针的改进
C++ 自诞生以来,指针一直是其核心特性之一。然而,传统指针的灵活性带来了诸多问题,如空指针解引用和悬挂指针等。在现代 C++(C++11 及之后)中,引入了许多新特性和机制来改进空指针的表示和管理,大大提高了代码的安全性和可维护性。
6.1、引入 nullptr
在 C++11 中,引入了关键字 nullptr
,作为专门表示空指针的类型安全常量。与传统的 NULL
和 0
不同,nullptr
的类型是 std::nullptr_t
,在语义上更加明确,能够避免空指针与整数之间的混淆。
传统空指针的问题:
NULL
是宏: 在大多数实现中,NULL
被定义为0
,可能会引发类型歧义。0
表示空指针: 使用0
作为空指针在函数重载中可能导致错误。
示例:
void foo(int) {std::cout << "整数版本被调用" << std::endl;
}void foo(void*) {std::cout << "指针版本被调用" << std::endl;
}int main() {foo(0); // 调用整数版本foo(NULL); // 调用整数版本 (潜在问题)foo(nullptr); // 调用指针版本 (推荐)return 0;
}
优势:
- 明确了指针为空的语义。
- 避免了整数和指针的混淆,特别是在函数重载场景中。
6.2、引入智能指针
传统指针的一个重大问题是手动管理内存容易引发空指针、悬挂指针和内存泄漏等问题。现代 C++ 提供了智能指针(std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
),有效地解决了这些问题。
6.2.1、std::unique_ptr
- 表示独占所有权的智能指针。
- 在生命周期结束时,
std::unique_ptr
自动释放资源,并将指针设置为nullptr
,避免悬挂指针。
示例:
#include <memory>
#include <iostream>int main() {std::unique_ptr<int> ptr = std::make_unique<int>(42);std::cout << "值: " << *ptr << std::endl;// 离开作用域时, 自动释放内存, 无需手动 deletereturn 0;
}
6.2.2、std::shared_ptr
和 std::weak_ptr
std::shared_ptr
提供共享所有权,多个std::shared_ptr
可以指向同一对象。std::weak_ptr
解决了std::shared_ptr
的循环引用问题,防止内存泄漏。
示例:
#include <memory>
#include <iostream>int main() {std::shared_ptr<int> sp1 = std::make_shared<int>(42);std::shared_ptr<int> sp2 = sp1; // 引用计数增加std::cout << "引用计数: " << sp1.use_count() << std::endl;sp1.reset(); // 释放一个引用std::cout << "引用计数: " << sp2.use_count() << std::endl;return 0;
}
6.3、引入标准库工具函数
C++11 起,标准库提供了许多与指针管理相关的工具函数,如 std::addressof
和 std::pointer_traits
,这些工具增强了指针的操作能力,同时提升了代码的安全性。
6.3.1、std::addressof
避免使用 &
操作符获取对象地址时的潜在重载问题。
示例:
#include <memory>
#include <iostream>class MyClass {
public:int operator&() const {return 42; // 重载 & 操作符}
};int main() {MyClass obj;std::cout << "&obj 的值: " << &obj << std::endl; // 使用重载的操作符std::cout << "真实地址: " << std::addressof(obj) << std::endl; // 获取实际地址return 0;
}
6.3.2、std::pointer_traits
- 提供指针类型的元信息。
- 用于自定义指针类型时,增强泛型编程的能力。
示例:
#include <memory>
#include <iostream>int main() {using Ptr = int*;std::cout << "指针差值类型: " << typeid(std::pointer_traits<Ptr>::difference_type).name() << std::endl;return 0;
}
6.4、使用空指针检查工具
现代 C++ 开发中,许多工具可以帮助检测空指针相关问题。常见的静态分析和动态检测工具包括:
- 静态分析:
- Clang-Tidy:检查潜在的空指针解引用问题。
- Cppcheck:发现未初始化指针或空指针误用。
- 动态检测:
- AddressSanitizer:运行时检测内存访问问题,包括空指针解引用。
- Valgrind:发现空指针引发的崩溃或内存泄漏问题。
示例(Clang-Tidy 提示):
int* ptr = nullptr;
std::cout << *ptr << std::endl; // Clang-Tidy 提示: 潜在的空指针解引用
6.5、提高代码的可读性与安全性
现代 C++ 提供的改进不仅解决了空指针问题,还提高了代码的可读性和安全性。例如:
- 使用智能指针避免手动管理内存。
- 使用
nullptr
提升代码表达的清晰度。 - 借助标准库函数和工具函数,简化指针操作,减少错误。
示例:
#include <memory>
#include <iostream>void process(std::shared_ptr<int> sp) {if (sp == nullptr) {std::cout << "空指针" << std::endl;} else {std::cout << "值: " << *sp << std::endl;}
}int main() {std::shared_ptr<int> sp = std::make_shared<int>(42);process(sp); // 有效指针process(nullptr); // 空指针return 0;
}
6.6、小结
现代 C++ 通过引入 nullptr
、智能指针和相关工具函数,为空指针的处理提供了更安全和高效的解决方案。这些改进不仅简化了开发者的工作,还显著降低了内存泄漏和未定义行为的风险。结合现代工具链和编程习惯,开发者可以更轻松地编写健壮的程序,从而充分利用 C++ 的强大能力,同时规避空指针带来的陷阱。
7、空指针的实际案例
空指针问题在软件开发中非常常见,尤其在大型系统或底层程序设计中,如果对空指针的使用不当,可能引发程序崩溃、内存泄漏或未定义行为。以下通过多个实际案例,展示空指针的应用、常见问题及其解决方案,帮助开发者更好地理解和处理空指针。
7.1、案例一:空指针作为函数参数的应用
在许多程序设计中,空指针常被用作函数的默认参数,表示某种缺省行为。例如,一个配置管理函数接受指针参数时,如果传入空指针,则使用默认配置。
示例:
#include <iostream>
#include <string>void configure(const std::string* config) {if (config == nullptr) {std::cout << "使用默认配置" << std::endl;} else {std::cout << "加载配置: " << *config << std::endl;}
}int main() {std::string userConfig = "用户配置文件";configure(nullptr); // 使用默认配置configure(&userConfig); // 使用用户配置return 0;
}
分析与注意事项:
- 优点: 通过检查指针是否为空,可以灵活控制函数的行为。
- 注意: 在多线程环境下,确保空指针检查和实际使用之间无竞争条件。
7.2、案例二:链表的终止条件
空指针在数据结构中也非常常见,例如链表的终止条件通常以空指针表示。下面通过一个单链表的实现展示空指针的作用。
示例:
#include <iostream>struct Node {int data;Node* next;Node(int val) : data(val), next(nullptr) {}
};void printList(Node* head) {Node* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;
}int main() {Node* head = new Node(1);head->next = new Node(2);head->next->next = new Node(3);printList(head);// 清理内存while (head != nullptr) {Node* temp = head;head = head->next;delete temp;}return 0;
}
分析与注意事项:
- 空指针的作用: 链表的终止条件以空指针为标志,简化了遍历逻辑。
- 注意: 在删除链表节点时,避免悬挂指针(未将删除节点的指针置空)。
7.3、案例三:防止空指针解引用
空指针解引用是常见的程序错误,通常发生在未正确初始化指针或指针被错误修改的情况下。以下示例展示了一种防止空指针解引用的方式。
示例:
#include <iostream>void process(int* ptr) {if (ptr == nullptr) {std::cout << "指针为空, 无法处理数据" << std::endl;return;}std::cout << "数据值: " << *ptr << std::endl;
}int main() {int* validPtr = new int(42);int* nullPtr = nullptr;process(validPtr); // 有效指针process(nullPtr); // 空指针delete validPtr;return 0;
}
分析与注意事项:
- 空指针检查: 在使用指针前,应始终检查其是否为空。
- 最佳实践: 对于裸指针,建议尽量使用智能指针(如
std::unique_ptr
),以减少潜在的空指针问题。
7.4、案例四:使用智能指针解决空指针问题
现代 C++ 提供了智能指针,能够显著降低空指针和内存泄漏的风险。以下展示了 std::shared_ptr
的应用场景,避免空指针问题。
示例:
#include <memory>
#include <iostream>void useResource(std::shared_ptr<int> ptr) {if (!ptr) {std::cout << "资源为空" << std::endl;return;}std::cout << "资源值: " << *ptr << std::endl;
}int main() {std::shared_ptr<int> resource = std::make_shared<int>(100);std::shared_ptr<int> nullResource;useResource(resource); // 有效资源useResource(nullResource); // 空资源return 0;
}
分析与注意事项:
- 优点: 使用智能指针不仅解决了空指针问题,还自动管理内存。
- 注意: 确保
std::shared_ptr
的使用遵循所有权语义,避免循环引用。
7.5、案例五:空指针检查的性能优化
在高性能环境中,空指针检查可能成为性能瓶颈。现代编译器支持一些优化技术,可避免冗余检查。例如,通过引入断言机制,确保指针的有效性:
示例:
#include <cassert>
#include <iostream>void process(int* ptr) {assert(ptr != nullptr && "指针不能为空");std::cout << "数据值: " << *ptr << std::endl;
}int main() {int data = 50;process(&data); // 有效指针// 在发布模式下, assert 会被移除, 提升性能// process(nullptr); // 调试模式下触发断言return 0;
}
分析与注意事项:
- 优点: 使用
assert
可以捕获开发阶段的潜在错误。 - 注意: 断言仅在调试模式有效,生产环境应结合其他机制(如静态分析)。
7.6、小结
空指针的实际案例展示了其在函数参数、数据结构和错误处理中的广泛应用。通过分析这些案例,可以得出以下结论:
- 始终对指针进行空值检查,避免解引用空指针。
- 在现代 C++ 中,尽量使用智能指针替代裸指针。
- 借助工具链(如静态分析和断言)捕获潜在空指针问题。
- 针对性能敏感的场景,合理设计空指针检查策略。
通过遵循这些原则,可以有效提升代码的安全性和健壮性,从而避免因空指针问题导致的严重后果。
8、常见问题解答
在使用空指针时,开发者经常会遇到各种疑问和挑战。以下是一些常见问题的解答,帮助读者全面理解空指针的使用细节和最佳实践。
8.1、为什么需要空指针?不能用普通值来表示空状态吗?
空指针是一种明确的手段,用来表示指针未指向任何有效地址或资源。在一些场景下,例如动态分配的内存或函数参数,空指针比其他表示方式(如特殊值)更加直观和一致。
举例:
void setPointer(int* ptr) {if (ptr == nullptr) {std::cout << "指针为空, 未分配资源" << std::endl;} else {std::cout << "指针指向有效内存: " << *ptr << std::endl;}
}
如果用普通值(如 0
或 -1
)表示 “空” 状态,可能会与有效值混淆,从而导致不可预期的行为。
8.2、为什么不能直接解引用指针,而是需要检查是否为空?
解引用空指针会导致未定义行为,通常会触发程序崩溃或异常。因此,检查指针是否为空是一种必要的保护措施。
错误示例:
int* ptr = nullptr;
std::cout << *ptr; // 未定义行为, 可能导致程序崩溃
正确示例:
int* ptr = nullptr;
if (ptr != nullptr) {std::cout << *ptr << std::endl;
} else {std::cout << "指针为空, 无法解引用" << std::endl;
}
8.3、nullptr
和 NULL
有什么区别?应该使用哪一个?
nullptr
: 是 C++11 引入的新关键字,用于表示空指针。它是类型安全的,适用于所有指针类型。NULL
: 在 C 和早期的 C++ 中使用,通常定义为0
,在某些场景下可能导致类型不匹配的问题。
推荐使用 nullptr
:
void func(int* ptr) {if (ptr == nullptr) {std::cout << "指针为空" << std::endl;}
}int main() {func(nullptr); // 更加安全和语义清晰return 0;
}
nullptr
的类型是 std::nullptr_t
,避免了 NULL
与整数混淆的问题。
8.4、空指针是否会占用内存?
空指针本身是一个变量,它需要占用存储指针地址的内存空间。例如,在 64 位系统中,一个空指针通常占用 8 字节内存。但它指向的地址(即内容)为空,不消耗额外资源。
8.5、如何避免空指针引发的问题?
可以通过以下几种方法避免空指针问题:
- 初始化指针: 在定义指针时,将其初始化为
nullptr
,确保指针有一个已知状态。 - 检查指针: 在使用指针前,始终检查其是否为空。
- 使用智能指针: 现代 C++ 提供了智能指针(如
std::unique_ptr
和std::shared_ptr
),能够有效管理指针生命周期。 - 工具辅助: 使用静态分析工具(如 Clang-Tidy)检测潜在的空指针问题。
示例:
#include <memory>
#include <iostream>void usePointer(std::unique_ptr<int>& ptr) {if (!ptr) {std::cout << "指针为空" << std::endl;} else {std::cout << "指针值: " << *ptr << std::endl;}
}int main() {std::unique_ptr<int> ptr = std::make_unique<int>(42);usePointer(ptr);ptr.reset(); // 清空指针usePointer(ptr);return 0;
}
8.6、空指针和悬挂指针有什么区别?
- 空指针: 指针未指向任何有效地址,通常被初始化为
nullptr
。 - 悬挂指针: 指针指向的内存已经被释放,但指针本身未被重置,导致指向无效地址。野指针。
示例:
int* danglingPtr = nullptr;{int value = 10;danglingPtr = &value; // 悬挂指针
}// 此时 danglingPtr 指向已释放的内存
解决方法: 使用智能指针或在释放内存后,将指针显式设置为 nullptr
。
8.7、为什么智能指针能更好地处理空指针?
智能指针(如 std::unique_ptr
和 std::shared_ptr
)能够自动管理指针生命周期,减少手动管理时出现的错误。它们支持空状态,当未分配任何资源时,智能指针的值为 nullptr
。
示例:
#include <memory>
#include <iostream>int main() {std::shared_ptr<int> ptr1 = nullptr; // 空状态std::shared_ptr<int> ptr2 = std::make_shared<int>(42);if (!ptr1) {std::cout << "ptr1 是空的" << std::endl;}std::cout << "ptr2 的值: " << *ptr2 << std::endl;return 0;
}
智能指针还可以防止悬挂指针问题,因为它们会在指针生命周期结束时自动释放资源。
8.8、什么是空指针陷阱?如何避免?
空指针陷阱指的是未正确检查或处理空指针所引发的问题。例如,在传递指针给第三方库时,如果该库未检查指针的有效性,可能会导致程序崩溃。
避免方法:
- 在传递指针前进行检查。
- 为函数参数提供默认值(如智能指针或
nullptr
)。 - 使用 RAII(资源获取即初始化)模式,确保资源被正确管理。
8.9、如何排查空指针相关的 Bug?
排查空指针相关 Bug 时,可以采用以下方法:
- 调试器: 使用调试器(如 GDB)查看程序崩溃时的指针值。
- 日志记录: 在代码中加入日志,记录指针的状态和变化。
- 静态分析工具: 利用工具(如 Clang-Tidy 或 Coverity)自动检测空指针问题。
- 断言检查: 在关键代码路径中加入断言,确保指针有效性。
8.10、小结
通过解答这些常见问题,我们可以更深入地理解空指针的正确使用方式。空指针虽然是一个简单的概念,但在实际应用中往往隐藏着复杂性。通过学习和应用这些知识,可以大幅减少空指针引发的错误,提高代码的健壮性和可维护性。
9、结论
C++ 中的空指针是指针机制的重要组成部分,它以简洁明确的方式表示 “无效” 或 “未初始化” 的状态。随着语言的演进,从传统的 NULL
到现代 C++ 引入的 nullptr
,空指针的使用变得更加安全和直观,减少了因指针操作而引发的潜在错误。然而,空指针仍然可能导致一些严重问题,例如解引用空指针、悬挂指针和资源泄漏等,这些问题需要开发者在编码时格外注意。
通过深入分析空指针的基础知识、表示方式、使用场景和注意事项,以及结合现代 C++ 提供的智能指针等工具,我们可以更有效地避免空指针带来的陷阱。在实际开发中,通过谨慎的指针管理、充分的指针有效性检查以及对现代工具和技术的合理运用,可以显著提升代码的健壮性和可维护性。
空指针的学习不仅仅是理解其概念,更是掌握其背后的设计思想以及在实际工程中的正确用法。通过本篇博客的全面解析,相信读者已经能够深入理解空指针的方方面面,成为高质量 C++ 编码的重要基石。在未来的开发中,我们鼓励采用现代 C++ 的最佳实践,充分利用语言提供的先进特性,让指针的使用更加安全、高效且易于维护。
希望这篇博客对您有所帮助,也欢迎您在此基础上进行更多的探索和改进。如果您有任何问题或建议,欢迎在评论区留言,我们可以共同探讨和学习。更多知识分享可以访问我的 个人博客网站
相关文章:
《 C++ 点滴漫谈: 二十五 》空指针,隐秘而危险的杀手:程序崩溃的真凶就在你眼前!
摘要 本博客全面解析了 C 中指针与空值的相关知识,从基础概念到现代 C 的改进展开,涵盖了空指针的定义、表示方式、使用场景以及常见注意事项。同时,深入探讨了 nullptr 的引入及智能指针在提升代码安全性和简化内存管理方面的优势。通过实际…...
SpringBoot中Excel表的导入、导出功能的实现
文章目录 一、easyExcel简介二、Excel表的导出2.1 添加 Maven 依赖2.2 创建导出数据的实体类4. 编写导出接口5. 前端代码6. 实现效果 三、excel表的导出1. Excel表导入的整体流程1.1 配置文件存储路径 2. 前端实现2.1 文件上传组件 2.2 文件上传逻辑3. 后端实现3.1 文件上传接口…...
CodeGPT使用本地部署DeepSeek Coder
目前NV和github都托管了DeepSeek,生成Key后可以很方便的用CodeGPT接入。CodeGPT有三种方式使用AI,分别时Agents,Local LLMs(本地部署AI大模型),LLMs Cloud Model(云端大模型,从你自己…...
SpringBoot 整合 SpringMVC:配置嵌入式服务器
修改和 server 相关的配置(ServerProperties): server.port8081 server.context‐path/tx server.tomcat.uri‐encodingUTF‐8 注册 Servlet 三大组件:Servlet、Fileter、Listener SpringBoot 默认是以 jar 包的方式启动嵌入式的 Servlet 容器来启动 Spr…...
浅谈Linux 权限、压缩、进程与服务
概述 放假回家,对Linux系统的一些知识进行重新的整理,做到温故而知新,对用户权限管理、文件赋权、压缩文件、进程与服务的知识进行了一次梳理和总结。 权限管理 Linux最基础的权限是用户和文件,先了解基础的用户权限和文件权限…...
LeetCode 0040.组合总和 II:回溯 + 剪枝
【LetMeFly】40.组合总和 II:回溯 剪枝 力扣题目链接:https://leetcode.cn/problems/combination-sum-ii/ 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates…...
springCload快速入门
原作者:3. SpringCloud - 快速通关 前置知识: Java17及以上、MavenSpringBoot、SpringMVC、MyBatisLinux、Docker 1. 分布式基础 1.1. 微服务 微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自…...
实现使用K210单片机进行猫脸检测,并在检测到猫脸覆盖屏幕50%以上时执行特定操作
要实现使用K210单片机进行猫脸检测,并在检测到猫脸覆盖屏幕50%以上时执行特定操作,以及通过WiFi上传图片到微信小程序,并在微信小程序中上传图片到开发板进行训练,可以按照以下步骤进行: 1. 硬件连接 确保K210开发板…...
FlashAttention v1 论文解读
论文标题:FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness 论文地址:https://arxiv.org/pdf/2205.14135 FlashAttention 是一种重新排序注意力计算的算法,它无需任何近似即可加速注意力计算并减少内存占用。…...
Kafka 副本机制(包含AR、ISR、OSR、HW 和 LEO 介绍)
文章目录 Kafka 副本机制(包含AR、ISR、OSR、HW 和 LEO 介绍)1. 副本的基本概念2. 副本同步和一致性2.1 AR(Assigned Replicas)2.2 ISR(In-Sync Replicas)2.3 OSR(Out-of-Sync Replicas…...
QtCreator在配置Compilers时,有一个叫ABI的选项,那么什么是ABI?
问题提出 QtCreator在配置Compilers时,有一个叫ABI的选项,那么什么是ABI? ABI(Application Binary Interface)介绍 ABI(Application Binary Interface,应用二进制接口)是指应用程序与操作系统或其他程序…...
ResNet--深度学习中的革命性网络架构
一、引言 在深度学习的研究和应用中,网络架构的设计始终是一个关键话题。随着计算能力和大数据的不断提升,深度神经网络逐渐成为解决复杂任务的主流方法。然而,随着网络层数的增加,训练深度神经网络往往面临梯度消失或梯度爆炸的…...
【 软件测试项目实战】 以淘宝网购物车管理功能为例
一、测试功能模块分析 选择淘宝网购物车管理功能进行测试,核心子功能包含: 单商品添加/删除购物车商品数量修改多商品勾选与批量删除失效商品识别与处理 二、测试用例设计方法论应用 1. 等价类划分法(商品添加操作) 分析&…...
Go 中 defer 的机制
文章目录 Go 语言中 defer 的底层机制与实战解析一、defer 的执行顺序:后进先出(LIFO)示例 :多个 defer 的执行顺序 二、defer 的参数预计算:值拷贝的陷阱示例 :参数预计算的影响 三、defer 与闭包…...
智能小区物业管理系统推动数字化转型与提升用户居住体验
内容概要 在当今快速发展的社会中,智能小区物业管理系统的出现正在改变传统的物业管理方式。这种系统不仅仅是一种工具,更是一种推动数字化转型的重要力量。它通过高效的技术手段,将物业管理与用户居住体验紧密结合,无疑为社区带…...
【memgpt】letta 课程4:基于latta框架构建MemGpt代理并与之交互
Lab 3: Building Agents with memory 基于latta框架构建MemGpt代理并与之交互理解代理状态,例如作为系统提示符、工具和agent的内存查看和编辑代理存档内存MemGPT 代理是有状态的 agents的设计思路 每个步骤都要定义代理行为 Letta agents persist information over time and…...
HTML DOM 对象
HTML DOM 对象 引言 HTML DOM(文档对象模型)是现代网页开发的核心技术之一。DOM 将 HTML 或 XML 文档结构化,使其成为可编程的对象。通过 DOM,开发者可以轻松地操作网页内容、样式和结构。本文将详细介绍 HTML DOM 对象的相关知识,包括其概念、结构、操作方法以及在实际…...
高温环境对电机性能的影响与LabVIEW应用
电机在高温环境下的性能可能受到多种因素的影响,尤其是对于持续工作和高负荷条件下的电机。高温会影响电机的效率、寿命以及可靠性,导致设备出现过热、绝缘损坏等问题。因此,在设计电机控制系统时,特别是在高温环境下,…...
【09-电源线布线与覆铜 GND与转孔】
走线 从接触点处走线 TYPEC画线-加铜皮 1.关闭不需要的层(锡膏层和阻焊层和机械层) 紫色阻焊层 L: 顶层锡膏 底层锡膏 顶层阻焊 底层阻焊 2.修改线框或者贴铜 3.顶层走不过去:打四个孔 核心:走线-打孔-贴铜皮 设置孔的参数:大小和人为盖有 挨一下其他才会有网络 4个孔也要贴…...
算法题(48):反转链表
审题: 需要我们将链表反转并返回头结点地址 思路: 一般在面试中,涉及链表的题会主要考察链表的指向改变,所以一般不会允许我们改变节点val值。 这里是单向链表,如果要把指向反过来则需要同时知道前中后三个节点&#x…...
C++ 泛型编程指南02 (模板参数的类型推导)
文章目录 一 深入了解C中的函数模板类型推断什么是类型推断?使用Boost TypeIndex库进行类型推断分析示例代码关键点解析 2. 理解函数模板类型推断2.1 指针或引用类型2.1.1 忽略引用2.1.2 保持const属性2.1.3 处理指针类型 2.2 万能引用类型2.3 传值方式2.4 传值方式…...
穷举vs暴搜vs深搜vs回溯vs剪枝系列一>单词搜索
题解如下 题目:解析决策树:代码设计: 代码: 题目: 解析 决策树: 代码设计: 代码: class Solution {private boolean[][] visit;//标记使用过的数据int m,n;//行,列char…...
9 点结构模块(point.rs)
一、point.rs源码 use super::UnknownUnit; use crate::approxeq::ApproxEq; use crate::approxord::{max, min}; use crate::length::Length; use crate::num::*; use crate::scale::Scale; use crate::size::{Size2D, Size3D}; use crate::vector::{vec2, vec3, Vector2D, V…...
基于vue船运物流管理系统设计与实现(源码+数据库+文档)
船运物流管理系统目录 目录 基于springboot船运物流管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员登录 2、货运单管理 3、公告管理 4、公告类型管理 5、新闻管理 6、新闻类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考…...
海思ISP开发说明
1、概述 ISP(Image Signal Processor)图像信号处理器是专门用于处理图像信号的硬件或处理单元,广泛应用于图像传感器(如 CMOS 或 CCD 传感器)与显示设备之间的信号转换过程中。ISP通过一系列数字图像处理算法完成对数字…...
【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.12 连续数组:为什么contiguous这么重要?
2.12 连续数组:为什么contiguous这么重要? 目录 #mermaid-svg-wxhozKbHdFIldAkj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wxhozKbHdFIldAkj .error-icon{fill:#552222;}#mermaid-svg-…...
Chromium132 编译指南 - Android 篇(一):编译前准备
1. 引言 欢迎来到《Chromium 132 编译指南 - Android 篇》系列的第一部分。本系列指南将引导您逐步完成在 Android 平台上编译 Chromium 132 版本的全过程。Chromium 作为一款由 Google 主导开发的开源浏览器引擎,为众多现代浏览器提供了核心驱动力。而 Android 作…...
Jenkins未在第一次登录后设置用户名,第二次登录不进去怎么办?
Jenkins在第一次进行登录的时候,只需要输入Jenkins\secrets\initialAdminPassword中的密码,登录成功后,本次我们没有修改密码,就会导致后面第二次登录,Jenkins需要进行用户名和密码的验证,但是我们根本就没…...
#define,源文件与头文件,赋值表达式
1.#define 1.1定义 #define 是一个预处理指令,用于定义宏 宏,是预处理阶段(在编译之前)由预处理器处理的代码片段 1.2使用 1.2.1 #define 可以定义常量 #define PI 3.14159 1.2.2 #define 可以定义宏函数 #define SQUARE(x) ((…...
wordpress外贸独立站常用询盘软件
LiveChat LiveChat是一家提供实时聊天软件的公司,帮助企业通过其平台与客户进行即时通讯,提高客户满意度和忠诚度。他们的产品允许企业在网站、应用程序或电子邮件等多个渠道与客户互动,从而提升客户体验并促进销售增长。 LiveChat的软件特…...
网络爬虫学习:应用selenium获取Edge浏览器版本号,自动下载对应版本msedgedriver,确保Edge浏览器顺利打开。
一、前言 我从24年11月份开始学习网络爬虫应用开发,经过2个来月的努力,于1月下旬完成了开发一款网络爬虫软件的学习目标。这里对本次学习及应用开发进行一下回顾总结。 前几天我已经发了一篇日志(网络爬虫学习:应用selenium从搜…...
Zookeeper入门部署(单点与集群)
本篇文章基于docker方式部署zookeeper集群,请先安装docker 目录 1. docker初期准备 2.启动zookeeper 2.1 单点部署 2.2 集群部署 3. Linux脚本实现快速切换启动关闭 1. docker初期准备 拉取zookeeper镜像 docker pull zookeeper:3.5.6 如果拉取时间过长…...
2025最新在线模型转换工具onnx转换ncnn,mnn,tengine等
文章目录 引言最新网址地点一、模型转换1. 框架转换全景图2. 安全的模型转换3. 网站全景图 二、转换说明三、模型转换流程图四、感谢 引言 在yolov5,yolov8,yolov11等等模型转换的领域中,时间成本常常是开发者头疼的问题。最近发现一个超棒的…...
文件读写操作
写入文本文件 #include <iostream> #include <fstream>//ofstream类需要包含的头文件 using namespace std;void test01() {//1、包含头文件 fstream//2、创建流对象ofstream fout;/*3、指定打开方式:1.ios::out、ios::trunc 清除文件内容后打开2.ios:…...
AJAX XML
AJAX XML 引言 随着互联网技术的不断发展,Web应用对用户交互性和实时性的要求越来越高。AJAX(Asynchronous JavaScript and XML)技术的出现,为Web应用开发提供了强大的支持。AJAX技术允许Web应用在不重新加载整个页面的情况下,与服务器进行异步通信。XML作为数据传输格式…...
Linux文件原生操作
Linux 中一切皆文件,那么 Linux 文件是什么? 在 Linux 中的文件 可以是:传统意义上的有序数据集合,即:文件系统中的物理文件 也可以是:设备,管道,内存。。。(Linux 管理的一切对象…...
JavaScript常用的内置构造函数
JavaScript作为一种广泛应用的编程语言,提供了丰富的内置构造函数,帮助开发者处理不同类型的数据和操作。这些内置构造函数在创建和操作对象时非常有用。本文将详细介绍JavaScript中常用的内置构造函数及其用途。 常用内置构造函数概述 1. Object Obj…...
Shell篇-字符串处理
目录 1.变量引用 2.获取字符串长度 3.字符串截取 4.删除子字符串 5.字符串替换 总结: Bash(Shell 脚本)中的字符串处理语法。以下是对其的介绍和总结:Bash 变量可以使用不同的语法来获取、修改和删除字符串的内容。图片中列…...
Arduino大师练成手册 -- 控制 AS608 指纹识别模块
要在 Arduino 上控制 AS608 指纹识别模块,你可以按照以下步骤进行: 硬件连接 连接指纹模块:将 AS608 指纹模块与 Arduino 连接。通常,AS608 使用 UART 接口进行通信。你需要将 AS608 的 TX、RX、VCC 和 GND 引脚分别连接到 Ardu…...
C++ Primer 命名空间的using声明
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
C#,入门教程(12)——数组及数组使用的基础知识
上一篇: C#,入门教程(11)——枚举(Enum)的基础知识和高级应用https://blog.csdn.net/beijinghorn/article/details/123917587https://blog.csdn.net/beijinghorn/article/details/123917587 数组是一种数据集合,是一组…...
深入浅出并查集(不相交集合实现思路)
引言 并查集(Disjoint Set Union,简称DSU)是一种用于处理一些不交集的合并及查询问题。它主要支持两种操作:查找(Find)和合并(Union)。 查找:确定某个元素属于哪一个子…...
M|哪吒之魔童闹海
rating: 8.5 豆瓣: 8.5 上映时间: “2025” 类型: M动画 导演: 饺子 主演: 国家/地区: 中国大陆 片长/分钟: 144分钟 M|哪吒之魔童闹海 制作精良,除了剧情逻辑有一点瑕疵,各方面都很到位。总体瑕不掩瑜。 上映时间: &…...
【c++】类与对象详解
目录 面向过程思想和面向对象思想类的定义引入类的关键字类定义的两种方式类的访问限定符类的作用域类大小的计算封装 this指针类的6个默认成员函数构造函数初步理解构造函数深入理解构造函数初始化列表单参数构造函数引发的隐式类型转换 析构函数拷贝构造函数赋值运算符重载运…...
LabVIEW如何有效地进行数据采集?
数据采集(DAQ)是许多工程项目中的核心环节,无论是测试、监控还是控制系统,准确、高效的数据采集都是至关重要的。LabVIEW作为一个图形化编程环境,提供了丰富的功能来实现数据采集,确保数据的实时性与可靠性…...
Golang 并发机制-4:用Mutex管理共享资源
并发性是Go的强大功能之一,它允许多个线程(并发线程)同时执行。然而,权力越大,责任越大。当多个例程并发地访问和修改共享资源时,可能会导致数据损坏、竞争条件和不可预测的程序行为。为了解决这些问题&…...
如何用微信小程序写春联
生活没有模板,只需心灯一盏。 如果笑能让你释然,那就开怀一笑;如果哭能让你减压,那就让泪水流下来。如果沉默是金,那就不用解释;如果放下能更好地前行,就别再扛着。 一、引入 Vant UI 1、通过 npm 安装 npm i @vant/weapp -S --production 2、修改 app.json …...
从零开始:用Qt开发一个功能强大的文本编辑器——WPS项目全解析
文章目录 引言项目功能介绍1. **文件操作**2. **文本编辑功能**3. **撤销与重做**4. **剪切、复制与粘贴**5. **文本查找与替换**6. **打印功能**7. **打印预览**8. **设置字体颜色**9. **设置字号**10. **设置字体**11. **左对齐**12. **右对齐**13. **居中对齐**14. **两侧对…...
LLMs之OpenAI o系列:OpenAI o3-mini的简介、安装和使用方法、案例应用之详细攻略
LLMs之OpenAI o系列:OpenAI o3-mini的简介、安装和使用方法、案例应用之详细攻略 目录 相关文章 LLMs之o3:《Deliberative Alignment: Reasoning Enables Safer Language Models》翻译与解读 LLMs之OpenAI o系列:OpenAI o3-mini的简介、安…...
DeepSeek-R1 低成本训练的根本原因是?
在人工智能领域,大语言模型(LLM)正以前所未有的速度发展,驱动着自然语言处理、内容生成、智能客服等众多应用的革新。然而,高性能的背后往往是高昂的训练成本,动辄数百万美元的投入让许多企业和研究机构望而…...