简易STL实现 | Vector的实现

1、内存管理

1、std::vector 维护了两个重要的状态信息:容量(capacity:当前 vector 分配的内存空间大小)和大小(size: vector 当前包含的元素数量)

2、当容量不足以容纳新元素时,std::vector 会分配一块新的内存空间,将原有元素复制到新的内存中,然后释放原内存
std::vector 采用了一种称为“指数增长”的策略进行动态扩容
当需要进行扩容时,std::vector 通常会将容量翻倍,以避免频繁的内存分配操作,从而减少系统开销
这种指数增长策略 确保了平均情况下的插入操作具有常数时间复杂度

3、在频繁插入或删除元素的情况下,std::vector 可能不是最佳选择,因为这样的操作可能触发频繁的动态扩容,导致性能下降
考虑使用 std::deque 或 std::list 这样的容器,对插入和删除操作有更好的性能

2、vector 工作原理

1、当调用 vector 的 push_back 等方法时,vector 可能会重新分配其底层的动态数组以适应新元素。这通常涉及申请新的更大的内存块,复制现有元素到新内存,添加新元素,然后释放旧的内存块。在 C++ 官方实现的 vector 中,这种动态内存管理通常是通过分配器来完成的,vector 使用一个默认的分配器 std::allocator,它封装了动态内存分配函数,如 new 和 delete
在这里插入图片描述
虚线以上的内存为 栈内存,虚线以下的内存为 堆内存
红色区域为 vector 对象控制结构存储的位置
紫色区域和灰色区域 为存储元素的数组的位置, 其中紫色区域 表示已经使用, 灰色区域 表示未使用

2、std::vector的存储位置 取决于两个方面:vector本身的对象、它管理的元素、该类对象的存储位置
1)std::vector 对象本身:这个对象是 一个轻量级的结构体,包含了 指向其元素的指针、元素的大小和容量等信息。这个结构体 本身可以存储在栈上或堆上,具体取决于 如何创建 vector

如果 在函数内部直接声明一个 vector(被直接声明为一个局部变量),它就会被分配在 栈上:

void func() {std::vector<int> vec; // vec对象在栈上
}

如果你通过 new 关键字动态分配 vector,那么它的结构体对象会被分配在 堆上:

std::vector<int>* vec = new std::vector<int>; // vec对象在堆上

2)std::vector 的元素:std::vector 管理的元素始终存储在堆上。无论 vector 对象本身在栈上还是堆上,它内部管理的元素总是 动态分配的,即在堆上。这是因为 vector 的容量可以动态扩展,因此 无法在栈上存储不确定大小的元素数组

std::vector<int> vec; // vec对象在栈上
vec.push_back(1); // 元素1存储在堆上

3)如果std::vector 是某个类的成员变量,那么它的存储位置 取决于 该类对象的存储位置。如果类对象在栈上,那么 vector 也在栈上;如果类对象在堆上,那么 vector 也在堆上。例如:

class MyClass {std::vector<int> vec; // vec的存储位置取决于MyClass对象的存储位置
};void func() {MyClass obj; // obj在栈上,因此vec在栈上MyClass* pObj = new MyClass(); // pObj指向堆上的对象,因此vec在堆上
}

2.1 不同类型的变量的存储位置

1、栈(Stack):局部变量(在栈上分配,分配和释放速度快,局部const变量,通常存储在栈上)、函数参数、临时对象
堆(Heap):动态分配的变量
数据段(Data Segment):全局变量、静态变量:全局变量和静态变量 在程序开始时分配,直到程序结束时 才会被释放

int globalVar = 10; // 全局变量
static int staticVar = 20; // 静态变量

只读数据段(Read-Only Data Segment):常量(程序运行期间一直存在)、字符串字面量

const char* str = "Hello"; // 字符串字面值

2、局部变量、函数参数、临时对象通常分配在栈上,而动态分配的变量则分配在堆上

  1. 栈(Stack)的特点
    栈的内存管理 由编译器自动处理。函数调用时 会在栈上分配一个称为“栈帧”的内存区域,用于存储局部变量、函数参数和返回地址。当函数结束时,栈帧自动释放,栈指针复位,内存立即回收
    分配速度:由于栈的内存分配是 连续的且自动管理,分配和释放内存的速度非常快,适合存储短生命周期的小型数据
    限制:栈的大小通常受限于 操作系统,因此不适合存储大数据块 或 生命周期较长的对象

  2. 堆(Heap)的特点
    结构:堆是一块较大的、灵活的内存区域,允许 不连续的内存分配。内存分配器(如malloc或new)管理堆上的内存分配和释放
    管理方式:堆上的内存分配 和 释放需要程序员手动管理。内存可以在程序的任何时候分配,并在不再需要时释放。堆上的内存 不依赖于函数调用的层次结构,因此适合存储需要动态大小 或 长生命周期的数据
    分配速度:由于堆 需要查找合适大小的内存块,内存分配和释放 比栈慢。堆的碎片化问题 也会影响内存分配效率
    优点:堆可以存储任意大小的数据块,且生命周期由程序员控制,因此适合 需要跨函数或线程共享的数据

  3. 为什么选择栈还是堆?
    栈:
    适合生命周期短且大小固定的变量(如局部变量、函数参数)
    内存分配和释放速度快,但空间有限
    堆:
    适合需要动态大小、复杂结构 或 跨函数共享的数据(如动态数组、大对象)
    内存管理 由程序员控制,提供灵活性,但分配和释放速度较慢

2.2 更多关于堆

堆能够实现 一块较大的、灵活的内存区域,并允许 不连续的内存分配,这主要是由于 堆的结构和管理方式与栈的显著不同

  1. 堆的结构
    非连续内存:堆内存不需要是连续的。与栈不同,堆内存 可以在物理地址上不连续,这意味着 堆能够利用整个可用内存空间,而不仅仅是 栈这种线性分配模式下的连续块
    自由分配:堆内存的分配是动态的,可以按需分配和释放。内存管理器 会跟踪已分配的内存块 和 可用的空闲块,从而在程序运行时 根据需要分配和回收内存
  2. 内存分配器
    分配策略:堆内存的分配 通常由操作系统或运行时库中的内存分配器(如malloc、new等)管理。内存分配器会根据 不同的分配策略(如首次适配、最佳适配、最差适配)来查找 适合的空闲块并进行分配
    空闲块管理:内存分配器 维护一个空闲块列表 或 其他数据结构来管理未使用的内存。这使得 分配器可以分配不连续的内存块,而不需要 像栈那样严格按照内存顺序分配
    碎片化处理:堆的灵活性带来了 内存碎片化的可能性。内存分配器 会通过合并相邻的空闲块 或 采用更智能的分配算法来减少碎片化的影响
  3. 堆的大小与灵活性
    堆的大小:堆的大小通常 远大于栈,具体取决于 操作系统和硬件配置。因为堆在进程地址空间中 占据了一大片区域,可以容纳大量数据,适合存储 需要大量内存的大对象
    灵活性:由于堆 允许非连续的内存分配,程序可以在 不同时间请求不同大小的内存块,而不受限于之前的分配。这种灵活性 使得堆能够满足复杂数据结构和应用程序的需求,如链表、树、图等,它们通常需要动态分配内存
  4. 内存分配和释放的独立性
    独立分配和释放:在堆上分配的内存块 可以在任何时候独立释放,而不影响其他内存块。这与栈不同,栈只能按照 LIFO 顺序释放内存
    动态大小:由于堆 允许动态分配和释放内存,程序可以根据 实际需求调整内存使用,而无需在编译时确定大小。这种灵活性 使得堆特别适合 需要动态调整大小的数据结构

3、实现vector

3.1 功能和特性

设计一个名为 MyVector 的 Vector 类,该类应具备以下功能和特性:

1、基础成员函数:
构造函数:初始化 Vector 实例
析构函数:清理资源,确保无内存泄露
拷贝构造函数:允许通过现有的 MyVector 实例来创建一个新实例
拷贝赋值操作符:实现 MyVector 实例之间的赋值功能

2、核心功能:
添加元素到末尾:允许在 Vector 的末尾添加新元素
获取元素个数:返回 Vector 当前包含的元素数量
获取容量:返回 Vector 可以容纳的元素总数
访问指定索引处的元素:通过索引访问特定位置的元素
在指定位置插入元素:在 Vector 的特定位置插入一个新元素
删除数组末尾元素:移除 Vector 末尾的元素
清空数组:删除 Vector 中的所有元素,重置其状态

3、迭代与遍历:
使用迭代器遍历:实现迭代器以支持对 Vector 从开始位置到结束位置的遍历
遍历并打印数组元素:提供一个函数,通过迭代器遍历并打印出所有元素

4、高级特性:
容器扩容:当前容量不足以容纳更多元素时,自动扩展 Vector 的容量以存储更多元素

3.2 输入和输出

题目的包含多行输入,第一行为正整数 N, 代表后续有 N 行命令序列
输入示例:

15
push 20
push 30
push 40
print
insert 0 10
size
print
get 1
pop
print
iterator
foreach
clear
size
print

输出示例:

20 30 40 
4
10 20 30 40 
20
10 20 30 
10 20 30 
10 20 30 
0
empty

3.3 代码实现

#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <string>
#include <sstream> // istringstreamtemplate <typename T>
class Vector {
private:T* elements;size_t capacity;size_t size;public:Vector() : elements(nullptr), capacity(0), size(0) {}~Vector() {delete[] elements;}Vector(Vector& vec) : capacity(vec.capacity), size(vec.size) { // 深拷贝elements = new T[capacity]; // 是capacity,不是sizestd::copy(vec.elements, vec.elements + size, elements);}Vector& operator=(Vector& vec) { // 深拷贝,跟拷贝构造函数很像if (*this != vec) { // 注意自赋值delete[] elements;elements = new T[capacity];capacity = vec.capacity;size = vec.size;std::copy(vec.elements, vec.elements + size, elements);}return *this;}void push_back(T& e) {if (size == capacity)reserve(capacity == 0 ? 1 : 2 * capacity);elements[size++] = e; // 指针可以直接用下标操作}size_t getSize() {return size;}size_t getCapacity() {return capacity;}T& operator[](size_t pos) { // 要返回T&if (pos >= size)throw std::out_of_range("index out of range");return elements[pos];}void insert(size_t pos, const T& ele) {if (capacity <= size) {reserve(capacity == 0 ? 1 : 2 * capacity);}if (pos >= size)throw std::out_of_range("index out of range");for (size_t i = size; i > pos; i--) {elements[i] = elements[i - 1];}elements[pos] = ele;++size; // 别忘了}void pop_back() {if (size > 0)size--;}void clear() {size = 0;}// 使用迭代器遍历数组的开始位置,就是指针// 非const版本的迭代器允许对容器中的元素进行修改// 例如,在一个非const的vector对象上使用begin()和end()返回的迭代器,可以对元素执行修改操作// 非const版本:// for (auto it = vec.begin(); it != vec.end(); ++it) {//      *it = *it * 2; // 修改元素值// }T* begin() {return elements;}T* end() {return elements + size;}// const版本的迭代器用于不允许修改容器内容的场景。// 在一个const的vector对象上使用begin()和end(),返回的是const版本的迭代器,它只允许读取元素,而不能修改它们// const版本:// const std::vector<int> vec = {1, 2, 3, 4, 5};// for (auto it = vec.begin(); it != vec.end(); ++it) {//      std::cout << *it << " "; // 只读,不允许修改元素// }const T* begin() const {return elements;}const T* end() const {return elements + size;}void printElements() {for (size_t i = 0; i < size; i++) {std::cout << elements[i] << " ";}std::cout << std::endl;}private:void reserve(size_t s) {if (s > capacity) { // 注意判断T* newEle = new T[s];std::copy(elements, elements + size, newEle);delete[] elements;elements = newEle;capacity = s;}}
};int main() {Vector<int> myVector;int line_num;std::cin >> line_num;// 读走回车getchar();for (int i = 0; i < line_num; i++) {// 先读取整行,再从中扣stringstd::string line;std::getline(std::cin, line);std::string command;std::istringstream iss(line);iss >> command;if (command == "push") {// 尽管 std::istringstream 是从字符串构造的,但它支持各种数据类型的提取操作,并不仅限于字符串int num;iss >> num;myVector.push_back(num);}else if (command == "size") {std::cout << myVector.getSize() << std::endl;}else if (command == "get") {size_t s;iss >> s;std::cout << myVector[s] << std::endl;}else if (command == "insert") {size_t index;int ele;iss >> index >> ele;myVector.insert(index, ele);}else if (command == "pop") {myVector.pop_back();}else if (command == "clear") {myVector.clear();}else if (command == "print") {if (myVector.getSize() == 0) { // 别忘了std::cout << "empty" << std::endl;continue;}myVector.printElements();}else if (command == "iterator") {if (myVector.getSize() == 0){std::cout << "empty" << std::endl;continue;}int* begin = myVector.begin();int* end = myVector.end();for (int* it = begin; it != end; it++) {std::cout << *it << " ";}std::cout << std::endl;}else if (command == "foreach") {if (myVector.getSize() == 0){std::cout << "empty" << std::endl;continue;}// 为了使这段代码正常工作,myVector 必须提供一个迭代器接口,即 begin() 和 end() 函数// begin() 和 end() 函数返回的是指向 elements 数组的指针,所以这个范围-based for 循环在 myVector 上的执行实际上是遍历 elements 数组中的每个元素/* 大致相当于for (int* it = myVector.begin(); it != myVector.end(); ++it) {int& element = *it;std::cout << element << " ";}*/for (auto& element : myVector) {std::cout << element << " ";}std::cout << std::endl;}}return 0;
}

本实现版本 和 C++ STL标准库实现版本的区别:
不同的实现复杂度,不同的功能覆盖范围,内存管理和性能,安全性和健壮性

4、相关面试题

1、emplace_back 是 C++ 标准库中 std::vector 和其他容器(如std::deque、std::list等)提供的一个成员函数。它的作用是 直接在容器末尾 构造一个元素,而不是 先构造元素 然后再拷贝或移动到容器中。与 push_back 相比,emplace_back 可以减少不必要的拷贝或移动操作,从而提高性能

#include <vector>
#include <string>
#include <iostream>struct MyStruct {int a;std::string b;MyStruct(int x, std::string y) : a(x), b(y) {std::cout << "Constructed MyStruct\n";}
};int main() {std::vector<MyStruct> vec;// 使用 emplace_backvec.emplace_back(10, "Hello");// 使用 push_backMyStruct obj(20, "World");vec.push_back(obj);return 0;
}

emplace_back(10, "Hello") 直接在 vec 中构造了一个 MyStruct 对象,而 push_back 则需要先创建一个 MyStruct 对象 obj,然后再将其复制或移动到 vec 中

2、使用 std::vector::empty() 方法可以检查 vector 是否没有元素。这比使用 size() 方法(比较 size() == 0)更首选
empty():empty() 函数的实现通常是直接检查内部的大小标识(如 size 或 begin 和 end 指针的比较),因此它几乎总是 O(1) 的时间复杂度,意味着它可以在常数时间内完成

size():虽然 size() 也是 O(1) 的时间复杂度,但在某些容器实现中,size() 可能需要 额外计算或检查以确定大小,尤其是 在某些数据结构(如 std::list)的实现中,size() 可能是 O(n) 的复杂度。不过,对于 std::vector,size() 仍然是 O(1) 的

3、可以使用 std::vector::shrink_to_fit 方法来请求移除未使用的容量,减少 vector 的内存使用。这个函数是 C++11 引入的,它会尝试压缩 std::vector 的容量,使其等于其大小。但是,这只是一个请求,并不保证容量会减少

4、如增加或删除元素,尤其是在中间插入或删除元素时,迭代器可能会失效。例如:
如果 vector 进行了重新分配,所有指向元素的迭代器都会失效
如果在 vector 中间插入或删除元素,从该点到末尾的所有迭代器都会失效

std::remove 和 std::remove_if (都不会真正删除元素,只是重新排列元素,剩下的无效元素 仍然存在于容器的末尾)结合 std::vector::erase 方法是 一种常见的模式,用于从 std::vector 中删除符合条件的元素

std::remove / std::remove_if:重新排列容器元素,将符合条件的元素移到末尾,并返回 指向新逻辑末尾的迭代器
vector::erase:使用返回的迭代器 来删除末尾的元素,从而真正移除这些元素 并调整容器的大小

#include <vector>
#include <algorithm>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5, 2, 6, 2};// 使用 std::remove 移除值为 2 的元素vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());// 使用 std::remove_if 移除所有偶数vec.erase(std::remove_if(vec.begin(), vec.end(), [](int n) { return n % 2 == 0; }), vec.end());// 输出结果for (int n : vec) {std::cout << n << " ";  // 输出: 1 3 4 5 6}return 0;
}

5、如果 std::vector 存储的是原始指针,那么仅仅清空 vector 或者让 vector 被销毁,并不会释放指针所指向的内存。因此,需要确保在 vector 被销毁之前,逐个删除所有动态分配的对象

#include <vector>
#include <iostream>class MyClass {
public:MyClass(int value) : value(value) {std::cout << "MyClass constructed with value: " << value << std::endl;}~MyClass() {std::cout << "MyClass destructed with value: " << value << std::endl;}
private:int value;
};int main() {std::vector<MyClass*> vec;// 动态分配对象并存储在 vector 中for (int i = 0; i < 5; ++i) {vec.push_back(new MyClass(i));}// 清空 vector 之前手动删除动态分配的对象for (MyClass* ptr : vec) {delete ptr;}vec.clear(); // 现在可以安全地清空 vectorreturn 0;
}

为了避免手动管理内存,可以考虑使用智能指针(如 std::unique_ptr 或 std::shared_ptr)来替代原始指针。使用智能指针时,std::vector 被销毁时,智能指针会 自动管理和释放其所指向的内存,从而避免内存泄漏

#include <vector>
#include <memory>int main() {std::vector<std::unique_ptr<MyClass>> vec;// 使用 std::unique_ptr 动态分配对象for (int i = 0; i < 5; ++i) {vec.push_back(std::make_unique<MyClass>(i));}// 不需要手动删除,智能指针会自动管理内存vec.clear(); // 或者 vec 被销毁时,内存会自动释放// 使用 clear() 后,vector 的大小 (size()) 将变为 0,但它的容量 (capacity()) 不会改变return 0;
}

6、深拷贝与浅拷贝:如果 需要复制这样的 vector,就需要决定是 进行深拷贝(复制指针指向的对象,两个 vector 是完全独立的,修改或删除一个 vector 中的对象 不会影响另一个 vector)还是浅拷贝(仅复制指针本身,拷贝后的 vector 中的指针与原 vector 中的指针指向同一块内存)

7、当 vector 的元素是指针对 std::vector 元素为指针的情况,需要注意以下几点:
内存管理:如果 std::vector 存储的是原始指针,那么仅仅清空 vector 或者 让 vector 被销毁,并不会释放指针所指向的内存。因此,需要确保在 vector 被销毁之前,逐个删除所有动态分配的对象

所有权和生命周期:需要确保在 vector 所包含的指针被使用期间,指向的对象是有效的。同时,需要清楚地定义谁拥有这些对象的所有权,以及在何时何地进行释放

异常安全:如果在创建和填充 vector 的过程中遇到异常,需要有一个清晰的机制来处理已经分配的内存,以避免内存泄漏

std::vector<MyClass*> vec;
try {for (int i = 0; i < 10; ++i) {vec.push_back(new MyClass(i));}
} catch (...) {// 清理已分配的内存for (MyClass* ptr : vec) {delete ptr;}vec.clear();throw; // 重新抛出异常
}

智能指针:为了简化内存管理,推荐使用智能指针(如 std::unique_ptr 或 std::shared_ptr)作为 vector 的元素类型。这样,当 vector 被清空或销毁时,智能指针会自动释放它们所拥有的资源

避免悬垂指针:当指针指向的对象被删除或移动时,需要确保没有悬垂指针指向无效的内存地址。同样,当 vector 被重新分配时,如果存储的是指向其他元素的指针,这些指针也会失效

深拷贝与浅拷贝:如果需要复制这样的 vector,就需要决定是进行深拷贝(复制指针指向的对象)还是浅拷贝(仅复制指针本身)

https://kamacoder.com/ 手写简单版本STL,内容在此基础上整理补充

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

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

相关文章

SSH 远程登录报错:kex_exchange_identification: Connection closed.....

一 问题起因 在公司,使用ssh登录远程服务器。有一天,mac终端提示:`kex_exchange_identification: Connection closed by remote host Connection closed by UNKNOWN port 65535`。 不知道为啥会出现这样的情形,最近这段时间登录都是正常的,不知道哪里抽风了,就提示这个。…

巴恩斯利蕨数学公式及源码实现——JavaScript版

为什么要写这篇文章 本篇接《张侦毅&#xff1a;巴恩斯利蕨数学公式及源码实现》。之前文章中源码的编程语言用的是Java&#xff0c;JDK的版本为8&#xff0c;现在我的JDK版本已经升级到22了&#xff0c;在新版本JDK中&#xff0c;原来的JApplet方法已经被废弃&#xff0c;不能…

鸿蒙实现在图片上进行标注

一.实现思路 现在需求是&#xff1a;后端会返回在这张图片上的相对位置&#xff0c;然后前端这边需要在图片上进行标注&#xff0c;就是画个框框圈起来&#xff0c;返回的数据里包括当前框的x,y坐标和图片大小&#xff0c;大体思路就是使用canvas绘制&#xff0c;使用鸿蒙的st…

vue-element-admin解决三级目录的KeepAlive缓存问题(详情版)

vue-element-admin解决三级目录的KeepAlive缓存问题&#xff08;详情版&#xff09; 本文章将从问题出现的角度看看KeepAlive的缓存问题&#xff0c;然后提出两种解决方法。本文章比较详细&#xff0c;如果只是看怎么解决&#xff0c;代码怎么改&#xff0c;请前往配置版。 一…

零工市场小程序应该有什么功能?

数字经济现如今正飞速发展&#xff0c;零工市场小程序在连接雇主与自由职业者方面发挥着越来越重要的作用。一个高效的零工市场小程序不仅需要具备基础的信息发布与匹配功能&#xff0c;还应该涵盖交易管理、安全保障以及个性化服务等多个方面。 那么&#xff0c;零工市场小程…

Ubuntu22.04下安装LDAP

目录 1 简单说明2 安装配置2.1 安装1、安装前准备2、安装 OpenLADP3、配置OpenLDAP4、设置基本组5、添加新组5、添加 OpenLDAP 用户 2.2 安装 LDAP 帐户管理器1、安装2、配置 LDAP 帐户管理器 3 简单使用3.1 创建一个组3.2 创建一个用户 总结 1 简单说明 之前写过在Centos下的…

nginx和tomcat负载均衡,动静分离

文章目录 一&#xff0c;tomcat1.tomca用途2.tomcat重要目录 二&#xff0c;nginx1.Nginx应用2.nginx作用3.nginx的正向代理和反向代理3.1正向代理3.2反向代理(单级)3.3反向代理(多级) 4.nginx负载均衡4.1Nginx支持的常见的分流算法1. 轮询(Round Robin):2.最少连接数(LeastCon…

[MRCTF2020]Hello_ misc

解压得一个png图片和一个flag.rar 图片拖入010editor 选择带zip头的这段蓝色全部复制&#xff0c;file-new-new Hex File&#xff0c;黏贴到新文件&#xff0c;另存为为1.zip 要密码,线索中断&#xff08;当然try to restore it.png&#xff0c;隐藏了zip压缩包&#xff0c;可…

uniapp - plugins的组件配置使用

点击进入到uniapp中mp-weixin的配置中 点击进入小程序的plugin的配置 在项目中&#xff0c;我们可引用插件的使用&#xff0c;例如一些快递100&#xff0c;点餐插件的业务引入 添加插件 在使用插件前&#xff0c;首先要在小程序管理后台的“设置-第三方服务-插件管理”中添加…

java ssl使用自定义证书或忽略证书

1.证书错误 Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 2.生成客户端证书 openssl x509 -in <(openssl s_client -connect 192.168.11.19:8101 -prexit 2>/dev/null) -ou…

C HTML格式解析与生成

cmake报错替换 if(NOT MyHTML_BUILD_WITHOUT_THREADS OR NOT MyCORE_BUILD_WITHOUT_THREADS) set(CMAKE_THREAD_PREFER_PTHREAD 1) if (WIN32) set(CMAKE_USE_WIN32_THREADS_INIT ON) set(CMAKE_THREAD_PREFER_PTHREADS TRUE) set(THREADS_PR…

windows配置jmeter定时任务

场景&#xff1a; 需要让脚本在指定的执行 步骤&#xff1a; 准备jmeter脚本&#xff0c;保证在命令行中可以调用脚本且脚本运行正常&#xff1a;"C:\Apache\jmeter\bin\jmeter.bat" -n -t C:\tests\test_plan.jmx -l C:\tests\results.jtl -t : 指定执行jmeter脚…

异步交互技术Ajax-Axios

目录 一、同步交互和异步交互 二、Ajax 1.概述 2.如何实现ajax请求 三、异步传输数据乱码的问题 regist.html页面代码 服务端代码处理 四、Axios 1. Axios的基本使用 &#xff08;1&#xff09;引入Axios文件 &#xff08;2&#xff09;使用Axios发送请求&#xff0…

通过Python绘制不同数据类型适合的可视化图表

在数据可视化中&#xff0c;对于描述数值变量与数值变量之间的关系常见的有散点图和热力图&#xff0c;以及描述数值变量与分类变量之间的关系常见的有条形图&#xff0c;饼图和折线图&#xff0c;可以通过使用Python的matplotlib和seaborn库来绘制图表进行可视化表达&#xff…

【大数据】什么是数据中台?

随着企业规模不断扩大、业务多元化——中台服务架构的应运而生。“中台”早期是由美军的作战体系演化而来的&#xff0c;技术上说的“中台”主要是指学习这种高效、灵活和强大的指挥作战体系。阿里在今年发布“双中台ET”数字化转型方法论&#xff0c;“双中台”指的是数字中台…

ResNet网络学习

简介 Residual Network 简称 ResNet (残差网络) 下面是ResNet的网络结构&#xff1a; ResNet详细介绍 原理 传统方法的问题&#xff1a; 对于一个网络&#xff0c;如果简单地增加深度&#xff0c;就会导致 梯度消失 或 梯度爆炸&#xff0c;我们采取的解决方法是 正则化。…

卸载nomachine

网上的方法:提示找不到命令 我的方法: step1. 终端输入 sudo find / -name nxserver 2>/dev/null确认 NoMachine 的实际安装路径。你可以使用 find 命令在系统中查找 nxserver 脚本的位置。 找到路径后,你可以使用该路径来卸载 NoMachine。 如下图,紫色框中是我的路径…

ProtoBuf简要介绍与快速上手使用(C++版)

文章目录 一、 初识ProtoBuf1. 序列化和反序列化概念2. ProtoBuf是什么3. ProtoBuf的使用特点 二、 讲解说明三、 快速上手1. 创建 .proto 文件2. 编译 contacts.proto 文件&#xff0c;生成C文件3. 序列化与反序列化的使用4. 小结 ProtoBuf 使用流程 一、 初识ProtoBuf 1. 序…

Linux权限维持实战

目录 介绍步骤 介绍 攻击者在获取服务器权限后&#xff0c;会通过一些技巧来隐藏自己的踪迹和后门文件 查看/tmp目录下的flag文件 查看/root目录下具有特殊文件属性的文件 操作机中共有几个SUID文件 操作机中共有几个SGID文件 查看操作机中ssh公私钥免密登陆 查看strace后门 …

Web3链上聚合器声呐已全球上线,开启区块链数据洞察新时代

在全球区块链技术高速发展的浪潮中&#xff0c;在创新发展理念的驱动下&#xff0c;区块链领域的工具类应用备受资本青睐。 2024年8月20日&#xff0c;由生纳&#xff08;香港&#xff09;国际集团倾力打造的一款链上应用工具——“声呐链上聚合器”&#xff0c;即“声呐链上数…