c++STL——vector的使用和模拟实现

文章目录

  • vector的使用和模拟实现
    • vector的使用
    • vector介绍
    • 重点接口的讲解
      • 迭代器部分
      • 默认成员函数
      • 空间操作
      • 增删查改操作
      • 迭代器失效问题(重要)
        • 调整迭代器
    • vector的模拟实现
      • 实现的版本
      • 模拟实现
        • 结构
        • 预先处理的函数
          • 尾插函数push_back
          • swap函数
          • 赋值重载
          • size函数
          • reserve函数
        • 迭代器
        • 默认成员函数
          • 默认构造
          • 普通构造
          • 拷贝构造
          • 析构函数
        • 容量操作
        • 容量 、判空
          • resize函数
        • 修改操作
          • 尾删
          • insert函数
          • clear函数
          • erase函数
        • 打印函数(针对不同容器)

vector的使用和模拟实现

vector的使用

vector介绍

对于STL中各类容器的学习其实是很相似的,因为c++的封装性。虽然是不同的容器,但是c++标准库在实现的时候是对各类的容易实现了一些一样的接口,我们只需要关注其封装的接口的使用即可。所以各类容器的操作是很类似的。

而对于vector其实是一个类模板,其底层的实现本质还是顺序表。只不过与string的底层实现是略有区别。更大的不同是vector中存储的元素不仅仅是一些内置类型,也可以是类,如string,甚至是vector类。

当然学习STL容易是先学会如何使用其对应接口,我们得学会查阅文档:https://cplusplus.com/reference/vector/vector/

重点接口的讲解

迭代器部分

vector的接口其实没有string实现的那么多。因为string是更早写进标准库中的,这是历史遗留问题。

对于string,我们在增删的时候,更多的是传入对应的位置,也就是下标。而当我们查阅vector使用的文档的时候,我们发现参数竟然是使用迭代器的:
在这里插入图片描述
erase函数,可以传一个迭代器的位置,也可以传迭代器指向的一段区间(左闭右开)。

其实迭代器的使用和string是一样的的,有八种。end型的迭代器都是指向最后一个有效元素的后一个位置。

使用的话重点掌握beginrbeginendrend这四个就可以了。

默认成员函数

函数使用
vector()(重点)无参构造
vector(size_type n, const value_type& val =value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
~vector()析构 编译器会自行调用
vector& operator= (const vector& x);赋值重载

这里面有很多不认识的符号,下面给出这些符号的对应表:
在这里插入图片描述
我们直到,vector其实是一个类模板,内部的数据类型其实都是由模板参数T来替代的。但是为了代码的可读性更好,所以对一些常用的类型取别名。

我们只需要知道常用的那几个就可以了。

而还有一个很奇怪的构造函数:vector (InputIterator first, InputIterator last),使用迭代器进行初始化构造。这个InputIterator是什么呢?

其实这是一个模板参数的声名,template< class InputIterator >,声名这一个模板参数是因为在构造一个vector的时候,我们很可能需要用别的迭代器进行构造。
举一个很常见的例子:

有时候我们想对链表(STL中的list)中的数据进行排序。但是链表排序其实是效率较低的。所以我们会经常的使用链表的迭代器区间来构造一个vectorvector的本质是顺序表,使用顺序表排序是比较高效率的。然后排好序后再依次将数据覆盖回链表中。

当然这个迭代器也可以是指向数组的:
在这里插入图片描述
在这里我们可以认为是数组也有自己的迭代器。

空间操作

函数使用
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity

重点我们来看看resizereserve的使用。

对于resize:
在这里插入图片描述
这是调整数据个数的函数。如果传入的n是小于当前数据个数,那么就会删除数据。
如果n大于当前数据个数,会往后续新加入的位置插入数据,空间不够的时候会扩容。

这个插入的数据是带有缺省参数的,即value_type val = value_type();。这个其实是调用了匿名类的默认构造函数

很多人会疑惑,如果是int等内置类型也能这样使用吗?答案是当然可以。在以往我们会认为,只有自定义类型才会有默认构造函数。但其实在c++内,对于内置类型也是可以有默认构造函数的:
在这里插入图片描述
如果我们进行初始化了,那么值当然就是初始化的值。但是如果我们像上面参数显示的那样去调用默认构造,我们发现不传参的时候默认值是0,传参的时候就是将参数的值给变量。这个用法和自定义类型是一样的。所以我们不用担心自定义类型会使用不了的问题。

对于reserve:
reserve函数就是预留空间,因为c++标准没有明确规定一些细节,导致不同平台对于其实现是有差异的。

vs编译器下坚决不缩容,只会扩容,且扩容大致是1.5倍。
而g++编译器是会缩容的,扩容的方式是很标准的2倍扩容。

增删查改操作

函数说明
push_back(重点)尾插
pop_back (重点)尾删
find查找(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[](重点) 像数组一样访问

只需要注意的是inserterase的操作是要传入对应位置的迭代器。还有就是vector内并没有像string内实现find接口,我们要使用的话就得在算法库algorithm中去调用。

而其余的用法是很简单的,自行查阅文档即可。

迭代器失效问题(重要)

string中也有迭代器,但是我们并没有说迭代器失效的问题。这是因为对于string我们更偏向于用下标来访问内部数据。就连删除插入等操作也是用下标进行访问元素的。而vector却是用迭代器实现的,而在实现的时候发现了一些问题,这些问题统称为迭代器失效,下面让我们一起来看看:

在这里我们先说明一个事情:vector的正向迭代器其实就是数据类型的原生指针。

1.迭代器变为野指针,原来指向的空间被释放
这种问题通常出现在vector容量被修改的时候。我们在模拟实现string的时候就对容量进行修改的时候,是需要新开辟一个空间,然后再将原来空间进行释放。是没有像c语言中realloc一样功能的函数的。

问题就出现在这里:
在这里插入图片描述
当然如果要进行缩容也是一样的。所以插入和删除函数在实现的时候,就考虑到这个问题,进行了修正。使得这两个功能能够正常的使用并且达到想要的效果。

2.非法使用迭代器和非法访问元素
这个点也是需要非常注意的,下面我们举一个例子看看:

现有vector v1,指向内容是{1,2,2,3,4,4,5}

我们来看一下下面这个代码:

int main(){vector<int>:: iterator it = v1.begin();while(it != v1.end()){if(*it % 2){erase(it);*it = -1;}++it;}return 0;
}

乍一看没啥问题,但其实问题很大。

首先这个代码在不同平台下的结果是不一样的。在vs2022上是会断言报错的。而在g++编译器上能够正常运行,但是达不到想要的效果。

我们先来说g++下的情况:
运行结果为 1 -1 3 -1 5,这是为什么呢?
这是因为我们非法使用迭代器了:

当遍历到第一个2的时候,就会进行删除操作,那后续的数据会被移动到前面来,数据变成{1,2,3,4,4,5}。原来的2的位置被后面一个2顶上来了。但此时原来的空间并没有被销毁,而正向迭代器的本质是原生指针,所以指向的仍然是原来的那个位置,也就是后顶上来的2的位置,然后对此时位置进行修改,数组变成{1,-1,3,4,4,5}。然后++it会走到3的那个位置。

然后以此类推,最后变成了输出的结果。变成-1的位置就是为了告诉我们,如果使用这个代码去删除偶数项,会有被遗漏的偶数。这其实也是迭代器失效的一个方面。就是会导致访问元素出现问题。

如果在vs2022下:
编译器会直接断言报错。这是因为vs编译器做了严格的检查,如果再执行了删除和插入操作后,迭代器会失效,一般是不能访问的。所以编译器内部自动检查是否有修正迭代器的情况。如果没有就会报错。因为编译器认为这样子是非法访问。

当然对于上面那段代码,如果被删除的元素在末尾也是会出现问题,因为删除后数量减一,但是又要执行++it操作,那么it会越界。也是会触发断言报错的。

调整迭代器

这些都是迭代器失效的问题。为了 防止这种现象发生,我们就得调整迭代器的值。实际上vs编译器也是这么做的。

对于插入操作,编译器会返回新插入的元素的第一个的迭代器。插入操作可能会插入一个怨怒是,有可能插入多个元素。对此返回的是插入元素的第一个位置的迭代器。

对于删除操作,返回的是删除元素的最后一个的后一个元素的新位置的迭代器:
如1 3 5 6 7,删除3 和 5 ,变成 1 6 7,返回的就是指向6的迭代器。如果删除的最后一个元素正好是原本空间中的最后一个元素,那么返回的迭代器其实是数组结束位置。

所以要想真正的能把上面例子种数组的偶数全部删除,需要改进代码:

int main(){vector<int>:: iterator it = v1.begin();while(it != v1.end()){if(*it % 2){it = v1.erase(it);}else ++it;}return 0;
}

这样子就能在vs的编译器上跑起来了。

vector的模拟实现

当然要想更好的学会使用vector,我们也是需要了解如何对vector中的一些重要功能进行实现的。

实现的版本

c++只是规定了容器对应的功能应该完成什么样的效果,但是并没有明确要求应该如何实现。所以不同的版本实现是有一些区别的。

vs2022中的实现其实是非常复杂的,涉及到内存池等内容。由于当前还未学习内存池等相关技术,所以并不适合模仿。而我们可以查看一下g++编译器的底层是如何实现的:
在这里插入图片描述
这个是g++编译器下实现版本的比较早期的源代码,我们发现protected成员里面有三个迭代器,分别是startfinishend_of_storage

我们再翻看一下迭代器是怎么实现的:
在这里插入图片描述
正向迭代器其实就是value_type*这个指针,也就是数据类型的指针。所以对于vector来讲,其正向迭代器就是原生指针。

而以往我们在实现顺序表的时候,都是一个指针配合整形数据空间、容量进行管理内容。但是在vector的源代码中我们发现是通过指针管理的。

start就是指向开头数据的指针,finish其实是有效数据的后一个位置,end_of_storage指示容量,就是当前已有空间的后一个位置。

我们实现的版本主要是这个版本。

模拟实现

源码放在我的码云上了:vector imitate achievement

既然是使用指针实现的,那我们就特别需要注意指针的一些问题,特别是野指针。需要我们能够正确的操作这几个指针变量。

结构

vector是一个类模板,和string不太一样。所以我们声名的是一个类模板,需要定义模板参数。使用模板的话就尽量不要将函数的声名和定义进行分离了,因为会导致链接错误。

所以我们采取以下策略:
MyVector这个命名空间内定义类模板,将短小多次调用的函数放在类里面进行定义。因为默认内联,方便多次调用。而代码量比较长的就放在类外进行定义。

预先处理的函数

还是一样的,有一些函数由于会被很多次的调用,所以我们需要先处理一下。

尾插函数push_back

尾插函数是非常重要的。特别是在写构造函数的时候,我们可以提前开好空间,然后将需要的数据依次插入到vector指向的空间中。

所以我们可以实现一下尾插函数:

void push_back(const T& x){if (_end_of_storage == _finish) {reserve((size() == 0) ? 4 : size() * 2);}*_finish = x;++_finish;
}

这里的reserve函数虽然还没写,但是当前符合逻辑就可以。只要能在调用方尾插函数前写完就好。

当然目前先不写的原因是会有特殊情况,这一点我们等下会说。

swap函数

这个交换函数最大的目的就是为了方便进行深拷贝,其实现也是非常简单:

void swap(vector<T>& v) {std::swap(_start, v._start);  std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage); 
}

我们直接调用标准库内的交换函数就可以了。

赋值重载

这个是非常重要的,在等一下的reserve函数中也是需要调用。

这个方法很简单的,在上一节string实现深拷贝的优化中我们就已经实现过了,所以就不再过多赘述。

T& operator=(vector<T> v) {swap(v);return *this;
}

其实就是调用拷贝构造,构造一个v,使得v就是需要赋值的数据。当然当前我们也没有实现拷贝构造。但是不要急,我们当前主要还是得厘清逻辑。保证当前接口主逻辑不会出错即可。

使用这个方法最大的有点就是不用担心自己给自己赋值。如果要自己开空间来操作赋值,就得先删掉空间,再来赋值。但是当自己给自己赋值的时候,就会导致原有数据被销毁。所以需要判断这个特殊情况。

size函数

这个没太多好说的,返回容量就好。

size_t size() const{return _finish - _start;
}
reserve函数

因为空间的浪费不会太大,所以为了效率更高,防止频繁扩容,所以我们采用vs底层一样的实现方法,reserve坚决不缩容。

template<class T>
void vector<T>::reserve(size_t n) {if (n <= size()) return; size_t old_size = size(); T* tmp = new T[n]; //复制for (int i = 0; i < old_size; ++i) { tmp[i] = _start[i]; }delete[] _start; _start = tmp; _finish = _start + old_size;  _end_of_storage = _start + n; 
}

由于代码量还是比较长的,我们放在类外面进行定义。

这里会有几个很容易出错的点:
1.管理内容的三个指针失效
此时我们不再是使用整形变量管理空间。而是使用指针。使用指针最怕的就是野指针。当我们扩容的时候,又需要将旧空间进行释放。那就会导致_finishend_of_storage两个指针变成野指针。所以需要调整这两个指针的位置。

但是很多人这么写的:

    _start = tmp; _finish = _start + size();  _end_of_storage = _start + n; 

这样子会出问题。因为size返回的是当前_start_finish的位置之差。但是我们发现一个事情,就是当我们先让_start指向新空间的时候,_finish会指向被释放的空间。那么这样算出来的size肯定不对。

还有就是从代入表达式的角度看,size()返回的是_start - _finish,代入表达式,
_finish = _start + _start - _finish = _finish,这根本没有改变啊。

所以我们得先记录一下_start_finish原本的差值,也就是有效数据个数old_size,然后再以此进行调整。源码中也是这么干的:
在这里插入图片描述
所以我们就学习这个方法进行调整这三个管理指针。

还有一个问题就是复制,在之前模拟实现string的时候我们是使用memcpy函数进行操作的。但是再vector中万万不能。

假设我们现在声名的是一个vector< string >,如果使用的是memcpy函数,就是将里面内容一个字节一个字节拷贝过去,这个方式是浅拷贝。那一旦碰到有指向资源的数据类型如string那就糟了,那资源是没有办法复制过去的。又或者是vector,也是有指向资源。这是万万不能的。具体内容可以看类和对象章节。

所以复制部分是要调用赋值重载的。这也就是为什么我们要先写赋值重载函数,就是怕内部存储的也是vector(自己写的),那就需要调用其赋值重载函数,那我们就得提供。

迭代器
//实现正向迭代器用的
typedef T* iterator;
typedef const T* const_iterator;//iterator
iterator begin() {return _start;
}
iterator end() {return _finish;
}
const_iterator cbegin() const { return _start;
}
const_iterator cend() const {return _finish;
}

迭代器就是原生指针,实现非常简单。

默认成员函数
默认构造
vector()
{}

我们会在定义三个管理指针的时候给定缺省参数nullptr,所以不需要存储任何东西的时候就不需要进行任何操作,所以无参构造函数这样写即可。

普通构造
vector(size_t n, const T& val = T()) {reserve(n);while (_finish != _end_of_storage){push_back(val);   ++_finish; }
}vector(int n, const T& val = T()) {reserve(n);while (_finish != _end_of_storage) {push_back(val);}
}template<class InputIterator>
vector(InputIterator first, InputIterator last) {while (first != last) {push_back(*first);++first;}
}

这里的const T& val = T()是调用默认构造函数,前面部分已经讲过了。
这里有三个函数,最后一个是迭代器区间构造。

有人看到前面两个仅仅是n参数类型不同,为什么要多次一举写多一个呢?

这是因为当我们想要这样写的时候vector(5, 4);,会导致一个问题,因为不写第二个的那个版本,5在编译器中默认为int,4也为int,那么第一个就不是那么的匹配。而传给迭代器区间的时候会更加匹配一些,所以编译器会调用迭代器区间构造的那个。所以我们需要多写一个。而使用其他类型的时候就不会有这个问题。

拷贝构造
vector(const vector<T>& v) {reserve(v.size());const_iterator it = v.cbegin();while (it != v.cend()) {push_back(*it);++it;}
}

这里没有采用以往的那个深拷贝的优化方法。因为在string实现中,我们可以把传入的string的指向字符串的指针拿去构造出一个一样的string。而我们在这里并没有实现这么一个函数,因为只有字符出啊怒这样做是比较方便。所以我们直接自己开空间进行尾插即可。

析构函数
~vector() {delete[] _start;_start = _finish = _end_of_storage = nullptr; 
}

析构函数比较简单,就不再赘述。

容量操作

现在来对容量的操作进行实现

容量 、判空
size_t capacity() const {return _end_of_storage - _start;
}bool empty() const {return (_start == _finish);
}

数据个数已经在预处理部分处理过了,所以只需要实现一下容量和判空即可。

resize函数
template<class T> 
void vector<T>::resize(size_t n, const T& val) {  //声名与定义分离时 定义中不能有缺省参数if (n <= size()) {_finish = _start + n; }else {if (_finish + n > _end_of_storage) { size_t old_size = size();size_t old_capacity = capacity();reserve( old_size + n > old_capacity * 2 ? (old_size + n) : old_capacity * 2);}while (_finish != _end_of_storage) { push_back(val); } }
}

根据文档的要求进行实现即可。注意是否需要删除数据以及扩容即可。

修改操作
尾删
void pop_back() {assert(!empty());   --_finish;
}

需要注意是否为空,否则无法删除。

insert函数
template<class T>
typename vector<T>::iterator vector<T>::insert
(typename vector<T>::iterator pos, const T& val) { assert(pos <= _finish);assert(pos >= _start);size_t old_size = size();size_t posdiff = pos - _start;//必须写这个 要不然迭代器失效了if (_finish == _end_of_storage) {reserve((old_size == 0) ? 4 : 2 * old_size);pos = _start + posdiff;}//挪动数据typename vector<T>::iterator it = end() - 1;while (it >= pos) {*(it + 1) = *it;--it;}*pos = val;++_finish;return pos;
}

很多人会疑问,为什么要加typename这个关键字呢?这是因为我们是在类外面定义这个函数。而这个类此时还没有实例化,那要从里面取东西是需要特别注意的。对于iterator,编译器会不知道这是一个变量还是类型名称。所以加上typename就是告诉编译器这是一个类型。

注意一下之前讲过的迭代器失效的问题即可。

clear函数

这个函数只对数据进行清空,但是不进行缩容:

void clear() {_finish = _start;
}
erase函数

这个函数实现了两个版本:

template<class T>
typename vector<T>::iterator vector<T>::erase
(typename vector<T>::iterator pos) {assert(pos >= _start);assert(pos <= _finish);//不考虑缩容typename vector<T>::iterator it = pos;while (it != end()) {*it = *(it + 1);++it;}--_finish;return pos;
}template<class T>
typename vector<T>::iterator vector<T>::erase
(typename vector<T>::iterator first, typename vector<T>::iterator last) {assert(first <= last);assert(first >= _start);assert(last <= _finish);assert(!(first == end() && last == end()));vector<T>::iterator prev = first; vector<T>::iterator rear = last + 1;while (rear != end()) {*prev = *rear;++prev;++rear;}_finish = prev;return first;
}

当然insert函数也是可以实现迭代器区间插入的(我忘记了哈哈哈哈哈),逻辑都不难,最主要的就是注意一下删除的位置是否合法(通过断言报错),然后实现数据挪动逻辑。然后需要注意迭代器失效得问题,要通过返回值来修正。

最好是通过画图来赋值写代码,然后考虑一下一些特殊位置即可。

打印函数(针对不同容器)
template<class Container>
void Print_container(Container& con) { for (auto& x : con) { cout << x << " "; }cout << endl;  
}

通过传入容器以及范围for得使用就可以实现了,这是十分简单的。

到此所有的操作就完成了,想要更详细代码的可以进入我的码云空间获取。

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

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

相关文章

Java深入

String相关的类 1.String不可变的类 源码&#xff1a; public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];/** Cache the hash code for th…

【Java编程】【计算机视觉】一种简单的图片加/解密算法

by Li y.c. 一、内容简介 本文介绍一种简单的图片加/解密算法&#xff0c;算法的基本原理十分简单&#xff0c;即逐个&#xff08;逐行、逐列&#xff09;地获取图片的像素点颜色值&#xff0c;对其进行一些简单的算数运算操作进行加密&#xff0c;解密过程则相应地为加密运算…

从GPT到Gemini 大模型进化史

从GPT到Gemini&#xff1a;大模型进化史 在过去的几年里&#xff0c;人工智能领域经历了翻天覆地的变化&#xff0c;其中最引人注目的莫过于大规模语言模型的发展。从最初的GPT系列到最近的Gemini&#xff0c;这些模型不仅在技术上取得了重大突破&#xff0c;还在实际应用中展…

【AI提示词】中国历史与世界发展对比器

提示说明 输入特定年份&#xff0c;输出该时期中国与世界的发展状况。 提示词 # Role 中国历史与世界发展对比器## Profile - author: xxx - version: 1.0 - description: 输入特定年份&#xff0c;输出该时期中国与世界的发展状况。## Attention 请深入挖掘历史资料&#x…

阿里云OSS应对DDoS攻击策略

阿里云对象存储服务&#xff08;OSS&#xff09;若遭遇DDoS攻击&#xff0c;可结合阿里云提供的安全服务与自身配置优化进行综合防御。以下是具体的解决方案及步骤&#xff1a; 1. 启用阿里云DDoS防护服务 防护服务类型&#xff1a;阿里云提供基础DDoS防护&#xff08;默认免费…

MyCat 分库分表

介绍 问题分析 随着互联网及移动互联网的发展&#xff0c;应用系统的数据量也是成指数式增长&#xff0c;若采用单数据库进行数据存 储&#xff0c;存在以下性能瓶颈&#xff1a; 1. IO瓶颈&#xff1a;热点数据太多&#xff0c;数据库缓存不足&#xff0c;产生大量磁盘IO&a…

C++笔记-list

list即是我们之前学的链表&#xff0c;这篇主要还是讲解list的底层实现&#xff0c;前面会讲一些list区别于前面string和vector的一些接口以及它们的注意事项。 一.list的基本使用 和之前的string&#xff0c;vector一样&#xff0c;有很多之前见过的一些接口&#xff0c;经过…

unityTEngine学习记录2

上一篇了解了下载项目与外部调用的接口&#xff0c;接下来就继续学习根据这个框架来加载场景首先打开te官网&#xff0c;进入教程。 了解框架目录以及功能 首先要了解的就是这个框架的文件结构目录&#xff0c;知道他都是干啥的&#xff0c;在官网的目录结构中介绍了其中重要…

逻辑过期怎么设计

设计“逻辑过期”通常用于缓存、令牌管理、数据有效性验证等场景&#xff0c;其核心是通过业务逻辑判断数据是否过期&#xff08;而非单纯依赖物理时间&#xff09;。以下是设计逻辑过期的关键思路和实现方案&#xff1a; 1. 核心思想 物理过期&#xff1a;基于固定的时间&…

DAY 47 leetcode 232--栈与队列.用栈实现队列

题号232 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; class MyQueue {Stack<Integer> stackIn;Stack<Integer> stackOut;/** Initialize your data structure here. */pu…

逻辑回归 (Logistic Regression)

文章目录 逻辑回归 (Logistic Regression)问题的引出Sigmoid function逻辑回归的解释决策边界 (Decision boundary)逻辑回归的代价函数机器学习中代价函数的设计1. 代价函数的来源&#xff08;1&#xff09;从概率模型推导而来&#xff08;统计学习视角&#xff09;&#xff08…

关于C语言的模拟物理模型

声明&#xff1a;本文全部代码效果基于C语言easyx图形界面库。 引言 关于很多游戏和模型的开发&#xff0c;都需要模拟真实的物理模型 比如&#xff1a;基本矢量运动模型&#xff08;位移&#xff0c;速度&#xff0c;加速度&#xff09;&#xff0c;重力模型&#xff0c;碰撞…

C++编译与链接:从源码到可执行文件的魔法之旅(Visual Studio实践)

文章目录 **C++编译与链接:从源码到可执行文件的魔法之旅(Visual Studio实践)****一、C++编译器的工作流程****二、Visual Studio环境配置实战****三、示例项目:Hello World全流程解析****四、高级技巧与工具链****五、总结与参考资料**C++编译与链接:从源码到可执行文件的…

现代C++的范式演进与工程实践深度解析(本文序号不知道怎么整的,有点问题)

引言:C++的复兴时代 在经历了"已死语言"的质疑后,现代C++正迎来前所未有的复兴。据2024年TIOBE指数显示,C++以8.33%的占比稳居第三,较2020年上升2.1个百分点。这种复兴并非偶然——随着C++20标准的全面落地和C++23特性的逐步实现,这门已有40年历史的语言正在系…

通过gird布局实现div的响应式分布排列

目标&#xff1a;实现对于固定宽度的div盒子在页面中自适应排布&#xff0c;并且最后一行的div盒子可以与前面的盒子对齐。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" con…

WSL2-Ubuntu22.04安装URSim5.21.3

WSL2-Ubuntu22.04安装URSim5.21.3 准备安装启动 准备 名称版本WSL2Ubuntu22.04URSim5.21.3VcXsrvNaN WSL2安装与可视化请见这篇:WSL2-Ubuntu22.04-配置。 安装 我们是wsl2-ubuntu22.04&#xff0c;所以安装Linux版本的URSim&#xff0c;下载之前需要注册一下&#xff0c;即…

产品研发项目管理6大痛点

在产品研发项目管理实践中&#xff0c;企业普遍面临六大系统性挑战&#x1f937;‍♀️&#xff0c;直接影响研发效能与战略目标达成&#x1f514;&#xff0c;具体表现为&#xff1a; ① 产品需求管理不完善&#xff1a;需求与市场脱节&#xff0c;需求不明确、需求变更频繁…

计算机网络基础概论

计算机网络基础概论 目录 一、网络基本概念 1.1. 网络 1.2 互联网 1.3 ip地址 1.3.1 作用 1.3.2 分类 1.4 MAC地址 1.4.1 MAC地址与 IP 地址的关系 1.5 网络协议 二、网络分层模型 2.1 物理层 2.2 数据链路层 2.3 网络层 2.4 传输层 2.5 会话层 2.6 表示层 2.7…

Windows下导入文件中的环境变量

在Windows批处理脚本&#xff08;.bat&#xff09;中&#xff0c;通过文件获取并设置环境变量通常涉及逐行读取文件内容并动态赋值给变量。以下是具体实现方法及示例&#xff1a; 一、从文件读取变量并设置到环境变量 假设有一个配置文件&#xff08;如env_config.txt&#xf…

WebSocket 实现数据实时推送原理

WebSocket 实现数据实时推送的核心机制在于其全双工通信能力和持久的连接特性。以下是其工作原理的详细步骤&#xff1a; 1. 握手阶段&#xff08;HTTP 升级协议&#xff09; 客户端发起请求&#xff1a;通过发送一个带有特殊头部的 HTTP 请求&#xff0c;请求协议升级。 GET …