数据结构与算法是计算机科学的核心概念之一,它们在编程和软件开发过程中发挥着至关重要的作用。数据结构指的是存储和组织数据的方式,而算法则是解决特定问题所需的步骤和方法。数据结构与算法的有效性和效率对软件性能有很大影响,因此,对这些基础知识有深入了解和掌握对程序员而言是非常重要的。
数组(Array)是一种基本的数据结构,它的概念与作用在计算机科学领域具有广泛应用。数组是一种线性数据结构,可以存储一系列固定大小的相同类型元素。数组的每个元素可以通过索引(下标)进行访问,从而使得查找操作非常快速。数组是编程中最常用的数据结构之一,因为它可以解决许多实际问题。
在现代C++编程中,数组有着广泛的应用场景,包括但不限于以下几个方面:
数组常用于表示数学上的向量和矩阵,广泛应用于各种数学和科学计算,例如线性代数、统计学、信号处理等。
在计算机图形学中,数组用于表示像素数据、纹理和颜色。此外,数组也用于存储顶点和索引数据,以表示3D模型的几何信息。
数组在游戏开发中被用于存储游戏状态、角色属性、地图数据等信息。此外,数组还常用于表示多维空间中的物体分布,从而提高空间查询的效率。
编译器和解释器使用数组存储符号表、词法分析和语法分析的中间结果。此外,数组在生成和执行目标代码时也起到关键作用。
数组在数据库管理系统(DBMS)和文件系统中用于表示和管理存储空间。例如,使用数组实现索引结构,可以提高数据访问速度和存储效率。
总之,在现代C++编程中,数组作为一种基本数据结构,有着广泛的应用场景和重要价值。深入学习和掌握数组的概念与应用,将有助于程序员编写更高效、健壮的软件系统。
数组是一种线性数据结构,它可以存储一组相同类型的元素。在C++编程中,数组的使用方法丰富多样,以下是一些基本概念。
在C++中,可以通过以下语法定义一维数组:
type array_name[array_size];
其中,type
表示数组元素的数据类型,如int
、float
、double
等;array_name
是数组的名称;array_size
是数组的大小,表示数组中元素的个数。
定义数组时,可以选择初始化数组元素。初始化方法有多种,如下所示:
// 方式1:初始化数组时指定每个元素的值
int numbers[] = {10, 20, 30, 40, 50};
// 方式2:在定义数组时指定元素值
int scores[5] = {100, 90, 80, 70, 60};
// 方式3:部分初始化,未初始化的元素将自动设为0
int ages[5] = {18, 20, 22};
二维数组可以看作是一个由一维数组组成的表格,即一个矩阵。二维数组的定义与初始化如下:
type array_name[row_size][column_size] =
{
{element1, element2, ...},
{element3, element4, ...},
...
};
例如:
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
除了二维数组外,C++还支持多维数组,如三维数组、四维数组等。多维数组的定义与初始化与二维数组类似,只需根据数组的维度进行扩展。
数组和指针在C++中密切相关。数组名可以作为指针常量,指向数组中第一个元素的地址。例如:
int numbers[] = {10, 20, 30, 40, 50}; int *ptr = numbers;
在这个例子中,ptr
指针指向数组numbers
的首元素。通过指针,可以访问数组中的各个元素,例如:
// 使用指针访问数组元素
cout << "第一个元素:" << *ptr << endl;
cout << "第二个元素:" << *(ptr + 1) << endl;
需要注意的是,数组名作为指针常量,它本身的值是不能被修改的。此外,当指针用于访问数组元素时,要确保指针不会越界,以防止访问非法内存。
在C++编程中,数组是一种常用的数据结构,通过数组可以存储和操作大量相同类型的数据。以下是一些关于数组访问与操作的基本概念。
在C++中,可以通过下标(索引)访问数组中的元素。数组的下标从0开始,最大下标为数组大小减1。访问数组元素的语法如下:
array_name[index];
例如:
int numbers[] = {10, 20, 30, 40, 50};
cout << "第一个元素:" << numbers[0] << endl;
cout << "第三个元素:" << numbers[2] << endl;
在处理数组时,通常需要遍历数组中的所有元素。遍历数组有多种方法,包括使用循环和迭代器。
使用循环遍历数组:
使用for循环或while循环可以遍历数组中的所有元素。例如:
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
// 使用for循环遍历数组
for (int i = 0; i < size; i++) {
cout << "元素 " << i << ":" << numbers[i] << endl;
}
// 使用while循环遍历数组
int i = 0;
while (i < size) {
cout << "元素 " << i << ":" << numbers[i] << endl;
i++;
}
使用迭代器遍历数组:
C++11引入了基于范围的for循环(range-based for loop),可以更简洁地遍历数组。例如:
int numbers[] = {10, 20, 30, 40, 50};
// 使用基于范围的for循环遍历数组
for (int number : numbers) {
cout << "元素:" << number << endl;
}
1.3 数组边界问题与安全访问
在访问数组元素时,需要注意数组边界问题。如果访问越界,可能导致未定义行为,甚至引发程序崩溃。为确保安全访问,可以采用以下方法:
检查下标是否越界:
在访问数组元素之前,检查下标是否在有效范围内。例如:
int index = 5;
int size = sizeof(numbers) / sizeof(numbers[0]);
if (index >= 0 && index < size) {
cout << "元素 " << index << ":" << numbers[index] << endl;
} else {
cout << "下标越界" << endl;
}
使用C++标准库中的std::array
或std::vector
:
C++标准库中的std::array
和std::vector
提供了一些安全检查和辅助功能,可以帮助程序员更安全、更方便地处理数组。以下是这两种容器的使用方法。
使用std::array
:
std::array
是一个固定大小的容器,使用方法与原生数组类似,但提供了一些辅助功能,如获取大小、安全访问等。需要包含头文件
。
#include
#include
int main() {
std::array<int, 5> numbers = {10, 20, 30, 40, 50};
// 使用at()函数访问元素,如果下标越界,将抛出std::out_of_range异常
try {
std::cout << "元素 0:" << numbers.at(0) << std::endl;
std::cout << "元素 5:" << numbers.at(5) << std::endl;
} catch (const std::out_of_range &e) {
std::cout << "下标越界:" << e.what() << std::endl;
}
return 0;
}
使用std::vector
:
std::vector
是一个动态大小的容器,提供了很多方便的功能,如动态调整大小、安全访问等。需要包含头文件
。
#include
#include
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
// 使用at()函数访问元素,如果下标越界,将抛出std::out_of_range异常
try {
std::cout << "元素 0:" << numbers.at(0) << std::endl;
std::cout << "元素 5:" << numbers.at(5) << std::endl;
} catch (const std::out_of_range &e) {
std::cout << "下标越界:" << e.what() << std::endl;
}
return 0;
}
通过检查下标边界或使用C++标准库提供的容器,可以确保在访问数组时不会发生越界问题,从而提高程序的安全性和稳定性。
C++11、C++14和C++17为数组操作带来了一些新特性,使得编写和处理数组更加简便和安全。以下是关于数组与这些新特性之间的关系的一些说明。
C++11引入了列表初始化(也称为统一初始化),它为数组的初始化提供了一种更加简洁的语法。统一初始化可以使用花括号({})初始化数组,例如:
int numbers[] = {10, 20, 30, 40, 50}; // 列表初始化
// 在C++11中,可以使用{}代替=进行初始化
int scores[]{100, 90, 80, 70, 60};
// 统一初始化可以避免窄化转换错误
float heights[]{1.72f, 1.83f, 1.65f}; // 正确
float heights2[]{1.72, 1.83, 1.65}; // 错误:存在窄化转换
在C++11中,标准库引入了std::array
容器,它可以用来替代传统的C风格数组。std::array
具有固定大小,提供了诸如获取大小、安全访问等方便功能。以下是使用std::array
的一个示例:
#include
#include
int main() {
std::array<int, 5> numbers = {10, 20, 30, 40, 50};
// 使用基于范围的for循环遍历数组
for (int number : numbers) {
std::cout << "元素:" << number << std::endl;
}
return 0;
}
C++17引入了if constexpr
特性,它可以在编译时根据条件选择执行不同的代码。if constexpr
对于元编程和处理数组等静态数据结构非常有用。以下是使用if constexpr
的一个示例:
#include
template <typename T, std::size_t N>
constexpr std::size_t getArraySize(const T (&)[N]) {
return N;
}
template <typename T>
void printArray(const T &arr) {
if constexpr (std::is_same_v<T, std::array<int, 3>>) {
std::cout << "数组大小为3" << std::endl;
} else {
std::cout << "数组大小不为3" << std::endl;
}
}
int main() {
int numbers[]{1, 2, 3};
std::array<int, 3> numbersArray = {4, 5, 6};
std::array<int, 5> otherArray = {7, 8, 9, 10, 11};
printArray(numbers); // 输出:数组大小为3
printArray(numbersArray); // 输出:数组大小为3
printArray(otherArray); // 输出: 数组大小不为3
return 0;
}
在这个示例中,printArray
函数使用if constexpr
根据传入的数组类型进行不同的处理。对于大小为3的数组(无论是C风格数组还是std::array
),输出"数组大小为3",否则输出"数组大小不为3"。
这些C++11/14/17新特性为数组操作带来了更好的安全性、简洁性和灵活性,使得处理数组更加高效和方便。熟练掌握这些新特性将有助于提高C++编程能力。
在C++中,动态数组是在堆上分配内存的数组。动态数组可以在运行时调整大小,为内存管理提供了灵活性。以下是关于C++动态数组与内存管理的一些概念。
使用new
和delete
操作符可以在堆上分配和释放动态数组。例如:
int n = 10;
int* numbers = new int[n]; // 分配大小为10的动态数组
// 使用动态数组
for (int i = 0; i < n; i++) {
numbers[i] = i * 10;
}
// 释放动态数组
delete[] numbers;
在使用动态数组时,需要注意内存泄漏问题。如果忘记释放分配的内存,可能导致内存泄漏,从而影响程序性能。为避免内存泄漏,可以采用以下方法:
使用delete[]
释放动态数组:
在不再需要动态数组时,使用delete[]
操作符及时释放内存。例如:
delete[] numbers;
使用RAII(资源获取即初始化):
使用RAII技术可以确保资源(如动态数组)在离开作用域时自动释放。例如,将动态数组封装在一个类中:
class IntArray {
public:
IntArray(int size) : size_(size), data_(new int[size]) {}
~IntArray() { delete[] data_; }
int& operator[](int index) { return data_[index]; }
private:
int size_;
int* data_;
};
C++11引入了智能指针,如std::unique_ptr
和std::shared_ptr
,它们可以自动管理动态数组的生命周期。使用智能指针可以简化内存管理,避免内存泄漏。以下是使用智能指针管理动态数组的示例:
std::unique_ptr
管理动态数组:#include
int main() {
int n = 10;
std::unique_ptr<int[]> numbers(new int[n]);
// 使用动态数组
for (int i = 0; i < n; i++) {
numbers[i] = i * 10;
}
// 不需要手动释放内存,unique_ptr会在离开作用域时自动释放
return 0;
}
std::shared_ptr
管理动态数组:#include
int main() {
int n = 10;
std::shared_ptr<int> numbers(new int[n], std::default_delete<int[]>());
// 使用动态数组
for (int i = 0; i < n; i++) {
numbers.get()[i] = i * 10;
}
// 不需要手动释放内存,shared_ptr会在引用计数为零时自动释放
return 0;
}
使用智能指针管理动态数组可以自动处理内存分配和释放,减少内存泄漏的风险。
然而,对于大多数场景,使用std::vector
作为动态数组是更好的选择,因为它是一个功能更丰富、内存管理更友好的容器。例如:
#include
int main() {
int n = 10;
std::vector<int> numbers(n);
// 使用动态数组
for (int i = 0; i < n; i++) {
numbers[i] = i * 10;
}
// 不需要手动释放内存,vector会自动处理
return 0;
总之,在C++中使用动态数组时,需要关注内存管理。采用适当的技术,如智能指针和RAII,可以有效地避免内存泄漏问题。此外,使用标准库提供的容器,如std::vector
,也是一种推荐的实践。
在C++编程中,数组和容器是两种常见的数据结构。容器是C++标准库提供的一种模板类,用于管理相同类型的对象集合。本节将介绍数组与C++标准容器std::vector
和std::array
的关系以及如何根据场景选择合适的数据结构。
std::vector
和std::array
是C++标准库中两种常用的容器,分别用于表示动态数组和静态数组。
std::vector
:是一种动态数组,可以在运行时调整大小。它提供了很多方便的功能,如动态调整大小、访问边界检查等。std::array
:是一种静态数组,具有固定大小。它在初始化时需要指定大小,不能在运行时调整。std::array
具有与原生数组类似的性能,同时提供了一些便利的成员函数,如访问边界检查、获取大小等。std::array
:与原生数组类似,std::array
在内存中也是连续存储的。与原生数组相比,std::array
提供了更好的安全性和功能,但性能几乎没有损失。std::vector
:std::vector
作为动态数组,需要在堆上分配内存。在内存分配和释放方面,它的性能可能略低于原生数组和std::array
。然而,std::vector
在许多情况下提供了足够好的性能,并提供了很多方便的功能。std::array
。它与原生数组具有相似的性能,同时提供了一些便利的成员函数。std::vector
。它是一个功能丰富的动态数组,可以在运行时调整大小,适用于大多数场景。总之,根据数组大小是否在编译时已知以及性能和功能需求,可以在原生数组、std::array
和std::vector
之间进行选择。实际编程中,通常推荐使用std::array
和std::vector
,因为它们具有更好的安全性和便捷功能。在性能至关重要的场景下,原生数组可以作为一种选择,但要注意避免边界溢出和内存泄漏等问题。
在C++中,高级数组应用和优化包括排序算法、使用数组实现查找表与哈希表,以及数组与缓存友好编程等方面。
排序是数组应用的基本操作之一。以下是几种常用的排序算法:
为了提高程序性能,需要关注缓存友好性。数组作为连续内存存储的数据结构,有助于提高缓存的利用率。以下是一些缓存友好编程的建议:
alignas
关键字进行数据结构对齐。总之,在C++中,数组应用的高级技巧和优化可以提高程序性能和可维护性。例如,使用高效的排序算法、实现高效的查找表和哈希表,以及关注缓存友好性等方面的技巧,都是有效提高程序性能和可维护性的方法。
#include
#include
#include
#include
#include
// 排序策略接口
class SortingStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
};
// BloomFilter 类
class BloomFilter : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
// 在这里实现布隆过滤器排序算法
}
};
// ExternalSort 类
class ExternalSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
// 在这里实现外部排序算法
}
};
// Timsort 类
class Timsort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
// 在这里实现 Timsort 算法
}
};
// Introsort 类
class Introsort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
// 在这里实现混合排序算法
}
};
// SortingContext 类
class SortingContext {
private:
SortingStrategy* strategy_;
public:
SortingContext(SortingStrategy* strategy) : strategy_(strategy) {}
void set_strategy(SortingStrategy* strategy) {
strategy_ = strategy;
}
void sort_data(std::vector<int>& data) {
strategy_->sort(data);
}
};
int main() {
std::vector<int> data = {5, 2, 8, 1, 6, 4, 9, 7, 3};
SortingContext context(new Timsort());
context.sort_data(data);
// 更换排序策略为 Introsort
context.set_strategy(new Introsort());
context.sort_data(data);
return 0;
}
#include
#include
#include
#include
const size_t kBitsetSize = 10000; // 位数组大小
const size_t kNumHashes = 3; // 哈希函数数量
class BloomFilter : public SortingStrategy {
private:
std::bitset<kBitsetSize> bitset_;
std::vector<std::function<size_t(int)>> hash_functions_;
size_t hash(int x, size_t i) {
return (std::hash<int>{}(x) + i * std::hash<int>{}(~x)) % kBitsetSize;
}
void init_hash_functions() {
for (size_t i = 0; i < kNumHashes; i++) {
hash_functions_.push_back([this, i](int x) { return hash(x, i); });
}
}
public:
BloomFilter() {
init_hash_functions();
}
void add(int x) {
for (const auto& hash_function : hash_functions_) {
bitset_.set(hash_function(x));
}
}
bool contains(int x) {
for (const auto& hash_function : hash_functions_) {
if (!bitset_.test(hash_function(x))) {
return false;
}
}
return true;
}
void sort(std::vector<int>& data) override {
// 在这里实现一个简单的计数排序算法,结合布隆过滤器
int min_value = *std::min_element(data.begin(), data.end());
int max_value = *std::max_element(data.begin(), data.end());
std::vector<int> count(max_value - min_value + 1, 0);
for (const int x : data) {
if (!contains(x)) {
add(x);
count[x - min_value]++;
}
}
size_t index = 0;
for (int i = min_value; i <= max_value; i++) {
while (count[i - min_value]-- > 0) {
data[index++] = i;
}
}
}
};
#include
#include
#include
#include
#include
#include
class ExternalSort : public SortingStrategy {
public:
const std::string temp_file_prefix = "temp_sorted_block_";
void sort(std::vector<int>& data) override {
const size_t memory_limit = 1000; // 假设内存限制为1000个整数
size_t num_blocks = (data.size() + memory_limit - 1) / memory_limit;
// 1. 分块排序
for (size_t i = 0; i < num_blocks; i++) {
size_t left = i * memory_limit;
size_t right = std::min(left + memory_limit, data.size());
std::sort(data.begin() + left, data.begin() + right);
// 将排序后的块写入临时文件
std::ofstream out(temp_file_prefix + std::to_string(i));
for (size_t j = left; j < right; j++) {
out << data[j] << " ";
}
out.close();
}
// 2. 归并排序已排序的块
std::priority_queue<std::pair<int, size_t>,
std::vector<std::pair<int, size_t>>,
std::greater<std::pair<int, size_t>>>
pq;
std::vector<std::ifstream> input_files(num_blocks);
for (size_t i = 0; i < num_blocks; i++) {
input_files[i].open(temp_file_prefix + std::to_string(i));
int value;
if (input_files[i] >> value) {
pq.push({value, i});
}
}
size_t index = 0;
while (!pq.empty()) {
auto [value, block_index] = pq.top();
pq.pop();
data[index++] = value;
int next_value;
if (input_files[block_index] >> next_value) {
pq.push({next_value, block_index});
}
}
// 清理临时文件
for (size_t i = 0; i < num_blocks; i++) {
input_files[i].close();
std::remove((temp_file_prefix + std::to_string(i)).c_str());
}
}
};
#include
#include
class Timsort : public SortingStrategy {
public:
void insertion_sort(std::vector<int>& data, size_t left, size_t right) {
for (size_t i = left + 1; i < right; i++) {
int key = data[i];
size_t j = i;
while (j > left && data[j - 1] > key) {
data[j] = data[j - 1];
j--;
}
data[j] = key;
}
}
void merge(std::vector<int>& data, size_t left, size_t mid, size_t right) {
std::vector<int> temp(data.begin() + left, data.begin() + right);
size_t i = 0, j = mid - left, k = left;
while (i < mid - left && j < right - left) {
if (temp[i] <= temp[j]) {
data[k++] = temp[i++];
} else {
data[k++] = temp[j++];
}
}
while (i < mid - left) {
data[k++] = temp[i++];
}
while (j < right - left) {
data[k++] = temp[j++];
}
}
void timsort(std::vector<int>& data, size_t left, size_t right) {
const size_t run_length = 32;
for (size_t i = left; i < right; i += run_length) {
insertion_sort(data, i, std::min(i + run_length, right));
}
for (size_t sz = run_length; sz < right - left; sz *= 2) {
for (size_t start = left; start < right - sz; start += sz * 2) {
merge(data, start, start + sz, std::min(start + sz * 2, right));
}
}
}
void sort(std::vector<int>& data) override {
timsort(data, 0, data.size());
}
};
#include
#include
#include
#include
class Introsort : public SortingStrategy {
public:
void insertion_sort(std::vector<int>& data, size_t left, size_t right) {
for (size_t i = left + 1; i < right; i++) {
int key = data[i];
size_t j = i;
while (j > left && data[j - 1] > key) {
data[j] = data[j - 1];
j--;
}
data[j] = key;
}
}
size_t partition(std::vector<int>& data, size_t left, size_t right) {
int pivot = data[left];
size_t i = left + 1;
size_t j = right - 1;
while (true) {
while (i <= j && data[i] < pivot) i++;
while (i <= j && data[j] > pivot) j--;
if (i <= j) {
std::swap(data[i], data[j]);
i++;
j--;
} else {
break;
}
}
std::swap(data[left], data[j]);
return j;
}
void introsort(std::vector<int>& data, size_t left, size_t right, size_t depth_limit) {
if (right - left <= 1) {
return;
} else if (depth_limit == 0) {
insertion_sort(data, left, right);
} else {
size_t pivot = partition(data, left, right);
introsort(data, left, pivot, depth_limit - 1);
introsort(data, pivot + 1, right, depth_limit - 1);
}
}
void sort(std::vector<int>& data) override {
size_t depth_limit = 2 * log2(data.size());
introsort(data, 0, data.size(), depth_limit);
}
};
我们将结合享元模式、工厂模式和观察者模式来实现一个基于 C++ 数组容器的缓冲区管理器。
首先,我们定义一个抽象的缓冲区类 Buffer,并实现一个具体的缓冲区类 ArrayBuffer,该类派生自 Buffer。
#include
#include
class Buffer {
public:
virtual ~Buffer() = default;
};
class ArrayBuffer : public Buffer {
public:
ArrayBuffer(size_t size) : data(size) {}
private:
std::vector<int> data;
};
接下来,我们定义观察者接口 Observer 和具体的观察者类 BufferObserver。
class Observer {
public:
virtual ~Observer() = default;
virtual void onBufferAllocated(Buffer *buffer) = 0;
virtual void onBufferReleased(Buffer *buffer) = 0;
};
class BufferObserver : public Observer {
public:
void onBufferAllocated(Buffer *buffer) override {
std::cout << "Buffer allocated: " << buffer << std::endl;
}
void onBufferReleased(Buffer *buffer) override {
std::cout << "Buffer released: " << buffer << std::endl;
}
};
然后我们定义一个 BufferFactory 类,它将负责创建 ArrayBuffer 对象。
class BufferFactory {
public:
std::shared_ptr<Buffer> createBuffer(size_t size) {
return std::make_shared<ArrayBuffer>(size);
}
};
接下来,我们实现一个基于享元模式的缓冲区管理器 BufferManager,它将缓冲区对象保存在一个内部容器中,并将观察者附加到缓冲区管理器以接收分配和释放事件的通知。
#include
class BufferManager {
public:
BufferManager() : factory(std::make_unique<BufferFactory>()) {}
std::shared_ptr<Buffer> allocate(size_t size) {
auto it = pool.find(size);
if (it != pool.end()) {
auto buffer = it->second;
pool.erase(it);
notifyBufferAllocated(buffer.get());
return buffer;
}
auto buffer = factory->createBuffer(size);
notifyBufferAllocated(buffer.get());
return buffer;
}
void release(const std::shared_ptr<Buffer> &buffer) {
notifyBufferReleased(buffer.get());
pool[buffer.use_count()] = buffer;
}
void addObserver(const std::shared_ptr<Observer> &observer) {
observers.push_back(observer);
}
private:
void notifyBufferAllocated(Buffer *buffer) {
for (auto &observer : observers) {
observer->onBufferAllocated(buffer);
}
}
void notifyBufferReleased(Buffer *buffer) {
for (auto &observer : observers) {
observer->onBufferReleased(buffer);
}
}
std::unique_ptr<BufferFactory> factory;
std::unordered_map<size_t, std::shared_ptr<Buffer>> pool;
std::vector<std::shared_ptr<Observer>> observers;
};
现在你可以使用 BufferManager 类创建和释放缓冲区,同时通知观察者关于缓冲区分配和释放事件。以下是如何使用这个 BufferManager 类的示例:
#include
int main() {
// 创建缓冲区管理器
BufferManager bufferManager;
// 创建一个观察者
auto observer = std::make_shared<BufferObserver>();
// 将观察者附加到缓冲区管理器
bufferManager.addObserver(observer);
// 分配缓冲区
auto buffer1 = bufferManager.allocate(10);
auto buffer2 = bufferManager.allocate(20);
// 释放缓冲区
bufferManager.release(buffer1);
bufferManager.release(buffer2);
return 0;
}
首先,定义字幕数据结构:
#include
#include
struct Subtitle {
double start_time;
double end_time;
std::string text;
};
然后,在主程序中定义一个 std::vector
来存储字幕数据:
std::vector
extern "C" {
#include
#include
}
#include
#include
int main() {
const std::string input_file = "input.mp4";
// 注册所有编解码器和复用器
av_register_all();
// 打开输入文件
AVFormatContext *format_ctx = nullptr;
if (avformat_open_input(&format_ctx, input_file.c_str(), nullptr, nullptr) < 0) {
std::cerr << "Could not open input file." << std::endl;
return 1;
}
// 查找输入文件的字幕流
int subtitle_stream_index = -1;
for (unsigned int i = 0; i < format_ctx->nb_streams; i++) {
if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
subtitle_stream_index = i;
break;
}
}
if (subtitle_stream_index == -1) {
std::cerr << "No subtitle stream found." << std::endl;
avformat_close_input(&format_ctx);
return 1;
}
// 打开字幕解码器
AVCodec *codec = avcodec_find_decoder(format_ctx->streams[subtitle_stream_index]->codecpar->codec_id);
if (!codec) {
std::cerr << "Subtitle decoder not found." << std::endl;
avformat_close_input(&format_ctx);
return 1;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
std::cerr << "Could not allocate subtitle codec context." << std::endl;
avformat_close_input(&format_ctx);
return 1;
}
if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
std::cerr << "Could not open subtitle codec." << std::endl;
avcodec_free_context(&codec_ctx);
avformat_close_input(&format_ctx);
return 1;
}
// 读取并处理字幕数据
AVPacket packet;
while (av_read_frame(format_ctx, &packet) >= 0) {
if (packet.stream_index == subtitle_stream_index) {
AVSubtitle subtitle;
int got_subtitle = 0;
int len = avcodec_decode_subtitle2(codec_ctx, &subtitle, &got_subtitle, &packet);
if (len < 0) {
std::cerr << "Error while decoding subtitle." << std::endl;
av_packet_unref(&packet);
continue;
}
#if 1
if (got_subtitle) {
for (unsigned int i = 0; i < subtitle.num_rects; i++) {
if (subtitle.rects[i]->type == SUBTITLE_TEXT) {
double start_time = static_cast<double>(subtitle.start_display_time) / 1000;
double end_time = static_cast<double>(subtitle.end_display_time) / 1000;
std::string text(subtitle.rects[i]->text);
subtitles.push_back({start_time, end_time, text});
}
}
// 释放字幕数据
avsubtitle_free(&subtitle);
}
#else
if (got_subtitle) {
// 这里处理字幕数据
// 示例:打印字幕矩形的文本
for (unsigned int i = 0; i < subtitle.num_rects; i++) {
if (subtitle.rects[i]->type == SUBTITLE_TEXT) {
std::cout << "Subtitle text: " << subtitle.rects[i]->text << std::endl;
}
}
// 释放字幕数据
avsubtitle_free(&subtitle);
}
#endif
}
av_packet_unref(&packet);
}
// 释放资源
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
avformat_close_input(&format_ctx);
return 0;
}
C++数组是一种内建的数据结构,用于存储相同类型的元素。在 C++ 中,数组的优势和局限性如下:
优势:
局限性:
std::vector
或 std::list
),数组在功能方面有限。例如,数组不提供自动内存管理、迭代器、动态调整大小等功能。综上所述,C++数组在某些情况下可能是一个合适的选择,特别是在对内存连续性和随机访问性能有要求的情况下。然而,它们也存在一些局限性,尤其是在需要动态大小、元素插入/删除和类型混合的场景中。在这些情况下,使用 C++ 标准库中提供的更高级的数据结构可能更为合适。
在 C++ 编程中,数组作为一种基本的数据结构在许多领域都有应用。以下是一些数组在 C++ 编程中的应用领域:
尽管在许多领域中都可以使用数组,但它们也存在一定的局限性,如固定大小、插入/删除操作的低效等。在需要动态调整大小、高级功能或非连续内存的场景中,可以考虑使用 C++ 标准库中提供的其他数据结构(如 std::vector
、std::list
或 std::deque
)。总之,在选择适合的数据结构时,需要权衡数组的优点和局限性。
要提高 C++ 数组编程技巧和应用,可以遵循以下建议:
std::vector
、std::array
、std::list
和 std::deque
。这些数据结构提供了更强大的功能,并在某些场景中比原始数组更加适用。std::vector
或 std::array
,因为它们提供了 at()
成员函数,可进行安全的元素访问。
头文件,它提供了许多用于操作数组和其他序列容器的实用算法,如 std::sort
、std::find
、std::count
等。本文主要探讨了 C++ 数组在现代编程中的应用与技巧,以下是一个简要总结:
通过本文的学习,读者可以更好地理解 C++ 数组在编程中的应用与技巧,从而在实际编程中更加得心应手。