C++个人复习(4)
C++中为什么要引入make_shared,它有什么优点
1. 减少内存分配次数
使用 make_shared
时,内存分配只发生一次,它同时分配了对象和控制块(用于管理引用计数等信息)。而如果直接使用 new
创建对象并传递给 shared_ptr
,则会分别进行两次内存分配:一次为对象本身,另一次为控制块。
// 使用 new
std::shared_ptr<MyClass> ptr1(new MyClass());// 使用 make_shared
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
在上面的例子中,ptr1
进行了一次对象的内存分配和一次控制块的内存分配,而 ptr2
只进行了一次内存分配。
2. 提高性能
由于 make_shared
只进行一次内存分配,它可以减少内存分配和释放的开销,尤其是在频繁创建和销毁对象的场景下。这样可以提高程序的性能。
3. 避免内存泄漏
使用 make_shared
可以减少内存泄漏的风险。因为它提供了一种更安全的方式来创建 shared_ptr
,避免了在使用 new
时可能忘记释放内存的情况。使用 make_shared
,即使在构造过程中发生异常,内存也会被正确管理。
4.简化代码
使用make_shared创建代码可以简化创建shared_ptr实例的代码,让代码逻辑更加清晰
shared_ptr相关的知识点
-
shared_ptr基础:与之对应的是unique_ptr,但使用它有限制,比如不能共享所有权。而std::shared_ptr是一个可以共享控制权的智能指针,可以自动管理动态分配的对象生命周期。
-
new和shared_ptr:在没有make_shared之前,我们通常这样创建shared_ptr:std::shared_ptr<int> sp(new int(5));。这个过程其实做了两个动作:创建一个临时对象,又创建一个shared_ptr对象。如果第一步的内存分配成功,但第二步抛出异常,那么就会发生内存泄漏。
-
make_shared内部原理:make_shared将对象的动态内存和控制块内存(存储引用计数的那块内存)一次性分配,减少了内存分配的次数。
例如:auto sp = std::make_shared<int>(5);,这种方式比前一种方式高效,并且更加安全。
-
性能更好:单次内存分配意味着分配器只调用一次,这比多次调用(可能导致的内存碎片问题)更加高效。此外,这种方式在多线程环境中也有一定优势,减少了分配内存时的竞争。
-
异常安全:使用make_shared,如果在创建过程中抛出异常,因为它是“全有或全无”的过程,所以不需要担心部分资源分配成功导致的内存泄漏。例如,make_shared可以保证在对象和控制块都构建成功之后才开始使用它们。
C++的string内部使用的是堆内存还是栈内存
这个题目主要考察的就是string内部的一个优化,首先std::string这个对象本身是保存在栈内存的,单这个对象所管理的数据是保存在堆中的,但是在string中存在一个短字符串优化(SSO),这个优化让字符串比较小时(字符串个数少于一个值时)会将这个数据对象也存在在string对象的缓冲区中,此时这个对象管理的数据就在栈中。而当字符串的长度超过了这个值时,会将管理的数据放入到堆的空间中。此时这个对象管理的数据就在栈中。
说明C++中的future,promise,packaged_task,async的区别
1. std::future
- 定义:
std::future
是一个用于获取异步操作结果的对象。它可以从一个异步任务中获取结果,并且可以在结果可用时进行等待。 - 用途: 用于获取异步操作的结果,通常与
std::promise
、std::packaged_task
或std::async
一起使用。 - 特性:
- 可以通过
get()
方法获取结果,如果结果尚不可用,它会阻塞当前线程直到结果可用。 - 可以检查结果是否已经准备好(通过
valid()
和wait_for()
等方法)。
- 可以通过
2. std::promise
- 定义:
std::promise
是一个用于设置异步操作结果的对象。它可以与std::future
结合使用,将结果从一个线程传递到另一个线程。 - 用途: 用于在一个线程中设置值,另一个线程可以通过
std::future
获取这个值。 - 特性:
- 通过
set_value()
方法设置结果,或者通过set_exception()
方法设置异常。 - 创建与
promise
关联的future
对象,使用get_future()
方法。
- 通过
3. std::packaged_task
- 定义:
std::packaged_task
是一个可调用对象,它可以封装任何可调用的任务(如函数、lambda 表达式等)并将其与std::future
关联。 - 用途: 用于将任务与一个
future
关联起来,使得可以异步执行这个任务并获取结果。 - 特性:
- 可以通过
operator()
调用封装的任务。 - 通过
get_future()
方法获取与packaged_task
关联的future
对象。 - 适合于需要将任务封装并在不同线程中执行的场景。
- 可以通过
4. std::async
- 定义:
std::async
是一个用于异步执行函数的标准库函数。它返回一个std::future
,可以用来获取异步操作的结果。 - 用途: 用于简单地启动异步任务并获取结果,通常不需要手动管理线程。
- 特性:
- 可以指定任务的执行策略(如
std::launch::async
或std::launch::deferred
)。 - 自动管理线程,用户不需要显式地创建和管理线程。
std::launch::async
: 任务立即在新线程中执行,适合需要并发执行的场景。std::launch::deferred
: 任务延迟到调用get()
时执行,适合轻量任务或可能不会执行的情况。
- 可以指定任务的执行策略(如
总结
组件 | 定义 | 用途 | 主要特性 |
---|---|---|---|
std::future | 获取异步操作结果的对象 | 获取异步操作的结果 | 阻塞等待结果,检查结果状态 |
std::promise | 设置异步操作结果的对象 | 在线程中设置值,另一个线程获取 | 设置值或异常,创建与 future 关联的对象 |
std::packaged_task | 可调用对象,封装任务与 future 关联 | 封装任务并异步执行,获取结果 | 封装任意可调用对象,调用后可以获取结果 |
std::async | 异步执行函数的标准库函数 | 简单启动异步任务并获取结果 | 自动管理线程,支持执行策略 |
简化之后的说明如下:
-
std::future
:用于在不同线程间传递结果。它可以从异步操作中获取返回值。 -
std::promise
:用于设置一个值或者异常,这些值或异常会被对应的std::future
获取。 -
std::packaged_task
:包装一个可调用对象(函数、lambda表达式、bind表达式等),方便异步执行,并将结果通过std::future
获取。 -
std::async
:用于异步地启动任务,并返回一个std::future
对象以便获取任务结果。
这些特性在不同场合下分别发挥各自的作用:
std::future
和std::promise
更适用于实现自定义的异步操作。std::packaged_task
更适合将现有的函数包装为异步任务,而无需显式使用线程。std::async
最为简便,用于从简化的异步调用中获取结果。
C++的async在使用的时候有什么注意事项
-
选择合适的启动策略(launch policy):
std::async
可以接受一个启动策略作为参数,如std::launch::async
和std::launch::deferred
。std::launch::async
会创建一个新的线程,而std::launch::deferred
只会在需要结果时才开始任务。所以,要根据实际需求选择合适的策略。注意,一定要明确指定创建策略。如果不明确指定创建策略,以上两个都不是async
的默认策略,而是undefined
,它是一个基于任务的程序设计,内部有一个调度器(线程池),会根据实际情况决定采用哪种策略。 -
保证获取结果(future.get):
std::async
返回一个std::future
对象,一定要记得调用future.get()
或者future.wait()
来获取任务结果或者等待任务完成。如果没有获取结果,程序的行为是不确定的。 -
异常处理:当异步任务中抛出异常时,这些异常会被
future.get()
捕获。所以,一定要在调用future.get()
时准备好捕获和处理可能的异常。 -
确认 async 对象的生命周期:如果从
std::async
获得的std::future
未被移动或绑定到引用,则在完整表达式结尾这个std::future对象会被销毁。
扩展知识:
-
返回值的类型推导:
std::async
会自动推导返回值类型,而且会在异步任务完成后把结果存储在std::future
对象里。这种类型推导可以让代码更加简洁,不需要明确指定返回值类型。 -
任务的生命周期:异步任务的生命周期与
std::future
对象绑定。如果future
对象被销毁,那么异步任务也会被取消。所以,确保future
对象的生命周期覆盖任务的执行时间。 -
共享结果:
std::shared_future
:有时候你可能需要多个线程访问同一个异步结果,这时可以使用std::shared_future
。它可以让多个线程共享异步结果,而不是使用单一的std::future
,确保多线程访问时的安全性。 -
并发:虽然
std::async
提供了一种简单实用的并发机制,但在实际应用中,你可能还需要使用其他并发容器如std::mutex
、std::lock_guard
、std::atomic
等来处理复杂的共享数据访问问题。
二叉搜索树的删除逻辑
在二叉搜索树(Binary Search Tree, BST)中,删除节点的逻辑相对复杂,因为我们需要保持树的性质。删除节点的过程可以分为三种主要情况:
- 删除的节点是叶子节点(没有子节点)
- 删除的节点有一个子节点
- 删除的节点有两个子节点
删除逻辑
1. 删除叶子节点
直接将该节点从树中移除。
2. 删除有一个子节点的节点
将该节点的父节点指向该节点的唯一子节点,从而移除该节点。
3. 删除有两个子节点的节点
此时,我们需要找到该节点的后继节点(在右子树中最小的节点)或前驱节点(在左子树中最大的节点),然后用该节点的值替换要删除的节点的值,并递归地删除该后继或前驱节点。
C++ 实现
以下是一个简单的 C++ 实现,展示了二叉搜索树的删除逻辑:
#include <iostream>struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};class BST {
public:TreeNode* root;BST() : root(nullptr) {}// 插入节点TreeNode* insert(TreeNode* node, int val) {if (!node) return new TreeNode(val);if (val < node->val)node->left = insert(node->left, val);elsenode->right = insert(node->right, val);return node;}// 删除节点TreeNode* deleteNode(TreeNode* root, int key) {if (!root) return nullptr;if (key < root->val) {root->left = deleteNode(root->left, key);} else if (key > root->val) {root->right = deleteNode(root->right, key);} else {// 找到要删除的节点if (!root->left) {return root->right; // 只有右子树} else if (!root->right) {return root->left; // 只有左子树} else {// 有两个子节点,找后继节点TreeNode* successor = minValueNode(root->right);root->val = successor->val; // 替换值root->right = deleteNode(root->right, successor->val); // 删除后继节点}}return root;}// 找到右子树中的最小节点TreeNode* minValueNode(TreeNode* node) {TreeNode* current = node;while (current && current->left) {current = current->left;}return current;}// 中序遍历(用于测试)void inorder(TreeNode* node) {if (node) {inorder(node->left);std::cout << node->val << " ";inorder(node->right);}}
};int main() {BST tree;tree.root = tree.insert(tree.root, 50);tree.insert(tree.root, 30);tree.insert(tree.root, 20);tree.insert(tree.root, 40);tree.insert(tree.root, 70);tree.insert(tree.root, 60);tree.insert(tree.root, 80);std::cout << "Inorder traversal of the BST: ";tree.inorder(tree.root);std::cout << std::endl;std::cout << "Delete 20\n";tree.root = tree.deleteNode(tree.root, 20);std::cout << "Inorder traversal after deleting 20: ";tree.inorder(tree.root);std::cout << std::endl;std::cout << "Delete 30\n";tree.root = tree.deleteNode(tree.root, 30);std::cout << "Inorder traversal after deleting 30: ";tree.inorder(tree.root);std::cout << std::endl;std::cout << "Delete 50\n";tree.root = tree.deleteNode(tree.root, 50);std::cout << "Inorder traversal after deleting 50: ";tree.inorder(tree.root);std::cout << std::endl;return 0;
}
C++什么场景下使用锁(lock)?什么场景下使用原子变量(atomic)?
锁(lock)和原子变量(atomic)都可以用作同步机制,它们有各自的适用场景:
1) 使用锁的场景:
- 当需要保护长时间访问的临界区时,比如复杂的操作或逻辑(如链表、树等复杂数据结构的操作)。
- 当多个共享资源需要同步访问时,锁可以一次性锁定多个资源,确保整体一致性。
- 在涉及到复杂的操作时,比如需要一次性更新多个共享变量。
2) 使用原子变量的场景:
- 当操作可以在一个原子步骤内完成时,比如简单的整数增减、标志位切换。
- 当性能非常关键,且锁的开销和上下文切换的成本过高时。原子操作通常比使用锁更轻量级。
- 用于实现非阻塞算法时,因为原子变量不会导致线程挂起而等待锁释放。
建议:优先使用原子变量,如果发现使用原子变量不能满足同步机制的需求,那就使用锁。
使用锁来保护的例子:
std::mutex mtx;
std::map<int, std::string> sharedMap;void insertIntoMap(int key, const std::string& value) {std::lock_guard<std::mutex> lock(mtx);sharedMap[key] = value;
}
使用原子变量的例子:
class AtomicCounter {
public:AtomicCounter() : count(0) {}// 增加计数void increment() {count.fetch_add(1, std::memory_order_relaxed);}// 获取当前计数int get() const {return count.load(std::memory_order_relaxed);}private:std::atomic<int> count; // 原子变量
};
扩展知识:
1) 锁的类型:
- 互斥锁(Mutex):最常见的普通锁,用于保护一个共享资源。
- 读写锁(Read-Write Lock):允许多个读者并行访问,但写者访问需要独占。
- 自旋锁(Spinlock):线程在等待时会不断轮询锁状态,而不是挂起,非常适合短时间持有锁的场景。
2) 原子操作:
- 可以使用
std::atomic
库提供的原子类型,如std::atomic<int>
,std::atomic<bool>
,atomic
是个模板类,你还可以使用double
等类型填充。 - 这些操作通常由硬件直接支持,比如 x86 架构的 "Lock" 前缀指令,确保读取-修改-写入一个不可分割的操作。
C++中锁的底层原理是什么
1. 互斥锁(Mutex)
互斥锁是最常见的锁类型,用于保护共享数据。C++11 引入了 <mutex>
头文件,提供了 std::mutex
类。
- 实现原理:
- 原子操作:互斥锁的实现依赖于原子操作,例如测试并设置(Test-and-Set)或比较并交换(Compare-and-Swap,CAS)。这些操作通常由 CPU 提供,并且是线程安全的。
- 系统调用:在用户空间中,互斥锁的状态(锁定或未锁定)通常用一个标志位表示。当一个线程尝试获取锁时,它会检查这个标志位。如果锁未被占用,线程可以成功获取锁并将标志位设置为已锁定。如果锁已被占用,线程可能会进入阻塞状态,直到锁被释放。
- 调度:操作系统负责调度和唤醒线程。当一个线程释放锁时,操作系统会选择一个等待线程来获取锁。
2. 自旋锁(Spinlock)
自旋锁是一种轻量级锁,适用于锁持有时间非常短的场景。
- 实现原理:
- 自旋锁使用原子操作来检查锁的状态。如果锁未被占用,线程会通过原子操作获取锁;如果锁已被占用,线程会不断循环(自旋)检查锁的状态,直到锁可用。
- 自旋锁避免了线程上下文切换的开销,但在持锁时间较长时可能会导致 CPU 资源浪费。
3. 读写锁(Read-Write Lock)
读写锁允许多个线程同时读取共享资源,但在写入时会阻塞其他线程。
- 实现原理:
- 读写锁通常使用一个计数器来跟踪当前的读操作数量和一个互斥锁来保护写操作。
- 当一个线程请求写锁时,它会检查当前是否有读锁或写锁被持有。如果有,写锁请求将被阻塞;如果没有,线程可以获得写锁。
- 当一个线程请求读锁时,它会检查当前是否有写锁被持有。如果没有,线程可以获得读锁,并增加读锁计数器。
4. 条件变量(Condition Variable)
条件变量用于线程间的同步,允许一个或多个线程在某个条件发生时被唤醒。
- 实现原理:
- 条件变量通常与互斥锁一起使用。线程在等待条件变量时会释放互斥锁,并在条件满足时被唤醒。
- 条件变量的实现通常依赖于操作系统提供的信号量或其他同步原语。
5. 内存屏障(Memory Barrier)
在多线程环境中,内存屏障用于确保特定的内存操作顺序,以避免编译器和 CPU 的优化导致的数据不一致。
- 实现原理:
- 内存屏障可以防止编译器重排代码,确保在屏障之前的所有操作在屏障之后的操作之前完成。
- 在 C++ 中,可以使用原子操作和内存序(memory order)来控制这些操作的顺序。
相关文章:

C++个人复习(4)
C中为什么要引入make_shared,它有什么优点 1. 减少内存分配次数 使用 make_shared 时,内存分配只发生一次,它同时分配了对象和控制块(用于管理引用计数等信息)。而如果直接使用 new 创建对象并传递给 shared_ptr,则会…...

Dockerhub镜像加速
一、背景 dockerhub由于被封锁和站点处于国外的原因,docker pull拉取镜像非常慢,有时候直接都无法拉取。严重妨碍了我们的学习进度以及日常使用。 总结了一些proxy代理的镜像站点,配置之后速度会有明显提升,大家可以参考使用。 二…...

11.20讲座笔记
信息门户 -------- 人才培养方案(重要) 结构化矛盾------需求方(企业) ------供给方(高校) 电子方向职业 -------- 基建、基础算力 -------中国 1st (已经相对完善饱和) 网…...

网络协议之UDP
一、UDP协议定义 UDP(User Datagram Protocol,用户数据报协议)是一种面向无连接的、不可靠的、基于数据报的传输层通信协议。UDP在传输数据时不需要建立连接,直接将数据包发送出去。这种特性使得UDP在实时性要求较高的应用场景中…...

Elasticsearch面试内容整理-常见问题和解决方案
在使用 Elasticsearch 的过程中,可能会遇到各种常见问题,如集群状态异常、分片未分配、查询性能低下等。这些问题往往影响系统的可用性和性能,因此理解这些问题的成因和解决方案非常重要。以下是 Elasticsearch 常见问题及其解决方案的整理。 集群状态问题 Elasticsearch 集…...

React 表单Form 中的 useForm
1、介绍 useForm 是 React Hook Form 中的核心 Hook,用于管理表单的状态和行为。它提供了处理表单验证、数据收集、状态管理等功能的简便方法。useForm 本质上是用于创建和配置表单,并允许你在组件中与表单字段交互。 2、基本用法 useForm 是一个函数…...

用指针函数寻找数组中的最大值与次大值
#include <stdio.h>// 函数用于找出数组中的最大值和次大值 void LargestTow(int a[], int n, int *pfirst, int *psecond) {*pfirst a[0];*psecond a[1];if (*psecond > *pfirst) {// 如果初始的次大值大于最大值,交换它们int temp *pfirst;*pfirst *…...

人工智能在金融领域的创新与应用
引言:AI如何重塑金融行业? 金融行业是人工智能(AI)技术的最佳应用场景之一。通过数据分析、模式识别和自动化处理,AI正为金融行业提供高效、安全和智能化的解决方案。从反欺诈到投资决策,AI正逐步改变金融服…...

shell脚本(4)一文解决比较运算符用户交互
免责声明 学习视频来自B 站up主泷羽sec,如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识,以下代码、网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。 比较运算符 在Shell脚本中…...

windows 操作系统下载 Android源码教程
前言 开始我是装了hyber-v 虚拟机ubuntu 的,然而非常的卡顿且难用。因此我尝试在windows上使用repo,因此有了这篇文章 补充 第二天发现编译源码也需要linux命令因为源码中的很多脚本都是.sh的 因此最终通过安装WSL解决(在window应用商店就…...

【AIGC】如何使用高价值提示词Prompt提升ChatGPT响应质量
博客主页: [小ᶻZ࿆] 本文专栏: AIGC | 提示词Prompt应用实例 文章目录 💯前言💯提示词英文模板💯提示词中文解析1. 明确需求2. 建议额外角色3. 角色确认与修改4. 逐步完善提示5. 确定参考资料6. 生成和优化提示7. 生成最终响…...

vue3-input 搜索框
第一种 实现效果 实现代码 <template><div class="input-box mb20"><input type="text" class="input" /><span class="span">搜</span></div> </template><script setup> import …...

记录eslint报错的情况
这几天在调试vue的eslint,害,我领导说eslint要打开规范代码,顺带看了一下eslint的规则,并且研究一下报错。切记每次修改了.eslintrc配置文件,需要重启项目再查看控制台,否则之前的报错会一直存在。 第一个…...

mongodb多表查询,五个表查询
需求是这样的,而数据是从mysql导入进来的,由于mysql不支持数组类型的数据,所以有很多关联表。药剂里找药物,需要药剂与药物的关联表,然后再找药物表。从药物表里再找药物与成分关联表,最后再找成分表。 这里…...

Git Bash + VS Code + Windows11 Git命令报错莫名奇妙的问题
环境: git version 2.47.0.windows.1 gitbash版本:Git-2.47.0-64-bit windows版本: Windows 11 专业版 版本号 23H2 安装日期 2024/11/16 操作系统版本 22631.4460 体验 Windows Feature Experience Pack 1000.22700.10…...

湛江市社保卡申领指南:手机获取电子照片回执单号
在湛江市,社保卡的申领流程已经实现了数字化,为市民带来了极大的便利。特别是通过手机获取数码照片回执单号,这一环节更是简化了申领过程。今天,我们将详细介绍如何不去照相馆,利用手机来获取数码照片回执单号…...

Linux离线安装Docker命令,简单镜像操作
解压安装包 首先,使用 tar 命令解压 docker-27.3.1.tgz 安装包: tar -zxvf docker-27.3.1.tgz 将二进制文件移动到可执行路径上的目录 接着,将解压出来的 Docker 二进制文件复制到系统的可执行路径(通常是 /usr/bin/)…...

【Node.js】Node.js 和浏览器之间的差异
Node.js 是一个强大的运行时环境,它在现代 JavaScript 开发中扮演着重要角色。然而,许多开发者在使用 Node.js 时常常会感到困惑,尤其是与浏览器环境的对比。本文将深入探讨 Node.js 和浏览器之间的差异,帮助你全面理解两者的设计…...

基于MySQL的 CMS(内容管理系统)的表结构设计
目录 1. 用户管理 (Users) 2. 内容管理 (Content/Posts) 3. 分类 (Categories) 4. 标签 (Tags) 5. 内容与分类关系 (Content_Category) 6. 内容与标签关系 (Content_Tag) 7. 媒体库 (Media) 8. 设置 (Settings) 9. 评论 (Comments) 10. 活动日志 (Activity_Log) 11. …...

2.13 转换矩阵
转换矩阵引用了库nalgebra,使用时研究具体实现。 use std::ops;use nalgebra::Perspective3;use crate::Scalar;use super::{Aabb, LineSegment, Point, Triangle, Vector};/// An affine transform #[repr(C)] #[derive(Debug, Clone, Copy, Default)] pub struct…...

【C语言】遗传算法matlab程序
遗传算法matlab程序 遗传算法是一种模拟自然选择过程的优化技术,用于解决复杂问题。在MATLAB中编写遗传算法程序,通常包括以下几个步骤: 初始化种群:创建一个初始解集(种群),每个解代表一个问题…...

Java LinkedList 详解
LinkedList 是 Java 集合框架中常用的数据结构之一,位于 java.util 包中。它实现了 List、Deque 和 Queue 接口,是一个双向链表结构,适合频繁的插入和删除操作。 1. LinkedList 的特点 数据结构:基于双向链表实现,每个…...

mac-mini的时间机器,数据备份到alist 中的网盘
苹果的时间机器不能直接将备份存储在 alist 上的网盘,但可以通过一些中间步骤来实现类似的效果,以下是具体介绍: 方法原理 通过将支持 WebDAV 协议的网络存储挂载到本地,再将其设置为时间机器的备份磁盘,从而间接实现…...

【HarmonyOS】鸿蒙应用加载读取csv文件
【HarmonyOS】鸿蒙应用加载读取csv文件 一、问题背景: 1. csv文件是什么? csv是一种文本文件格式,与json类似。会存储一些文本内容,应用需要读取该文件,进行UI内容得填充等。 文件中的数据是以纯文本形式存储的&…...

Java retainAll() 详解
在 Java 中,retainAll() 是 Collection 接口(List、Set 等集合类实现该接口)的一种方法,用于保留集合中与指定集合交集的元素,删除其他所有元素。 以下是对 retainAll() 方法的详细讲解。 1. 方法定义 方法签名 boo…...

Redis的基本数据类型
初识Redis缓存 Redis缓存: 实际开发中经常使用Redis作为缓存数据库,从而提高数据存取效率,减轻后端数据库的压力。 可以将经常被查询的数据缓存起来,比如热点数据,这样当用户来访问的时候,就不需要到MyS…...

通过vite+vue3+pinia从0到1搭建一个uniapp应用
最近项目上要做一个app,选择了用uniapp作为开发框架;我大概看了一下uniapp的文档,根据文档从0到1搭了一个uniapp应用供大家参考。 因为本人习惯使用了WebStorm编译器,但是uniapp官方推荐使用HBuilder搭建,如果和我一样…...

Linux的桌面
Linux的桌面是可以卸载的 的确,Linux并不像Windows,Linux本身是一个基于命令行的操作系统,在内核眼中,桌面只不过是个普通的应用程序,所以,在Linux的桌面中可以完成的事情,命令行中也基本可以完…...

Easyexcel(5-自定义列宽)
相关文章链接 Easyexcel(1-注解使用)Easyexcel(2-文件读取)Easyexcel(3-文件导出)Easyexcel(4-模板文件)Easyexcel(5-自定义列宽) 注解 ColumnWidth Data…...

操作系统实验 C++实现死锁检测算法
实验目的 模拟实现死锁检测算法 实验内容 1、 输入: “资源分配表”文件,每一行包含资源编号、进程编号两项(均用整数表示,并用空格分隔开),记录资源分配给了哪个进程。 “进程等待表”文件&…...