【C++入门到精通】C++入门 —— vector (STL)

阅读导航
- 前言
- 一、vector简介
- 1. 概念
- 2. 特点
- 二、vector的使用
- 1.vector 构造函数
- 2. vector 空间增长问题
- ⭕resize 和 reserve 函数
- 3. vector 增删查改
- ⭕operator[] 函数
- 三、迭代器失效
- 温馨提示
前言
前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点——STL(vector)。下面话不多说坐稳扶好咱们要开车了😍
一、vector简介
1. 概念
std::vector是C++标准库中的一个容器类模板,是一种动态数组,可以存储相同类型的元素。它提供了动态调整大小、快速随机访问、插入和删除元素的操作。(vector的官方介绍链接)
2. 特点
-
动态调整大小:
std::vector会自动处理内存的分配和释放,可以根据需要在运行时动态调整容器的大小。当添加或删除元素时,std::vector会自动调整内部数组的大小以适应新的元素数量。 -
快速随机访问:
std::vector中的元素在内存中是连续存储的,因此可以通过下标或迭代器快速访问任意位置的元素。具有常数时间复杂度的随机访问使得std::vector非常适合需要频繁访问元素的场景。 -
动态增长和收缩:当需要添加更多元素时,
std::vector会自动扩展内部数组的大小,以容纳新的元素;而当需要删除元素时,std::vector会自动收缩内部数组的大小,以减少内存的使用。 -
插入和删除操作:
std::vector提供了插入和删除元素的方法。可以在指定位置插入一个或多个元素,也可以通过下标或迭代器删除指定位置的元素。 -
元素的访问和遍历:可以通过下标或迭代器访问指定位置的元素。使用迭代器可以对
std::vector进行遍历,包括正向和反向遍历。 -
其他功能:除了上述基本功能外,
std::vector还提供了其他一些有用的功能,如获取容器的大小、判断容器是否为空、重新分配容器的大小、与其他容器进行交换、排序等。
注意:使用std::vector需要包含头文件<vector>,命名空间为 std。std::vector是一个模板类,需要指定存储的元素类型,如std::vector<int>表示存储int类型的元素。
二、vector的使用
1.vector 构造函数
std::vector 提供了多个构造函数来创建和初始化向量,这些构造函数使得创建 std::vector 对象变得灵活,并提供了多种初始化向量的方式。选择适当的构造函数取决于具体的需求和数据源。
- 默认构造函数
vector()
该构造函数创建一个空的 std::vector 对象。
- 大小和初始值构造函数
vector(size_type count, const T& value = T())
该构造函数创建一个包含 count 个元素的 std::vector 对象,并用 value 的值初始化每个元素。如果未提供 value 的值,则使用类型 T 的默认构造函数进行初始化。
- 范围构造函数
template <class InputIt>
vector(InputIt first, InputIt last)
该构造函数使用迭代器范围 [first, last) 中的元素创建一个 std::vector 对象。可以通过传递两个迭代器来指定范围,从而将其他容器中的元素复制到新的 std::vector 对象中。
- 拷贝构造函数
vector(const vector& other)
该构造函数创建一个与 other 相同的 std::vector 对象,复制 other 中的所有元素。
- 移动构造函数
vector(vector&& other)
该构造函数使用 other 的资源来创建一个新的 std::vector 对象,同时将 other 置为空。
- 初始化列表构造函数
vector(std::initializer_list<T> init)
该构造函数使用花括号 {} 内的元素列表初始化 std::vector 对象。可以通过传递元素的列表来创建和初始化向量。
下面是所有使用构造函数创建 std::vector 对象的代码:
#include <vector>
#include <iostream>int main() {// 默认构造函数std::vector<int> numbers; // 空的向量// 大小和初始值构造函数[10, 10, 10, 10, 10]std::vector<int> numbers1(5, 10); // 范围构造函数[1, 2, 3, 4, 5]std::vector<int> numbers2 = {1, 2, 3, 4, 5}; // 拷贝构造函数[1, 2, 3, 4, 5]std::vector<int> numbers3 = numbers2; // 移动构造函数[1, 2, 3, 4, 5]std::vector<int> numbers4 = std::move(numbers2); // numbers2 现在为空// 初始化列表构造函数[1, 2, 3, 4, 5]std::vector<int> numbers5 {1, 2, 3, 4, 5}; // 输出向量内的元素for (const auto& num : numbers5) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
2. vector 空间增长问题
在 std::vector 中,空间增长(space growth)是指在向向量添加元素时,当容量不足时,向量自动增加内部存储空间的过程。这是为了确保向量能够容纳更多的元素,并减少频繁的内存重新分配操作。
- 容量和大小
-
大小(size):指的是
std::vector中当前拥有的元素个数。 -
容量(capacity):指的是
std::vector内部存储空间的大小,即它可以容纳的元素个数。
- 空间分配器(allocator)
std::vector 使用内部的空间分配器来动态管理存储空间。默认情况下,它使用标准全局的 operator new 和 operator delete 来分配和释放内存。也可以提供自定义的空间分配器来满足特定的需求。
- 容量增长策略
std::vector 使用一种策略来决定何时以及如何增加内部存储空间的容量:
-
固定增长因子:许多实现会以固定的因子(例如2倍)来增加容量。这意味着每次增长时,容量都会以固定比例增加。这种策略通常具有较好的性能,但可能会占用更多的内存。
-
线性增长:某些实现会以固定的大小(如每次增加n个元素)增加容量。这可以提供更节省内存的方式,但在某些情况下可能导致频繁的重新分配操作。
容量增长过程通常包括以下步骤:
-
std::vector判断当前的容量是否能够容纳新的元素。如果容量不足,进入空间增长阶段。 -
分配新的更大的内部存储空间。这通常涉及到申请更大的内存块,并将已有的元素从旧的内存块拷贝到新的内存块中。
-
释放旧的内部存储空间,回收内存资源。
-
更新
std::vector的容量信息,以反映新的内部存储空间大小。
空间增长的频率取决于添加元素的数量和容器的策略。过于频繁的空间增长可能会导致性能下降,因为内存重新分配和元素拷贝都是开销较大的操作。因此,预先分配一定容量的空间(使用 std::vector::reserve())可以减少空间增长的次数,提高性能。
注意:自定义类型的元素在空间增长过程中会涉及到拷贝构造函数和析构函数的调用。确保自定义类型的正确实现这些特殊函数是非常重要的,以避免潜在的内存错误和资源泄漏。
⭕resize 和 reserve 函数
在 C++ 的 std::vector 中,reserve() 和 resize() 是两个用于管理向量容量和大小的成员函数。
- reserve(n)
reserve() 函数用于预留至少能够容纳 n 个元素的内部存储空间,而不会改变向量的大小。这样做是为了避免频繁的重新分配和拷贝操作,提高性能。
调用 reserve(n) 后,如果 n 大于当前容量(capacity()),则向量会重新分配新的内部存储空间,容量会大于等于 n。如果 n 小于或等于当前容量,则不会有任何变化。
void reserve(size_type n);
- resize(n, value)
resize() 函数用于改变向量的大小,并根据需要添加或删除元素。
- 如果
n小于当前大小(size()),则会删除超出n个元素后的元素,向量的大小变为n。 - 如果
n大于当前大小,则会添加足够的元素,以便向量的大小变为n。添加的元素将使用value进行初始化。
void resize(size_type n);
void resize(size_type n, const value_type& value);
注意:如果增加了向量的大小,新添加的元素(在当前大小和 n 之间)将通过拷贝构造函数进行初始化。如果存储的是自定义类型的元素,确保该类型的拷贝构造函数正确实现是很重要的。
以下是 reserve() 和 resize() 的示例代码:
#include <iostream>
#include <vector>int main() {std::vector<int> numbers;numbers.reserve(10); // 预留至少能容纳 10 个元素的空间std::cout << "Capacity: " << numbers.capacity() << std::endl; // 输出容量numbers.resize(5); // 更改大小为 5std::cout << "Size after resize: " << numbers.size() << std::endl; // 输出大小numbers.resize(8, 42); // 更改大小为 8,并用值 42 进行初始化std::cout << "Size after resize 2: " << numbers.size() << std::endl; // 输出大小return 0;
}
总的来说,reserve() 用于预留足够的存储空间以容纳更多的元素,而 resize() 用于改变向量的大小并根据需要添加或删除元素。这两个函数可以帮助我们灵活地管理 std::vector 的容量和大小,以满足不同的需求。
3. vector 增删查改
当使用 C++ 的 std::vector 容器时,可以使用下面的方法来执行向向量中增加数据、删除数据、查找数据和修改数据的操作。
- 增加数据
向 std::vector 中增加数据可以使用 push_back() 函数将元素添加到向量的末尾。
void push_back(const T& value);
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> numbers;numbers.push_back(10);numbers.push_back(20);numbers.push_back(30);for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
- 删除数据
可以使用 erase() 函数从向量中删除数据。它接受一个迭代器参数,指示要删除的元素位置。
iterator erase(iterator position);
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> numbers = {10, 20, 30};numbers.erase(numbers.begin() + 1); // 删除索引为 1 的元素for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
- 查找数据
可以使用 std::find() 算法函数在向量中查找特定的元素。该函数接受两个迭代器参数,指示要搜索的范围,以及要查找的值。它返回指向找到的元素的迭代器,如果未找到,则返回指向容器末尾的迭代器。
iterator find(iterator first, iterator last, const T& value);
使用示例:
#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> numbers = {10, 20, 30};auto it = std::find(numbers.begin(), numbers.end(), 20);if (it != numbers.end()) {std::cout << "Element found at index: " << std::distance(numbers.begin(), it) << std::endl;} else {std::cout << "Element not found" << std::endl;}return 0;
}
- 修改数据
可以通过迭代器来访问和修改 std::vector 中的元素。通过修改迭代器指向的元素来实现数据的修改。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> numbers = {10, 20, 30};auto it = numbers.begin();++it;*it = 25; // 修改元素值for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
⭕operator[] 函数
还可以使用 operator[] 运算符来访问 std::vector 中的元素并进行修改。operator[] 函数的官方链接
operator[] 运算符允许通过索引来访问向量中的元素,并提供了对元素的非常快速的随机访问。索引从 0 开始,依次增加。
使用示例:
#include <iostream>
#include <vector>int main() {std::vector<int> numbers = {10, 20, 30};std::cout << numbers[0] << std::endl; // 访问索引为 0 的元素numbers[1] = 25; // 修改索引为 1 的元素的值for (const auto& num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
需要注意以下几点:
-
使用
operator[]访问索引超出向量范围的元素是未定义行为,因此要确保索引值在有效范围内。 -
如果要对不存在的元素使用
operator[]进行访问,它将创建一个新元素并分配给该索引位置。因此,在使用operator[]之前,通常需要确保向量具有足够的大小。 -
operator[]返回对索引位置的引用,因此可以通过返回的引用进行修改和赋值操作。
operator[] 运算符是一种快速访问和修改 std::vector 中元素的便捷方式,它提供了类似于数组的索引操作。
三、迭代器失效
在 std::vector 中,迭代器失效指的是当在向量进行插入或删除操作后,原本指向向量元素的迭代器可能会失效,即不能再安全地使用。
迭代器失效的问题存在于插入和删除操作中,因为这些操作可能会导致向量重新分配内存,使得原来的迭代器指向的位置变得无效。下面是常见的迭代器失效情况:
- 在插入或删除一个元素后,迭代器失效
当向向量中插入或删除元素时,迭代器可能会失效。插入操作可能导致内部存储重新分配,而删除操作可能使元素位置向前移动。这会导致之前的迭代器指向的位置变得无效。
std::vector<int> numbers = {10, 20, 30};
auto it = numbers.begin();numbers.insert(it + 1, 15); // 在索引为 1 的位置插入元素// 在插入之后,迭代器 it 可能失效,不能再使用
- 使用被删除元素的迭代器,迭代器失效
当使用被删除元素的迭代器继续访问向量时,迭代器可能会失效。因为删除元素会导致元素位置的变动,原本的迭代器指向的位置可能已经被其他元素占据。
std::vector<int> numbers = {10, 20, 30};
auto it = numbers.begin();numbers.erase(it); // 删除首个元素// 在删除之后,迭代器 it 变成无效的迭代器,不能再使用
为了避免迭代器失效问题,可以采取以下几种方法:
-
使用索引替代迭代器:使用基于索引的访问方式,而不是依赖迭代器。
-
在插入或删除元素后重新获取迭代器:在插入或删除操作后,重新获取迭代器,而不是继续使用之前的迭代器。
std::vector<int> numbers = {10, 20, 30};
auto it = numbers.begin();numbers.insert(it + 1, 15);
it = numbers.begin(); // 插入后重新获取迭代器numbers.erase(it);
it = numbers.begin(); // 删除后重新获取迭代器
- 使用算法和函数:使用标准库提供的算法和函数,而不是手动操作迭代器。这些函数会处理好迭代器失效的情况,例如使用
std::for_each遍历向量。
std::vector<int> numbers = {10, 20, 30};
std::for_each(numbers.begin(), numbers.end(), [](int& num) {// 修改元素值
});
了解迭代器失效的情况,并采取适当的措施来避免迭代器失效问题,是在操作 std::vector 时需要注意的重要方面。
温馨提示
感谢您对博主文章的关注与支持!在阅读本篇文章的同时,我们想提醒您留下您宝贵的意见和反馈。如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

相关文章:
【C++入门到精通】C++入门 —— vector (STL)
阅读导航 前言一、vector简介1. 概念2. 特点 二、vector的使用1.vector 构造函数2. vector 空间增长问题⭕resize 和 reserve 函数 3. vector 增删查改⭕operator[] 函数 三、迭代器失效温馨提示 前言 前面我们讲了C语言的基础知识,也了解了一些数据结构࿰…...
git简单使用
1.在 远端仓库创建好仓库 2.在本地中创建仓库 mkdir 仓库名 cd 仓库名 3.初始化(可以省略) git init 4.添加远端仓库 git remote add origin https://gitee.com/zengtian_7/pet_home.git 5.初始化代码库:当你创建一个全新的代码库时,…...
CSS—选择器
目录 一、CSS简介 二、HTML页面中常用的元素 三、CSS语法规则 四、常用的选择器 五、CSS的三种使用方法 六、选择器参考 一、CSS简介 CSS (Cascading Style Sheets,层叠样式表),是一种用来为结构化文档(如 HTML 文档或 XML 应…...
【Unity实战系列】Unity的下载安装以及汉化教程
君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们,这里是君兮_,怎么说呢,其实这才是我以后真正想写想做的东西,虽然才刚开始,但好歹,我总算是启程了。今天要分享…...
电脑IP地址错误无法上网怎么办?
电脑出现IP地址错误后就将无法连接网络,从而无法正常访问互联网。那么当电脑出现IP地址错误时该怎么办呢? 确认是否禁用本地连接 你需要先确定是否禁用了本地网络连接,如果发现禁用,则将其启用即可。 启用方法:点击桌…...
机器视觉项目流程和学习方法
机器视觉项目流程: 00001. 需求分析和方案建立 00002. 算法流程规划和业务逻辑设计 00003. 模块化编程和集成化实现 00004. 调试和优化,交付客户及文档 学习机器视觉的方法: 00001. 实战学习,结合项目经验教训 00002. 学习…...
LNMP环境搭建wordpress以及跳转后台报404解决
基于上文配置好的LNMP环境继续搭建wordpress 目录 一.到官网下载tar.gz包,并上传到Linux上,也可以通过复制链接地址进行下载 二. 将wordpress中的所有文件移动到你nginx.conf中指定目录中 三.为wordpress配置数据库 四.到浏览器进行注册 1.刚开始…...
Nginx+Tomcat的动静分离
首先准备好5台机子:2台装有tomcat,3台装有nginx 1.关闭5台机子的防火墙 systemctl stop firewalld systemctl disable firewalld setenforce 0 Nginx1 vim /usr/local/nginx/conf/nginx.conf#在--#pid-- 下做四层代理 stream {upstream test {server …...
Tomcat部署与优化
目录 一、Tomcat介绍 二、Tomcat核心组件 1、web容器:完成web服务器的功能,web应用 2、servlet容器:名字:catalina,处理servlet代码 servlet的功能 3、jsp:jsp动态页面翻译成servlet代码,用…...
jmeter工具使用
jmeter工具使用 官方下载 安装好jdk后,下载之后直接运行即可 基本流程 1、首先添加线程组 线程组:JMeter是由Java实现的,并且使用一个Java线程来模拟一个用户,因此线程组(Thread Group)就是指一组用户的…...
【uniapp】封装一个全局自定义的模态框
【需求描述】 在接口401处,需要实现全局提示并弹出自定义模态框的功能。考虑到uni-app内置的模态框和app原生提示框的自定义能力有限,我决定自行封装全局自定义的模态框,以此为应用程序提供更加统一且个性化的界面。 【效果图】 【封装】 主…...
UNIX 入门
与 UNIX 建立连接启动会话登录命令提示符修改口令退出系统 简单的 UNIX 命令命令格式ls 命令who 命令虚拟终端 tty伪终端 ptywho am i 命令 cal 命令help 命令man 命令 shell 概述shell 命令更换 shell临时更改 shell永久更改 shell 登录过程 与 UNIX 建立连接 启动会话 要启…...
Golang通过alibabaCanal订阅MySQLbinlog
最近在做redis和MySQL的缓存一致性,一个方式是订阅MySQL的BinLog文件,我们使用阿里巴巴的Canal的中间件来做。 Canal是服务端和客户端两部分构成,我们需要先启动Canal的服务端,然后在Go程序里面连接Canal服务端,即可监…...
Python flask-restful 框架讲解
1、简介 Django 和 Flask 一直都是 Python 开发 Web 的首选,而 Flask 的微内核更适用于现在的云原生微服务框架。但是 Flask 只是一个微型的 Web 引擎,所以我们需要扩展 Flask 使其发挥出更强悍的功能。 python flask框架详解:https://blog.…...
MySQL_约束、多表关系
约束 概念:就是用来作用表中字段的规则,用于限制存储在表中的数据。 目的:保证数据库中数据的正确性,有效性和完整性。 约束演示 #定义一个学生表,表中要求如下: #sn 表示学生学号,要求使用 …...
在Qt中使用LoadLibrary无法加载DLL
Qt系列文章目录 文章目录 Qt系列文章目录前言一、问题分析 前言 最近因项目需要使用qt做开发,之前使用LoadLibrary加载dll成功,很庆幸,当一切都那么顺风顺水的时候,测试同事却发现,在windows平台上个别电脑上加载dll会…...
如何将区块链新闻稿发布到海外媒体?
随着区块链技术的不断发展,越来越多的区块链项目涌现出来,各大媒体也开始关注和报道区块链新闻。然而,如何将区块链新闻稿发布到海外媒体成为了许多区块链项目所面临的难题。本文将介绍一些有效的方法,帮助区块链项目将新闻稿发布…...
基于 CentOS 7 构建 LVS-DR 群集。
1.准备实验环境 本次实验我准备了4台虚拟机 DS:DIP--192.168.163.138 VIP--192.168.163.200 RIP1(web1)--192.168.163.140 RIP2(web2)--192.168.163.141 Client:user--192.168.163.142 2.配置服务器环境 1)搭建简易的web服务 RIP1 [rootlocalhost ~]# yum …...
防火墙组建双击热备后,点击管理对端设备,老是打不开,怎么办?
环境: 防火墙 8.0.75 AF-2000-FH2130B-SC 问题描述: 防火墙组建双击热备后,点击管理对端设备,老是打不开,怎么办? 浏览器老是加载 解决方案: 1.打开设置查看双机连接的心跳接口是哪个端口 …...
【Kubernetes】Kubernetes之Pod详解
Pod 一、 Pod1. Pod 基础概念2. 在 Kubrenetes 集群中 Pod 使用方式2.1 pasue 容器2.2 kubernetes 中的 pause 容器提供的功能 3. Pod 的概念和结构组成4. Pod 的分类5. Pod 容器的分类5.1 基础容器(infrastructure container)5.2 初始化容器(…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
