Vector
<stdexcept>定义了一些标准的异常类。分为两大类:逻辑错误和运行时错误。其中运行时错误是程序员不能控制的。
#include <sstream>这是一个C++标准库中的头文件,提供了stringstream类,用于将字符串转换成数字或者将数字转换成字符串
#include <algorithm>
是C++中一个常用的预处理指令,它包含了algorithm
库。这个库提供了大量用于操作序列(例如数组、向量、列表等容器)的通用算法,这些算法包括查找、排序、复制、移动、修改和其他操作
必须使用delete[]来释放动态数组的内存
关于迭代器
begin指向数组第一个元素的前面
elements[0]第一个元素
begin
const
版本的迭代器函数(如你提供的 const T* begin() const
)是为了确保在只读上下文中,调用者不能修改通过迭代器访问的数据
3.other是被拷贝的对象
std::copy需要三个参数:两个指定要复制的元素范围的迭代器(起始迭代器和结束迭代器),以及一个指向目标位置开始的迭代器。 指针也是一种天然的迭代器
4.释放调用者对象的内存
5.size当前元素个数 capacity 设置的可容纳元素个数
如果容量0表示还没设置,从1开始;否则翻倍
7.throw std::out_of_range("Index out of range"); 抛出 std::out_of_range 异常
如果不提供第二种形式,那么将无法访问const对象的任何数组元素
const修饰函数调用const对象 非const不能调用const
8.将insert及之后的元素后移一位
9.不实际释放内存,这意味着即使调用了 pop_back,capacity 保持不变。
10.std中vector没提供
12.函数后const 只读
getchar(); std::string line; // 接收命令 for (int i = 0; i < N; i++) { std::getline(std::cin, line); std::istringstream iss(line); std::string command; iss >> command; int value; if (command == "push_back") { iss >> value; myList.push_back(value); }
getchar();
:由于std::cin >> N;
读取了输入的整数N
,但是不会读取输入后的回车符(换行符),因此紧接着的getchar();
用来消耗掉这个回车符,避免它在后续的std::getline()
中被误认为是输入的一部分。
std::string line;
:声明一个字符串line
,用于存储每次输入的整行命令。
for (int i = 0; i < N; i++)
:循环读取N
次命令,其中N
应该是预先定义的命令数量。
std::getline(std::cin, line)
:使用getline
函数从标准输入读取一整行的命令,存储到line
中。
std::istringstream iss(line)
:使用istringstream
来解析输入的命令,将字符串line
封装为一个输入流对象iss
。
std::string command;
iss >> command;
:从输入流iss
中读取第一个单词,将其赋值给字符串command
,用于判断具体是哪种命令。
if (command == "push_back")
:判断是否输入的命令是push_back
,如果是,就继续读取一个整数value
,并将其加入myList
列表的末尾。
面试6.在使用 std::vector 的 erase 函数时,通常不需要直接使用 vector:: 前缀。
只要你通过一个 std::vector 对象或实例来调用 erase 函数,它就会正常工作
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.erase(vec.begin() + 2); // 移除第三个元素
如果你在代码中有多个 erase 函数(例如在不同的类或命名空间中),
并且你需要明确调用的是 std::vector 的 erase 函数,你可以使用 vector::erase。vec.vector::erase(vec.begin() + 2); // 明确调用 std::vector 的 erase
面试7 野指针 悬垂指针
野指针:是未初始化的指针,指向随机的内存地址。它们可能引发不可预测的行为。
解决方法:在声明指针时将其初始化为 nullptr,或者立即将其指向一个合法的内存地址。悬垂指针:是曾经指向有效内存,但该内存已经被释放或重新分配的指针。
继续使用它们同样可能引发未定义行为。
解决方法:在释放内存后,将指针设置为 nullptr。避免返回指向局部变量的指针。
#include <iostream>
#include <algorithm>
#include <sstream>
#include <string>
#include <stdexcept>
template <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(const Vector &other) : capacity(other.capacity), size(other.size)
{
elements = new T[capacity];
std::copy(other.elements, other.elements + size, elements);
}
// 拷贝赋值操作符 不是删other指针而是调用者对象指针
//比如g1 g2已有对象,将g1付给g2,要删掉g2原指针
Vector &operator=(const Vector &other)
{
if (this != &other)
{
delete[] elements;
capacity = other.capacity;
size = other.size;
elements = new T[capacity];
std::copy(other.elements, other.elements + size, elements);
}
return *this;
}
// 添加元素到数组末尾
void push_back(const T &value)
{
if (size == capacity)
{
// 如果数组已满,扩展容量
reserve(capacity == 0 ? 1 : 2 * capacity);
}
elements[size++] = value;
}
// 获取数组中元素的个数
size_t getSize() const
{
return size;
}
// 获取数组的容量
size_t getCapacity() const
{
return capacity;
}
// 访问数组中的元素
T &operator[](size_t index)
{
// 检查索引是否越界
if (index >= size)
{
throw std::out_of_range("Index out of range");
}
return elements[index];
}
// const版本的访问数组中的元素
const T &operator[](size_t index) const
{
// 检查索引是否越界
if (index >= size)
{
throw std::out_of_range("Index out of range");
}
return elements[index];
}
// 在指定位置插入元素
void insert(size_t index, const T &value)
{
if (index > size)
{
throw std::out_of_range("Index out of range");
}
if (size == capacity)
{
reserve(capacity == 0 ? 1 : capacity * 2);
}
for (size_t i = size; i > index; --i)
{
elements[i] = elements[i - 1];
}
elements[index] = value;
++size;
}
// 删除数组末尾的元素
//删元素就是控制size
void pop_back()
{
if (size > 0)
{
--size;
}
}
// 清空数组
void clear()
{
size = 0;
}
// 使用迭代器遍历数组的开始位置
T *begin()
{
return elements;
}
// 使用迭代器遍历数组的结束位置
T *end()
{
return elements + size;
}
// 使用迭代器遍历数组的开始位置(const版本)
const T *begin() const
{
return elements;
}
// 使用迭代器遍历数组的结束位置(const版本)
const T *end() const
{
return elements + size;
}
// 打印数组中的元素
void printElements() const
{
for (size_t i = 0; i < size; ++i)
{
std::cout << elements[i] << " ";
}
std::cout << std::endl;
}
private:
// 扩展数组容量
//需要改变指针 因为指针指向大小改变
void reserve(size_t newCapacity)
{
if (newCapacity > capacity)
{
T *newElements = new T[newCapacity];
std::copy(elements, elements + size, newElements);
delete[] elements;
elements = newElements;
capacity = newCapacity;
}
}
};
int main()
{
// 创建一个 Vector 对象
Vector<int> myVector;
int N;
std::cin >> N;
// 读走回车
getchar();
std::string line;
for (int i = 0; i < N; i++)
{
// 读取整行
std::getline(std::cin, line);
std::istringstream iss(line);
std::string command;
iss >> command;
if (command == "push")
{
int value;
iss >> value;
myVector.push_back(value);
}
else if (command == "print")
{
if (myVector.getSize() == 0) {
std::cout << "empty" << std::endl;
continue;
}
myVector.printElements();
}
else if (command == "size")
{
std::cout << myVector.getSize() << std::endl;
}
else if (command == "get")
{
int index;
iss >> index;
std::cout << myVector[index] << std::endl;
}
else if (command == "insert")
{
int index, value;
iss >> index >> value;
myVector.insert(index, value);
}
else if (command == "pop")
{
myVector.pop_back();
}
else if (command == "iterator")
{
if (myVector.getSize() == 0)
{
std::cout << "empty" << std::endl;
continue;
}
for (auto it = myVector.begin(); it != myVector.end(); ++it)
{
std::cout << *it << " ";
}
std::cout << std::endl;
}
else if (command == "foreach")
{
if (myVector.getSize() == 0)
{
std::cout << "empty" << std::endl;
continue;
}
for (const auto &element : myVector)
{
std::cout << element << " ";
}
std::cout << std::endl;
}
else if (command == "clear")
{
myVector.clear();
}
}
return 0;
}
List
std::vector 是基于数组的动态数组,而 std::list 是基于双向链表的数据结构。
list适用于需要在序列中执行频繁插入和删除操作的场景。
要访问list中的元素,必须通过迭代器进行;
std::list 是一个双向链表容器,它和数组或 std::vector 不同,无法通过下标访问元素。
这是因为链表的节点在内存中不是连续存储的,而是通过指针进行链接的,
因此无法像数组那样通过索引直接访问特定位置的元素。
list在进行插入和删除操作时,能够更好地保持迭代器的有效性。这意味着在进行这些操作后,不会导致所有迭代器失效
由于list使用了链表结构,可能引入一些额外的内存开销。在内存使用方面,vector和deque可能更为紧凑
每次插入都要添加一个结构体大小的内存
链表的默认结构是先进先出
双向链表:可以在尾部插入元素,随后在尾部移除元素,这样也能实现先进后出
List类控制结构, 包含head, tail, size三个成员, 分别控制链表的头尾指针和大小
创建新节点:将原tail的下一个指向新节点,然后更新tail;
访问节点元素,因为节点指针head和tail指向的是节点,并没指向元素
删除链表末尾元素:如果仅有一个元素,head=nullptr
获取指定值节点:&node->next 第next节点数据val的地址
清空链表:清空所有节点指针 size=0
#include <iostream>
#include <stdexcept>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <string>
template <typename T>
class List
{
private:
// 定义节点结构
struct Node
{
T data; // 数据
Node *next; // 指向下一个节点的指针
Node *prev; // 指向前一个节点的指针
// 构造函数
Node(const T &value, Node *nextNode = nullptr, Node *prevNode = nullptr)
: data(value), next(nextNode), prev(prevNode) {}
};
Node *head; // 头节点指针
Node *tail; // 尾节点指针
size_t size; // 链表中节点的数量
public:
// 构造函数
List() : head(nullptr), tail(nullptr), size(0) {}
// 析构函数
~List() { clear(); }
// 在链表末尾添加元素
void push_back(const T &value)
{
// 创建新的节点
Node *newNode = new Node(value, nullptr, tail);
if (tail)
{
// 如果链表非空,将尾节点的 next 指针指向新节点
tail->next = newNode;
}
else
{
// 如果链表为空,新节点同时也是头节点
head = newNode;
}
// 更新尾节点指针和链表大小
tail = newNode;
++size;
}
// 在链表开头添加元素
void push_front(const T &value)
{
// 创建新的节点
Node *newNode = new Node(value, head, nullptr);
if (head)
{
// 如果链表非空,将头节点的 prev 指针指向新节点
head->prev = newNode;
}
else
{
// 如果链表为空,新节点同时也是尾节点
tail = newNode;
}
// 更新头节点指针和链表大小
head = newNode;
++size;
}
// 获取链表中节点的数量
size_t getSize() const { return size; }
// 访问链表中的元素
T &operator[](size_t index)
{
// 从头节点开始遍历链表,找到第 index 个节点
Node *current = head;
for (size_t i = 0; i < index; ++i)
{
if (!current)
{
// 如果 index 超出链表长度,则抛出异常
throw std::out_of_range("Index out of range");
}
current = current->next;
}
// 返回节点中的数据
return current->data;
}
// const版本的访问链表中的元素
const T &operator[](size_t index) const
{
// 从头节点开始遍历链表,找到第 index 个节点
Node *current = head;
for (size_t i = 0; i < index; ++i)
{
if (!current)
{
// 如果 index 超出链表长度,则抛出异常
throw std::out_of_range("Index out of range");
}
current = current->next;
}
// 返回节点中的数据
return current->data;
}
// 删除链表末尾的元素
void pop_back()
{
if (size > 0)
{
// 获取尾节点的前一个节点
Node *newTail = tail->prev;
// 删除尾节点
delete tail;
// 更新尾节点指针和链表大小
tail = newTail;
if (tail)
{
tail->next = nullptr;
}
else
{
head = nullptr; // 如果链表为空,头节点也置为空
}
--size;
}
}
// 删除链表开头的元素
void pop_front()
{
if (size > 0)
{
// 获取头节点的下一个节点
Node *newHead = head->next;
// 删除头节点
delete head;
// 更新头节点指针和链表大小
head = newHead;
if (head)
{
head->prev = nullptr;
}
else
{
tail = nullptr; // 如果链表为空,尾节点也置为空
}
--size;
}
}
// 获取指定值的节点
Node *getNode(const T &val)
{
Node *node = head;
while (node != nullptr && node->val != val)
{
node = node->next;
}
return node;
}
T *find(const T &val)
{
Node *node = getNode(val);
if (node == nullptr)
{
return nullptr;
}
return &node->val;
}
// 删除指定值的节点
void remove(const T &val)
{
Node *node = head;
while (node != nullptr && node->data != val)
{
node = node->next;
}
if (node == nullptr)
{
// 没有找到
return;
}
if (node != head && node != tail)
{
// 既不是头结点也不是尾结点
node->prev->next = node->next;
node->next->prev = node->prev;
}
else if (node == head && node == tail)
{
// 既是头结点也是尾结点
head = nullptr;
node = nullptr;
}
else if (node == head)
{
// 是头结点
head = node->next;
head->prev = nullptr;
}
else
{
// 是尾结点
tail = node->prev;
tail->next = nullptr;
}
--size;
delete node;
node = nullptr;
}
bool empty() { return size == 0; }
// 清空链表
void clear()
{
while (head)
{
// 从头节点开始,依次删除节点
Node *temp = head;
head = head->next;
delete temp;
}
// 更新尾节点指针和链表大小
tail = nullptr;
size = 0;
}
// 使用迭代器遍历链表的开始位置
Node *begin() { return head; }
// 使用迭代器遍历链表的结束位置
Node *end() { return nullptr; }
// 使用迭代器遍历链表的开始位置(const版本)
const Node *begin() const { return head; }
// 使用迭代器遍历链表的结束位置(const版本)
const Node *end() const { return nullptr; }
// 打印链表中的元素
void printElements() const
{
for (Node *current = head; current; current = current->next)
{
std::cout << current->data << " ";
}
std::cout << std::endl;
}
};
int main() {
// 创建一个 List 对象
List<int> myList;
int N;
std::cin >> N;
// 读走回车
getchar();
std::string line;
// 接收命令
for (int i = 0; i < N; i++) {
std::getline(std::cin, line);
std::istringstream iss(line);
std::string command;
iss >> command;
int value;
if (command == "push_back") {
iss >> value;
myList.push_back(value);
}
if (command == "push_front") {
iss >> value;
myList.push_front(value);
}
if (command == "pop_back") {
myList.pop_back();
}
if (command == "pop_front") {
myList.pop_front();
}
if (command == "remove") {
iss >> value;
myList.remove(value);
}
if (command == "clear") {
myList.clear();
}
if (command == "size") {
std::cout << myList.getSize() << std::endl;
}
if (command == "get") {
iss >> value;
std::cout << myList[value] << std::endl;
}
if (command == "print") {
if (myList.getSize() == 0) {
std::cout << "empty" << std::endl;
} else {
myList.printElements();
}
}
}
return 0;
}