一 参考C++ STL的string类构造一个自定义的字符串类
构造一个自定义的字符串类类似于 C++ 标准库中的 std::string
,需要支持字符串的动态分配、拷贝、拼接、访问字符等基本操作。我们可以从构造函数、析构函数、运算符重载、以及常见的字符串操作函数等方面来设计这个类。
1. 基本思路
- 存储方式:字符串本质是一个字符数组,我们可以用一个指针来存储字符串数据,并通过动态分配内存来管理字符数组。
- 长度管理:使用一个变量来存储字符串的长度。
- 内存管理:实现拷贝构造函数、赋值运算符和析构函数以避免内存泄漏和重复分配。
- 功能实现:添加一些常见的功能,比如字符串拼接、访问单个字符、获取字符串长度等。
2. 代码实现
#include <iostream>
#include <cstring> // 用于 std::strlen 和 std::strcpy
class MyString {
private:
char* data; // 存储字符串数据
size_t length; // 存储字符串的长度
public:
// 默认构造函数
MyString() : data(nullptr), length(0) {}
// 带参数的构造函数
MyString(const char* str) {
if (str) {
length = std::strlen(str); // 获取传入字符串的长度
data = new char[length + 1]; // 为字符串分配内存(+1 为了放终止符 '\0')
std::strcpy(data, str); // 复制字符串数据
} else {
data = nullptr;
length = 0;
}
}
// 拷贝构造函数
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1]; // 分配新的内存
std::strcpy(data, other.data); // 复制字符串数据
}
// 移动构造函数 (C++11)
MyString(MyString&& other) noexcept : data(other.data), length(other.length) {
other.data = nullptr; // 转移所有权,防止双重释放
other.length = 0;
}
// 析构函数
~MyString() {
delete[] data; // 释放内存
}
// 赋值运算符重载 (拷贝赋值)
MyString& operator=(const MyString& other) {
if (this == &other) {
return *this; // 防止自我赋值
}
delete[] data; // 释放当前对象的内存
length = other.length;
data = new char[length + 1]; // 分配新内存
std::strcpy(data, other.data); // 复制数据
return *this;
}
// 赋值运算符重载 (移动赋值) (C++11)
MyString& operator=(MyString&& other) noexcept {
if (this == &other) {
return *this;
}
delete[] data; // 释放当前对象的内存
data = other.data; // 转移所有权
length = other.length;
other.data = nullptr;
other.length = 0;
return *this;
}
// 重载加号运算符,用于字符串拼接
MyString operator+(const MyString& other) const {
size_t newLength = length + other.length;
char* newData = new char[newLength + 1];
std::strcpy(newData, data); // 复制第一个字符串
std::strcat(newData, other.data); // 拼接第二个字符串
MyString newString(newData); // 构造新字符串对象
delete[] newData; // 释放临时的内存
return newString;
}
// 获取字符串长度
size_t size() const {
return length;
}
// 获取字符串数据
const char* c_str() const {
return data;
}
// 重载 [] 操作符,访问字符
char& operator[](size_t index) {
if (index >= length) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
const char& operator[](size_t index) const {
if (index >= length) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
// 输出流重载,用于打印字符串
friend std::ostream& operator<<(std::ostream& os, const MyString& str) {
os << str.data;
return os;
}
};
int main() {
MyString str1("Hello");
MyString str2(" World!");
MyString str3 = str1 + str2; // 拼接字符串
std::cout << "Concatenated string: " << str3 << std::endl; // 输出 Hello World!
str1[0] = 'h'; // 修改字符
std::cout << "Modified str1: " << str1 << std::endl; // 输出 hello
return 0;
}
3. 代码解释
(1) 构造函数与析构函数
- 默认构造函数:构造一个空字符串(即
data
指向nullptr
)。 - 带参数的构造函数:根据传入的 C 风格字符串初始化
MyString
对象。 - 拷贝构造函数:深拷贝,确保两个对象拥有独立的字符串内存。
- 移动构造函数:将另一个对象的资源“转移”给当前对象,避免不必要的内存分配和释放。
- 析构函数:释放动态分配的内存。
(2) 赋值运算符重载
- 拷贝赋值:确保在赋值时进行深拷贝,并处理自我赋值的情况。
- 移动赋值:通过移动语义实现高效的资源转移,避免重复分配内存。
(3) +
运算符重载
实现字符串拼接,将两个字符串连接成一个新字符串,并返回新的 MyString
对象。
(4) []
运算符重载
允许通过索引访问或修改字符串中的单个字符,同时处理索引越界的情况。
(5) size()
和 c_str()
方法
size()
:返回字符串的长度。c_str()
:返回 C 风格字符串(即char*
),方便与 C 库函数交互。
(6) 输出流运算符重载
重载 <<
运算符,使得 MyString
对象可以通过标准输出流 std::cout
输出。
4. 总结
这个自定义的 MyString
类提供了基本的字符串操作功能,包括动态内存管理、字符串拼接、字符访问、以及支持移动语义。它与 C++ STL 中的 std::string
类有类似的功能,但实际应用中 std::string
已经过高度优化,所以在实际项目中我们一般会优先选择标准库中的 std::string
。
二 参考C++ STL的vector类模板构造一个自定义的顺序表类模板。
要基于C++ STL 的 vector
类模板构造一个自定义的顺序表类模板,可以使用模板类,并实现常见的顺序表操作,例如添加元素、删除元素、访问元素、清空等。为了使这个类更具通用性和实用性,我们将使用模板来支持不同类型的数据,并且模仿 vector
类的一些常用接口。
顺序表类模板的基本设计
设计一个 SequenceList
类模板来实现顺序表功能。它将具备以下特性:
- 内部使用动态分配的数组来存储元素。
- 提供增删、插入、访问等基本操作。
- 提供容量管理(例如扩容和缩容)。
- 提供访问操作符和边界检查。
#include <iostream>
#include <stdexcept>
template <typename T>
class SequenceList {
private:
T* data; // 动态数组,存储顺序表的元素
size_t size; // 当前存储的元素个数
size_t capacity; // 当前容量(分配的内存大小)
// 辅助函数:扩容
void expandCapacity() {
// 新的容量是原来的两倍
size_t newCapacity = capacity == 0 ? 1 : capacity * 2;
T* newData = new T[newCapacity];
// 将旧数据拷贝到新的数组
for (size_t i = 0; i < size; ++i) {
newData[i] = data[i];
}
// 删除旧数组
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
// 构造函数
SequenceList(size_t initialCapacity = 10)
: size(0), capacity(initialCapacity) {
data = new T[capacity];
}
// 析构函数
~SequenceList() {
delete[] data;
}
// 返回当前元素个数
size_t getSize() const {
return size;
}
// 返回当前容量
size_t getCapacity() const {
return capacity;
}
// 判断顺序表是否为空
bool isEmpty() const {
return size == 0;
}
// 添加元素到顺序表的末尾
void pushBack(const T& value) {
if (size == capacity) {
expandCapacity();
}
data[size++] = value;
}
// 删除顺序表末尾的元素
void popBack() {
if (isEmpty()) {
throw std::out_of_range("SequenceList is empty");
}
--size;
}
// 插入元素到指定位置
void insert(size_t index, const T& value) {
if (index > size) {
throw std::out_of_range("Index out of range");
}
if (size == capacity) {
expandCapacity();
}
// 将元素向后移动
for (size_t i = size; i > index; --i) {
data[i] = data[i - 1];
}
data[index] = value;
++size;
}
// 删除指定位置的元素
void removeAt(size_t index) {
if (index >= size) {
throw std::out_of_range("Index out of range");
}
// 将后面的元素向前移动
for (size_t i = index; i < size - 1; ++i) {
data[i] = data[i + 1];
}
--size;
}
// 获取指定位置的元素(带边界检查)
T& at(size_t index) {
if (index >= size) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
// 操作符[],不带边界检查
T& operator[](size_t index) {
return data[index];
}
// 清空顺序表
void clear() {
size = 0;
}
// 打印顺序表元素
void print() const {
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};
int main() {
SequenceList<int> list; // 创建一个存储int类型的顺序表
// 添加元素
list.pushBack(10);
list.pushBack(20);
list.pushBack(30);
std::cout << "顺序表元素: ";
list.print();
// 插入元素
list.insert(1, 15);
std::cout << "在索引1处插入15后: ";
list.print();
// 删除元素
list.removeAt(2);
std::cout << "删除索引2处的元素后: ";
list.print();
// 访问元素
std::cout << "索引1处的元素: " << list.at(1) << std::endl;
return 0;
}
代码详解
-
构造函数与析构函数:
SequenceList(size_t initialCapacity = 10)
:初始化时为顺序表分配一定的容量(默认10),并将元素个数size
初始化为0。- 析构函数
~SequenceList()
:释放动态分配的内存。
-
扩容函数
expandCapacity()
:- 当顺序表的容量不足时,将当前容量扩大一倍,并将原有数据复制到新分配的内存中。
-
顺序表的常用操作:
pushBack(const T& value)
:向顺序表末尾添加元素。如果容量不足,自动扩容。popBack()
:删除顺序表末尾的元素,并抛出异常以处理空表。insert(size_t index, const T& value)
:在指定位置插入元素,并将后续元素向后移动。removeAt(size_t index)
:删除指定位置的元素,并将后续元素向前移动。at(size_t index)
:访问指定位置的元素,带边界检查。operator[]
:提供不带边界检查的元素访问操作符。clear()
:清空顺序表。
三 参考C++ STL的stack类模板构造一个自定义的栈类模板。
要参考 C++ STL 的 stack
类模板构造一个自定义的栈类模板,我们可以使用模板类,并通过一个底层的顺序表(例如动态数组或链表)来实现栈的基本操作。栈是一种后进先出(LIFO, Last In First Out)的数据结构,支持以下主要操作:
push()
:将元素压入栈顶。pop()
:将栈顶元素弹出。top()
:获取栈顶元素,但不弹出。empty()
:判断栈是否为空。size()
:获取栈中元素的数量。
自定义栈类模板的基本设计
- 使用模板使得该栈可以存储任意类型的数据。
- 栈的操作应支持基本的 LIFO 逻辑。
- 为了实现内部数据的动态存储,可以使用我们之前实现的
SequenceList
类,也可以直接使用动态数组来实现。
#include <iostream>
#include <stdexcept>
template <typename T>
class Stack {
private:
T* data; // 动态数组,存储栈的元素
size_t size; // 当前栈中的元素个数
size_t capacity; // 当前栈的容量(分配的内存大小)
// 辅助函数:扩容
void expandCapacity() {
size_t newCapacity = capacity == 0 ? 1 : capacity * 2;
T* newData = new T[newCapacity];
// 将旧数据拷贝到新数组
for (size_t i = 0; i < size; ++i) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
// 构造函数
Stack(size_t initialCapacity = 10)
: size(0), capacity(initialCapacity) {
data = new T[capacity];
}
// 析构函数
~Stack() {
delete[] data;
}
// 返回栈中的元素个数
size_t getSize() const {
return size;
}
// 判断栈是否为空
bool isEmpty() const {
return size == 0;
}
// 将元素压入栈顶
void push(const T& value) {
if (size == capacity) {
expandCapacity();
}
data[size++] = value;
}
// 将栈顶元素弹出
void pop() {
if (isEmpty()) {
throw std::out_of_range("Stack is empty");
}
--size;
}
// 获取栈顶元素,不弹出
T& top() {
if (isEmpty()) {
throw std::out_of_range("Stack is empty");
}
return data[size - 1];
}
// 清空栈
void clear() {
size = 0;
}
// 打印栈元素(从栈顶到栈底)
void print() const {
for (size_t i = size; i > 0; --i) {
std::cout << data[i - 1] << " ";
}
std::cout << std::endl;
}
};
// 测试栈类模板
int main() {
Stack<int> stack;
// 压入元素
stack.push(10);
stack.push(20);
stack.push(30);
std::cout << "栈中元素: ";
stack.print();
// 弹出栈顶元素
stack.pop();
std::cout << "弹出栈顶元素后: ";
stack.print();
// 获取栈顶元素
std::cout << "当前栈顶元素: " << stack.top() << std::endl;
// 清空栈
stack.clear();
std::cout << "清空栈后,栈是否为空: " << (stack.isEmpty() ? "是" : "否") << std::endl;
return 0;
}
代码详解
-
构造函数与析构函数:
Stack(size_t initialCapacity = 10)
:初始化时为栈分配一定的容量(默认10),并将栈的大小size
初始化为 0。- 析构函数
~Stack()
:释放动态分配的内存。
-
辅助函数
expandCapacity()
:- 当栈的容量不足时,将当前容量扩大一倍,并将原有数据复制到新的数组中。
-
栈的基本操作:
push(const T& value)
:向栈顶添加元素。如果容量不足,自动扩容。pop()
:将栈顶元素弹出,并抛出异常处理空栈情况。top()
:返回栈顶元素,但不弹出,并抛出异常处理空栈情况。isEmpty()
:判断栈是否为空。getSize()
:返回当前栈中的元素个数。clear()
:清空栈中的所有元素。
四 参考C++ STL的queue类模板构造一个自定义的队列类模板
要参考 C++ STL 的 queue
类模板构造一个自定义的队列类模板,我们可以使用模板类并实现一个基本的队列操作。队列是一种先进先出(FIFO, First In First Out)的数据结构,主要操作包括:
enqueue()
:将元素入队。dequeue()
:将队列前端元素出队。front()
:获取队列前端元素,但不出队。back()
:获取队列后端元素,但不出队。empty()
:判断队列是否为空。size()
:获取队列中元素的数量。
自定义队列类模板的基本设计
- 使用模板类来支持不同类型的数据。
- 内部实现可以使用动态数组或链表。为了简化,我们可以使用循环数组(环形缓冲区)作为底层数据结构。
- 提供基本的队列操作
#include <iostream>
#include <stdexcept>
template <typename T>
class Queue {
private:
T* data; // 动态数组,存储队列的元素
size_t head; // 队列前端的索引
size_t tail; // 队列后端的索引
size_t size; // 当前队列中的元素个数
size_t capacity; // 当前队列的容量(分配的内存大小)
// 辅助函数:扩容
void expandCapacity() {
size_t newCapacity = capacity == 0 ? 1 : capacity * 2;
T* newData = new T[newCapacity];
// 复制旧数据到新数组
for (size_t i = 0; i < size; ++i) {
newData[i] = data[(head + i) % capacity];
}
delete[] data;
data = newData;
head = 0;
tail = size;
capacity = newCapacity;
}
public:
// 构造函数
Queue(size_t initialCapacity = 10)
: head(0), tail(0), size(0), capacity(initialCapacity) {
data = new T[capacity];
}
// 析构函数
~Queue() {
delete[] data;
}
// 返回队列中的元素个数
size_t getSize() const {
return size;
}
// 判断队列是否为空
bool isEmpty() const {
return size == 0;
}
// 将元素入队
void enqueue(const T& value) {
if (size == capacity) {
expandCapacity();
}
data[tail] = value;
tail = (tail + 1) % capacity;
++size;
}
// 将队列前端元素出队
void dequeue() {
if (isEmpty()) {
throw std::out_of_range("Queue is empty");
}
head = (head + 1) % capacity;
--size;
}
// 获取队列前端元素,不出队
T& front() {
if (isEmpty()) {
throw std::out_of_range("Queue is empty");
}
return data[head];
}
// 获取队列后端元素,不出队
T& back() {
if (isEmpty()) {
throw std::out_of_range("Queue is empty");
}
return data[(tail - 1 + capacity) % capacity];
}
// 清空队列
void clear() {
head = 0;
tail = 0;
size = 0;
}
// 打印队列元素(从前端到后端)
void print() const {
for (size_t i = 0; i < size; ++i) {
std::cout << data[(head + i) % capacity] << " ";
}
std::cout << std::endl;
}
};
// 测试队列类模板
int main() {
Queue<int> queue;
// 入队
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
std::cout << "队列中元素: ";
queue.print();
// 出队
queue.dequeue();
std::cout << "出队后: ";
queue.print();
// 获取队列前端和后端元素
std::cout << "队列前端元素: " << queue.front() << std::endl;
std::cout << "队列后端元素: " << queue.back() << std::endl;
// 清空队列
queue.clear();
std::cout << "清空队列后,队列是否为空: " << (queue.isEmpty() ? "是" : "否") << std::endl;
return 0;
}
代码详解
-
构造函数与析构函数:
Queue(size_t initialCapacity = 10)
:初始化时为队列分配一定的容量(默认10),并将队列的头尾索引head
和tail
以及元素个数size
初始化为0。- 析构函数
~Queue()
:释放动态分配的内存。
-
辅助函数
expandCapacity()
:- 当队列的容量不足时,将容量扩大一倍,并将原有数据复制到新的数组中。此时头索引
head
重置为0,尾索引tail
更新为新的元素数量。
- 当队列的容量不足时,将容量扩大一倍,并将原有数据复制到新的数组中。此时头索引
-
队列的基本操作:
enqueue(const T& value)
:将元素入队。如果容量不足,自动扩容。入队时,尾索引tail
循环移动。dequeue()
:将队列前端元素出队,头索引head
循环移动,并抛出异常处理空队列情况。front()
:返回队列前端元素,但不出队,抛出异常处理空队列情况。back()
:返回队列后端元素,但不出队,抛出异常处理空队列情况。isEmpty()
:判断队列是否为空。getSize()
:返回当前队列中的元素个数。clear()
:清空队列中的所有元素,重置头尾索引和大小。
-
测试代码:
- 在
main()
函数中对队列进行增删查操作,并输出操作后的结果。
- 在
队列中元素: 10 20 30
出队后: 20 30
队列前端元素: 20
队列后端元素: 30
清空队列后,队列是否为空: 是
Matrix
类模板中添加矩阵运算功能,包括矩阵相加、相减、相乘,以及矩阵与标量的加、减、乘、除,矩阵转置和元素反号,可以大大增强类的功能性。以下是这些运算的详细实现。
#pragma once
#include <vector>
#include <iostream>
#include <initializer_list>
#include <stdexcept>
using namespace std;
template<class T>
class Matrix {
private:
size_t r = 0, c = 0; // 行数和列数
vector<T> buf; // 用一维数组保存矩阵各元素
// 辅助函数:检查维度
void checkDimensions(const Matrix<T>& other) const {
if (r != other.r || c != other.c) {
throw std::invalid_argument("Matrix dimensions must agree");
}
}
// 辅助函数:检查乘法维度
void checkMultiplicationDimensions(const Matrix<T>& other) const {
if (c != other.r) {
throw std::invalid_argument("Matrix dimensions must agree for multiplication");
}
}
public:
Matrix() {} // 默认初始化
// 由行数和列数初始化
Matrix(size_t row, size_t col) : r(row), c(col) {
buf.resize(r * c);
}
// 由行数和列数及指定值初始化
Matrix(size_t row, size_t col, const T& v) : r(row), c(col) {
buf.assign(r * c, v);
}
// 使用形如{{1, 2}, {3, 4}}的初始值列表初始化
Matrix(const initializer_list<initializer_list<T>>& m) : r(m.size()), c(m.begin()->size()) {
auto F = m.begin()->begin(), L = F + r * c;
buf.assign(F, L);
}
// 元素访问
auto operator()(size_t i, size_t j) -> decltype(buf[0]) {
return buf[i * c + j];
}
auto operator()(size_t i, size_t j) const -> decltype(buf[0]) {
return buf[i * c + j];
}
// 输出矩阵的所有元素
friend ostream& operator<<(ostream& out, const Matrix<T>& m) {
for (size_t i = 0; i < m.r; ++i) {
for (size_t j = 0; j < m.c; ++j) {
out << m(i, j) << ' ';
}
out << endl;
}
return out;
}
// 矩阵相加
Matrix<T> operator+(const Matrix<T>& other) const {
checkDimensions(other);
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = buf[i] + other.buf[i];
}
return result;
}
// 矩阵相减
Matrix<T> operator-(const Matrix<T>& other) const {
checkDimensions(other);
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = buf[i] - other.buf[i];
}
return result;
}
// 矩阵相乘
Matrix<T> operator*(const Matrix<T>& other) const {
checkMultiplicationDimensions(other);
Matrix<T> result(r, other.c, T{0});
for (size_t i = 0; i < r; ++i) {
for (size_t j = 0; j < other.c; ++j) {
for (size_t k = 0; k < c; ++k) {
result(i, j) += (*this)(i, k) * other(k, j);
}
}
}
return result;
}
// 矩阵与数相加
Matrix<T> operator+(const T& value) const {
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = buf[i] + value;
}
return result;
}
// 矩阵与数相减
Matrix<T> operator-(const T& value) const {
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = buf[i] - value;
}
return result;
}
// 矩阵与数相乘
Matrix<T> operator*(const T& value) const {
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = buf[i] * value;
}
return result;
}
// 矩阵与数相除
Matrix<T> operator/(const T& value) const {
if (value == T{0}) {
throw std::invalid_argument("Division by zero");
}
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = buf[i] / value;
}
return result;
}
// 矩阵转置
Matrix<T> transpose() const {
Matrix<T> result(c, r);
for (size_t i = 0; i < r; ++i) {
for (size_t j = 0; j < c; ++j) {
result(j, i) = (*this)(i, j);
}
}
return result;
}
// 元素反号
Matrix<T> operator-() const {
Matrix<T> result(r, c);
for (size_t i = 0; i < r * c; ++i) {
result.buf[i] = -buf[i];
}
return result;
}
// 清空矩阵
void clear() {
buf.clear();
r = 0;
c = 0;
}
// 获取行数
size_t rows() const {
return r;
}
// 获取列数
size_t cols() const {
return c;
}
};
代码详解
-
矩阵相加 (
operator+
):- 先检查两个矩阵的维度是否一致。
- 遍历所有元素,计算对应位置元素的和,并存储在结果矩阵中。
-
矩阵相减 (
operator-
):- 检查维度一致性。
- 遍历所有元素,计算对应位置元素的差,并存储在结果矩阵中。
-
矩阵相乘 (
operator*
):- 检查第一个矩阵的列数是否等于第二个矩阵的行数。
- 计算每个元素的乘积。使用三重循环进行矩阵乘法。
-
矩阵与数相加 (
operator+
):- 遍历所有元素,每个元素加上标量值,存储在结果矩阵中。
-
矩阵与数相减 (
operator-
):- 遍历所有元素,每个元素减去标量值,存储在结果矩阵中。
-
矩阵与数相乘 (
operator*
):- 遍历所有元素,每个元素乘以标量值,存储在结果矩阵中。
-
矩阵与数相除 (
operator/
):- 遍历所有元素,每个元素除以标量值。确保标量值不为零。
-
矩阵转置 (
transpose
):- 创建一个新的矩阵,其行数和列数交换。将原矩阵的元素位置进行交换。
-
元素反号 (
operator-
):- 遍历所有元素,对每个元素取反,存储在结果矩阵中。
-
清空矩阵 (
clear
):- 清空矩阵的数据并重置维度。
使用示例
int main() {
Matrix<int> m1 = { {1, 2}, {3, 4} };
Matrix<int> m2 = { {5, 6}, {7, 8} };
cout << "Matrix m1:" << endl << m1;
cout << "Matrix m2:" << endl << m2;
cout << "m1 + m2:" << endl << m1 + m2;
cout << "m1 - m2:" << endl << m1 - m2;
cout << "m1 * m2:" << endl << m1 * m2;
cout << "m1 + 10:" << endl << m1 + 10;
cout << "m1 - 10:" << endl << m1 - 10;
cout << "m1 * 10:" << endl << m1 * 10;
cout << "m1 / 10:" << endl <<