RoadMap6:C++的引用与指针

摘要:本文首先介绍 C++ 的内存模型和变量周期作为知识背景,接着对C++中的引用和指针(原始指针和智能指针)进行介绍。

1. 对象生命周期

什么是对象生命周期?简单来说,对象生命周期指的是:对象从创建直到被释放的时间跨度。很自然地,我们会意识到不是所有变量的创建方式和释放时间都是一样的,据此,我们把对象的生命周期氛围四种类型:静态存储周期、线程存储周期、自动存储周期和动态存储周期。

  1. 静态存储周期: Static Storage Duration

静态存储周期类型的对象在程序执行开始的时候就会分配内存,直至整个程序结束了才会释放。主要包括:全局变量、静态的类数据成员和函数中的局部变量,如下例子所示:

// 1. 全局变量
int global_var = 10;
// 2. 类中的静态数据成员
class MyClass{static int static_var_class;
}
// 3. 函数中的静态局部变量
int myFunc(){static int static_var_func;
}
  1. 线程存储周期: Thread Storage Duration

线程存储周期类型的对象只会在指定的线程内进行内存的分配与释放。在多线程编程中,为了避免数据紊乱,可以使用该方法,当然以下提及的自动存储周期严格来说也能算是线程周期,只不过这个线程是代码默认的主线程,因此不需要额外标注。如下例子展示如何使用线程存储周期的变量:

thread_local int thread_var;
  1. 自动存储周期: Automatic Storage Duration

自动存储周期类型的对象只会存在于其被声明和定义的作用域内,一旦退出作用域则自动释放,如函数的参数或者其内部定义的局部变量。这是最常见或者说默认的定义类型,不需要额外的关键字,以下例子展示的是函数内部定义的局部变量:

void myFunction() {int local_var;           // Automatic storage duration
}
  1. 动态存储周期: Dynamic Storage Duration

动态存储周期类型的对象在程序执行的时候通过关键字 new 或 malloc 实时分配内存,直至整个作用域退出也不会自动释放内存,必须通过使用关键字 delete 或 free 函数进行手动释放,否则会造成内存泄漏的问题。如下例子展示如何分配和释放一个原始指针变量:

int* ptr = new int;  // 定义一个原始指针
delete ptr;  // 释放原始指针

1*. 补充:内存泄漏

内存泄漏:指的是程序从堆分配内存但是不把内存释放到操作系统中,导致内存耗尽或者程序奔溃。以下一个例子展示内存泄漏:

void memory_leak(){int* ptr = new int[100];  // 创建一个原始指针并指向一个整型数组// 其他功能代码 ...// 确实手动释放内存操作: delete[] ptr;
}   // 导致内存泄漏:函数作用域结束了,ptr 指针已经没用了,但是没有释放内存

除了通过使用关键字 delete 或 free 函数进行手动释放外,还可以通过智能指针、RAII(Resource Acquisition Is Initialization)和C++标准库的容器(vector)来进行自动释放。

2. 内存模型

为什么需要了解C++的内存模型?内存模型定义了如何在C++去存储和使用数据(与变量的生命周期对应),了解C++的有利于优化对内存资源的使用和整个程序的表现。

C++的数据模型主要包括四个部分:栈、堆、数据段和代码段。

  1. 栈内存

自动生命周期的变量,如函数参数或局部变量都是使用栈的形式进行存储的。栈内存通过编译器进行管理,可以实现自动分配和释放。根据栈 先进后出 的特定,很容易知道最后定义的变量其实是最先释放的。

  1. 堆内存

堆内存则被用于动态生命周期变量,如通过 new 关键字手动定义的对象。根据堆 logn 的查找复杂度特定,很容易知道由堆内存管理的对象存储空间更大,但查找速度更快。

  1. 数据段

数据段包括两个部分:初始化数据段和未初始化数据段。两者的区别在于是否在变量声明的同时做定义,数据段主要包括:全局变量、静态变量和常变量。以下例子进行简单解释:

// 初始化数据段
int global_var = 10;  // 全局变量
static int static_var = 20;  // 静态变量
const int const_var = 30; // 常变量// 未初始化数据段
int global_var2;  // 只是声明了变量,但是没有对取值进行初始化定义
  1. 代码段

代码段,也成为文本段,用于存储程序的可执行代码(机器语言),通常存在仅读的内存,防止被意外修改;

3. 引用 reference

引用,常跟别名联系在一起,变量的引用和变量本身共享同一块内存,修改变量的引用时,变量本身的取值也会发生改变,通俗来说,两者只是一个对象的两个名字而已,故称为别名。在另一个角度,引用操作可以看作一个常指针,一旦这个常指针存储了地址,这个地址是无法修改的。

  1. 引用的声明与初始化:见例子
int raw = 10;  // 原始变量
int& ref = raw; // 创建一个引用变量 ref 指向(引用)变量 raw
raw = 20;
std::cout << "ref is: " << ref << endl; // 修改原数据的取值,引用变量的取值也会发生变换,反之亦然;
  1. 引用作为函数参数:浅拷贝/地址传递
void swap(int& a, int& b) {int temp = a;a = b;b = temp;
}int main() {int x = 5, y = 10;cout << "Before Swap: x = " << x << " y = " << y << endl; // Outputs 5 10swap(x, y);cout << "After Swap: x = " << x << " y = " << y << endl;  // Outputs 10 5
}

4. 指针

指针,本质上也是一个变量,只是这个变量存储的是另外一个变量/函数的地址/首地址。

4.1 原始指针

原始指针指的是直接存储其他低层次数据的地址的指针,如 int/float/char 、数组等。可以通过关键字 new/new[] 来创建指针,通过关键字 delete/delete[] 来释放内存,如下面例子所示:

int* ptr_int = new int;  // 创建整型指针
float* ptr_array = new float[100];  // 创建浮点型数组for(i=0; i<100;i++){ptr_array[i] = i; // 数组指针的使用;
}delete[] ptr_array;  // 释放数组指针
delete ptr_int;  // 释放整型指针

4.2 智能指针

智能指针相对原始指针,一个显著的区别在于智能指针不需要手动释放。智能指针主要包括:unique指针、shared指针和weak指针。

4.2.1 unique指针

unique指针可通过 std::unique_ptr 标准库创建,本质上是一个用于管理单个对象或者数组的模板类。

unique,顾名思义,唯一的。它表示每个对象/数组只能被一个unique指针所指,这个对象/数组可以可以通过所有权的转移,实现被另一个unique指针所指,但是无法同时被两个unique指针所指。

为什么需要unique指针?

unique指针具有避免悬垂指针、减少内存泄漏和避免手动释放内存的好处!
对于原始指针,手动释放内存可以避免内存泄漏,这个在1.1*有提及到。那么指针悬空指针是什么?悬空指针的定义是:指针最初指向的内存已经被释放了的一种指针,其所指向的地址存储的值是无法预测的随机值,以下举例说明出现指针悬空的情形。而在unique指针根本就不存在 delete unique_p1语句,也不存在两个unique指针指向同一个变量,故而指针悬空的问题。

#include<iostream>void func(int* p){// 局部作用域int var = 5;p = &var;std::cout << *p << std::endl;  // 输出:5
}int main(){// 情形1:指针所指的内存被释放,则指针悬空,返回值无法估计int* p1 = new int;*p1 = 5;std::cout << *p1 << std::endl;  // 输出:5delete p1;std::cout << *p1 << std::endl;  // 输出:-1152576448// 情形2:两个指针指向同一块内存,其中指针所指的内存被释放,则另外一个指针悬空,返回值无法估计int* p2 = new int;int* p3 = new int;*p2 = 5;p3 = p2;std::cout << *p3 << std::endl;  // 输出:5delete p2;std::cout << *p3 << std::endl;  // 输出:-2100685696// 情形3:指针所指向的局部变量退出作用域,则局部变量被自动释放,指针悬空int* p4 = new int;func(p4);std::cout << *p4 << std::endl;  // 输出:-1163005939
}

接下来就是 unique指针 如何使用?包括如何创建 unique 指针,如何转移变量的所有权,以及如何自定义删除智能指针;

#include<iostream>
#include<memory> // 1. 引入 memory 头文件int main(){// 2. 初始化变量:创建unique指针指向整型变量的两种方式std::unique_ptr<int> p1(new int(666));  // 所指内存存储取值为666的整型变量std::unique_ptr<int> p2 = std::make_unique<int>(999);  // 更常用std::cout << *p1 << ", " << *p2 << std::endl; //输出: 666, 999// 3. 创建数组:创建unique指针指向整型数组的两种方式std::unique_ptr<int[]> p3(new int[10]);  // 长度为10的整型数组,数组取值未初始化std::unique_ptr<int[]> p4 = std::make_unique<int[]>(10);   // 更常用for(int i=0;i<10;i++){p3[i] = i;p4[i] = i;std::cout << p3[i] << ", " << p4[i] << std::endl;}// 4. 变量所有权的转移std::unique_ptr<int> p5 = std::move(p1); // p5 拥有变量,而指针 p1 自动销毁if(p1){std::cout << "p1 owns the object" << std::endl;}else if (p5){std::cout << "p5 owns the object" << std::endl;   // 输出:p5 owns the object}// 5. 自定义析构函数:智能指针默认会自动销毁,但是也可以自定义销毁方法;struct MyDeleter{void operator()(int* ptr){std::cout << "Custom Deleter: Deleting pointer" << std::endl;delete ptr;}};// std::unique_ptr<int, MyDeleter> p6 = std::make_unique<int, MyDeleter>(999, MyDeleter()); 使用此方法自定义析构函数会报错std::unique_ptr<int, MyDeleter> p7(new int(999), MyDeleter());return 0; //主函数结束后自动调用 MyDeleter() 删除指针,输出:Custom Deleter: Deleting pointer
}

4.2.2 shared 指针

unique 指针提到:每个对象/数组只能被一个unique指针所指,这个对象/数组可以可以通过所有权的转移,实现被另一个unique指针所指,但是无法同时被两个unique指针所指。

那么很自然的一个想法就是:存不存在一种智能指针,可以实现多个指针指向同一个变量?
答案是可以的,这种指针就是 shared 指针。

那么这种多个指针指向同一个变量智能指针会导致什么问题吗
很自然的一个问题就是:被多个指针所指的变量什么时候才会销毁?举个例子就是10个shared指针指向同一个变量,那么其中的一个或两个指针销毁后,被指的变量还在不在?当然,为了避免悬空指针,我们通过希望的是所有指针销毁后,变量才被销毁。为了实现这个直观的想法,不得不引入一个引用计数的概念,因此,每当增加一个shared指针指向变量,引用计数 +1,当引用计数等于0的时候,证明已经没有指针指向该变量了,该变量就可以自动销毁了。

那么我们下面用两个智能shared指针指向类对象来说明引用计数的使用方法:

#include<iostream>
#include<memory> // 1. 引入 memory 头文件// 2. 定义一个类
class MyClass{public:// 类里面只有构造函数和析构函数,通俗来说就是在对象的创建和销毁时就会调用该函数// 我们在构造函数和析构函数print一些内容就可以知道被shared指针所指的对象何时创建/销毁MyClass(){ std::cout << "Object is Constructed !" << std::endl;};~MyClass(){ std::cout << "Object is Destructed !" << std::endl;};
};int main(){std::shared_ptr<MyClass> p1(new MyClass());{// 以下进入局部作用域std::shared_ptr<MyClass> p2 = p1; // 类对象同时被 p1 和 p2 所指,引用计数为 2std::cout << "Inside the inner scope." << std::endl;// 退出局部作用域}// 退出局部作用域,引用计数减少为 1std::cout << "Outside the inner scope." << std::endl;
}

该段程序依次打印的内容是:

Object is Constructed !
Inside the inner scope.
Outside the inner scope.
Object is Destructed !

Destructed 语句在 Outside the inner scope语句之后,证明了只有引用计数为0,才会销毁变量;

4.2.3 weak 指针

弱指针,也叫做弱智能指针,顾名思义也就是处于智能指针和原始指针的指针类型。它能够处理 share 指针 中存在的循环引用的问题。

很自然什么是循环引用呢?以下举个例子说明:

#include<iostream>
#include<memory> // 1. 引入 memory 头文件// 2. 定义一个类
class MyClassB;
class MyClassA{public:MyClassA(){ std::cout << "Object A is Constructed !" << std::endl;};~MyClassA(){ std::cout << "Object A is Destructed !" << std::endl;};std::shared_ptr<MyClassB> pB;
};
class MyClassB{public:MyClassB(){ std::cout << "Object B is Constructed !" << std::endl;};~MyClassB(){ std::cout << "Object B is Destructed !" << std::endl;};std::shared_ptr<MyClassA> pA;
};
int main(){// while(True)  // 3. 如果添加 while 循环会造成内存验证泄漏,可能导致死机,欢迎试一试{// 以下进入局部作用域// 4. 创建两个 shared 指针,std::shared_ptr<MyClassA> p1(new MyClassA());  // 对象 A 的引用计数为 1std::shared_ptr<MyClassB> p2(new MyClassB());   // 对象 B 的引用计数为 1// 两个指针的成员函数互相指向对方, -> 表示取成员变量p1->pB = p2;  // 对象 B 的引用计数为 2p2->pA = p1;  // 对象 A 的引用计数为 2std::cout << "Inside the inner scope." << std::endl;// 退出局部作用域,智能指针无法自动释放}std::cout << "Outside the inner scope." << std::endl;
}

该段程序依次打印的内容是:

Object A is Constructed !
Object B is Constructed !
Inside the inner scope.
Outside the inner scope.

Destructed 语句在退出局部作用域之后(Inside the inner scope之后,Outside the inner scope.之前)并没有打印,证明了循环引用导致退出局部作用域后,引用计数仍为2,不会销毁变量,导致内存泄漏;如此一来,A和B都互相指着对方吼,“放开我的引用!“,“你先发我的我就放你的!”,于是悲剧发生了。

那么接下来的问题就是:什么是 weak 指针,它是如何避免内存泄漏的?
weak 指针和 shared 指针的显著区别在于 weak 指针不会增加被指对象的引用计数。这能保证当一个 shared 指针和一个weak指针同时指向一个对象时,只要 shared 指针退出作用域后,这个对象就会被自动销毁。

那么 weak 指针的使用方法和 shared 指针有什么区别?如何去使用 weak 指针?
在使用 weak 指针时,必须要使用 lock() 函数基于weak指针创建一个新的 shared 指针,然后在新的作用域里面安全地使用这个新的 shared 指针,以下我们将基于上述循环引用的例子,将类A的shared指针转化为weak指针,讲述如何避免内存泄漏的。

#include<iostream>
#include<memory> // 1. 引入 memory 头文件// 2. 定义一个类
class MyClassB;
class MyClassA{public:MyClassA(){ std::cout << "Object A is Constructed !" << std::endl;};~MyClassA(){ std::cout << "Object A is Destructed !" << std::endl;};std::weak_ptr<MyClassB> pB;  // 使用弱指针
};
class MyClassB{public:MyClassB(){ std::cout << "Object B is Constructed !" << std::endl;};~MyClassB(){ std::cout << "Object B is Destructed !" << std::endl;};std::shared_ptr<MyClassA> pA;void DoSomething(){std::cout << "Doing something..." << std::endl;}
};
int main(){// while(True)  // 3. 如果添加 while 循环会造成内存验证泄漏,可能导致死机,欢迎试一试{// 以下进入局部作用域// 4. 创建两个 shared 指针,std::shared_ptr<MyClassA> p1(new MyClassA());  // 对象 A 的引用计数为 1std::shared_ptr<MyClassB> p2(new MyClassB());   // 对象 B 的引用计数为 1// 两个指针的成员函数互相指向对方, -> 表示取成员变量p1->pB = p2;  // weak指针不增加引用计数,对象 B 的引用计数为 1p2->pA = p1;  // 对象 A 的引用计数为 2if (auto shareFromWeak = p1->pB.lock()){  // 通过lock函数创建新的 shared 指针,shareFromWeak->DoSomething();  // 在新的局部作用域里面安全间接调用std::cout << "Shared uses count: " << shareFromWeak.use_count() << std::endl; //此时对象 B 的引用计数为 2}// 退出 if 函数的局部作用域,shareFromWeak指针自动释放,对象 B 的引用计数变为 1std::cout << "Inside the inner scope." << std::endl;// 退出局部作用域,智能指针自动释放}std::cout << "Outside the inner scope." << std::endl;
}

该段程序依次打印的内容是:

Object A is Constructed !
Object B is Constructed !
Doing something...
Shared uses count: 2
Inside the inner scope.
Object B is Destructed !
Object A is Destructed !
Outside the inner scope.

可以看到,退出局部作用域后,对象A和对象B都得到了释放,也就是说避免了循环引用导致的内存泄漏。

4. 总结

在本博客中,为了讲述 智能指针这一个概念,我们首先铺垫了一些基础知识,例如变量的声明周期和C++的内存模型,这对于理解内存的释放和局部作用域等概念非常有用。接着我们快速地介绍了引用和原始指针,针对原始指针的内存管理(释放和泄漏问题),我们进一步解释了智能指针,这包括 unique, shared 和 weak指针。其中,我们着重地介绍了shared因为循环引用导致的内存泄漏问题,以及如何使用weak指针避免这个循环引用,使得智能指针能够正确释放!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/591906.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python序列之字典

系列文章目录 Python序列之列表Python序列之元组Python序列之字典&#xff08;本篇文章&#xff09;Python序列之集合 Python序列之字典 系列文章目录前言一、字典是什么&#xff1f;二、字典的操作1.创建&#xff08;1&#xff09;通过{}、dict()创建&#xff08;2&#xff0…

TDD-LTE TAU流程

目录 1. TAU成功流程 1.1 空闲态TAU 1.2 连接态TAU 2. TAU失败流程 当UE进入一个小区&#xff0c;该小区所属TAI不在UE保存的TAI list内时&#xff0c;UE发起正常TAU流程&#xff0c;分为IDLE和CONNECTED&#xff08;即切换时&#xff09;下。如果TAU accept分配了一个新的…

浅析PCIe 6.0功能更新与实现的挑战-5

设备Ready报告机制 Device Readiness Status (DRS) 是PCIe规范中引入的一种机制&#xff0c;旨在改进设备初始化和就绪状态的检测与报告。 在以往的PCIe版本中&#xff0c;系统通常依赖于固定的超时机制来判断设备是否已经成功初始化并准备好进行数据传输。然而&#xff0c;这…

Linux命令和介绍

常见目录介绍 / 根目录 /home/username 普通用户的家目录 /etc 配置文件目录 /bin 命令目录 /sbin 管理命令目录 /usr/bin/sbin 系统预装的其他命令 su - root #切换为root用户 一.万能的帮助命令 1.man 帮助 &#xff08;manual的缩写&#xff09; 路径&#xff1a;…

JavaWeb之jQuery

28、jQuery 28.1、jQuery的概述 概念&#xff1a;一个JavaScript框架。简化JS开发 jQuery是一个快速、简洁的JavaScript框架&#xff0c;是继Prototype之后又一个优秀的JavaScript代码库&#xff08;或JavaScript框架&#xff09;。jQuery设计的宗旨“write Less&#xff0c…

LeetCode每日一题:1154. Day of the Year

文章目录 一、题目二、题解 一、题目 Given a string date representing a Gregorian calendar date formatted as YYYY-MM-DD, return the day number of the year. Example 1: Input: date “2019-01-09” Output: 9 Explanation: Given date is the 9th day of the year…

K8S学习指南(52)-k8s包管理工具Helm

文章目录 引言Helm 基本概念Helm 的架构Helm 使用示例1. 安装 Helm2. 初始化 Helm3. 创建一个 Chart4. 编辑 Chart5. 打包 Chart6. 发布 Chart7. 部署 Release Helm 的高级用法1. 使用 Helm Secrets 进行敏感信息加密2. 使用 Helmfile 进行多Chart管理 Helm 的进阶主题1. Helm …

AI绘图之风景画

这一段时间AI画图比较火&#xff0c;笔者也尝试了一些工具&#xff0c;在使用的过程中发现midjourney比较适合小白&#xff0c;而且画的画比较符合要求。质量也高。当然AI时代的来临大家也不要太慌&#xff0c;毕竟人才是最重要的&#xff0c;AI还是要靠人输入内容才可以生成内…

线程死锁检测组件逻辑与源码

死锁介绍 任务的执行体之间互相持有对方所需的资源而不释放&#xff0c;形成了相互制约而都无法继续执行任务的情况&#xff0c;被称为“死锁”。 死锁案例 线程A持有锁a不释放&#xff0c;需要去获取锁b才能继续执行任务&#xff0c; 线程B持有锁b不释放&#xff0c;需要去…

k8s陈述式资源管理(命令行)

1、资源管理 &#xff08;1&#xff09;陈述式资源管理&#xff08;常用——查、增&#xff09; 使用kubectl工具进行命令行管理 ①特点&#xff1a;对资源的增删查比较方便&#xff0c;对改不友好 ②优点&#xff1a;90%以上的场景都可以满足 ③缺点&#xff1a;命令冗长…

React Grid Layout基础使用

摘要 React Grid Layout是一个用于在React应用程序中创建可拖拽和可调整大小的网格布局的库。它提供了一个灵活的网格系统&#xff0c;可以帮助开发人员构建响应式的布局&#xff0c;并支持拖拽、调整大小和动画效果。本文将介绍如何使用React Grid Layout来创建自适应的布局。…

canvas绘制圆点示例

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

如何移除视频中的背景音乐或人物声音

移除视频声音是将视频指定的声音移除&#xff0c;可以选择移除人物声音还是视频的背景音乐&#xff0c;方便实现二次创作。 小编给大家推荐一些方法帮助大家更轻松地移除视频中的背景音乐或人物声音&#xff0c;有兴趣的朋友请自行百度查找&#xff0c;或小程序查找 1、方法&a…

跟我用路由器学Linux编程实例

跟我用路由器学Linux编程实例 本专栏文章以梅林、openwrt等linux路由为基础硬件&#xff0c;和笔者一起学习使用shell语言。带你从编写简单的插件开始&#xff0c;学习怎么折腾路由器&#xff0c;顺便学会编程。软路由用的都是Linux基础系统&#xff0c;学会了在路由上编程&am…

P8722 [蓝桥杯 2020 省 AB3] 日期识别(C语言)

题目描述 小蓝要处理非常多的数据, 其中有一些数据是日期。 在小蓝处理的日期中有两种常用的形式&#xff1a;英文形式和数字形式。 英文形式采用每个月的英文的前三个字母作为月份标识&#xff0c;后面跟两位数字表示日期&#xff0c;月份标识第一个字母大写&#xff0c;后…

2023福建省“信息安全管理与评估“---单机取证(高职组)

2023福建省“信息安全管理与评估“---单机取证(高职组) 2023福建省“信息安全管理与评估“---单机取证(高职组):公众号:鱼影安全(有联系方式)Evidence1:Evidence2:Evidence3:Evidence4:Evidence5:Evidence6:Evidence7:Evidence8:Evidence9:Evidence10:<

从入门到精通,30天带你学会C++【第十四天:洛谷选题讲解】

彩蛋 这么长的目录应该没人看吧。 Bi------------------------------------------------------------------------------- 目录 Everyday English 前言 函数 sqrt ( ) for循环 题目网址 分析题意 思路点拨 优化程序 AC代码 AC截图 数学 if判断 题目网址 思路…

视频剪辑技巧:轻松制作短视频,一键合并、剪辑、添加背景音乐

随着社交媒体的普及&#xff0c;短视频已是分享生活、娱乐和传递信息的重要方式。如果要制作短视频&#xff0c;但又不熟悉复杂的视频编辑软件&#xff0c;那么本文将讲解一些实用的视频剪辑技巧&#xff0c;轻松制作出高质量的短视频。现在一起来看看云炫AI智剪如何批量合并视…

光纤通信系统中常见类型的损伤和均衡方法

相干光纤通信系统中常见的损伤和均衡算法 光纤通信中的系统损伤损耗色度色散 CD偏振相关损耗PMD 偏振模色散RSOP 偏振态旋转PDL 偏振相关损耗 CFO 载波频率偏移和CPN 载波相位噪声 光纤通信系统损伤均衡算法 光纤通信中的系统损伤 信道中常见的损伤包括损耗、色散和偏振相关损…

【HarmonyOS开发】分布式应用的开发实践(元旦快乐)

元旦快乐&#xff0c;再见2023&#xff0c;加油2024&#xff0c;未来可期&#xff0c;愿新的一年带来健康、幸福和成功&#xff01;&#x1f4aa; &#x1f4aa;&#x1f4aa; 多种设备之间能够实现硬件互助、资源共享&#xff0c;依赖的关键技术包括分布式软总线、分布式设备虚…