C++之vector类(超详细)

article/2025/8/14 14:31:06

这节我们来学习一下,C++中一个重要的工具——STL,这是C++中自带的一个标准库,我们可以直接调用这个库中的函数或者容器,可以使效率大大提升。这节我们介绍STL中的vector。

文章目录

前言

一、标准库类型vector

二、vector的使用

2.1 vector的初始化和定义

2.2 vector的元素访问

2.2.1 vector iterator的使用

2.3 vector的空间增长问题

2.4 vector增删改查

2.5 vector迭代器失效问题

三、vector的深度剖析与模拟实现

3.1 成员变量及迭代器实现

3.2 使用memcpy的拷贝问题

3.3 动态二维数组的理解

3.4 vector的模拟实现(代码)


前言

我们在上一节中学习了string类,我们通过这个类可以实现各种各样的操作。然而string类是专门设计来用来处理字符类型的,它是用来对字符的高级处理以及内存优化,适用于文本数据。但是在本节中我们将要的vector,是一个通用的容器类,它可以存储任意类型的数据,适用于处理任意类型的集合。


一、标准库类型vector

标准库类型vector表示对象的集合,其中所有对象的类型都是相同的。集合中每一个对象都有对应的索引,索引用于访问对象。因为vector中容纳着其他对象,因此我们也将它称为容器(container)。vector其实与我们C语言学习的数组很像,存放一组相同数据类型的集合,我们可以通过索引值来访问某个值,但是vector通过了类进行了封装,于是我们可以使用vector进行各种操作,可以任意改变存储空间的大小,这是我们前面的数组所没有,大大提高了操作的灵活性。

C++语言既有类模板(class template),也有函数模板,其中vector就是一个类模板。模板我们在之前就已经学习过了,模板并不是一个类或者函数,它们只是一个类或者函数的雏形,我们可以使用这些模板来实例出来一个具体的类或者函数。因此我们对于vector的数据类型要注意了:vector是模板并非类型,由vector生成的类型必须包含vector中的元素类型,例如vector中的元素类型是int,那么它的数据类型就是vector<int>。

vector可以容纳大多数类型的对象作为其元素,但是因为引用并不是一个对象,因此我们不存在包含引用的vector,其他大多数的内置类型(非引用)和类类型都可以构成vector对象。

二、vector的使用

在使用vector之前,我们要先包含一个头文件:#include<vector>和using namespace std这个标准库命名空间。vector的函数都放在这里面,如果我们没写这个头文件,编译器就不认识vector了。

2.1 vector的初始化和定义

同样地,vector有着属于自己的构造函数,vector要存储数据,因此它肯定是要进行资源的申请的。它的构造函数同样很多,主要的就是我们上面所展示的哪几种。我们通过VS2022上的监视调试可以看到,我们可以直接通过vector中的那几个构造函数来进行初始化,它也赋上了相应的值。

除了上面那几种构造方法,在C++11新标准中还提供了另一种为vector对象的元素赋初值的方法,即列表初始化。列表初始化和我们之前对数组初始化的方式很像,都是使用一对{ },然后我们将要初始化的内容放进去。我们可以直接使用{ }初始化,也可以将内容放到{ }中,然后赋值给vector对象。然后编译器就会自动创建相应的大小空间以及赋上我们给定的初始值。

这时可能会有人来问:这个列表初始化,会不会与我们上面那个初始化n个相同的值弄混呢?

这里我们要注意一下:在某些情况下,初始化的真实含义依赖于传递初始值使用的是{ }还是()。

例如,我们使用一个整数来初始vector<Int>时,整数的含义可能是vector的对象容量,也可能是vector的元素的值。类似的,我们如果使用两个整数来初始化vector<int>的话,那么这两个整数就可能是一个是vector的容量大小另一个是要初始化的值,也可能它们是容量为2的vector对象的两个元素的初始值。我们可以通过花括号{ }和圆括号()来区分上面的含义:

vector<int> v1(10) //v1有10个元素,每个元素的初始值都是0
vector<int> v2{10} //v2有1个元素,这个元素的初始值是10vector<int> v3(10,1) //v3有10个元素,每个元素的初始值都是1
vector<int> v4{10,1} //v4有2个元素,分别是10和1

如果我们使用的是圆括号的话,那么可以说提供的值是用来构造(construct)vector对象。如果我们使用的是花括号的话,那么可以表述为我们想列表初始化该vector对象。也就是说,初始化过程会尽可能地把花括号内的值当成元素初始值的列表来进行处理,只有无法执行列表初始化时才会考虑其他初始化方法。另一方面,如果初始化时使用了花括号但是花括号中的内容不适合进行列表初始化,那么这时候就要考虑使用这样的值使用其他构造方法来构造vector对象了。例如,我们如果想要初始化一个含有string类型的元素的vector对象,我们应该赋给能够赋值给string对象的初始值。

vector<string> v5{"hi"} //列表初始化,v5中只有一个元素“hi"
vector<string> v6("hi") //错误,我们不能使用字符串字面值来构造一个vector对象vector<string> v7{10} //v7中有10个默认值(string类型的)的元素
vector<string> v8{10,”hi“} //v8中有10个”hi“的元素

在之前,我们学习了,我们不能使用一个字面值来进行初始化了,因为字面值是一个常量,是一个不可以进行修改的值,如果我们使用这个来进行初始化,相当于将一个只读的元素放到了一个可读可修改的容器中,那样就造成了权限放大的情况,这样是不允许的。因此,我们就不可以使用()来初始化string类了,如果我们想要初始化多个相同的string对象,我们可以使用{ },同样地第一个参数放容量大小,第二个参数放初始化的值。

2.2 vector的元素访问

我们在string类中有三种访问元素的方法:1.使用普通for循环进行遍历访问;2.使用范围for进行遍历循环;3.使用迭代器进行访问。对于vector。同样适用于上面那三种方法。在此之前,我们先来学习一下vector中的迭代器。

2.2.1 vector iterator的使用

vector中的迭代器和string类中的迭代器大致一样,都是指向某个位置的元素。begin()函数是指向vector中首元素的迭代器,end()函数是指向vector中最后一个元素的下一个位置。至于rbegin(),rend()函数则是方向遍历vector。其具体使用方法,我们在string类中已经详细介绍过了。范围for是在迭代器的基础上实现的,这个一般适用于不知道容器中的元素的个数时。

元素访问的三种方式代码如下:

	vector<int> v1{ 1,2,3,4,5 };//1.使用普通for循环进行遍历访问v1for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;//2.使用范围for进行遍历访问for (auto e : v1){cout << e << " ";}cout << endl;//3.使用迭代器进行访问vector<int>::iterator it = v1.begin();while (it!=v1.end()){cout << *it << " ";it++;}cout << endl;

2.3 vector的空间增长问题

string有的那些接口vector同样有,而且它们的功能基本都是一样的,不过是处理的数据类型不同而已,这里我们就不重复介绍这些接口的使用方法了。

我们主要讲讲上面那两个改变size和capacity的接口,这两个的实现原理有所不同:对于resize,它是与原来的那个vector的size值进行比较一下,如果是小于原来的size值,那么它就会删去一些原有的数据,如果大于原来的size值,它会补充我们给定的缺省值,如果大于我们原来的capacity,编译器将会给它重新分配一个内存空间了,这里往往也调用了reserve中的扩容操作。对于reserve,一般都是编译器自己执行这个函数的,我们在插入数据的时候,编译器会调用这个函数来进行扩容,有时候如果我们能够提前知道数据的个数,那么我们也可以使用reserve来提前预留空间大小,这样就能够缓解vector增容的代价缺陷问题,注意reserve一般都是异地创建一个新的内存空间,然后将我们的内容深拷贝过去,并非是在原来的空间中来扩容。

2.4 vector增删改查

上面的那些接口,我们可以实现vector的增删改查,大大提高了其灵活性,我们可以对vector对象中的数据进行修改。其中的find接口,我们需要注意一下,它并不是vector的成员接口,它是在算法库中的,但是我们可以使用vector来进行调用。

这是其在算法库中的函数原型,我们可以看到,它是一个函数模板,因此它并不是一个具体的函数,而是一个可以对于任意类型使用的函数出翔,我们可以传参实例化出来一个具体的函数来进行使用。上面的函数原型中,它的参数是两个迭代器和一个想要查找的值。对于那两个迭代器所表示的区间是:[first,last)。如果我们没有在那段区间中找到我们想要查找的值时,编译器就会返回last迭代器,用于表示未查找出来的情况。

2.5 vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对 指针进行了封装,比如:vector的迭代器就是原生态指针T* (因为,对于vector的底层是一个数组)。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即 如果继续使用已经失效的迭代器,程序可能会崩溃)。在vector中迭代器失效有好几种,接下来我们一一介绍一下:

1.修改vector的大小(如使用resize)

我们在前面就已经说过了对于那些我们设置的新size值如果比原来的size小的话,那样就会导致超出范围的元素被删除,那么与那些超出范围的元素有关联的迭代器就可能会失效。

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.resize(2); // 删除了第三个元素,导致 it 失效,那么我们就不能够使用迭代器,来访问到这第三个元素了

2.vector容量的改变(如使用insert,push_back)

vector在内存空间不够的情况下会自动进行扩容操作,当我们调用insert和push_back时,编译器就会进行扩容,我们在上面已经说过了编译器是异地扩容,在其他地方另外开辟一个新的空间,那么原来的那些迭代器就都会失效,因为它们所指向的位置都已经被改变了。

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.push_back(4); // 如果触发扩容,it 会失效,因为我们开辟了一个新的内存块,原来的那些迭代器所指向的位置都已经改变了

3.erase操作

使用erase删除vector中的元素时,会导致被删除位置后的所有元素都会被移动,那样就会导致逻辑错误,原来的指向那些元素的那些迭代器也会失效。

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.erase(it); // 删除了第一个元素,it 失效,第一位位置后面的元素都往前移动一位,后面的迭代器所指的元素可能都发生改变了,导致逻辑错误

4.clear操作

使用clear清除ector中的所有元素,会导致vector中有的迭代器全部都失效。

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.clear(); // 所有迭代器都失效

5.insert操作

这个与上面的erase差不多,当我们插入某个位置之后,那个位置之后的所有元素都会发生改变,因此那些指向的迭代器就会改变,如果我们插入大量元素或触发内存扩展时,可能导致所有的迭代器都失效。

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.insert(it, 0); // 插入元素导致 it 失效

6.swap操作

当我们调用swap函数改变两个vector的内容和容量时,可能会导致两个vetcor中原有的迭代器都失效了。

std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
auto it = vec1.begin();
vec1.swap(vec2); // it 失效,因为 vec1 和 vec2 的内存交换了

7.底层分配器的改变

如果你使用自定义的分配器(allocator)来管理vector的内存,某些内存分配或重新分配的策略也可能会导致迭代器失效。因为分配器所作用的对象就是内存,内存都改变了,原来的迭代器就不能指向原来的那些内容了,因此会失效。

上面这么多情况都会可能导致迭代器失效,因此我们在使用那些增删函数时要小心一点。如果需要避免迭代器失效,我们可以考虑使用其他容器,或者在修改vector时重新获取有效的迭代器(对迭代器进行一个更新),这样做之后我们就可以获取我们所需的迭代器了。

//string类中与vector类似,也会遇到迭代器失效的问题,我们可以使用如下方法来规避
#include <string>
void TestString()
{string s("hello");auto it = s.begin();// 放开之后代码会崩溃,因为resize到20会string会进行扩容// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了// 后序打印时,再访问it指向的空间程序就会崩溃//s.resize(20, '!');while (it != s.end()){cout << *it;++it;}cout << endl;it = s.begin();while (it != s.end())
{it = s.erase(it);// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后// it位置的迭代器就失效了// s.erase(it);  ++it;}
}

三、vector的深度剖析与模拟实现

3.1 成员变量及迭代器实现

上面是由源文件中截取的源码,我们可以看出来vector的成员函数是三个迭代器,它们分别指向上面那些位置。我们在最开始学习的时候就说了,vector的底层实现是一个数组,为啥我们不像之前模拟实现顺序表那样设置一个数组,一个size值和一个capacity作为成员变量呢?其实这些在源文件中都已经有提及到了,在源文件中我们可以通过上面那三个指针来表示这几个变量。使用这些迭代器能够更加直观地看到vector中的具体位置。

在vetor中的底层中,它直接将那些数据类型的指针重名为迭代器了,因此,我们如果要实现那些begin(),end()函数直接使用我们开始定义的那几个迭代器变量表示就可以了。

3.2 使用memcpy的拷贝问题

我们在string类模拟实现时,我们对于两个字符串的拷贝是使用strcpy这个专门用来字符串拷贝的函数,对于其他类型的变量拷贝,我们就需要使用其他拷贝函数,而memcpy就是我们在C语言阶段学习的一个用来内存拷贝的函数,它可以将内存及内容拷贝过去。memcpy拷贝有如下两个特点:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存 空间中 。

2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型 元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

在C语言中,我们没有涉及到析构这一说,但是到了C++中,我们学习了析构函数,我们知道我们每次函数在销毁时都会自动调用析构函数来释放内存资源,这里我们使用了memcpy这个浅拷贝函数获取的那个对象,由于我们的一些函数会将拷贝前的那个对象释放掉,那么就会调用析构函数,那么就会将原来的那个空间释放掉,但是我们拷贝后的那个新对象仍然是指向那个旧空间的地址,这样我们新对象的地址就是一个被释放的空间地址,相当于一个野指针,野指针是十分危险的。因此我们不能使用这个浅拷贝的函数,我们需要自己来实现深拷贝(对于那些有资源申请的对象)。

结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为 memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

3.3 动态二维数组的理解

二维数组这一概念,我们是在C语言阶段就已经接触到了。那么二维数组是如何实现的呢?二维数组其实就是一个数组中存放了一组数组的指针,这样就形成了二维数组。而vector的底层实现就是数组,于是我们也可以使用vector来表示一个二维数组,我们可以使用嵌套的方式来进行表示。例如vector<vector<int>>,这个就表示一个元素类型为int的二维数组。如下代码,我们使用vector来表示一个杨辉三角,杨辉三角的实现是一个二维数组。

// 以杨辉三角的前n行为例:假设n为5
void test2vector(size_t n)
{// 使用vector定义二维数组vv,vv中的每个元素都是vector<int>vector<vector<int>> vv(n);// 将二维数组每一行中的vecotr<int>中的元素全部设置为1for (size_t i = 0; i < n; ++i)vv[i].resize(i + 1, 1);// 给杨慧三角出第一列和对角线的所有元素赋值for (int i = 2; i < n; ++i){
for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}
}

vector<vector<int>>  vv(n); 构造一个vv动态二维数组,vv中总共有n个元素,每个元素 都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:

vv中元素填充完成之后,如下图所示:

3.4 vector的模拟实现(代码)

#pragma once
#include<assert.h>
namespace hjc
{template <class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}template <class InputIterator> //类模板中也可以定义函数模板vector(InputIterator first, InputIterator last ) //这两个参数的类型是迭代器类型{while (first != last){push_back(*first);first++;}}vector(){}vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}vector(size_t n, const T& val=T()){reserve(n);for (size_t i = 0; i <n; i++){push_back(val);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}//拷贝构造vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}~vector(){if (_start)delete[]_start;_start = _finish = _end_of_storage = nullptr;}T& operator[](size_t i) //我们使用下标运算符,最终返回的是一个元素,因此它的类型是元素的类型{assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}vector<T>& operator=(vector<T> v){swap(v);return *this;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void resize(size_t n, T val = T()) //我们第二个参数直接写元素的类型即可, 然后我们初始化为默认初始值{if (n < size())//缩小size,那么size的值就到_start + n位置即可{_finish = _start + n;}else{reserve(n); //扩大size,我们要往里面添加值while (_finish< _start+n)  //size=_finish-_start且_start=0于是_finish=size。这里_finish=4{*_finish = val;++_finish;}}}//扩容void reserve(size_t n){if (n >= capacity()){size_t oldSize = size();T* tmp = new T[n];for (size_t i = 0; i < oldSize; i++){tmp[i] = _start[i];}delete[]_start;_start = tmp;_finish = _start + oldSize;_end_of_storage = _start + n;}}void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;_finish++;}void swap(vector<T>& tmp){std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_end_of_storage, tmp._end_of_storage);}bool empty(){return _start == _finish;}void pop_back(){assert(!empty());_finish--;}iterator insert(iterator pos, const T& x) //这里的位置起始位置是1{assert(_start <= pos && pos <= _finish);if (_finish==_end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator i= _finish - 1;while (i>=pos){*(i + 1) = *i;i--;}*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(_start <= pos && pos < _finish);iterator i = pos + 1;while (i<_finish){*(i - 1) = *i;i++;}_finish--;return pos;}private:iterator _start=nullptr;iterator _finish=nullptr;iterator _end_of_storage=nullptr;};void test_vector1(){vector<int> v1;v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (size_t i = 0; i < v1.size(); i++){cout <<v1[i]<< " ";}cout << endl;for (int i : v1){cout << i << " ";}cout << endl;v1.pop_back();v1.pop_back();vector<int>::iterator it = v1.begin();while (it!=v1.end()){cout << *it << " ";it++;}cout << endl;}void test_vector2(){vector<int> v1;v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);cout << v1.size() << endl;cout << v1.capacity() << endl;cout << endl;v1.resize(2);cout << v1.size() << endl;cout << v1.capacity() << endl;for (int i : v1){cout << i << " ";}cout << endl;cout << endl;v1.resize(6,999);cout << v1.size() << endl;cout << v1.capacity() << endl;for (int i : v1){cout << i << " ";}cout << endl;cout << endl;vector<int> v2;v2.push_back(1);v2.push_back(1);v1.swap(v2);for (int i : v1){cout << i << " ";}cout << endl;cout << endl;for (int i : v2){cout << i << " ";}cout << endl;}void test_vector3(){vector<int> v1={ 1,2,3,4 }; //列表初始化//vector<int> v1 { 1,2,3,4 }; //列表初始化vector<int>v2(v1);for (auto i : v1){cout << i << " ";}cout << endl;for (auto i : v2){cout << i << " ";}cout << endl;vector<int>v3(10, 1);for (auto i : v3){cout << i << " ";}cout << endl;v2 = v3;for (auto i : v2){cout << i << " ";}cout << endl;for (auto i : v3){cout << i << " ";}cout << endl;}void test_vector4(){vector<int> v1 = { 1,2,3,4,5,6};vector<int> v2(v1.begin(), v1.end()); //使用函数模板实例化出来的函数进行初始化for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;v1.insert(v1.begin()+2, 30);v1.insert(v1.begin(), 30);v1.insert(v1.begin()+8, 30);for (auto e : v1){cout << e << " ";}cout << endl;v1.erase(v1.begin());for (auto e : v1){cout << e << " ";}cout << endl;int x;cin >> x;auto i = find(v1.begin(), v1.end(), x);if (i != v1.end()){//对于这种改变了原来指定的顺序,可能会导致逻辑错误,因此我们要更新一下,才能够进行访问i = v1.insert(i, 10 * x); //如果不是我们所输入的数,就将它扩大十倍放到指定位置(找到那个数的位置)cout << *i << endl;}for (auto e : v1){cout << e << " ";}cout << endl;//删除v1中的所以偶数auto e = v1.begin(); //确定起始位置while (e!=v1.end()) //然后通过刚刚定义的位置来进行遍历{if (*e % 2 == 0)//如果为偶数,就进行删除,对于指定位置删除,我们要更新它的位置,即到删除元素的位置{e = v1.erase(e);}else //如果不是偶数,我们就直接跳过去{++e;}}for (auto i : v1){cout << i << " ";}cout << endl;}void test_vector5(){vector<string>v1;v1.push_back("111111111111111");v1.push_back("111111111111111");v1.push_back("111111111111111");v1.push_back("111111111111111");v1.push_back("111111111111111");for (auto e : v1){cout << e << " ";}cout << endl;}}


http://www.hkcw.cn/article/usqQFDopUp.shtml

相关文章

C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)

&#x1f4da; 本文主要总结了一些常见的C面试题&#xff0c;主要涉及到语法基础、STL标准库、内存相关、类相关和其他辅助技能&#xff0c;掌握这些内容&#xff0c;基本上就满足C的岗位技能&#xff08;红色标记为重点内容&#xff09;&#xff0c;欢迎大家前来学习指正&…

『C++成长记』string模拟实现

🔥博客主页:小王又困了 📚系列专栏:C++ 🌟人之为学,不日近则日退 ❤️感谢大家点赞👍收藏⭐评论✍️ ​ 目录 一、存储结构 二、默认成员函数 📒2.1构造函数 📒2.2析构函数 📒2.3拷贝构造 📒2.4赋值重载 三、容量操作 📒3.1获取有效字符长度…

多态的使用和原理(c++详解)

一、多态的概念 多态顾名思义就是多种形态&#xff0c;它分为编译时的多态&#xff08;静态多态&#xff09;和运行时的多态&#xff08;动态多态&#xff09;&#xff0c;编译时多态&#xff08;静态多态&#xff09;就是函数重载&#xff0c;模板等&#xff0c;通过不同的参数…

C++ 底层实现细节隐藏全攻略:从简单到复杂的五种模式

目录标题 1 引言&#xff1a;为什么要“隐藏实现”1.1 头文件暴露带来的三大痛点1.2 ABI 稳定 vs API 兼容&#xff1a;先分清概念1.3 选型三问法——评估你到底要不要隐藏 2 模式一&#xff1a;直接按值成员 —— “裸奔”也能跑2.1 典型写法与最小示例2.2 何时按值最合适&…

使用国内镜像网站在线下载安装Qt(解决官网慢的问题)——Qt

国内镜像网站 中国科学技术大学&#xff1a;http://mirrors.ustc.edu.cn/qtproject/清华大学&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/qt/北京理工大学&#xff1a;http://mirror.bit.edu.cn/qtproject/ 南京大学&#xff1a;https://mirror.nju.edu.cn/qt腾讯镜像&…

超全超详细!JDK 安装及环境配置(Java SE 8 保姆级教程)

一、JDK 简介 JDK&#xff08;Java Development Kit&#xff09;是用于开发 Java 程序的工具包&#xff0c;包括编译器 javac、Java 运行环境&#xff08;JRE&#xff09;以及各种开发工具。安装和配置 JDK 是学习和使用 Java 编程的第一步&#xff0c;以下是 Java 和 JDK 的具…

Java 大视界 -- 基于 Java 的大数据分布式数据库在社交网络数据存储与查询中的架构设计与性能优化(225)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

C++协程从入门到精通

文章目录 一、C协程入门知识&#xff08;一&#xff09;基本概念&#xff08;二&#xff09;特点&#xff08;三&#xff09;应用场景 二、C协程精通知识&#xff08;一&#xff09;高级特性&#xff08;二&#xff09;优化技巧&#xff08;三&#xff09;错误处理机制&#xf…

蓝桥杯第十六届c组c++题目及个人理解

本篇文章只是部分题目的理解&#xff0c;代码和思路仅供参考&#xff0c;切勿当成正确答案&#xff0c;欢迎各位小伙伴在评论区与博主交流&#xff01; 目录 题目&#xff1a;2025 题目解析 核心提取 代码展示 题目&#xff1a;数位倍数 题目解析 核心提取 代码展示 …

C++日新月异的未来代码:C++11(上)

文章目录 1.统一的列表初始化1.1 普通{ }初始化1.2 initializer_list 2.声明2.1 auto、nullptr2.2 decltype 3.左值右值3.1 概念3.2 左值引用与右值引用比较3.3 左值引用与右值引用的应用3.4 完美转发 希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xf…

C++从入门到实战(十二)详细讲解C++如何实现内存管理

C从入门到实战&#xff08;十二&#xff09;详细讲解C如何实现内存管理 前言一、C内存管理方式1. new/delete操作内置类型2. 异常与内存管理的联系&#xff08;简单了解&#xff09;3. new和delete操作自定义类型 二、 operator new与operator delete函数&#xff08;重点&…

【2025年最新版】Java JDK安装、环境配置教程 (图文非常详细)

文章目录 【2025年最新版】Java JDK安装、环境配置教程 &#xff08;图文非常详细&#xff09;1. JDK介绍2. 下载 JDK3. 安装 JDK4. 配置环境变量5. 验证安装6. 创建并测试简单的 Java 程序6.1 创建 Java 程序&#xff1a;6.2 编译和运行程序&#xff1a;6.3 在显示或更改文件的…

【Linux系统】从 C 语言文件操作到系统调用的核心原理

文章目录 前言lesson 15_基础IO一、共识原理二、回顾C语言接口2.1 文件的打开操作2.2 文件的读取与写入操作2.3 三个标准输入输出流 三、过渡到系统&#xff0c;认识文件系统调用3.1 open 系统调用1. 比特位标志位示例 3.2 write 系统调用1. 模拟实现 w 选项2. 模拟实现 a 选项…

JavaSwing之--JTextField

JavaSwing之–JTextField JTextField 是一个允许编辑单行文本的轻量级组件&#xff0c;它提供了一系列的构造方法和常用方法用来编写可以存储文本的文本框满足程序功能的需求。 以下在简要介绍常用构造方法、普通方法后详解各种方法的应用及举例。 一、构造方法 方法名称功…

Windows系统之VHD安装

环境准备 工具说明Dism部署系统、提取和转换系统镜像等等&#xff0c;还有很多功能大家可以自行探索。这里只用到Dism的部署系统功能。 Releases Chuyu-Team/Dism-Multi-language GitHubbcdedit.exe自带工具 C:\Windows\System32\bcdedit.exe 创建虚拟磁盘 首先右键点击我…

解决Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field ‘com.sun.tools.javac.tre

问题描述 在更新自建基础项目过程中&#xff0c;compile、install报错。 Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid 解决方案 问题原因是Lombok &#xff0c;与 JDK 21 兼容的最低 Lombok 版本是…

【C++】二叉搜索树 - 从基础概念到代码实现

&#x1f4cc; 个人主页&#xff1a; 孙同学_ &#x1f527; 文章专栏&#xff1a;C &#x1f4a1; 关注我&#xff0c;分享经验&#xff0c;助你少走弯路 文章目录 1. 二叉搜索树的概念2. 二叉搜索树的性能分析3. 二叉搜索树的插入4. 二叉搜素树的查找5. 二叉搜索树的删除6.二…

C++之类和对象基础

⾯向对象三⼤特性&#xff1a;封装、继承、多态 类和对象 一.类的定义1. 类的定义格式2.类域 二.实例化1.对象2.对象的大小 三.this指针 在 C 的世界里&#xff0c;类和对象构成了面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;的核心框架&…

报错java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not ...解决方法

在运行项目时出现java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualidzz这样的报错 解决方法 1.第一步&#xff1a;在pom文件中将lombok的版本改成最新的 此时1.18.34是新…

2025-03-12 Python深度学习1——安装Anaconda与PyTorch库

文章目录 1 配置 Anaconda1.1 下载1.2 安装1.3 配置环境变量1.4 检查安装 2 安装 PyTorch 库2.1 创建 DL 环境2.2 安装/升级 CUDA2.3 配置环境变量2.4 安装 Pytorch 库方法一&#xff08;不稳定&#xff09;方法二&#xff08;推荐&#xff09; 2.5 检查安装 3 Pycharm Communi…