在C++中创建通用类型链表
在本文中,我们将探讨如何在C++中创建一个自定义链表结构,用于储存任意类型的数据文章中包含了两个完整的用例,自定义一个包含当前坐标和前一个坐标的点的结构体,并用我们的通用链表储存起来。
在C++中,一个通用的链表结构体通常涉及到模板的使用,这样可以允许链表存储任何类型的数据。以下是使用C++模板实现的一个通用链表类的示例,包括了链表节点结构体和链表类本身。链表节点使用指针来存储数据,而链表类提供了在链表前后插入和删除节点的方法,以及其他有用的功能。
#include <iostream>
#include <memory>
// 链表节点的定义
template <typename T>
struct ListNode {
std::unique_ptr<T> data; // 使用unique_ptr来自动管理内存
ListNode<T>* next;
// 构造函数,用于初始化节点
ListNode(T* data) : data(data), next(nullptr) {}
// 禁止拷贝构造和赋值
ListNode(const ListNode&) = delete;
ListNode& operator=(const ListNode&) = delete;
// 支持移动构造和赋值
ListNode(ListNode&&) = default;
ListNode& operator=(ListNode&&) = default;
};
// 链表类的定义
template <typename T>
class LinkedList {
private:
ListNode<T>* head;
ListNode<T>* tail;
public:
// 构造函数
LinkedList() : head(nullptr), tail(nullptr) {}
// 禁止拷贝构造和赋值
LinkedList(const LinkedList&) = delete;
LinkedList& operator=(const LinkedList&) = delete;
// 支持移动构造和赋值
LinkedList(LinkedList&&) = default;
LinkedList& operator=(LinkedList&&) = default;
// 析构函数
~LinkedList() {
clear();
}
// 在链表前端插入节点
void push_front(T* data) {
ListNode<T>* node = new ListNode<T>(data);
if (!head) {
head = tail = node;
} else {
node->next = head;
head = node;
}
}
// 在链表末端插入节点
void push_back(T* data) {
ListNode<T>* node = new ListNode<T>(data);
if (!head) {
head = tail = node;
} else {
tail->next = node;
tail = node;
}
}
// 从链表前端删除节点
std::unique_ptr<T> pop_front() {
if (!head) {
return nullptr;
}
ListNode<T>* old_head = head;
head = head->next;
if (head == nullptr) {
tail = nullptr;
}
return std::move(old_head->data);
}
// 从链表末端删除节点
std::unique_ptr<T> pop_back() {
if (!head) {
return nullptr;
}
if (head == tail) {
return pop_front();
}
ListNode<T>* current = head;
while (current->next != tail) {
current = current->next;
}
std::unique_ptr<T> data = std::move(tail->data);
delete tail;
tail = current;
tail->next = nullptr;
return data;
}
// 检查链表是否为空
bool empty() const {
return head == nullptr;
}
// 清除链表中的所有节点
void clear() {
while (head != nullptr) {
pop_front();
}
}
// 打印链表中的元素(需要T类型支持输出操作)
void print() const {
ListNode<T>* current = head;
while (current != nullptr) {
std::cout << *(current->data) << " -> ";
current = current->next;
}
std::cout << "null" << std::endl;
}
};
// 示例使用
int main() {
LinkedList<int> list;
// 在链表末端添加元素
list.push_back(new int(1));
list.push_back(new int(2));
list.push_back(new int(3));
// 打印链表
list.print();
// 从链表前端删除元素
auto data = list.pop_front();
if (data) {
std::cout << "Popped from front: " << *data << std::endl;
}
// 再次打印链表
list.print();
// 清除链表
list.clear();
return 0;
}
这个链表类使用了unique_ptr
来自动管理内存,确保不会发生内存泄漏。请注意,这个实现假设存储在链表中的类型T
支持输出操作(即重载了operator<<
),这样才能在print
方法中打印每个元素。如果类型T
不支持输出操作,你需要移除或者修改print
方法。
在实际使用中,你可能需要根据实际情况调整或者扩展这个链表类的功能,以满足特定的需求。例如,你可能会添加遍历、查找或排序等方法。
自定义数据结构
在任何数据结构的设计中,首先要确定的是它将如何存储和处理数据。在我们的例子中,我们需要存储一系列的点。为了创建一个包含当前坐标 (x, y)
和前一个坐标 (prev_x, prev_y)
的自定义结构体,并将这些结构体组织成链表。
在这个示例中,Point
结构体包含了两个整数对,分别表示当前点的x
和y
坐标,以及先前点的prev_x
和prev_y
坐标。此外,Point
结构体还重载了输出操作符<<
,以便于在LinkedList
的print
方法中直接打印点的信息。
当你在链表中添加Point
实例时,你需要手动管理这些实例的内存,因为我们使用了裸指针。在本例中,我们通过new
操作符创建了Point
的实例,并通过push_back
方法添加到链表中。当我们通过pop_front
方法从链表中移除一个Point
实例时,unique_ptr
会自动释放这个实例占用的内存。在实际应用中,你可能需要考虑使用智能指针来进一步简化内存管理。
#include <iostream>
#include <memory>
// ... (包含先前提供的ListNode和LinkedList的定义)
// 定义一个表示点的结构体
struct Point {
int x, y; // 当前点的坐标
int prev_x, prev_y; // 先前点的坐标
Point(int x, int y, int prev_x, int prev_y)
: x(x), y(y), prev_x(prev_x), prev_y(prev_y) {}
// 输出操作符重载,用于打印点的信息
friend std::ostream& operator<<(std::ostream& os, const Point& point) {
os << "Current: (" << point.x << ", " << point.y << "), "
<< "Previous: (" << point.prev_x << ", " << point.prev_y << ")";
return os;
}
};
// 示例使用
int main() {
// 创建一个链表,存储Point结构体
LinkedList<Point> pointList;
// 添加点到链表
pointList.push_back(new Point(1, 2, 0, 0));
pointList.push_back(new Point(3, 4, 1, 2));
pointList.push_back(new Point(5, 6, 3, 4));
// 打印链表中的点
pointList.print();
// 从链表前端删除点
auto data = pointList.pop_front();
if (data) {
std::cout << "Popped from front: " << *data << std::endl;
}
// 再次打印链表
pointList.print();
// 清除链表
pointList.clear();
return 0;
}
完整代码
#include <iostream>
#include <memory>
// 链表节点的定义
template <typename T>
struct ListNode {
std::unique_ptr<T> data; // 使用unique_ptr来自动管理内存
ListNode<T>* next;
// 构造函数,用于初始化节点
ListNode(T* data) : data(data), next(nullptr) {}
// 禁止拷贝构造和赋值
ListNode(const ListNode&) = delete;
ListNode& operator=(const ListNode&) = delete;
// 支持移动构造和赋值
ListNode(ListNode&&) = default;
ListNode& operator=(ListNode&&) = default;
};
// 链表类的定义
template <typename T>
class LinkedList {
private:
ListNode<T>* head;
ListNode<T>* tail;
public:
// 构造函数
LinkedList() : head(nullptr), tail(nullptr) {}
// 禁止拷贝构造和赋值
LinkedList(const LinkedList&) = delete;
LinkedList& operator=(const LinkedList&) = delete;
// 支持移动构造和赋值
LinkedList(LinkedList&&) = default;
LinkedList& operator=(LinkedList&&) = default;
// 析构函数
~LinkedList() {
clear();
}
// 在链表前端插入节点
void push_front(T* data) {
ListNode<T>* node = new ListNode<T>(data);
if (!head) {
head = tail = node;
} else {
node->next = head;
head = node;
}
}
// 在链表末端插入节点
void push_back(T* data) {
ListNode<T>* node = new ListNode<T>(data);
if (!head) {
head = tail = node;
} else {
tail->next = node;
tail = node;
}
}
// 从链表前端删除节点
std::unique_ptr<T> pop_front() {
if (!head) {
return nullptr;
}
ListNode<T>* old_head = head;
head = head->next;
if (head == nullptr) {
tail = nullptr;
}
return std::move(old_head->data);
}
// 从链表末端删除节点
std::unique_ptr<T> pop_back() {
if (!head) {
return nullptr;
}
if (head == tail) {
return pop_front();
}
ListNode<T>* current = head;
while (current->next != tail) {
current = current->next;
}
std::unique_ptr<T> data = std::move(tail->data);
delete tail;
tail = current;
tail->next = nullptr;
return data;
}
// 检查链表是否为空
bool empty() const {
return head == nullptr;
}
// 清除链表中的所有节点
void clear() {
while (head != nullptr) {
pop_front();
}
}
// 打印链表中的元素(需要T类型支持输出操作)
void print() const {
ListNode<T>* current = head;
while (current != nullptr) {
std::cout << *(current->data) << " -> ";
current = current->next;
}
std::cout << "null" << std::endl;
}
};
// 定义一个表示点的结构体
struct Point {
int x, y; // 当前点的坐标
int prev_x, prev_y; // 先前点的坐标
Point(int x, int y, int prev_x, int prev_y)
: x(x), y(y), prev_x(prev_x), prev_y(prev_y) {}
// 输出操作符重载,用于打印点的信息
friend std::ostream& operator<<(std::ostream& os, const Point& point) {
os << "Current: (" << point.x << ", " << point.y << "), "
<< "Previous: (" << point.prev_x << ", " << point.prev_y << ")";
return os;
}
};
// 示例使用
int main() {
LinkedList<int> list;
// 在链表末端添加元素
list.push_back(new int(1));
list.push_back(new int(2));
list.push_back(new int(3));
// 打印链表
list.print();
// 从链表前端删除元素
auto data = list.pop_front();
if (data) {
std::cout << "Popped from front: " << *data << std::endl;
}
// 再次打印链表
list.print();
// 清除链表
list.clear();
// 创建一个链表,存储Point结构体
LinkedList<Point> pointList1;
// 添加点到链表
pointList1.push_back(new Point(1, 2, 0, 0));
pointList1.push_back(new Point(3, 4, 1, 2));
pointList1.push_back(new Point(5, 6, 3, 4));
// 打印链表中的点
pointList1.print();
// 从链表前端删除点
auto data1 = pointList1.pop_front();
if (data1) {
std::cout << "Popped from front: " << *data1 << std::endl;
}
// 再次打印链表
pointList1.print();
// 清除链表
pointList1.clear();
return 0;
}
总结
通过本文的指导,你现在可以在C++中创建一个功能齐全的通用链表。这种数据结构不仅增加了代码的灵活性和可重用性,而且通过智能指针的使用,还提高了程序的安全性和内存管理效率。记住,无论是创建链表还是其他复杂数据结构,核心在于理解和应用面向对象的原则,以及熟悉C++的模板和内存管理机制。随着实践的增加,你将能更加熟练地运用这些技术来解决各种编程挑战。