【C++11 —— 智能指针】

C++11 —— 智能指针

  • 为什么需要智能指针
  • 内存泄漏
    • 什么是内存泄漏,内存泄漏的危害
    • 内存泄漏分类
    • 如何避免内存泄漏
  • 智能指针的使用及原理
    • RAII
    • 智能指针的原理
    • std::auto_ptr
    • unique_ptr
    • std::shared_ptr
      • shared_ptr的线程安全问题
  • 智能指针的历史

为什么需要智能指针

下面我们先分析一下下面这段程序有没有什么内存方面的问题?

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

我们可以看到,上面的p1p2都是int *指针,如果new操作抛出了异常,而且没有正确的处理异常,就可能会导致内存泄漏或者未定义行为,内存泄漏在C++11是非常严重的问题,所以就得使用上节课介绍的try/catch块来捕获异常。

int div()
{///
}void Func()
{int* p1 = nullptr;int* p2 = nullptr;try {p1 = new int;p2 = new int;}catch (exception& e) {cout << e.what() << endl;}  if (p1) delete p1;if (p2) delete p2;
}int main()
{try {Func();}catch (exception& e) {cout << e.what() << endl;}return 0;
}

但是使用了try/catch块来捕获异常就会导致我们的代码显得异常的繁杂,因为new几次就得try几次,所以,我们可以使用智能指针来解决这个问题。

内存泄漏

什么是内存泄漏,内存泄漏的危害

内存泄漏是指程序在动态分配内存后,在不需要使用该内存时未能及时释放,结果导致内存空间被一直占用,直到程序运行结束。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,造成了内存的浪费。


内存泄漏的危害:

  1. 性能下降:内存泄漏会导致程序占用的内存越来越多,从而降低程序的运行速度。
  2. 系统崩溃:严重的内存泄漏会导致程序占用的内存耗尽,从而引发系统崩溃或程序异常退出。
  3. 资源浪费:内存泄漏会导致系统中的可用内存越来越少,从而浪费系统资源。

代码示例
下面的代码示例展示了内存泄漏的一种情况:

void MemoryLeaks()
{// 1. 内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2. 异常安全问题int* p3 = new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放delete[] p3;
}

在这个例子中,有两种情况会导致内存泄漏:

  1. p1p2指针指向的内存在函数退出时没有被释放,造成内存泄漏。
  2. 如果Func()函数抛出异常,delete[] p3语句将不会被执行,导致p3指针指向的内存泄漏。

为了避免内存泄漏,我们需要在合适的时候释放不再使用的内存,并且要注意异常安全问题。

内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

堆内存泄漏(Heap Leak)
 堆内存泄漏是指程序在堆上动态分配内存后,在不需要使用该内存时未能及时释放,导致该内存空间无法被重复利用。这种情况下,已分配但无法访问的内存块就会一直占用着,直到程序结束。

堆内存泄漏的主要原因包括:

  1. 忘记释放动态分配的内存: 使用malloccallocnew 等函数在堆上分配内存后,忘记调用 freedelete 来释放内存。
  2. 丢失指向动态内存的指针: 如果程序丢失了指向动态分配内存的指针,就无法释放该内存。
  3. 异常处理不当: 如果在抛出异常的情况下没有释放内存,也会导致内存泄漏。
  4. 复杂数据结构的内存管理不善: 在链表、树等复杂数据结构中,如果内存管理不当也容易造成泄漏。


系统资源泄漏
系统资源泄漏指程序使用系统分配的资源(如套接字、文件描述符、管道等)后,没有及时关闭或释放,导致系统资源被占用而无法被其他程序使用。这种情况下,系统资源会被逐渐耗尽,严重影响系统性能和稳定性。

系统资源泄漏的主要原因包括:

  1. 忘记关闭打开的文件或释放其他系统资源。
  2. 在异常情况下没有正确处理和释放系统资源。
  3. 在复杂的控制流程中,某些分支没有释放资源。

如何避免内存泄漏

  1. 使用智能指针: C++11及以上版本提供了unique_ptrshared_ptr等智能指针,可以自动管理动态分配的内存,在指针销毁时自动释放内存。
  2. 尽量减少动态内存分配: 减少动态内存分配的数量可以降低内存泄漏的风险。
  3. 优先使用栈内存: 在可能的情况下,优先使用栈内存而不是堆内存。栈内存在函数返回时会自动释放。
  4. 养成良好的内存管理习惯: 在动态分配内存后,要及时检查是否成功分配。在不需要时立即释放内存。
  5. 使用内存分析工具: 可以使用ValgrindAddressSanitizer 等工具来检测内存泄漏。
  6. 编写异常安全的代码: 在可能抛出异常的地方,要确保即使发生异常也能正确释放内存。
  7. 定期检查和优化内存使用: 在开发和测试过程中,要定期检查内存使用情况,发现并修复内存泄漏。

智能指针的使用及原理

RAII

RAII的基本概念
RAII (Resource Acquisition Is Initialization) 是一种 C++ 编程技术,旨在通过对象的生命周期管理资源(如内存、文件句柄、网络连接等),以确保资源在不再需要时能够被自动释放,从而避免资源泄漏。RAII的核心思想是将资源的获取和释放与对象的构造和析构绑定在一起。
 在RAII中,当对象被构造时,它会获取所需的资源,并在对象析构时自动释放这些资源。

这种方法有两个主要好处:

  1. 自动管理资源:程序员不需要显式地释放资源,减少了出错的可能性。
  2. 资源有效性:在对象的生命周期内,所需的资源始终保持有效。

RAII的实现示例
以下是一个使用RAII思想设计的SmartPtr类示例:

template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr)	delete _ptr;cout << "~SmartPtr" << endl;}
private:T* _ptr;	
};

在上面实现的类中,SmartPtr类负责管理动态分配内存的指针,当SmartPtr对象创建时就动态分配内存,而当SmartPtr对象被销毁的时候,它会自动释放其持有的内存。

异常安全性
RAII还提供了异常安全性。在 C++ 中,如果在函数执行过程中抛出异常,局部变量会被销毁,从而调用它们的析构函数。例如,在以下代码中,即使发生了除零错误,SmartPtr仍然会确保内存被正确释放:

int div()
{int a, b;cin >> a >> b;if (b == 0){throw invalid_argument("除0错误");}return a / b;
}void Func()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(new int);cout << div() << endl;
}int main()
{try {Func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

在这里插入图片描述
在这个示例中,即使div()函数抛出异常,sp1sp2的析构函数也会被调用,从而确保内存得到释放。

智能指针的原理

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将*->重载下,才可让其像指针一样去使用。


template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr)	delete _ptr;cout << "~SmartPtr" << endl;}//禁止拷贝构造和拷贝赋值SmartPtr(const SmartPtr& t) = delete;SmartPtr& operator=(const SmartPtr& t) = delete;//移动构造和移动赋值SmartPtr(SmartPtr&& sp):_ptr(sp._ptr){sp._ptr = nullptr;}SmartPtr& operator=(SmartPtr&& sp){if (this != sp){delete _ptr;_ptr = sp._ptr;sp._ptr = nullptr;}return *this;}T* get() const{return _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}
private:T* _ptr;	
};
int main()
{SmartPtr<int> p(new int[10]);	//声明一个int的p指针p[0] = 10;	//访问p的第一个元素,设为10cout << p.get() << endl;	//get p的地址cout << p.operator*() << endl;	//解引用,取第一个元素的值cout << p.operator->() << endl;	//-> 取地址return 0;
}

在这里插入图片描述

移动构造:


SmartPtr<int> getp()
{SmartPtr<int> p(new int[10]);return p;
}int main()
{SmartPtr<int> p(new int[10]);	//声明一个int的p指针cout << "---------------" << endl;SmartPtr<int> p2(getp());cout << "---------------" << endl;
}

在这里插入图片描述
注: 后构造的对象先被析构。

std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题,std::auto_ptr文档。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份auto_ptr来了解它的原理。

#include <iostream>
using namespace std;template<class T>
class auto_ptr {
public:// 构造函数,接受一个指向动态分配内存的指针auto_ptr(T* ptr): _ptr(ptr) // 初始化成员变量 _ptr{}// 析构函数,释放所管理的资源~auto_ptr() {if (_ptr) {delete _ptr; // 释放动态分配的内存cout << "~auto_ptr()" << endl; // 输出析构信息}}// 拷贝构造函数,转移资源所有权auto_ptr(auto_ptr<T>& sp): _ptr(sp._ptr) // 将传入的 auto_ptr 的指针赋值给当前对象{sp._ptr = nullptr; // 将传入的 auto_ptr 的指针置为 nullptr,防止双重释放}// 移动赋值操作符,转移资源所有权auto_ptr<T>& operator()(auto_ptr<T>& sp) {if (this != &sp) { // 防止自我赋值if (_ptr) { // 如果当前对象有资源,先释放它delete _ptr;}// 转移资源所有权_ptr = sp._ptr; // 将传入的 auto_ptr 的指针赋值给当前对象sp._ptr = nullptr; // 将传入的 auto_ptr 的指针置为 nullptr,防止双重释放}return *this; // 返回当前对象的引用}// 箭头操作符,支持像指针一样访问成员T* operator->() {return _ptr; // 返回内部指针}// 解引用操作符,支持像指针一样解引用T& operator*() {return *_ptr; // 返回指向的对象引用}private:T* _ptr; // 管理的原始指针
};

但是auto_ptr并不是一个好的设计,因为auto_ptr的本质是管理权的转移,假设有sp1sp2,然后吧sp1转移给了sp2,这时的sp1指针处于悬空态,指向nullptr,所以后续的sp1就不能再继续被使用了!

int main()
{qq::auto_ptr<int> sp1(new int);qq::auto_ptr<int> sp2(sp1);*sp2 = 10;cout << "*sp1 = " << *sp1 << endl;cout << "*sp2 = " << *sp2 << endl;return 0;
}

在这里插入图片描述

unique_ptr

C++11中开始提供更靠谱的unique_ptr - unique_ptr文档

unique_ptr 的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份unique_ptr 来了解它的原理:

template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){delete _ptr;cout << "~unique_ptr" << endl;}}unique_ptr(unique_ptr<T>&& sp):_ptr(sp._ptr){sp._ptr = nullptr;		//将源指针置为nullptr}unique_ptr& operator=(unique_ptr<T>&& sp){if (this != &sp)		//防止自我赋值{delete _ptr;		//释放当前的资源_ptr = sp._ptr;		//转移sp._ptr = nullptr;	//将源指针置为nullptr}return *this;}T* operator->(){return _ptr;}T& operator*(){return *_ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;private:T* _ptr;};

在这里插入图片描述
实现一个class A使用unique_ptr来管理一个动态数组,这里使用库里面的unique_ptr

#include <iostream>
#include <memory>class A {
public:int _a1;int _a2;A() : _a1(0), _a2(0) {}
};int main() {// 使用 unique_ptr 管理一个 A 类型的动态数组std::unique_ptr<A[]> sp1(new A[10]);// 访问和修改数组元素sp1[0]._a1 = 1;sp1[0]._a2 = 2;std::cout << "sp1[0]._a1: " << sp1[0]._a1 << std::endl; // 输出 1std::cout << "sp1[0]._a2: " << sp1[0]._a2 << std::endl; // 输出 2return 0; // 当 main 函数结束时,sp1 会自动释放其管理的数组
}

在这里插入图片描述

std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的 shared_ptr


通过一个例子来解释 shared_ptr 的工作原理

假设有一个图书馆,里面有很多书籍。每本书都有一个唯一的编号,比如 123 等等。每当有读者想要借阅一本书时,图书馆就会给这个读者一张借书证,上面写着这本书的编号。

现在,我们把这个图书馆比作内存,每本书就相当于内存中的一个资源。读者就相当于 shared_ptr 对象,借书证就相当于 shared_ptr 内部维护的引用计数

  1. 当第一个读者借阅一本书时,图书馆会给他一张写有该书编号的借书证,并将该书的借阅次数设为 1。这就相当于创建了第一个 shared_ptr 对象,引用计数初始化为 1
  2. 如果第二个读者也想借阅同一本书,图书馆就会再给他一张写有该书编号的借书证,并将该书的借阅次数加 1。这就相当于创建了第二个 shared_ptr 对象,引用计数变为 2
  3. 当一个读者还书时,他会将借书证交回图书馆。图书馆会将该书的借阅次数减 1。这就相当于一个 shared_ptr 对象被销毁,引用计数减 1
  4. 如果一本书的借阅次数变为 0,说明没有人在使用这本书了。图书馆就可以将这本书放回书架。这就相当于引用计数变为 0 , shared_ptr 会自动释放所管理的资源。
  5. 如果一本书的借阅次数不为 0,说明还有读者在使用这本书。图书馆就不能将这本书放回书架,否则其他读者就无法继续借阅了。这就相当于引用计数不为 0 ,shared_ptr 不会释放资源。

所以,当新增一个对象管理这块资源时则将该资源对应的引用计数进行++,当一个对象不再管理这块资源或该对象被析构时则将该资源对应的引用计数进行--,当该资源-0的时候就释放这个资源。

所以怎么来控制这里的计数呢?


这里采用使用 int* _pRefcount 来控制这里的计数问题!
因为,这里的计数是一个共享资源,当多个 shared_ptr 实例指向同一个资源时,必须有一个机制来跟踪有多少个指针正在使用该资源!
所以使用 int* _pRefcount 可以在堆上动态分配内存来存储这个引用计数。

手动模拟实现简单的shared_ptr

template<class T>
class shared_ptr {
public:// 构造函数shared_ptr(T* ptr): _ptr(ptr), _count(new int(1)) {} // 初始化指针和引用计数// 拷贝构造函数shared_ptr(const shared_ptr& sp): _ptr(sp._ptr), _count(sp._count) {(*_count)++; // 增加引用计数}// 赋值操作符shared_ptr& operator=(const shared_ptr& sp) {if (this != &sp) { // 防止自我赋值release(); // 释放当前资源_ptr = sp._ptr; // 转移指针_count = sp._count; // 转移引用计数(*_count)++; // 增加引用计数}return *this; // 返回当前对象的引用}// 析构函数~shared_ptr() {release(); // 释放资源}void release() {if (--(*_count) == 0) { // 减少引用计数并检查是否为0delete _ptr; // 释放资源delete _count; // 释放引用计数内存}}T& operator*() {return *_ptr; // 解引用操作符}T* operator->() {return _ptr; // 箭头操作符}int use_count() const { // 获取当前引用计数return *_count;}private:T* _ptr; // 指向资源的指针int* _count; // 引用计数指针
};

测试:

// 测试类 A
class A {
public:A() {cout << "A() 被构造." << endl;}~A() {cout << "~A() 被销毁." << endl;}
};// 测试函数
int main() {cout << "创建 shared_ptr sp1." << endl;shared_ptr<A> sp1(new A); // 创建一个 shared_ptr 管理 A 对象cout << "创建 shared_ptr sp2 从 sp1." << endl;shared_ptr<A> sp2(sp1); // 拷贝构造,引用计数增加cout << "sp1 的引用计数: " << sp1.use_count() << endl; // 输出引用计数cout << "sp2 的引用计数: " << sp2.use_count() << endl; // 输出引用计数return 0; // 当 main 函数结束时,所有共享的资源会被正确释放
}

在这里插入图片描述

shared_ptr的线程安全问题

是的,引用计数的加减是加锁保护或者使用原子类型来声明计数变量。但是指向资源不是线程安全的!

智能指针的历史

C++ 的发展历史中,智能指针的概念经历了多个阶段,从早期的 auto_ptr 到后来的 unique_ptrshared_ptr weak_ptr ,其演变反映了 C++ 对内存管理和资源管理的不断改进。

早期的智能指针auto_ptr
C++98 标准中,auto_ptr 是第一个引入的智能指针。它的设计目的是自动管理动态分配的内存,确保在对象超出作用域时能够自动释放资源。尽管 auto_ptr 提供了基本的内存管理功能,但它存在一些缺陷,例如:

  • 拷贝语义问题auto_ptr 在拷贝时会转移所有权,而不是创建一个新的拷贝。这可能导致意外的双重释放和未定义行为。
  • 不支持移动语义:在 C++11 之前,缺乏对移动语义的支持,使得资源管理不够灵活。

由于这些问题,auto_ptr 在 C++11 中被弃用,并被更先进的智能指针所取代。

Boost 库的贡献
C++11 发布之前,Boost 库提供了一系列更强大和灵活的智能指针,包括:

  • scoped_ptr:用于管理局部对象生命周期,当对象超出作用域时自动释放。
  • shared_ptr:允许多个指针共享同一资源,通过引用计数来管理资源的生命周期。
  • weak_ptr:与 shared_ptr 配合使用,避免循环引用的问题。

Boost 的这些实现为 C++11 的标准库提供了重要的基础,使得智能指针的功能更加完善。

C++11 的智能指针
C++11 标准吸收了 Boost 库中的智能指针精华,推出了以下三种主要类型的智能指针:

  1. std::unique_ptr
  • 实现独占所有权,确保同一时间只有一个 unique_ptr 可以管理某个资源。
  • unique_ptr 超出作用域时,它会自动释放资源。
  • 不支持拷贝,但支持移动语义。
  1. std::shared_ptr
    允许多个 shared_ptr 实例共享同一资源,通过引用计数来跟踪资源的使用情况。
    只有当最后一个指向该资源的 shared_ptr 被销毁时,资源才会被释放。

  2. std::weak_ptr:
    作为对shared_ptr的补充,不增加引用计数,用于打破循环引用。
    可以安全地观察 shared_ptr 管理的对象,但不拥有它。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

AcWing 803.区间和并

题目&#xff1a; 假定有一个无限长的数轴&#xff0c;数轴上每个坐标上的数都是 0。 现在&#xff0c;我们首先进行 n 次操作&#xff0c;每次操作将某一位置 x 上的数加 c。 接下来&#xff0c;进行 m 次询问&#xff0c;每个询问包含两个整数 l 和 r &#xff0c;你需要求…

在UE5中使用AirSim, 无人机无法移动,如何解决??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

枚举(not二分)

前言&#xff1a;这一题似乎用不了二分以及三分&#xff0c;害我写这么久&#xff0c;但是查找下一个元素的时候要用到二分查找 题目地址 #include<bits/stdc.h> using namespace std; #define endl "\n"int n; const int N (int)2e510; vector<int> a;…

SCSAI平台面向对象建模技术的设计和实现(1)

SCSAI平台面向对象建模技术的设计和实现&#xff08;1&#xff09; 原创 团长团 AI智造AI编程 2024年09月19日 20:09 北京 用爱编程30年&#xff0c;倾心打造工业和智能智造软件研发平台SCSAI,用创新的方案、大幅的让利和极致的营销&#xff0c;致力于为10000家的中小企业实现…

搜维尔科技:OptiTrack采集到的平衡数据,并对人形机器人进行编程,可以确保机器人的动作精度和准确性

OptiTrack具备高精度以及远追踪距离的双层特点&#xff0c;其捕捉范围最远可达91m&#xff0c;是大型场地&#xff08;如体育馆、足球场、虚拟拍摄制作棚等&#xff09;捕捉的最佳选择。 OptiTrack光学动作捕捉系统是目前全球市占率较高的全身动捕产品&#xff0c;可实现精度误…

初中生物--7.生物圈中的绿色植物(二)

绿色植物与生物圈的水循环 1.植物对水分的吸收和运输 1.植物主要通过根吸收水分。根吸收水分的主要部位是根尖的成熟区。 2.外界溶液浓度<根毛细胞溶液浓度→细胞吸水&#xff1b; 1.在这种情况下&#xff0c;根毛细胞内的溶液浓度高于外界溶液&#xff0c;因此细胞内的…

鸿蒙媒体开发系列06——输出设备与音频流管理

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 1、音频输出设备管理 有时设备同时连接多个音频输出设备&#xff0c;需要指定音频输…

Maya动画基础

Maya动画基础教程&#xff08;完整&#xff09;_哔哩哔哩_bilibili 第一集 动画基础设置 altv播放动画 选择撕下副本 右键---播放预览 第二集 k帧记录物体的空间信息 初始位置清零 删除历史记录 s键key帧 自动记录位置信息 删除帧&#xff0c;按住右键选择delete 按shif…

UAC2.0 麦克风——多采样率支持

文章目录 USB麦克风多采样率支持配置描述符集合Clock Source多采样率支持get range 命令返回数据格式返回的数据枚举效果采样率切换USB麦克风多采样率支持 配置描述符集合 09 02 8D 00 02 01 00 80 32 08 0B 00 02 01 00

C语言 11 字符串

前面学习了数组&#xff0c;而对于字符类型的数组&#xff0c;比较特殊&#xff0c;它实际上可以作为一个字符串&#xff08;String&#xff09;表示&#xff0c;字符串就是一个或多个字符的序列&#xff0c;比如在一开始认识的"Hello World"&#xff0c;像这样的多个…

ROS 设置dhcp option 6 多个地址格式

ROS routeOS 手工设置 dhcp 服务 option 6 多个dns 地址格式。字符串方式

一对一,表的设计

表很大&#xff0c;比如用户 用户登录只需要部分数据&#xff0c;所以把用户表拆成两个表 用户登录表 用户信息表 一对一设计有两种方案&#xff1a; 加外键&#xff0c;唯一 主键共享

erlang学习:Linux常用命令1

Linux的概念 Linux&#xff0c;一般指GNU/Linux&#xff08;单独的Linux内核并不可直接使用&#xff0c;一般搭配GNU套件&#xff0c;故得此称呼&#xff09;&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦&#xff08;Linus…

免费的PDF编辑工具有哪些?这4个专业工具不容错过!

PDF格式的文件用来分享和传输是很方便的&#xff0c;但是编辑的话还是需要使用一些专门的编辑工具。如果大家需要常常编辑PDF文件的话&#xff0c;可以看看这几看PDF编辑工具&#xff0c;都是能够免费使用的。 1、福昕PDF 编辑 直通车&#xff1a;editor.foxitsoftware.cn 如…

microchip中使用printf给AVR单片机串口重定向

重定向中修改需要的串口 #ifndef USART1_H_ #define USART1_H_#ifndef F_CPU #define F_CPU 11059200UL #endif #define BAUDRATE 9600 #include <avr/io.h> #include <avr/interrupt.h>#include <stdio.h> #include <string.h>#define PRINT /*…

大数据新视界 --大数据大厂之SaaS模式下的大数据应用:创新与变革

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

探索RESTful风格的网络请求:构建高效、可维护的API接口【后端 20】

探索RESTful风格的网络请求&#xff1a;构建高效、可维护的API接口 在当今的软件开发领域&#xff0c;RESTful&#xff08;Representational State Transfer&#xff09;风格的网络请求已经成为构建Web服务和API接口的标配。RESTful风格以其简洁、无状态、可缓存以及分层系统等…

基于微信小程序的健身房管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSpringBootVueMySQL的健…

向上转移和向下转型

向上转型 实际就是创建一个子类对象&#xff0c;将其当成父类对象来使用。格式&#xff1a;父类类型 对象名new 子类类型&#xff08;&#xff09;&#xff1b;eg&#xff1a;Animal animalnew Cat&#xff08;&#xff09;&#xff1b;animal是父类类型&#xff0c;但可以引用…

英飞凌最新AURIX™TC4x芯片介绍

概述: 英飞凌推出最新的AURIX™TC4x系列,突破了电动汽车、ADAS、汽车e/e架构和边缘应用人工智能(AI)的界限。这一代面向未来的微控制器将有助于克服安全可靠的处理性能和效率方面的限制。客户将可缩短快速上市时间并降低整体系统成本。为何它被称为汽车市场新出现的主要颠覆…