C++11 shared_ptr---面试常考

shared_ptr简介

  • 共享对其所指堆内存空间的所有权,当最后⼀个指涉到该对象的shared_ptr不再指向他时,shared_ptr会⾃动析构所指对象
  • 如何判断⾃⼰是否指涉到该资源的最后⼀个?《引⽤计数》
    • shared_ptr构造函数,使引⽤计数++
    • 析构函数,–
    • 赋值运算符,sp1 = sp2; sp1++,sp2–
    • 移动构造函数,会将源shared_ptr置空,所引引⽤计数不变,所以移动操作⽐复制快(复制要递增引⽤计数,移动不需要)
  • 引⽤计数使得shred_ptr⼤小是裸指针的2倍(包含:指向资源的裸指针**+**控制块的裸指针)
  • ⾃定义析构器:
    • 对于unique_ptr来说析构器型别是智能指针⼀部分,而shared_ptr不是;
    • ⾃定义析构器不会改变shared_ptr⼤小,这部分内存不属于shared_ptr,位于堆上
  • ⼀个对象的控制块是由第⼀个指涉到该对象的shared_ptr函数来确定
    • make_shared总会创建⼀个控制块
    • ⽤裸指针作为shared_ptr构造函数的实参
    • unique_ptr出发构造⼀个shared_ptr
为什么建议使用make_share()??

举例:

用法简单

std::shared_ptr<Test> ptr = std::make_shared<Test>(1);
std::shared_ptr<Test> ptr2 = std::make_shared<Test>(2, 3);
std::shared_ptr<Test> ptr3 = std::make_shared<Test>(4, 5, 6);
直接构造的问题?

由于智能指针指针和引用计数的内存块是分开的,直接构造过程中出现问题,没办法保证两个内存块都能够一起分配成功。

make_shared的优缺点:

  • 优点:
  • 引用计数内存块和指针内存块是一起开辟的,在一块内存上。内存分配效率高了,同时也能够防止资源泄露的风险。
  • 缺点
  • 由于是一起开辟的,那么在引用计数为0时,资源也不一定能够释放,只有当观察计数也为0,资源才能够释放,造成了延迟释放的问题。
  • 无法自定义删除器。

在这里插入图片描述

  • 从同⼀个裸指针出发构造多个shared_ptr,会产⽣未定义⾏为
int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3 = p1;
std::shared_ptr<int> p2(p1);
std::shared_ptr<int> p3(p1);
// 都是可以的 具体看以下shared_ptr简单实现
int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);//错误

因为⽤裸指针作为shared_ptr构造函数的实参会创建⼀个控制块,那么被指向的对象就拥有多个控制块,也就是有多个引⽤计数,每个引⽤计数最后都会变为0,从而导致析构多次,产⽣未定义⾏为。

  • this指针创建⼀个shared_ptr
    • ⾸先继承⾃ class A : public std::enable_shared_from_this<A>
    • 成员函数`` shared_from_this 创建⼀个shared_ptr对象指向当前对象(内部实现,查询当前对象的控制块并创建⼀个shared_ptr`指向当前对象,前提是当前对象已有控制块)
    • 为了避免⽤⼾再shared_ptr`指向该对象之前就调⽤shared_from_this`
      • 将类的构造函数声明为private
      • 只允许⽤⼾调⽤返回shared_ptr的⼯⼚函数来创建对象

为什么多线程读写 shared_ptr 要加锁?/ shared_ptr是否是线程安全的?

(shared_ptr)的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。 shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样,即:

  • 一个 shared_ptr 对象实体可被多个线程同时读取;
  • 两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
  • 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

shared_ptr的线程安全问题?

shared_ptr本身不是线程安全的

  • 多线程代码操作同一个shared_ptr的对象的时候不是线程安全的
  • 当多个线程操作同一个shared_tr对象的时候,如果发生赋值操作,那么就会涉及到引用计数的加一和减一操作,由于这两个操作本身并不是线程安全的,所以就可能出现不安全的情况
  • 多线程操作不是同一个对象的shared_ptr对象,由于shared_ptr本身的引用计数是原子的,所以是线程安全的。

shared_from_this解决了什么问题?

参考上述内容,从同⼀个裸指针出发构造多个shared_ptr,会产⽣未定义⾏为

那么返回指向自身对象的shared_ptr指针也会有这个问题

// A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针。
shared_ptr<A> getSharedPtr() { 
  /*注意:不能直接返回this,在多线程环境下,根本无法获知this指针指向
  的对象的生存状态,通过shared_ptr和weak_ptr可以解决多线程访问共享		
  对象的线程安全问题,参考我的另一篇介绍智能指针的博客*/
  return shared_ptr<A>(this); 
}

也就相当于从一个裸指针构造两个shared_ptr指针,所以也会引发未定义行为,(也就是拷贝了两份引用计数,两份引用计数都是1;而不是引用计数增加到2。)

在这里插入图片描述
在这里插入图片描述

shared_from_this解决了这个问题

为什么解决了这个问题?原理是什么?

本质是一个weak_ptr,如果对象存活才会提升到shared_ptr

template<class _Ty2>
bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other)
{	// implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
// if通过判断资源的引用计数是否还在,判定对象的存活状态,对象存活,提升成功;
// 对象析构,提升失败!
  if (_Other._Rep && _Other._Rep->_Incref_nz())
  {
      _Ptr = _Other._Ptr;
      _Rep = _Other._Rep;
      return (true);
  }
  return (false);
}

所有过程都没有再使用shared_ptr的普通构造函数,没有在产生额外的引用计数对象,不会存在把一个内存资源,进行多次计数的过程;更关键的是,通过weak_ptrshared_ptr的提升,还可以在多线程环境中判断对象是否存活或者已经析构释放,在多线程环境中是很安全的,通过this裸指针进行构造shared_ptr,不仅仅资源会多次释放,而且在多线程环境中也不确定this指向的对象是否还存活。

shared_from_this有什么问题?注意事项?
  • 智能指针管理的是堆上的对象,所以用如果管理栈对象的话会内存泄漏!!!

  • 注意智能指针的循环引用问题!(类中有个成员变量,通过成员函数赋值为shared_from_this()),就会造成循环引用问题。

weak_ptr简介

  • weak_ptr⼀般由shared_ptr初始化,两者指向相同位置,但weak_ptr不会影响引⽤计数

  • weak_ptr可以⽤expired函数检测空悬

  • 多线程场景下,在expired函数和访问之间,另⼀个线程可能析构最后⼀个指向该对象的shared_ptr,导致该对象被析构,引发未定义⾏为,所以需要⼀个源⾃操作来完成校验与访问

//1-通过lock函数获取weak_ptr检测的shared_ptr对象,如果指针空悬,结果shared_ptr为空
std::shared_ptr<A> sp1 = wp.lock();
//2-将weak_ptr对象作为shared_ptr的构造函数的型参传⼊,如果指针空悬,抛出bad weak_ptr的异常
std::shared_ptr<A> sp2(wp);
  • weak_ptr解决循环引⽤的问题

存在ABC三个类,AC都持有⼀个指向Bshared_ptr,此时⼀个指针从B指向A,应该采⽤什么型别?

    • 采⽤裸指针,若A析构,C仍指向BB中有A的空悬指针,但是却⽆法检测到,若进⾏访问,出现未定义⾏为
    • 采⽤shared_ptrA<-- ->B形成环路,引⽤计数始终不能为0,资源⽆法回收
    • 采⽤weak_ptr,若A析构,B中有A的空悬指针,但是B可以检测到空悬,同时weak_ptr不会增加引⽤计数,不会阻⽌析构。
  • (AB两个类也行,这里仅仅举一个例子)

unique_ptr和shared_ptr在析构函数中都使用delete而不是delete[],所以不应该new一个数组

std::shared_ptr<int> ii(new [1024]);  //错误的方式!delete[]一个数组

多线程访问共享对象问题

void threadProc(Test *p)
{
	// 睡眠两秒,此时main主线程已经把Test对象给delete析构掉了
	std::this_thread::sleep_for(std::chrono::seconds(2));
	/* 
	此时当前线程访问了main线程已经析构的共享对象,结果未知,隐含bug。
	此时通过p指针想访问Test对象,需要判断Test对象是否存活,如果Test对象
	存活,调用show方法没有问题;如果Test对象已经析构,调用show有问题!
	*/
	p->show();
}
int main()
{
	// 在堆上定义共享对象
	Test *p = new Test();
	// 使用C++11的线程类,开启一个新线程,并传入共享对象的地址p
	std::thread t1(threadProc, p);
	// 在main线程中析构Test共享对象
	delete p;
	// 等待子线程运行结束
	t1.join();
	return 0;
}

发现在main主线程已经delete析构Test对象以后,子线程threadProc再去访问Test对象的show方法,无法打印出*_ptr的值20

可以通过weak_ptrshared_ptr解决

void threadProc(weak_ptr<Test> pw) // 通过弱智能指针观察强智能指针
{
	// 睡眠两秒
	std::this_thread::sleep_for(std::chrono::seconds(2));
	/* 
	如果想访问对象的方法,先通过pw的lock方法进行提升操作,把weak_ptr提升
	为shared_ptr强智能指针,提升过程中,是通过检测它所观察的强智能指针保存
	的Test对象的引用计数,来判定Test对象是否存活,ps如果为nullptr,说明Test对象
	已经析构,不能再访问;如果ps!=nullptr,则可以正常访问Test对象的方法。
	*/
	shared_ptr<Test> ps = pw.lock();
	if (ps != nullptr)
	{
		ps->show();
	}
}
int main()
{
	// 在堆上定义共享对象
	shared_ptr<Test> p(new Test);
	// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
	std::thread t1(threadProc, weak_ptr<Test>(p));
	// 在main线程中析构Test共享对象
	// 等待子线程运行结束
	t1.join();
	return 0;
}

因为main线程调用了t1.join()方法等待子线程结束,此时pw通过lock提升为ps成功

如果设置t1为分离线程,让main主线程结束,p智能指针析构,进而把Test对象析构,此时show方法已经不会被调用,因为在threadProc方法中,pw提升到ps时,lock方法判定Test对象已经析构,提升失败

int main()
{
	// 在堆上定义共享对象
	shared_ptr<Test> p(new Test);
	// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
	std::thread t1(threadProc, weak_ptr<Test>(p));
	// 在main线程中析构Test共享对象
	// 设置子线程分离
	t1.detach();
	return 0;
}
//或者在main中将p reset了
int main()
{
	// 在堆上定义共享对象
	shared_ptr<Test> p(new Test);
	// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
	std::thread t1(threadProc, weak_ptr<Test>(p));

p.reset();
	// 在main线程中析构Test共享对象
	// 设置子线程分离
	t1.join();
	return 0;
}

只要在子线程执行前将指针释放了,那么weak_ptr就不会提升成功,即判断shared_ptr判断存活失败。

实现

注意点:

  • ref_count的实现仅仅是一个简单版本,其内的引用计数不仅有user还有weak。
  • 对象的第一次构造,引用计数为1
  • 对象的拷贝构造,引用计数+1
  • 对象的移动构造,原对象为nullptr、引用计数为0;所有权转移的对象继承了源对象的引用计数和指针
  • 对象的拷贝赋值,引用计数+1
  • 对象的移动赋值,类似移动构造
  • use_count()需要判断裸指针是否存在
#include <iostream>
#include <atomic>
#include <mutex>
#include <memory>
using namespace std;

class ref_count{
public:
    ref_count():count_(1){}
    void add_ref(){
        ++count_;
    }
    int reduce_ref(){
        --count_;
        return use_count();
    }
    int use_count() const{
        return count_.load();
    }
private:
    atomic_int count_;
};

template<typename T>
class Shared_Ptr{
public:
    Shared_Ptr() : ptr_(nullptr), ref_count_(nullptr){}

    explicit Shared_Ptr(T* ptr) :ptr_(ptr){
        std::cout << " default constructor ." << std::endl;
        if(ptr){
            ref_count_ = new ref_count();
        }
    }
    ~Shared_Ptr(){
        std::cout << " ~ constructor ." << std::endl;
        release();
    }
    Shared_Ptr(Shared_Ptr<T>& obj): ptr_(obj.ptr_), ref_count_(obj.ref_count_){
        std::cout << " copy constructor ." << std::endl;
        ref_count_->add_ref();
    }
    Shared_Ptr(Shared_Ptr<T>&& obj){
        std::cout << " move constructor ." << std::endl;
        swap(*this, obj);
        obj.ptr_ = nullptr;
        obj.ref_count_ = nullptr;
    }

    Shared_Ptr<T>& operator=(Shared_Ptr<T> &obj){
        std::cout << " copy = constructor ." << std::endl;
        if(this != &obj){
            release();
            ptr_ = obj.ptr_;
            ref_count_ = obj.ref_count_;
            ref_count_->add_ref();
        }
        return *this;
    }
    Shared_Ptr<T>& operator=(Shared_Ptr<T>&& obj){
        std::cout << " move = constructor ." << std::endl;
        release();
        swap(*this, obj);
        obj.ptr_ = nullptr;
        obj.ref_count_ = nullptr;
        return *this;
    }
    T* operator ->(){
        return ptr_;
    }
    T& operator *(){
        return *ptr_;
    }
    T* get(){
        return ptr_;
    }
    int use_count() const{
        if(ptr_){
            return ref_count_->use_count();
        }
        return 0;
    }
    void reset(T* ptr = nullptr){
        if(ptr_){
            ref_count_->reduce_ref();
            ptr_ = nullptr;
            ref_count_ = nullptr;
        }
        if(ptr){
            ptr_ = ptr;
            ref_count_ = new ref_count();
        }
    }
private:
    T* ptr_;
    ref_count* ref_count_;
    void release(){
        if(ptr_ && ref_count_->reduce_ref() == 0){
            delete ref_count_;
            delete ptr_;
        }
    }
    friend void swap(Shared_Ptr<T> &lhs, Shared_Ptr<T> &rhs) noexcept{
        using std::swap;
        swap(lhs.ptr_, rhs.ptr_);
        swap(lhs.ref_count_, rhs.ref_count_);
    }
};

// g++ ../MySharedPtr.cpp -o mshared_ptr -g -fsanitize=address
void testMySharedPtr(){
Shared_Ptr<int> test = Shared_Ptr<int>(new int(0));
    {
        // 拷贝构造
        Shared_Ptr<int> test2 = test;
        std::cout << test2.use_count() << std::endl;
    }
    std::cout << test.use_count() << std::endl;
    {
        // 移动构造
        Shared_Ptr<int> test2 = std::move(test);
        std::cout << test2.use_count() << std::endl;
    }
    test.reset(new int(1));
    std::cout << test.use_count() << std::endl;
    {
        // 拷贝操作符
        Shared_Ptr<int> test3(new int(2));
        Shared_Ptr<int> test2 = test3;
        std::cout <<" before test3.count:"<< test3.use_count() << std::endl;

        test2 = test;
        std::cout <<" after test3.count:"<< test3.use_count() << std::endl;
    }
    std::cout << test.use_count() << std::endl;
    {
        // 移动操作符
        Shared_Ptr<int> test3(new int(2));
        std::cout << test.use_count() << std::endl;
        test3 = std::move(test);
        std::cout << test.use_count() << std::endl;
        std::cout <<" after test3.val:"<< *test3.get() << std::endl;
    }
    // std::cout << test.use_count() << std::endl;
}
void testSharedPtr(){
    shared_ptr<int> test = shared_ptr<int>(new int(0));
    {
        // 拷贝构造
        shared_ptr<int> test2 = test;
        std::cout << test2.use_count() << std::endl;
    }
    std::cout << test.use_count() << std::endl;
    {
        // 移动构造
        shared_ptr<int> test2 = std::move(test);
        std::cout << test2.use_count() << std::endl;
    }
    test.reset(new int(1));
    std::cout << test.use_count() << std::endl;
    {
        // 拷贝操作符
        shared_ptr<int> test3(new int(2));
        shared_ptr<int> test2 = test3;
        std::cout <<" before test3.count:"<< test3.use_count() << std::endl;

        test2 = test;
        std::cout <<" after test3.count:"<< test3.use_count() << std::endl;
    }
    std::cout << test.use_count() << std::endl;
    {
        // 移动操作符
        shared_ptr<int> test3(new int(2));
        test3 = std::move(test);
        std::cout << test.use_count() << std::endl;
        std::cout <<" after test3.val:"<< *test3.get() << std::endl;
    }
    std::cout << test.use_count() << std::endl;
}
int main(){
    testMySharedPtr();
    cout << "----------------------" << endl;
    testSharedPtr();
    return 0;
}

参考:大秦坑王的智能指针详解

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应用了网络变压器的PC网卡连接转换器后不好连网,有掉线现象,但外接路由器无问题,可能是什么原因?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;今天分享的是应用了网络变压器的PC网卡连接转换器后不好连网&#xff0c;有掉线现象&#xff0c;但外接路由器无问题&#xff0c;可能是什么原因呢&#xff1f;如何解决呢&#xff1f; 首先&#xff0c;我们要了解传…

Java后端开发(十二)-- 未配置 Spring Boot 配置注解处理器

目录 1. 错误表现形式 2. 解决方式 1. 错误表现形式 2. 解决方式 在 Spring Boot 应用程序中,通常使用 @ConfigurationProperties 注解来将配置文件中的属性绑定到 Java 对象中。如果没有配置 Spring Boot 配置注解处理器,那么这些注解将无法自动处理和加载。 …

清空flowable的表定义的相关表

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

qt 如果把像素点数据变成一个图片

1.概要 图像的本质是什么&#xff0c;就是一个个的像素点&#xff0c;对与显示器来说就是一个二维数组。无论多复杂的图片&#xff0c;对于显示器来说就是一个二维数组。 2.代码 #include "widget.h"#include <QApplication> #include <QImage> #incl…

兴业小课堂|什么是法拍房助拍机构?如何挑选靠谱的助拍机构?

随着法拍房市场的不断发展和扩大 使法拍房数量的增加 其交易的复杂性和专业性需求也日益凸显 这促使了专门机构的出现来满足市场需求 法拍房助拍机构存在的原因主要有以下几点&#xff1a; 1.专业知识和经验&#xff1a; 法拍房的交易流程相对复杂&#xff0c;涉及到法律法…

对标 GPT-4o 的开源实时语音多模态模型:Moshi

是由法国的 AI 实验室 Kyutai 推出的实时语音多模态模型&#xff0c;支持听、说、看&#xff0c;最关键的是你现在就可以在浏览器中使用&#xff0c;如果这个链接延迟高&#xff0c;可以试试这个, 无需输入邮箱&#xff0c;点击 Join queue 即可。 简单体验了下&#xff0c;比…

Perplexity: 推出 ProSearch 的新版本

Perplexity 更新了 ProSearch 功能&#xff0c;支持多步推理、高级数学和编程能力&#xff0c;比起快速搜索&#xff0c;提供了更深入、全面的搜索&#xff0c;以及详尽报告和分析。 白嫖用户每 4 个小时可免费使用 5 次&#xff0c;Pro 用户几乎无限制。

ChatGPT:SpringBoot解决跨域问题方法-手动设置请求头

ChatGPT&#xff1a;SpringBoot解决跨域问题方法-手动设置请求头 这里的设置响应头是为了发送请求方还是接收请求方 设置响应头是为了发送请求方。具体来说&#xff0c;添加 Access-Control-Allow-Origin 头部是为了告诉浏览器&#xff0c;哪些域名可以访问资源。当设置为 * 时…

二百四十二、Hive——Hive的动态分区表出现day=__HIVE_DEFAULT_PARTITION__分区

一、目的 Hive的DWD层动态分区表的分区出现day__HIVE_DEFAULT_PARTITION__&#xff0c;有点懵&#xff0c;而且表中数据的day字段也显示__HIVE_DEFAULT_PARTITION__ 1、DWD层动态分区表的分区 __HIVE_DEFAULT_PARTITION__ 2、DWD层分区字段day数据 __HIVE_DEFAULT_PARTITION…

【中项第三版】系统集成项目管理工程师 | 第 4 章 信息系统架构① | 4.1-4.2

前言 第4章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于技术相关的内容&#xff0c;学习要以教材为准。本章分值预计在4-5分。 目录 4.1 架构基础 4.1.1 指导思想 4.1.2 设计原则 4.1.3 建设目标 4.1.4 总体框架 4.2 系统架构 4.2.1 架构定义 4.…

通义灵码入选 2024 世界人工智能大会最高荣誉「镇馆之宝」

7 月 4 日&#xff0c;2024 上海世界人工智能大会正式开幕&#xff0c;并揭晓了今年的「镇馆之宝」名单&#xff0c;通义灵码入选&#xff0c;是首个入围该名单的 AI 编程助手。 镇馆之宝是世界人工智能大会展览的最高荣誉&#xff0c;从科技含量、市场前景、创新性以及社会经济…

pip install包出现哈希错误解决

如图&#xff0c;当遇到此类错误时&#xff0c;多半是连接不稳定导致的校验失败。我们可以在PC端&#xff0c;或Ubuntu通过浏览器下载.whl安装文件&#xff1a;直接复制报错信息中的网址到浏览器即可弹出下载窗口。

Vue3重构案例(使用vue3的语法重构element的button组件)

这篇文章紧接的上一篇文章&#xff0c;上篇文章是对给element的button组件写了一个单元测试&#xff0c;这篇文章是使用vue3的语法进行重构&#xff0c;这里说一下单元测试和重构的联系&#xff0c;当你给组件写了单元测试之后&#xff0c;重构会减少你很多的debug时间&#xf…

Transformer和Mamba强强结合!最新混合架构全面开源,推理速度狂飙8倍

最近发现&#xff0c;将Mamba和Transformer模块混合使用&#xff0c;效果会比单独使用好很多&#xff0c;这是因为该方法结合了Mamba的长序列处理能力和Transformer的建模能力&#xff0c;可以显著提升计算效率和模型性能。 典型案例如大名鼎鼎的Jamba&#xff1a;Jamba利用Tr…

【Unity小知识】UnityEngine.UI程序集丢失的问题

问题表现 先来说一下问题的表现&#xff0c;今天在开发的时候工程突然出现了报错&#xff0c;编辑器提示UnityEngine.UI缺少程序集引用。 问题分析与解决&#xff08;一&#xff09; 既然是程序集缺失&#xff0c;我们首先查看一下工程项目是否引用了程序集。在项目引用中查找一…

自定义流程表单开发优势体现在什么地方?

提质、增效、降本&#xff0c;应该是很多职场办公需要实现的发展目标。那么&#xff0c;应用什么样的软件平台可以实现&#xff1f;低代码技术平台、自定义流程表单开发是目前流行于职场行业中的软件产品&#xff0c;可视化操作界面、够灵活、易维护等优势特点明显&#xff0c;…

java项目总结4

1.正则表达式 用于验证字符串是否满足自己所需要的规则。方法&#xff1a;matches 注意&#xff1a;\在Java中有特殊涵义&#xff0c;是将其它的意思本来化&#xff0c;假设"是用来引用字符串的&#xff0c;但是你如果想要输出它&#xff0c;那是不是就变成了System.out…

气压传感器在自动驾驶汽车还有哪些应用场景

气压传感器在近年来被广泛应用于各种新兴领域&#xff0c;以下是其中几个最新的应用&#xff1a; 1、自动驾驶汽车&#xff1a;自动驾驶汽车需要精确的气压传感器来监测道路上的气压变化&#xff0c;帮助车辆进行准确的定位和导航。气压传感器可以提供高精度、可靠的气压数据&…

利用git将Qt代码托管到U盘或者网盘

文章目录 1.前言2.解决方案3.操作步骤3.1.软件安装3.2.在U盘创建裸仓库3.3.创建Qt程序并将代码上传至U盘3.4.版本标记及切换3.4.在别的电脑上clone代码出来 4.其他5.结语 1.前言 我们在开发项目时&#xff0c;随着项目需求的增加以及bug的修复&#xff0c;肯定会涉及到版本管理…

【山东大学】web数据管理——复习笔记

写在前面 若有图片加载失败&#xff0c;请科学上网 。本文为对软件学院连老师的PPT课件总结所得的复习笔记&#xff0c;仅供参考。不保证对考点的全覆盖&#xff0c;以PPT为主。对往年考过的题相关知识点前面都标注了“考过”&#xff0c;并高亮&#xff0c;供参考。写的比较匆…