当前位置: 首页 > news >正文

C++ 指针从入门到精通实战:全面掌握指针的概念与应用

在这里插入图片描述

C++ 指针从入门到精通实战:全面掌握指针的概念与应用

指针(Pointer)是C++中一个极其重要且强大的概念,它赋予了程序员直接操作内存的能力,从而实现高效的代码和复杂的数据结构。然而,指针的使用也伴随着诸多挑战,如内存管理、指针悬挂、野指针等问题。如果掌握得当,指针能够极大地提升C++程序的性能和灵活性。本文将从指针的基本概念入手,逐步深入到高级应用,通过详细的示例和关键注释,帮助读者从入门到精通C++指针。
在这里插入图片描述

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用,熟悉DICOM医学影像及DICOM协议,业余时间自学JavaScript,Vue,qt,python等,具备多种混合语言开发能力。撰写博客分享知识,致力于帮助编程爱好者共同进步。欢迎关注、交流及合作,提供技术支持与解决方案。
技术合作请加本人wx(注明来自csdn):xt20160813

目录

  1. 指针基础概念
    • 什么是指针
    • 指针的声明与初始化
    • 指针的运算符
  2. 指针的基本操作
    • 指针与变量的关系
    • 指针的解引用
    • 指针的比较与运算
  3. 指针与数组
    • 数组名与指针
    • 指针遍历数组
    • 多维数组与指针
  4. 指针与函数
    • 函数指针的定义与使用
    • 函数指针作为参数
    • 回调函数的实现
  5. 指针与动态内存管理
    • 动态内存分配
    • 内存释放与避免内存泄漏
    • 智能指针的介绍与使用
  6. 指针的高级应用
    • 指针的指针
    • 指向成员函数的指针
    • 指向类的指针与对象
  7. 常见指针错误与调试
    • 野指针(Dangling Pointer)
    • 悬空指针
    • NULL指针与空指针
    • 指针越界
  8. 实战案例:指针在数据结构中的应用
    • 单链表的实现
    • 二叉树的实现
    • 图的邻接表表示
  9. 总结
  10. 参考资料

指针基础概念

什么是指针

在C++中,指针是一种变量,用于存储另一个变量的内存地址。指针提供了一种间接访问和操作内存的方式,使得程序员能够高效地管理内存和数据结构。

示例:

#include <iostream>
using namespace std;int main() {int var = 10;      // 普通变量int* ptr = &var;   // 指针变量,存储var的地址cout << "var的值: " << var << endl;           // 输出:10cout << "ptr存储的地址: " << ptr << endl;      // 输出var的地址cout << "*ptr的值: " << *ptr << endl;          // 输出:10return 0;
}

输出:

var的值: 10
ptr存储的地址: 0x7ffee3b1b6fc
*ptr的值: 10

指针的声明与初始化

指针的声明语法为:类型* 指针名;。其中,类型是指针所指向的变量的类型,*用于表示这是一个指针。

示例:

double d = 3.14;
double* d_ptr = &d;  // 声明一个指向double类型的指针,并初始化为d的地址

在C++中,指针可以在声明时未初始化,但这样会导致悬空指针问题,因此最佳实践是在声明时立即初始化指针。

指针的运算符

C++中与指针相关的两个主要运算符是:

  • 取地址运算符(&):用于获取变量的内存地址。
  • 解引用运算符(*):用于访问指针所指向的变量的值。

示例:

int a = 5;
int* p = &a;  // 使用&取地址运算符
cout << "a的值: " << a << endl;      // 输出:5
cout << "*p的值: " << *p << endl;    // 输出:5*p = 10;  // 使用*进行解引用并修改a的值
cout << "修改后a的值: " << a << endl;  // 输出:10

输出:

a的值: 5
*p的值: 5
修改后a的值: 10

指针的基本操作

指针与变量的关系

指针变量存储的是其他变量的内存地址。通过指针,可以访问和修改指针所指向的变量的值。

示例:

#include <iostream>
using namespace std;int main() {int x = 25;int* p = &x;  // p指向xcout << "x的值: " << x << endl;      // 输出:25cout << "通过指针p访问x的值: " << *p << endl;  // 输出:25*p = 30;  // 通过指针p修改x的值cout << "修改后x的值: " << x << endl;  // 输出:30return 0;
}

输出:

x的值: 25
通过指针p访问x的值: 25
修改后x的值: 30

指针的解引用

解引用是指通过指针访问其所指向的变量的值。使用*运算符可以获得指针所指向的变量。

示例:

#include <iostream>
using namespace std;int main() {double pi = 3.14159;double* ptr = &pi;cout << "pi的值: " << pi << endl;      // 输出:3.14159cout << "*ptr的值: " << *ptr << endl;  // 输出:3.14159*ptr = 3.14;  // 通过指针修改pi的值cout << "修改后的pi的值: " << pi << endl;  // 输出:3.14return 0;
}

输出:

pi的值: 3.14159
*ptr的值: 3.14159
修改后的pi的值: 3.14

指针的比较与运算

指针可以进行比较和算术运算,但必须注意以下几点:

  1. 比较:只有指向同一类型的指针才可以进行比较。
  2. 算术运算:指针可以进行加减操作,但操作数必须是整数。加法和减法会根据指针类型自动调整偏移量。

示例:

#include <iostream>
using namespace std;int main() {int arr[5] = {10, 20, 30, 40, 50};int* p1 = &arr[0];int* p2 = &arr[2];// 指针比较if (p1 < p2) {cout << "p1指向的元素在p2指向的元素之前" << endl;}// 指针算术运算cout << "p1指向的元素: " << *p1 << endl;  // 输出:10p1 += 3;  // p1现在指向arr[3]cout << "经过加法后的p1指向的元素: " << *p1 << endl;  // 输出:40return 0;
}

输出:

p1指向的元素在p2指向的元素之前
p1指向的元素: 10
经过加法后的p1指向的元素: 40

指针与数组

数组名与指针

在C++中,数组名在大多数情况下会被解释为指向数组第一个元素的指针。这使得指针和数组在使用上具有高度的相似性。

示例:

#include <iostream>
using namespace std;int main() {int arr[5] = {1, 2, 3, 4, 5};int* p = arr;  // 数组名arr被当作指向第一个元素的指针cout << "arr[0]: " << arr[0] << endl;  // 输出:1cout << "*(p + 0): " << *(p + 0) << endl;  // 输出:1cout << "arr[2]: " << arr[2] << endl;  // 输出:3cout << "*(p + 2): " << *(p + 2) << endl;  // 输出:3return 0;
}

输出:

arr[0]: 1
*(p + 0): 1
arr[2]: 3
*(p + 2): 3

指针遍历数组

通过指针,可以高效地遍历数组。这种方式常用于需要高性能数据访问的场景。

示例:

#include <iostream>
using namespace std;int main() {int arr[5] = {10, 20, 30, 40, 50};int* p = arr;  // 指针指向数组第一个元素cout << "数组元素通过指针访问: ";for(int i = 0; i < 5; ++i) {cout << *(p + i) << " ";  // 指针偏移}cout << endl;return 0;
}

输出:

数组元素通过指针访问: 10 20 30 40 50 

多维数组与指针

多维数组可以使用指针进行访问,但需要理解其内存排列方式(通常是行优先)。

示例:

#include <iostream>
using namespace std;int main() {int matrix[2][3] = {{1, 2, 3},{4, 5, 6}};int (*p)[3] = matrix;  // p是指向含有3个整数的一维数组的指针cout << "矩阵元素通过指针访问:" << endl;for(int i = 0; i < 2; ++i) {for(int j = 0; j < 3; ++j) {cout << *(*(p + i) + j) << " ";}cout << endl;}return 0;
}

输出:

矩阵元素通过指针访问:
1 2 3 
4 5 6 

指针与函数

函数指针的定义与使用

函数指针是指向函数的指针变量。它可以存储函数的地址,并通过指针调用函数。

示例:

#include <iostream>
using namespace std;// 函数A
void greet() {cout << "Hello, World!" << endl;
}// 函数B
void farewell() {cout << "Goodbye!" << endl;
}int main() {// 定义函数指针,指向返回类型为void,参数为空的函数void (*funcPtr)();// 指向函数AfuncPtr = greet;funcPtr();  // 调用greet()// 指向函数BfuncPtr = farewell;funcPtr();  // 调用farewell()return 0;
}

输出:

Hello, World!
Goodbye!

函数指针作为参数

函数指针可以作为函数的参数,允许在运行时动态指定执行的函数逻辑。

示例:

#include <iostream>
using namespace std;// 定义一个接受函数指针的函数
void performOperation(int x, void (*operation)(int)) {cout << "执行操作前,x = " << x << endl;operation(x);  // 通过函数指针调用函数cout << "执行操作后,x = " << x << endl;
}// 操作函数
void increment(int& x) {x += 1;cout << "增量操作:x = " << x << endl;
}void decrement(int& x) {x -= 1;cout << "减量操作:x = " << x << endl;
}int main() {int value = 10;// 定义函数指针类型typedef void (*Operation)(int&);// 使用函数指针调用incrementOperation op = increment;performOperation(value, (void(*)(int))op);  // 需要类型转换// 使用函数指针调用decrementop = decrement;performOperation(value, (void(*)(int))op);  // 需要类型转换return 0;
}

输出:

执行操作前,x = 10
增量操作:x = 11
执行操作后,x = 11
执行操作前,x = 11
减量操作:x = 10
执行操作后,x = 10

注意: 在上述示例中,incrementdecrement函数的参数类型与performOperation函数的函数指针类型不匹配,因此需要进行类型转换。为避免这种情况,建议统一函数指针的签名。

优化后的示例:

#include <iostream>
using namespace std;// 定义函数指针类型
typedef void (*Operation)(int&);// 定义一个接受函数指针的函数
void performOperation(int x, Operation operation) {cout << "执行操作前,x = " << x << endl;operation(x);  // 通过函数指针调用函数cout << "执行操作后,x = " << x << endl;
}// 操作函数
void increment(int& x) {x += 1;cout << "增量操作:x = " << x << endl;
}void decrement(int& x) {x -= 1;cout << "减量操作:x = " << x << endl;
}int main() {int value = 10;// 使用函数指针调用incrementOperation op = increment;performOperation(value, op);  // 无需类型转换// 使用函数指针调用decrementop = decrement;performOperation(value, op);  // 无需类型转换return 0;
}

输出:

执行操作前,x = 10
增量操作:x = 11
执行操作后,x = 11
执行操作前,x = 11
减量操作:x = 10
执行操作后,x = 10

回调函数的实现

回调函数是一种通过函数指针、std::function或其他机制,在函数执行过程中动态调用指定函数的模式。回调函数广泛应用于事件处理、异步操作和库接口设计中。

示例:

#include <iostream>
#include <functional>
using namespace std;// 定义回调类型
typedef void (*Callback)(int);// 模拟事件触发函数,接受回调函数
void onEvent(int eventCode, Callback cb) {cout << "事件触发,事件代码: " << eventCode << endl;// 事件处理逻辑...cb(eventCode);  // 执行回调函数
}// 回调函数实现
void handleEvent(int code) {cout << "处理事件,事件代码: " << code << endl;
}int main() {// 注册并触发事件onEvent(100, handleEvent);return 0;
}

输出:

事件触发,事件代码: 100
处理事件,事件代码: 100

使用std::function和Lambda表达式的优化示例:

#include <iostream>
#include <functional>
using namespace std;// 使用std::function定义回调类型
typedef function<void(int)> Callback;// 模拟事件触发函数,接受回调函数
void onEvent(int eventCode, Callback cb) {cout << "事件触发,事件代码: " << eventCode << endl;// 事件处理逻辑...cb(eventCode);  // 执行回调函数
}int main() {int multiplier = 2;// 使用Lambda表达式作为回调函数Callback cb = [multiplier](int code) {cout << "处理事件,事件代码: " << code << ", 乘数: " << multiplier << ", 结果: " << code * multiplier << endl;};// 注册并触发事件onEvent(150, cb);return 0;
}

输出:

事件触发,事件代码: 150
处理事件,事件代码: 150, 乘数: 2, 结果: 300

指针与动态内存管理

动态内存分配

C++提供了newdelete运算符,用于在堆上动态分配和释放内存。这使得程序能够在运行时根据需要动态地管理内存。

示例:

#include <iostream>
using namespace std;int main() {// 动态分配单个整数int* p = new int;*p = 42;cout << "动态分配的整数值: " << *p << endl;// 动态分配整数数组int* arr = new int[5];for(int i = 0; i < 5; ++i) {arr[i] = i * 10;}cout << "动态分配的数组元素: ";for(int i = 0; i < 5; ++i) {cout << arr[i] << " ";}cout << endl;// 释放内存delete p;       // 释放单个整数delete[] arr;   // 释放数组return 0;
}

输出:

动态分配的整数值: 42
动态分配的数组元素: 0 10 20 30 40 

注意事项:

  1. 匹配分配与释放方式:使用new分配的单个对象必须使用delete释放,使用new[]分配的数组必须使用delete[]释放。
  2. 避免内存泄漏:每次动态分配的内存都必须在不再需要时释放,否则会导致内存泄漏,从而消耗系统资源。

内存释放与避免内存泄漏

内存泄漏是指程序中动态分配的内存未被释放,导致这些内存无法被重新利用。为了避免内存泄漏,必须确保每次new对应一个delete,每次new[]对应一个delete[]

示例:

#include <iostream>
using namespace std;void createMemoryLeak() {int* leakPtr = new int(100);// 忘记释放内存,导致内存泄漏
}int main() {createMemoryLeak();cout << "程序结束,内存泄漏已发生。" << endl;return 0;
}

输出:

程序结束,内存泄漏已发生。

避免方法:

  1. 手动管理:严格遵循动态分配与释放的匹配规则,确保每次new都有对应的delete
  2. 智能指针:使用C++标准库提供的智能指针(如std::unique_ptrstd::shared_ptr),自动管理内存,防止泄漏。
  3. RAII(资源获取即初始化)原则:将资源管理与对象生命周期绑定,通过构造函数获取资源,通过析构函数释放资源。

示例:使用智能指针避免内存泄漏

#include <iostream>
#include <memory>
using namespace std;int main() {// 使用unique_ptr管理单个对象unique_ptr<int> p = make_unique<int>(50);cout << "智能指针管理的整数值: " << *p << endl;// 使用unique_ptr管理数组unique_ptr<int[]> arr = make_unique<int[]>(5);for(int i = 0; i < 5; ++i) {arr[i] = i * 5;}cout << "智能指针管理的数组元素: ";for(int i = 0; i < 5; ++i) {cout << arr[i] << " ";}cout << endl;// 无需手动delete,智能指针自动释放内存return 0;
}

输出:

智能指针管理的整数值: 50
智能指针管理的数组元素: 0 5 10 15 20 

优势:

  • 自动释放:智能指针在其生命周期结束时自动释放内存,防止内存泄漏。
  • 异常安全:在发生异常时,智能指针也能确保内存被正确释放。
  • 使用简便:无需手动调用delete,减少人为错误。

智能指针的介绍与使用

C++11引入了智能指针,主要包括std::unique_ptrstd::shared_ptrstd::weak_ptr。它们提供了自动内存管理的机制,降低了内存泄漏的风险。

1. std::unique_ptr

std::unique_ptr是一个独占所有权的智能指针,不能被复制,只能被移动。适用于拥有唯一所有者的资源。

示例:

#include <iostream>
#include <memory>
using namespace std;int main() {// 创建unique_ptr,管理单个对象unique_ptr<string> ptr1 = make_unique<string>("Hello, Unique Pointer!");cout << *ptr1 << endl;// 移动所有权unique_ptr<string> ptr2 = move(ptr1);if(!ptr1) {cout << "ptr1现在为空。" << endl;}cout << *ptr2 << endl;return 0;
}

输出:

Hello, Unique Pointer!
ptr1现在为空。
Hello, Unique Pointer!

2. std::shared_ptr

std::shared_ptr是一个共享所有权的智能指针,允许多个指针共同拥有同一个资源。资源会在最后一个shared_ptr被销毁时被释放。

示例:

#include <iostream>
#include <memory>
using namespace std;int main() {// 创建shared_ptr,管理单个对象shared_ptr<int> sp1 = make_shared<int>(100);cout << "sp1引用计数: " << sp1.use_count() << endl;  // 输出:1{// 复制shared_ptr,共享所有权shared_ptr<int> sp2 = sp1;cout << "sp1引用计数: " << sp1.use_count() << endl;  // 输出:2cout << "sp2引用计数: " << sp2.use_count() << endl;  // 输出:2}// sp2已销毁cout << "sp1引用计数: " << sp1.use_count() << endl;  // 输出:1return 0;
}

输出:

sp1引用计数: 1
sp1引用计数: 2
sp2引用计数: 2
sp1引用计数: 1

3. std::weak_ptr

std::weak_ptr是一个弱引用智能指针,不增加引用计数,用于观察由shared_ptr管理的对象而不拥有资源所有权,防止循环引用。

示例:

#include <iostream>
#include <memory>
using namespace std;int main() {shared_ptr<int> sp1 = make_shared<int>(200);weak_ptr<int> wp1 = sp1;  // weak_ptr指向sp1管理的资源cout << "sp1引用计数: " << sp1.use_count() << endl;  // 输出:1if(auto sp2 = wp1.lock()) {  // 尝试提升为shared_ptrcout << "wp1成功提升,值为: " << *sp2 << endl;cout << "sp1引用计数: " << sp1.use_count() << endl;  // 输出:2} else {cout << "wp1提升失败,资源已释放。" << endl;}sp1.reset();  // 释放资源if(auto sp3 = wp1.lock()) {cout << "wp1成功提升,值为: " << *sp3 << endl;} else {cout << "wp1提升失败,资源已释放。" << endl;}return 0;
}

输出:

sp1引用计数: 1
wp1成功提升,值为: 200
sp1引用计数: 2
wp1提升失败,资源已释放。

总结:

  • std::unique_ptr:适用于拥有独占所有权的资源。
  • std::shared_ptr:适用于需要共享所有权的资源。
  • std::weak_ptr:适用于观察资源而不拥有所有权,防止循环引用。

指针的高级应用

指针的指针

指针的指针是指向指针变量的指针。通过多级指针,可以实现对多级数据结构的访问和操作。

示例:

#include <iostream>
using namespace std;int main() {int var = 50;int* p = &var;    // p指向varint** pp = &p;    // pp指向pcout << "var的值: " << var << endl;              // 输出:50cout << "*p的值: " << *p << endl;                // 输出:50cout << "**pp的值: " << **pp << endl;            // 输出:50// 修改var的值通过多级指针**pp = 100;cout << "修改后var的值: " << var << endl;        // 输出:100return 0;
}

输出:

var的值: 50
*p的值: 50
**pp的值: 50
修改后var的值: 100

应用场景:

  • 多级数据结构:如二维数组、链表的链表等。
  • 动态内存管理:可以方便地管理多维动态数组的内存。

指向成员函数的指针

指针不仅可以指向变量,还可以指向类的成员函数。通过成员函数指针,可以实现更加灵活的对象行为调用。

示例:

#include <iostream>
using namespace std;// 定义类
class MyClass {
public:void display(int x) {cout << "MyClass::display called with x = " << x << endl;}void show() {cout << "MyClass::show called." << endl;}
};int main() {MyClass obj;// 指向成员函数的指针void (MyClass::*ptr)(int) = &MyClass::display;void (MyClass::*ptrShow)() = &MyClass::show;// 调用成员函数(obj.*ptr)(10);        // 输出:MyClass::display called with x = 10(obj.*ptrShow)();     // 输出:MyClass::show called.// 使用指针操作对象的成员函数MyClass* pObj = &obj;(pObj->*ptr)(20);     // 输出:MyClass::display called with x = 20return 0;
}

输出:

MyClass::display called with x = 10
MyClass::show called.
MyClass::display called with x = 20

说明:

  1. 声明成员函数指针void (MyClass::*ptr)(int) = &MyClass::display; 定义了一个指向MyClass::display成员函数的指针。
  2. 调用成员函数
    • 通过对象(obj.*ptr)(10);
    • 通过指针(pObj->*ptr)(20);

指向类的指针与对象

指向类的指针是指向类实例的指针,通过它可以访问对象的成员函数和成员变量。

示例:

#include <iostream>
using namespace std;class Person {
public:string name;int age;void introduce() {cout << "我是" << name << ",今年" << age << "岁。" << endl;}
};int main() {Person p;p.name = "张三";p.age = 30;Person* ptr = &p;  // 指向类对象的指针// 访问成员变量cout << "姓名: " << ptr->name << endl;  // 使用箭头运算符cout << "年龄: " << ptr->age << "岁" << endl;// 调用成员函数ptr->introduce();return 0;
}

输出:

姓名: 张三
年龄: 30岁
我是张三,今年30岁。

说明:

  • 箭头运算符(->):用于通过指向对象的指针访问成员变量和成员函数。
  • 点运算符(.):用于通过对象直接访问成员。

常见指针错误与调试

在指针的使用过程中,常会遇到一些问题和错误,如野指针、悬空指针、空指针和指针越界等。了解这些错误的成因和调试方法,有助于编写更健壮的C++程序。

野指针(Dangling Pointer)

定义: 野指针指向的内存已被释放或尚未分配,指针本身未初始化或已被释放。

示例:

#include <iostream>
using namespace std;int main() {int* p = new int(10);  // 动态分配内存delete p;               // 释放内存// p现在成为野指针,指向已释放的内存cout << "*p的值: " << *p << endl;  // 未定义行为return 0;
}

解决方法:

  1. 及时将指针置为nullptr:释放内存后,将指针设置为nullptr,避免指针指向已释放的内存。
  2. 使用智能指针:智能指针自动管理内存,避免手动释放后的野指针问题。

优化示例:

#include <iostream>
#include <memory>
using namespace std;int main() {unique_ptr<int> p = make_unique<int>(10);  // 使用unique_ptr管理内存cout << "*p的值: " << *p << endl;          // 输出:10p.reset();  // 释放内存并将p置为nullptrif(p == nullptr) {cout << "p已经被重置为nullptr。" << endl;}return 0;
}

输出:

*p的值: 10
p已经被重置为nullptr。

悬空指针

定义: 悬空指针是指指向已经被释放内存的指针,和野指针类似,有时两者定义重叠。

示例:

#include <iostream>
using namespace std;int* getPointer() {int x = 100;return &x;  // 返回局部变量的地址,x在函数结束后被销毁
}int main() {int* p = getPointer();cout << "*p的值: " << *p << endl;  // 未定义行为return 0;
}

输出:

*p的值: 4201552  // 具体值不确定,属于未定义行为

解决方法:

  1. 避免返回指向局部变量的指针
  2. 使用动态内存分配或智能指针

优化示例:

#include <iostream>
#include <memory>
using namespace std;// 使用智能指针返回动态分配的内存
unique_ptr<int> getPointer() {return make_unique<int>(100);
}int main() {unique_ptr<int> p = getPointer();cout << "*p的值: " << *p << endl;  // 输出:100return 0;
}

输出:

*p的值: 100

NULL指针与空指针

定义: 空指针是指不指向任何有效对象的指针。C++11引入了nullptr关键字,用于表示空指针。

示例:

#include <iostream>
using namespace std;int main() {int* p1 = NULL;    // 使用NULL表示空指针int* p2 = nullptr; // C++11中的空指针if(p1 == nullptr) {cout << "p1是空指针。" << endl;}if(p2 == nullptr) {cout << "p2是空指针。" << endl;}return 0;
}

输出:

p1是空指针。
p2是空指针。

说明: 尽管NULL在C++中通常被定义为0((void*)0),但nullptr是专门用于表示空指针的类型安全指针,可以避免一些类型模糊的问题。

指针越界

定义: 指针越界是指指针指向的内存超出了其合法范围,如访问数组的非法索引位置。

示例:

#include <iostream>
using namespace std;int main() {int arr[3] = {1, 2, 3};int* p = arr;// 越界访问cout << "第四个元素: " << *(p + 3) << endl;  // 未定义行为return 0;
}

解决方法:

  1. 确保指针操作不超出边界
  2. 使用容器(如std::vector)和算法,减少手动指针操作。

优化示例:

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> vec = {1, 2, 3};for(auto it = vec.begin(); it != vec.end(); ++it) {cout << "元素值: " << *it << endl;}// 使用循环访问,避免越界return 0;
}

输出:

元素值: 1
元素值: 2
元素值: 3

实战案例:指针在数据结构中的应用

指针在数据结构的实现中发挥着至关重要的作用。以下通过几个常见的数据结构(单链表、二叉树和图的邻接表)展示指针的实际应用。

单链表的实现

单链表是一种动态数据结构,每个节点包含数据和指向下一个节点的指针。

示例:

#include <iostream>
using namespace std;// 定义单链表节点
struct Node {int data;Node* next;Node(int val) : data(val), next(nullptr) {}
};// 单链表类
class LinkedList {
private:Node* head;public:LinkedList() : head(nullptr) {}// 添加节点到链表末尾void append(int val) {Node* newNode = new Node(val);if(!head) {head = newNode;return;}Node* temp = head;while(temp->next)temp = temp->next;temp->next = newNode;}// 打印链表void print() {Node* temp = head;while(temp) {cout << temp->data << " -> ";temp = temp->next;}cout << "NULL" << endl;}// 释放链表内存~LinkedList() {Node* temp = head;while(temp) {Node* del = temp;temp = temp->next;delete del;}}
};int main() {LinkedList list;list.append(10);list.append(20);list.append(30);list.print();  // 输出:10 -> 20 -> 30 -> NULLreturn 0;
}

输出:

10 -> 20 -> 30 -> NULL

关键注释:

  • Node结构体:包含数据和指向下一个节点的指针。
  • LinkedList类
    • append函数:动态分配新节点,并将其添加到链表末尾。
    • print函数:遍历链表并打印每个节点的数据。
    • 析构函数:遍历链表并释放每个节点的内存,避免内存泄漏。

二叉树的实现

二叉树是一种分支结构,每个节点最多有两个子节点(左子节点和右子节点)。

示例:

#include <iostream>
using namespace std;// 定义二叉树节点
struct TreeNode {int data;TreeNode* left;TreeNode* right;TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};// 二叉树类
class BinaryTree {
private:TreeNode* root;public:BinaryTree() : root(nullptr) {}// 插入节点void insert(int val) {root = insertRec(root, val);}// 中序遍历void inorder() {inorderRec(root);cout << endl;}// 释放二叉树内存~BinaryTree() {destroyRec(root);}private:// 递归插入节点TreeNode* insertRec(TreeNode* node, int val) {if(!node)return new TreeNode(val);if(val < node->data)node->left = insertRec(node->left, val);elsenode->right = insertRec(node->right, val);return node;}// 递归中序遍历void inorderRec(TreeNode* node) {if(node) {inorderRec(node->left);cout << node->data << " ";inorderRec(node->right);}}// 递归释放内存void destroyRec(TreeNode* node) {if(node) {destroyRec(node->left);destroyRec(node->right);delete node;}}
};int main() {BinaryTree bt;bt.insert(50);bt.insert(30);bt.insert(70);bt.insert(20);bt.insert(40);bt.insert(60);bt.insert(80);cout << "中序遍历二叉树: ";bt.inorder();  // 输出:20 30 40 50 60 70 80 return 0;
}

输出:

中序遍历二叉树: 20 30 40 50 60 70 80 

关键注释:

  • TreeNode结构体:包含数据、指向左子节点和右子节点的指针。
  • BinaryTree类
    • insert函数:调用递归函数insertRec将新节点插入到合适的位置。
    • inorder函数:调用递归函数inorderRec进行中序遍历。
    • 析构函数:调用递归函数destroyRec释放所有节点的内存。

图的邻接表表示

是一种由节点(顶点)和边组成的复杂数据结构。邻接表是一种高效的图表示方法,利用指针来存储节点之间的连接关系。

示例:

#include <iostream>
#include <vector>
using namespace std;// 定义图的边
struct Edge {int dest;Edge(int d) : dest(d) {}
};// 定义图
class Graph {
private:int V;                      // 节点数量vector<vector<Edge>> adj;   // 邻接表public:Graph(int vertices) : V(vertices), adj(vertices, vector<Edge>()) {}// 添加无向边void addEdge(int src, int dest) {adj[src].emplace_back(dest);adj[dest].emplace_back(src);  // 无向图需要双向添加}// 打印图的邻接表void printGraph() {for(int v = 0; v < V; ++v) {cout << "节点 " << v << " 的邻接节点: ";for(auto& edge : adj[v]) {cout << edge.dest << " ";}cout << endl;}}
};int main() {int vertices = 5;Graph g(vertices);g.addEdge(0, 1);g.addEdge(0, 4);g.addEdge(1, 2);g.addEdge(1, 3);g.addEdge(1, 4);g.addEdge(2, 3);g.addEdge(3, 4);g.printGraph();return 0;
}

输出:

节点 0 的邻接节点: 1 4 
节点 1 的邻接节点: 0 2 3 4 
节点 2 的邻接节点: 1 3 
节点 3 的邻接节点: 1 2 4 
节点 4 的邻接节点: 0 1 3 

关键注释:

  • Edge结构体:表示图中的边,包含目的节点的编号。
  • Graph类
    • addEdge函数:向邻接表中添加新的边,实现图的构建。
    • printGraph函数:遍历邻接表并打印每个节点的邻接节点。

回调函数的优缺点

优点

  1. 灵活性高:回调函数允许在不同的场景下调用不同的函数,实现灵活的功能扩展。
  2. 代码解耦:可以将功能模块解耦,使得代码更加模块化和可维护。
  3. 事件驱动:在事件驱动编程中,回调函数是响应事件的核心机制。
  4. 异步处理:回调函数为异步编程提供了一种简单的实现方式,避免阻塞主线程。

缺点

  1. 代码复杂性增加:大量使用回调函数可能导致代码结构复杂,难以理解和维护。
  2. 调试困难:回调函数可能导致控制流不明确,增加了调试的难度。
  3. 生命周期管理:需要确保回调函数在调用前对象仍然存在,避免使用悬挂指针。
  4. 性能开销:尤其是在高频调用的场景下,回调函数可能引入额外的性能开销。

现代C++中的替代方案与改进

随着C++标准的发展,引入了一些替代回调函数的现代特性,如未来与承诺(Futures & Promises)和协程(Coroutines),这些机制提供了更高效和安全的异步编程方式。

使用未来与承诺(Futures & Promises)

**std::futurestd::promise**提供了一种机制,用于在线程间传递异步操作的结果。它们能够实现多线程间的同步和通信,而不需要显式使用回调函数。

示例:

#include <iostream>
#include <thread>
#include <future>
using namespace std;// 模拟耗时任务
int compute(int x) {cout << "计算开始,x = " << x << endl;this_thread::sleep_for(chrono::seconds(2));  // 模拟耗时cout << "计算完成,x * x = " << x * x << endl;return x * x;
}int main() {// 使用async启动异步任务,并获取futurefuture<int> fut = async(launch::async, compute, 5);cout << "主线程继续执行..." << endl;// 在需要结果时,通过future获取int result = fut.get();cout << "异步任务返回结果: " << result << endl;return 0;
}

输出:

主线程继续执行...
计算开始,x = 5
计算完成,x * x = 25
异步任务返回结果: 25

优势:

  • 简化代码:相比回调,通过futurepromise可以更直观地组织异步任务的执行和结果获取。
  • 异常处理future可以捕获并传播异步任务中的异常,提高代码的健壮性。
  • 同步与异步结合:可以灵活地在需要时同步获取异步任务的结果。

协程(Coroutines)的引入

C++20引入了协程,一种轻量级的线程机制,能够实现更高效的异步编程。协程使得异步代码看起来像同步代码,提升了代码的可读性和可维护性。

示例:

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
using namespace std;// 协程返回类型
struct ReturnObject {struct promise_type {ReturnObject get_return_object() { return {}; }suspend_never initial_suspend() { return {}; }suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() { std::terminate(); }};
};// 协程函数
ReturnObject asyncTask() {cout << "协程任务开始..." << endl;this_thread::sleep_for(chrono::seconds(2));  // 模拟耗时cout << "协程任务结束。" << endl;
}int main() {cout << "主线程继续执行..." << endl;auto task = asyncTask();  // 启动协程cout << "主线程等待协程完成..." << endl;this_thread::sleep_for(chrono::seconds(3));  // 等待协程完成cout << "主线程结束。" << endl;return 0;
}

输出:

主线程继续执行...
协程任务开始...
主线程等待协程完成...
协程任务结束。
主线程结束。

优势:

  • 简洁的异步代码:协程使得异步操作的代码结构简洁,类似于同步代码,提升了可读性。
  • 高效的上下文切换:协程的上下文切换开销低于传统线程,适合高并发场景。
  • 更好的可维护性:协程帮助维护更清晰的控制流,减少了回调嵌套。

注意事项:

  • 编译器支持:确保所使用的编译器支持C++20协程特性。
  • 理解协程机制:协程的概念相对复杂,需要深入理解其工作原理和使用场景。

总结

C++指针是掌握C++编程的基石,深入理解指针的概念、操作和应用对于编写高效、灵活的程序至关重要。本文从指针的基础概念入手,逐步探讨了指针的基本操作、与数组和函数的结合、动态内存管理、以及高级应用如多级指针和成员函数指针。通过丰富的实战案例,展示了指针在数据结构中的应用和指针相关错误的调试方法。

随着C++11及以后的标准引入了智能指针、Lambda表达式、std::function以及C++20的协程等现代特性,指针的使用变得更加安全和高效。智能指针的引入极大地减少了内存管理的复杂性,而协程则为异步编程提供了更为简洁和高性能的实现方式。

关键要点:

  • 指针基础:理解指针的声明、初始化、解引用等基本操作,熟悉指针的运算和类型转换。
  • 动态内存管理:熟练使用newdelete进行动态内存分配,避免内存泄漏和野指针问题。
  • 智能指针:利用std::unique_ptrstd::shared_ptrstd::weak_ptr自动管理内存,提高代码安全性。
  • 指针与数据结构:掌握指针在链表、树和图等数据结构中的应用,构建高效的动态数据结构。
  • 错误处理:识别和解决常见的指针错误,如野指针、悬空指针和指针越界,提升程序的稳定性。
  • 现代特性:结合C++的现代特性,如Lambda表达式、std::function和协程,编写更加灵活和高性能的代码。

指针虽然强大,但也需要谨慎使用。良好的指针管理习惯和现代C++特性的合理运用,能够帮助开发者有效地利用指针的优势,避免常见的陷阱和错误,编写出高效、安全的C++程序。


参考资料

  1. C++ Reference
  2. C++ Primer - Stanley B. Lippman 等
  3. Effective Modern C++ - Scott Meyers
  4. C++ Concurrency in Action - Anthony Williams
  5. The C++ Programming Language - Bjarne Stroustrup
  6. C++20 Coroutines
  7. Boost Libraries
  8. Smart Pointers Explained
  9. Memory Management in C++
  10. Modern C++ Design Patterns

标签

C++、指针、智能指针、动态内存管理、数据结构、函数指针、Lambda表达式、回调函数、协程、内存泄漏

版权声明

本文版权归作者所有,未经允许,请勿转载。

相关文章:

C++ 指针从入门到精通实战:全面掌握指针的概念与应用

C 指针从入门到精通实战&#xff1a;全面掌握指针的概念与应用 指针&#xff08;Pointer&#xff09;是C中一个极其重要且强大的概念&#xff0c;它赋予了程序员直接操作内存的能力&#xff0c;从而实现高效的代码和复杂的数据结构。然而&#xff0c;指针的使用也伴随着诸多挑…...

C++ 智能指针底层逻辑揭秘:优化内存管理的核心技术解读

目录 0.为什么需要智能指针&#xff1f; 1.智能指针的使用及原理 RAII&#xff1a; 智能指针的原理&#xff1a; 2.智能指针有哪些&#xff1f; std::auto_ptr std::unique_ptr std::shared_ptr std::weak_ptr 0.为什么需要智能指针&#xff1f; 想要回答这个问题&…...

Android基础入门、Android常见界面布局基础练习

第1章 Android基础入门、第2章Android常见界面布局 一. 填空题 1. (填空题)如果希望在XML布局文件中调用颜色资源&#xff0c;可以使用_____调用。 正确答案&#xff1a; (1) color 2. (填空题)Android程序入口的Activity是在_____文件中注册的。 正确答案&#xff1a; (1…...

Spring Cloud主要组件介绍

一、Spring Cloud 1、Spring Cloud技术概览 分为:服务治理,链路追踪,消息组件,配置中心,安全控制,分布式任务管理、调度,Cluster工具,Spring Cloud CLI,测试 2、注册中心:常用注册中心(Euerka[AP]、Zookeeper[CP]) 1)Euerka Client(服务提供者)=》注册=》Eue…...

【7】深入学习Buffer缓冲区-Nodejs开发入门

深入学习Buffer缓冲区 前言ASCII码GBK/GB2312UnicodeJavascript转换 BufferBuffer的作用Buffer的创建Buffer.allocBuffer.allocUnsafe(size)Buffer.allocUnsafeSlow(size)Buffer.from(array)Buffer.from(arrayBuffer[, byteOffset[, length]])Buffer.from(buffer)Buffer.from(s…...

酶动力学参数预测,瓶颈识别……中科院深圳先进技术研究院罗小舟分享AI在酶领域的创新应用

蛋白质&#xff0c;作为生命的基石&#xff0c;在生命活动中发挥着关键作用&#xff0c;其结构和功能的研究&#xff0c;对创新药物研发、合成生物学、酶制剂生产等领域&#xff0c;有着极其重要的意义。但传统蛋白质设计面临诸多难题&#xff0c;蛋白质结构复杂&#xff0c;序…...

Dockerfile

Dockerfile Dockerfile 是一个文本文件&#xff0c;其内包含了一条条指令&#xff0c;每一条指令构建镜像的一层&#xff0c;因此每一条指令的内容&#xff0c;就是描述该层应当如何构建。 定制镜像&#xff0c;可以将镜像制作的每一层的修改、安装、构建、操作的命令&#xf…...

Redis高频面试题(含答案)

当然可以&#xff0c;Redis 是面试中非常常见的高频考点&#xff0c;尤其在后台开发、分布式系统、缓存设计等方向&#xff0c;面试官常常通过 Redis 来考察你的高并发处理能力、系统设计能力和对缓存一致性理解。 以下是一些典型 Redis 的面试场景题目类型和你可以如何回答的…...

#3 物联网 的标准

商业化的技术都有标准&#xff0c; 标准的本质就是 可以重复多次实现的方法。而这些方法都是设定物联网的那些人布局的&#xff0c;当然在保证按方法操作的结果是属于物联网这个基本的操作里面&#xff0c;藏着的是对某些利益团队的维护&#xff0c;这里大家知道就可以了。 除 …...

Moviepy 视频编辑的Python库,可调整视频分辨率、格式

MoviePy简介 MoviePy 是一个用于视频编辑的Python库&#xff0c;支持视频剪辑、和合成、转码等多种操作&#xff0c;主要有点&#xff1a; 基于 FFmpeg&#xff1a;能够处理几乎所有常见的视频格式。 修改视频分辨率 方法一:指定新的宽度和高度 from moviepy.editor import V…...

【LeetCode 热题 100】哈希 系列

&#x1f4c1;1. 两数之和 本题就是将通过两层遍历优化而成的&#xff0c;为什么需要两层遍历&#xff0c;因为遍历 i 位置时&#xff0c;不知道i-1之前的元素是多少&#xff0c;如果我们知道了&#xff0c;就可以通过两数相加和target比较即可。 因为本题要求返回下标&#xf…...

蓝光三维扫描:汽车冲压模具与钣金件全尺寸检测的精准解决方案

随着汽车市场竞争日趋激烈&#xff0c;新车型开发周期缩短&#xff0c;安全性能要求提高&#xff0c;车身结构愈加复杂。白车身由多达上百个具有复杂空间型面的钣金件&#xff0c;通过一系列工装装配、焊接而成。 钣金件尺寸精度是白车身装配精度的基础。采用新拓三维XTOM蓝光…...

鲲鹏+昇腾部署集群管理软件GPUStack,两台服务器搭建双节点集群【实战详细踩坑篇】

前期说明 配置&#xff1a;2台鲲鹏32C2 2Atlas300I duo&#xff0c;之前看网上文档&#xff0c;目前GPUstack只支持910B芯片&#xff0c;想尝试一下能不能310P也部署试试&#xff0c;毕竟华为的集群软件要收费。 系统&#xff1a;openEuler22.03-LTS 驱动&#xff1a;24.1.rc…...

面试篇 - GPT-1(Generative Pre-Training 1)

GPT-1&#xff08;Generative Pre-Training 1&#xff09; ⭐模型结构 Transformer only-decoder&#xff1a;GPT-1模型使用了一个12层的Transformer解码器。具体细节与标准的Transformer相同&#xff0c;但位置编码是可训练的。 注意力机制&#xff1a; 原始Transformer的解…...

探索机器人创新技术基座,傅利叶开源人形机器人 Fourier N1

一&#xff0e;傅利叶为什么要开源&#xff1f; 2025年3月17日&#xff0c;傅利叶正式开源全尺寸人形机器人数据集Fourier ActionNet。 2025年4月11日&#xff0c;傅利叶正式发布首款开源人形机器人 Fourier N1。 傅利叶为什么要做这些开源工作呢&#xff1f;4月11日&#x…...

正则表达式和excel文件保存(python)

正则表达式 import re data """ <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8" /> <title>测试页面</title> </head> <body> <h1>《人工智能的发展趋势分析报…...

无人船 | 图解基于视线引导(LOS)的无人艇制导算法

目录 1 视线引导法介绍2 LOS制导原理推导3 Lyapunov稳定性分析4 LOS制导效果 1 视线引导法介绍 视线引导法&#xff08;Line of Sight, LOS&#xff09;作为无人水面艇&#xff08;USV&#xff09;自主导航领域的核心技术&#xff0c;通过几何制导与动态控制深度融合的机制&am…...

大腾智能获邀出席华为云2025生态大会,携全栈工业软件助力产业智能升级

4月10日-4月11日&#xff0c;以“聚力共创&#xff0c;加速行业智能跃迁”为主题的华为云生态大会2025在安徽芜湖召开。大腾智能受邀出席此次盛会&#xff0c;与众多行业精英、生态伙伴齐聚一堂&#xff0c;深度参与前沿技术演示、生态伙伴签约及商业场景共创&#xff0c;与行业…...

Java基础关键_037_Java 常见新特性

目 录 一、新语法 1.JShell 2.try-with-resources &#xff08;1&#xff09;jdk 7 之前 &#xff08;2&#xff09;jdk 7 之后 &#xff08;3&#xff09;jdk 9 之后 3.局部变量类型判断&#xff08;不推荐&#xff09; 4.instanceof 的模式匹配 &#xff08;1&a…...

鸿蒙公共通用组件封装实战指南:从基础到进阶

一、鸿蒙组件封装核心原则 1.1 高内聚低耦合设计 在鸿蒙应用开发中&#xff0c;高内聚低耦合是组件封装的关键准则&#xff0c;它能极大提升代码的可维护性与复用性。 从原子化拆分的角度来看&#xff0c;我们要把复杂的 UI 界面拆分为基础组件和复合组件。像按钮、输入框这…...

IntelliJ 配置(二)配置相关类库(2)LineMarkerProvider

一、介绍 LineMarkerProvider 是 IntelliJ 平台插件开发中的一个接口&#xff0c;它的作用是在编辑器左侧的“行标记区域”&#xff08;就是代码行号左边那一栏&#xff09;添加各种图标、标记或导航链接。比如Java 类中看到的&#xff1a; 小绿色三角形&#xff08;可以点击运…...

红宝书第四十二讲:Angular核心特性精讲:依赖注入 RxJS整合

红宝书第四十二讲&#xff1a;Angular核心特性精讲&#xff1a;依赖注入 & RxJS整合 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、依赖注入&#xff08;Dependency Injection&#xff09;&#xff1a;快…...

AD917X系列JESD204B MODE7使用

MODE7特殊在F8&#xff0c;M4使用2个复数通道 CH0_NCO10MHz CH1_NCO30MHZ DP_NCO50MHz DDS1偏移20MHz DDS2偏移40MHz...

软考高级系统架构设计师-第11章 系统架构设计

【本章学习建议】 根据考试大纲&#xff0c;本章不仅考查系统架构设计师单选题&#xff0c;预计考12分左右&#xff0c;而且案例分析和论文写作也是必考&#xff0c;对应第二版教材第7章&#xff0c;属于重点学习的章节。 软考高级系统架构设计师VIP课程https://edu.csdn.net/…...

中和农信的“三农”服务密码:科技+标准化助力乡村振兴

作为中国农村市场最大的专注服务农村小微客户的“三农”综合服务机构&#xff0c;中和农信凭借多年积累的农村服务经验&#xff0c;成功从单一小额信贷机构转型为覆盖金融、生产、生活及生态服务的综合型“三农”服务平台。近期&#xff0c;中和农信在由中保保险资产登记交易系…...

【Redis】布隆过滤器应对缓存穿透的go调用实现

布隆过滤器 https://pkg.go.dev/github.com/bits-and-blooms/bloom/v3 作用&#xff1a; 判断一个元素是不是在集合中 工作原理&#xff1a; 一个位数组&#xff08;bit array&#xff09;&#xff0c;初始全为0。多个哈希函数&#xff0c;运算输入&#xff0c;从而映射到位数…...

MyBatis-Plus笔记(下)

注解 tablename注解 - 描述&#xff1a;表名注解&#xff0c;标识实体类对应的表 - 使用位置&#xff1a;实体类 代码举例&#xff1a; TableName//可以不加&#xff0c;使用实体类的名字作为表名&#xff01;忽略大小写 //BaseMapper->User实体类-》实体类名-》表名数据…...

【项目管理】第14章 项目沟通管理-- 知识点整理

项目管理-相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 项目管理知识域 知识点: (项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域) 对应:第6章-第19章 第6章 项目管理概论 4分第13章 项目资源管理 3-4分第7章 项目…...

3个关键数据解密:首航上市如何重构ebay电商新能源供应链?

3个关键数据解密&#xff1a;首航上市如何重构eBay电商新能源供应链&#xff1f; 在跨境电商圈&#xff0c;一个新玩家的崛起往往意味着新的格局变动。2024年&#xff0c;伴随一家名为“首航”的新能源企业在港股成功上市&#xff0c;整个eBay类目的供应链悄然掀起新一轮洗牌。…...

《华为云Node.js部署:从开发环境到生产上线的完整指南》

目录 引言第一步&#xff1a; 重置密码第二步&#xff1a;连接到服务器第三步&#xff1a;安装必要软件第四步&#xff1a;创建项目目录第五步&#xff1a;将代码上传到服务器1、安装 FileZilla2、打开FileZilla&#xff0c;连接到您的服务器&#xff1a;3、连接后&#xff0c;…...

【网络原理】TCP/IP协议五层模型

目录 一. 协议的分层 二. OSI七层网络协议 三. TCP/IP五层网络协议 四. 网络设备所在分层 五. 封装 六. 分用 七. 传输中的封装和分用 八. 数据单位术语 一. 协议的分层 常见的分层为两种OSI七层模型和TCP/IP五层模型 为什么要协议分层&#xff1f; 在网络通信中&…...

Asp.Net Core学习随笔

学习自BLBL杨中科老师 依赖注入(Dependency Injection) 依赖注入是实现控制反转(Inversion Of Control 即IOC)的一种方式(还有一种叫服务定位器的实现,但是不如依赖注入好用),软件开发中实现解耦常用的方式. 比如吃饭 ​1. 传统写法&#xff08;没有DI&#xff0c;紧耦合&a…...

基于PHP的酒店网上订房系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 酒店服务是旅游行业的一个重要组成部分&#xff0c;它的作用已经从过去的单一的住宿、结算帐务向全面、高水平的服务型酒店转变。酒店的服务工作贯穿于整个酒店的市场营销、预定、入住、退房、结账等环节&#xff0c;酒店要提高整体工作水平&#xff0c;简化工作程序&…...

《MySQL从入门到精通》

文章目录 《MySQL从入门到精通》1. 基础-SQL通用语法及分类2. 基础-SQL-DDL-数据库操作3. 基础-SQL-DDL-表操作-创建&查询4. 基础-SQL-DDL-数据类型及案例4.1 数值类型4.2 字符串类型4.3 时间和日期类型 5. 基础-SQL-DDL-表操作-修改&删除5.1 DDL-表操作-修改5.2 DDL-表…...

MySQL聚合查询

聚合查询 group by...

生信初学者教程(三十四):文章的方法

文章目录 介绍数据收集和整理数据整合差异基因分析功能富集分析免疫浸润分析候选标记物识别诊断ROC曲线单细胞分析统计方法介绍 在数据分析进行的同时,我们可以逐步撰写方法部分,确保其与结果紧密相连。一旦结果部分完成,方法部分应根据结果的逻辑顺序进行分类和组织。在描…...

算力云平台部署—SadTalker的AI数字人视频

选择算力 部署选择 选择镜像 机器管理 控制台 通过平台工具进入服务器 认识管理系统 打开命令行 进入目录 stable-diffusion-webui# cd 增加执行权限 chmod x ./webui.sh 运行命令 bash ./webui.sh sudo apt install -y python3 python3-venv git 安装软件 Creating the …...

iPhone相册导出到电脑的完整指南

iPhone相册导出到电脑的完整指南 本文介绍通过数据线连接实现iPhone照片视频传输到电脑的标准操作方法&#xff0c;适用于需要备份移动设备影像资料的用户。 环境准备 使用原装Lightning或USB-C数据线连接设备与电脑需在电脑端安装设备管理工具&#xff08;如克魔助手&#…...

【数据结构】励志大厂版·初阶(复习+刷题):复杂度

前引&#xff1a;从此篇文章开始&#xff0c;小编带给大家的是数据结构初阶的刷题讲解 &#xff0c;此类文章将简略的包含相关知识&#xff0c;详细的思路拆分讲解&#xff0c;分析每一题的难点、易错点&#xff0c;看见题目如何分析&#xff0c;以上就是小编预备的内容&#x…...

Nginx底层架构(非常清晰)

目录 前言&#xff1a; 场景带入&#xff1a; HTTP服务器是什么&#xff1f; 反向代理是什么&#xff1f; 模块化网关能力&#xff1a; 1.配置能力&#xff1a; 2.单线程&#xff1a; 3.多worker进程 4.共享内存&#xff1a; 5.proxy cache 6.master进程 最后&…...

Golang|Channel 相关用法理解

文章目录 用 channel 作为并发小容器channel 的遍历channel 导致的死锁问题用 channel 传递信号用 channel 并行处理文件用channel 限制接口的并发请求量用 channel 限制协程的总数量 用 channel 作为并发小容器 注意这里的 ok 如果为 false&#xff0c;表示此时不仅channel为空…...

智能合约安全审计平台——以太坊虚拟机安全沙箱

目录 以太坊虚拟机安全沙箱 —— 理论、设计与实战1. 引言2. 理论背景与安全原理2.1 以太坊虚拟机(EVM)概述2.2 安全沙箱的基本概念2.3 安全证明与形式化验证3. 系统架构与模块设计3.1 模块功能说明3.2 模块之间的数据流与安全性4. 安全性与密码学考量4.1 密码学保障在沙箱中…...

趣说区块链隐私智能合约Shielder 实现原理

目录 核心理念 Deposit Withdraw Shielder 是 Aleph Zero 上的智能合约,它利用 zk-SNARK 技术实现隐私支付以及与 DeFi 的隐私交互。这与常规区块链的完全透明性形成鲜明对比,常规区块链允许追踪单个用户与链上合约以及其他用户的所有交互。Shielder 通过使第三方链观察者…...

TCPIP详解 卷1协议 五 Internet协议

5.1——Internet协议 IP是TCP/IP协议族中的核心协议。所有TCP、UDP、ICMP和IGMP数据都通过IP数据报传输。IP 提供了一种尽力而为、无连接的数据报交付服务。“尽力而为”的含义是不保证 IP 数据报能成功到达目的地。任何可靠性必须由上层&#xff08;例如TCP&#xff09;提供。…...

基于Oracle ADG通过dblink创建物化视图同步数据到目标库

基于Oracle ADG通过dblink创建物化视图同步数据到目标库 环境说明&#xff1a;源端环境Oracle ADG一主一备&#xff0c;版本11.2.0.4&#xff0c;目标端版本11.2.0.4&#xff0c;测试通过dblink方式在目标库创建物化视图同步ADG备库的数据。 PROD --> STANDBY – > TAR…...

openGauss新特性 | 自动参数化执行计划缓存

目录 自动化参数执行计划缓存简介 SQL参数化及约束条件 一般常量参数化示例 总结 自动化参数执行计划缓存简介 执行计划缓存用于减少执行计划的生成次数。openGauss数据库会缓存之前生成的执行计划&#xff0c;以便在下次执行该SQL时直接使用&#xff0c;可…...

qt中的正则表达式

问题&#xff1a; 1.在文本中把dog替换成cat&#xff0c;但可能会把dog1替换成cat1&#xff0c;如果原本不想替换dog1&#xff0c;就会出现问题 2文本中想获取某种以.txt为结尾的多有文本&#xff0c;普通的不能使用 3如果需要找到在不同的系统中寻找换行符&#xff0c;可以…...

开源项目 | 17款云原生安全相关的扫描和平台类开源工具

“ 随着云计算技术的不断发展&#xff0c;越来越多的企业开始将应用程序和数据存储到云上。然而&#xff0c;云安全问题也随之而来&#xff0c;因此&#xff0c;开源云原生安全工具的需求也越来越大。在本文中&#xff0c;我们将介绍一些流行的开源云原生安全工具&#xff0c;以…...

力扣面试150题—旋转图像和矩阵置零

Day21 题目描述 思路 矩阵转置 在将列反转 1 2 3 4 5 6 7 8 9 转置 1 4 7 2 5 8 3 6 9 反转 7 4 1 8 5 2 9 6 3 class Solution {public void rotate(int[][] matrix) { //分为两步 矩阵转置&#xff0c;将列倒序 int x0; int nmatrix.length; //转…...

ScholarCopilot:“学术副驾驶“

这里写目录标题 引言&#xff1a;学术写作的痛点与 AI 的曙光ScholarCopilot 的核心武器库&#xff1a;智能生成与精准引用智能文本生成&#xff1a;不止于“下一句”智能引用管理&#xff1a;让引用恰到好处 揭秘背后机制&#xff1a;检索与生成的动态协同快速上手&#xff1a…...