C++零基础实践教程 文件输入输出
模块八:文件输入输出 (数据持久化)
在之前的模块中,我们学习了如何使用程序处理数据。然而,当程序结束运行时,这些数据通常会丢失。数据持久化 (Data Persistence) 指的是将程序中的数据存储到非易失性存储介质(如硬盘文件)中,以便在程序下次运行时能够重新加载和使用这些数据。本模块将介绍如何在 C++ 中使用文件输入输出流来实现数据持久化。
1. 文件流: #include <fstream>
要进行文件输入输出操作,我们需要包含头文件 <fstream>
。这个头文件提供了几个用于文件操作的类,最常用的包括:
std::ofstream
: 用于将数据写入文件(Output File Stream)。std::ifstream
: 用于从文件读取数据(Input File Stream)。std::fstream
: 用于同时进行文件的读写操作(File Stream)。
在使用这些类之前,请确保在你的代码中包含了 <fstream>
头文件。
C++
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
2. std::ofstream
: 写入文件
std::ofstream
类用于创建文件并向其写入数据。要使用它,你需要创建一个 std::ofstream
类的对象,并将要写入的文件名作为参数传递给构造函数。
创建并打开文件进行写入:
C++
#include <fstream>
#include <iostream>int main() {std::ofstream outputFile("my_data.txt"); // 创建一个名为 my_data.txt 的文件,并打开用于写入if (outputFile.is_open()) {outputFile << "这是要写入文件的第一行。" << std::endl;outputFile << "这是第二行数据。" << std::endl;int number = 123;outputFile << "数字: " << number << std::endl;outputFile.close(); // 写入完成后关闭文件std::cout << "数据已成功写入文件。" << std::endl;} else {std::cerr << "无法打开文件进行写入。" << std::endl;}return 0;
}
在这个例子中:
std::ofstream outputFile("my_data.txt");
创建了一个名为outputFile
的std::ofstream
对象,并尝试打开名为 "my_data.txt" 的文件。如果该文件不存在,则会创建一个新文件。如果文件已存在,其内容将被清空(默认行为)。outputFile.is_open()
方法用于检查文件是否成功打开。这是一个很重要的步骤,因为文件可能由于各种原因(例如,权限问题、磁盘空间不足等)而无法打开。- 我们使用插入运算符 (
<<
) 将数据写入文件流outputFile
,这与使用std::cout
向控制台输出数据非常相似。std::endl
用于在文件中插入一个换行符。 outputFile.close();
用于显式关闭文件。虽然当outputFile
对象销毁时文件通常会自动关闭,但显式关闭是一个好的习惯,可以确保所有数据都被写入磁盘。
3. std::ifstream
: 读取文件
std::ifstream
类用于从文件中读取数据。与写入类似,你需要创建一个 std::ifstream
类的对象,并将要读取的文件名传递给构造函数。
打开文件并读取内容:
C++
#include <fstream>
#include <iostream>
#include <string>int main() {std::ifstream inputFile("my_data.txt"); // 打开名为 my_data.txt 的文件用于读取if (inputFile.is_open()) {std::string line;while (std::getline(inputFile, line)) { // 逐行读取文件内容std::cout << "读取到一行: " << line << std::endl;}inputFile.close(); // 读取完成后关闭文件} else {std::cerr << "无法打开文件进行读取。" << std::endl;}return 0;
}
在这个例子中:
std::ifstream inputFile("my_data.txt");
创建了一个名为inputFile
的std::ifstream
对象,并尝试打开名为 "my_data.txt" 的文件进行读取。如果文件不存在,打开操作可能会失败。- 我们再次使用
inputFile.is_open()
来检查文件是否成功打开。 std::getline(inputFile, line)
函数用于从inputFile
中读取一行数据,直到遇到换行符或文件结束符,并将读取到的内容存储到line
字符串中。这个函数会返回一个表示读取操作是否成功的布尔值,因此我们可以将其用在while
循环的条件中,以便逐行读取整个文件。inputFile.close();
用于显式关闭文件。
你也可以使用提取运算符 (>>
) 从文件中读取数据,类似于使用 std::cin
从控制台读取数据。但是,使用 >>
时,数据会以空格、制表符或换行符作为分隔符进行读取。
使用 >>
读取文件内容:
C++
#include <fstream>
#include <iostream>
#include <string>int main() {std::ifstream inputFile("my_data.txt");if (inputFile.is_open()) {std::string word;int number;while (inputFile >> word >> number) { // 尝试读取一个字符串和一个整数std::cout << "读取到的单词: " << word << ", 数字: " << number << std::endl;}inputFile.close();} else {std::cerr << "无法打开文件进行读取。" << std::endl;}return 0;
}
注意: 如果文件 "my_data.txt" 的内容与代码中期望的格式不符(例如,连续的字符串或数字),则读取操作可能会失败或产生意想不到的结果。
4. 打开文件、检查文件是否成功打开
正如我们在上面的例子中看到的,打开文件是进行文件操作的第一步。除了在创建文件流对象时通过构造函数指定文件名外,你还可以先创建一个文件流对象,然后使用 open()
方法打开文件。
使用 open()
方法打开文件:
C++
#include <fstream>
#include <iostream>int main() {std::ofstream outputFile;outputFile.open("another_data.txt");if (outputFile.is_open()) {outputFile << "使用 open() 方法写入数据。" << std::endl;outputFile.close();} else {std::cerr << "无法打开文件 'another_data.txt'。" << std::endl;}return 0;
}
检查文件是否成功打开:
检查文件是否成功打开至关重要。你可以使用以下方法进行检查:
is_open()
方法: 返回一个布尔值,表示文件是否成功打开。- 将文件流对象作为布尔值进行检查: 文件流对象可以隐式转换为布尔值。如果文件成功打开,则转换为
true
,否则转换为false
。
C++
#include <fstream>
#include <iostream>int main() {std::ifstream inputFile("non_existent_file.txt");if (inputFile.is_open()) {std::cout << "文件已成功打开。" << std::endl;inputFile.close();} else {std::cerr << "文件打开失败。" << std::endl;}std::ofstream outputFile("my_data.txt");if (outputFile) { // 隐式转换为布尔值std::cout << "输出文件已成功打开。" << std::endl;outputFile.close();} else {std::cerr << "输出文件打开失败。" << std::endl;}return 0;
}
5. 读写文件内容 (类似 std::cin
/std::cout
)
我们已经看到,可以使用插入运算符 (<<
) 将数据写入 std::ofstream
对象,使用提取运算符 (>>
) 从 std::ifstream
对象读取数据,这与使用 std::cout
和 std::cin
非常相似。
写入不同类型的数据:
C++
std::ofstream outputFile("mixed_data.txt");
if (outputFile.is_open()) {outputFile << "姓名: " << "张三" << std::endl;outputFile << "年龄: " << 30 << std::endl;outputFile << "身高: " << 1.75 << std::endl;outputFile.close();
}
读取不同类型的数据:
C++
std::ifstream inputFile("mixed_data.txt");
if (inputFile.is_open()) {std::string nameLabel, name;std::string ageLabel;int age;std::string heightLabel;double height;inputFile >> nameLabel >> name;inputFile >> ageLabel >> age;inputFile >> heightLabel >> height;std::cout << nameLabel << name << std::endl;std::cout << ageLabel << age << std::endl;std::cout << heightLabel << height << std::endl;inputFile.close();
}
使用 getline()
读取包含空格的行:
如前所述,std::getline()
函数非常适合读取包含空格的整行文本。
C++
std::ofstream outputFile("paragraph.txt");
if (outputFile.is_open()) {outputFile << "这是一个包含多个单词的段落。" << std::endl;outputFile << "第二行也有一些内容。" << std::endl;outputFile.close();
}std::ifstream inputFile("paragraph.txt");
if (inputFile.is_open()) {std::string line;while (std::getline(inputFile, line)) {std::cout << "读取到的行: " << line << std::endl;}inputFile.close();
}
6. 关闭文件 (close()
方法)
虽然当文件流对象超出作用域时,文件通常会被自动关闭,但显式地调用 close()
方法是一个良好的编程习惯。它可以确保所有缓冲的数据都被写入磁盘,并且释放与文件关联的系统资源。
C++
#include <fstream>
#include <iostream>int main() {std::ofstream outputFile("temp_file.txt");if (outputFile.is_open()) {outputFile << "一些临时数据。" << std::endl;outputFile.close(); // 显式关闭文件std::cout << "文件已关闭。" << std::endl;} else {std::cerr << "无法打开文件。" << std::endl;}return 0;
}
7. 实战项目 6: 持久化的学生成绩管理
我们将选择第一个目标:让【学生成绩管理】程序能够将学生数据保存到文件中,并在下次运行时加载。
步骤:
1. 确定文件格式。
我们将使用一个简单的文本文件格式来存储学生数据。每一行代表一个学生的信息,姓名和分数之间用逗号或其他分隔符(例如空格)分隔。
示例文件内容 (student_data.txt
):
张三 95
李四 88
王五 92
2. 修改 Student
类(如果尚未完成)。
确保你的 Student
类包含 getName()
和 getScore()
方法。
3. 实现保存学生数据到文件的函数。
创建一个名为 saveStudentsToFile
的函数,该函数接收一个 std::vector<Student>
对象和一个文件名作为参数。函数将遍历 vector
中的每个 Student
对象,并将学生的姓名和分数写入到文件中。
C++
#include <fstream>
#include <iostream>
#include <vector>
#include <string>// 假设 Student 类的定义如下(在 student.h 或当前文件中):
class Student {
private:std::string name;int score;public:Student(const std::string& studentName, int studentScore) : name(studentName), score(studentScore) {}std::string getName() const { return name; }int getScore() const { return score; }
};void saveStudentsToFile(const std::vector<Student>& students, const std::string& filename) {std::ofstream outputFile(filename);if (outputFile.is_open()) {for (const auto& student : students) {outputFile << student.getName() << " " << student.getScore() << std::endl;}outputFile.close();std::cout << "学生数据已保存到文件 " << filename << std::endl;} else {std::cerr << "无法打开文件 " << filename << " 进行写入。" << std::endl;}
}
4. 实现从文件加载学生数据的函数。
创建一个名为 loadStudentsFromFile
的函数,该函数接收一个文件名作为参数,并返回一个 std::vector<Student>
对象。函数将尝试打开文件,逐行读取学生数据,并创建 Student
对象添加到 vector
中。
C++
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <sstream> // 用于字符串流// 假设 Student 类的定义如上std::vector<Student> loadStudentsFromFile(const std::string& filename) {std::vector<Student> students;std::ifstream inputFile(filename);if (inputFile.is_open()) {std::string line;while (std::getline(inputFile, line)) {std::stringstream ss(line);std::string name;int score;if (ss >> name >> score) {students.emplace_back(name, score);} else {std::cerr << "警告:无法解析文件中的行: " << line << std::endl;}}inputFile.close();std::cout << "学生数据已从文件 " << filename << " 加载。" << std::endl;} else {std::cerr << "无法打开文件 " << filename << " 进行读取。" << std::endl;}return students;
}
5. 修改 main
函数。
在 main
函数的开始部分,调用 loadStudentsFromFile
函数加载已保存的学生数据。在程序结束前(或者在用户选择保存时),调用 saveStudentsToFile
函数将当前的学生数据保存到文件中。
修改后的 main
函数示例(简化版):
C++
#include <iostream>
#include <vector>
#include <string>
#include <numeric>// 假设 Student 类的定义和 saveStudentsToFile, loadStudentsFromFile 函数如上int main() {std::string filename = "student_data.txt";std::vector<Student> students = loadStudentsFromFile(filename);int numStudents;std::cout << "请输入要添加的学生人数 (如果不需要添加,请输入 0): ";std::cin >> numStudents;std::cin.ignore();for (int i = 0; i < numStudents; ++i) {std::cout << "请输入第 " << i + 1 << " 个学生的姓名: ";std::string name;std::getline(std::cin, name);std::cout << "请输入第 " << i + 1 << " 个学生的分数: ";int score;std::cin >> score;std::cin.ignore();students.emplace_back(name, score);}if (!students.empty()) {int totalScore = 0;for (const auto& student : students) {totalScore += student.getScore();std::cout << student.getName() << " 的分数是: " << student.getScore() << std::endl;}double averageScore = static_cast<double>(totalScore) / students.size();std::cout << "平均分是: " << averageScore << std::endl;} else {std::cout << "没有学生数据。" << std::endl;}saveStudentsToFile(students, filename); // 在程序结束前保存数据return 0;
}
现在,每次运行程序时,它会尝试从 "student_data.txt" 文件加载学生数据。你可以在程序运行期间添加新的学生数据,并在程序结束时将所有数据保存回文件。下次运行时,之前的数据将被重新加载。
模块九:进一步学习 (指引方向) (更完善版本)
目录
- 标准模板库 (STL) 深入 1.1.
std::map
(进阶) 1.1.1. 迭代器的更多用法 1.1.2. 自定义比较函数 1.1.3.std::multimap
1.2.std::set
(进阶) 1.2.1. 迭代器的更多用法 1.2.2. 自定义比较函数 1.2.3.std::multiset
和std::unordered_set
1.3. 算法 (<algorithm>
) (进阶) 1.3.1. 更多常用算法 1.3.2. 迭代器和算法的配合 1.3.3. Lambda 表达式和函数对象 - 更深入的 OOP 2.1. 继承 (进阶) 2.1.1. 构造函数和析构函数的调用顺序 2.1.2. 虚继承和菱形继承问题 2.1.3. final 关键字 2.2. 多态 (进阶) 2.2.1. 静态多态(编译时多态) 2.2.2. 抽象类和纯虚函数 2.2.3. 接口 (Interface) 的概念
- 异常处理 3.1.
try
,catch
,throw
(进阶) 3.1.1. 标准异常类 3.1.2. 异常安全 3.1.3. 自定义异常类 3.1.4.noexcept
规范 - 模板 4.1. 函数模板 (进阶) 4.1.1. 多个模板参数 4.1.2. 模板特化 4.2. 类模板 (进阶) 4.2.1. 多个模板参数 4.2.2. 成员函数模板 4.2.3. 类模板的特化 4.2.4. 模板元编程 (简介)
- 智能指针 5.1.
std::unique_ptr
(进阶) 5.1.1. 自定义删除器 (Custom Deleters) 5.1.2.unique_ptr
和数组 5.2.std::shared_ptr
(进阶) 5.2.1. 循环引用和std::weak_ptr
5.2.2.std::shared_ptr
的线程安全性 - 调试技巧 6.1. 如何使用调试器 (更详细) 6.1.1. 常用调试器命令和操作 (以 GDB 和 Visual Studio 为例) 6.1.2. 高级调试技巧 6.1.3. 使用断言 (
assert
) - 构建系统 7.1. CMake (简介) (更详细) 7.1.1. 更复杂的
CMakeLists.txt
示例 7.1.2. 外部依赖管理 7.1.3. 构建类型 (Debug, Release) 7.1.4. 其他构建系统简介
1. 标准模板库 (STL) 深入
1.1. std::map
(进阶)
1.1.1. 迭代器的更多用法
除了基本的 begin()
和 end()
之外,std::map
还提供了其他迭代器,例如 rbegin()
和 rend()
用于反向迭代。
C++
#include <iostream>
#include <map>
#include <string>int main() {std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}};std::cout << "Ages in reverse order:" << std::endl;for (auto it = ages.rbegin(); it != ages.rend(); ++it) {std::cout << it->first << ": " << it->second << std::endl;}return 0;
}
1.1.2. 自定义比较函数
std::map
默认使用 <
运算符对键进行排序。您可以提供自定义的比较函数或函数对象来改变排序方式。
C++
#include <iostream>
#include <map>
#include <string>struct CaseInsensitiveCompare {bool operator()(const std::string& a, const std::string& b) const {return std::lexicographical_compare(a.begin(), a.end(),b.begin(), b.end(),[](char c1, char c2) {return std::tolower(c1) < std::tolower(c2);});}
};int main() {std::map<std::string, int, CaseInsensitiveCompare> data;data["Apple"] = 1;data["banana"] = 2;data["Cherry"] = 3;std::cout << "Data (case-insensitive sorted):" << std::endl;for (const auto& pair : data) {std::cout << pair.first << ": " << pair.second << std::endl;}return 0;
}
1.1.3. std::multimap
std::multimap
允许存储具有相同键的多个键值对。
C++
#include <iostream>
#include <map>
#include <string>int main() {std::multimap<std::string, int> scores;scores.insert({"Alice", 90});scores.insert({"Bob", 85});scores.insert({"Alice", 95}); // 允许重复的键std::cout << "Scores:" << std::endl;for (const auto& pair : scores) {std::cout << pair.first << ": " << pair.second << std::endl;}auto range = scores.equal_range("Alice");std::cout << "\nAlice's scores:" << std::endl;for (auto it = range.first; it != range.second; ++it) {std::cout << it->second << std::endl;}return 0;
}
1.2. std::set
(进阶)
1.2.1. 迭代器的更多用法
类似于 std::map
,std::set
也支持 rbegin()
和 rend()
进行反向迭代。
1.2.2. 自定义比较函数
您可以为 std::set
提供自定义的比较函数或函数对象来定义元素的排序方式。
C++
#include <iostream>
#include <set>struct ReverseIntCompare {bool operator()(int a, int b) const {return a > b;}
};int main() {std::set<int, ReverseIntCompare> numbers = {5, 2, 8, 1, 9};std::cout << "Numbers in reverse order:" << std::endl;for (int num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
1.2.3. std::multiset
和 std::unordered_set
std::multiset
: 允许存储重复的元素,并保持排序。std::unordered_set
: 存储唯一的元素,但不保证排序。它通常使用哈希表实现,因此在平均情况下查找、插入和删除操作的时间复杂度为 O(1)。
C++
#include <iostream>
#include <set>
#include <unordered_set>int main() {std::multiset<int> multiNumbers = {5, 2, 8, 2, 9};std::cout << "MultiNumbers:" << std::endl;for (int num : multiNumbers) {std::cout << num << " ";}std::cout << std::endl;std::unordered_set<int> unorderedNumbers = {5, 2, 8, 1, 9, 2}; // 重复的 2 只会保留一个std::cout << "UnorderedNumbers (no guaranteed order):" << std::endl;for (int num : unorderedNumbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
1.3. 算法 (<algorithm>
) (进阶)
1.3.1. 更多常用算法
std::count()
: 统计容器中特定值的出现次数。std::remove()
和std::remove_if()
: 从容器中移除特定的值或满足特定条件的元素(注意:这些算法不会改变容器的大小,通常需要与容器的erase()
方法一起使用)。std::replace()
和std::replace_if()
: 将容器中特定的值或满足特定条件的元素替换为新的值。std::accumulate()
: 计算容器中元素的总和(或其他累积操作)。
C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric> // for std::accumulateint main() {std::vector<int> data = {1, 2, 2, 3, 4, 2, 5};int countOfTwo = std::count(data.begin(), data.end(), 2);std::cout << "Count of 2: " << countOfTwo << std::endl;auto it = std::remove(data.begin(), data.end(), 2);data.erase(it, data.end());std::cout << "Data after removing 2s: ";for (int num : data) {std::cout << num << " ";}std::cout << std::endl;std::vector<int> numbers = {1, 2, 3, 4, 5};int sum = std::accumulate(numbers.begin(), numbers.end(), 0);std::cout << "Sum of numbers: " << sum << std::endl;return 0;
}
1.3.2. 迭代器和算法的配合
STL 算法通常通过迭代器来操作容器中的元素。理解不同类型的迭代器(例如,输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器)对于有效地使用算法至关重要。不同的算法对迭代器有不同的要求。
1.3.3. Lambda 表达式和函数对象
Lambda 表达式和函数对象(也称为仿函数)可以作为参数传递给算法,以自定义算法的行为。
C++
#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};// 使用 lambda 表达式对偶数进行平方std::vector<int> squaredEvens;std::for_each(numbers.begin(), numbers.end(),[&](int n) {if (n % 2 == 0) {squaredEvens.push_back(n * n);}});std::cout << "Squared even numbers: ";for (int num : squaredEvens) {std::cout << num << " ";}std::cout << std::endl;// 使用函数对象判断是否为奇数struct IsOdd {bool operator()(int n) const {return n % 2 != 0;}};auto it = std::find_if(numbers.begin(), numbers.end(), IsOdd());if (it != numbers.end()) {std::cout << "First odd number: " << *it << std::endl;}return 0;
}
2. 更深入的 OOP
2.1. 继承 (进阶)
2.1.1. 构造函数和析构函数的调用顺序
在派生类对象创建时,基类的构造函数先于派生类的构造函数被调用。当派生类对象销毁时,派生类的析构函数先于基类的析构函数被调用。这是为了确保基类的初始化在派生类之前完成,而清理工作则相反。
2.1.2. 虚继承和菱形继承问题
当一个类从多个基类继承,而这些基类又继承自同一个更上层的基类时,就会出现菱形继承问题。这会导致派生类中存在多个相同的上层基类子对象。虚继承(使用 virtual
关键字)可以解决这个问题,确保只有一个共享的上层基类子对象。
C++
#include <iostream>class Grandparent {
public:Grandparent() { std::cout << "Grandparent constructor" << std::endl; }virtual ~Grandparent() { std::cout << "Grandparent destructor" << std::endl; }void commonFunction() { std::cout << "Grandparent function" << std::endl; }
};class Parent1 : public virtual Grandparent {
public:Parent1() { std::cout << "Parent1 constructor" << std::endl; }~Parent1() { std::cout << "Parent1 destructor" << std::endl; }
};class Parent2 : public virtual Grandparent {
public:Parent2() { std::cout << "Parent2 constructor" << std::endl; }~Parent2() { std::cout << "Parent2 destructor" << std::endl; }
};class Child : public Parent1, public Parent2 {
public:Child() { std::cout << "Child constructor" << std::endl; }~Child() { std::cout << "Child destructor" << std::endl; }
};int main() {Child c;c.commonFunction();return 0;
}
2.1.3. final
关键字
final
关键字可以用于防止类被继承或防止虚函数被进一步重写。
C++
class Base {
public:virtual void method1() {}virtual void method2() final {} // method2 不能在派生类中被重写
};class Derived : public Base {
public:void method1() override {} // 可以重写// void method2() override {} // 错误:method2 是 final 的
};class FinalClass final { // FinalClass 不能被继承
public:void method() {}
};// class AnotherDerived : public FinalClass {}; // 错误:FinalClass 是 final 的
2.2. 多态 (进阶)
2.2.1. 静态多态(编译时多态)
静态多态主要通过函数重载和模板来实现。编译器在编译时根据函数参数的类型或模板参数来确定要调用的具体函数。
函数重载示例:
C++
#include <iostream>void print(int x) { std::cout << "Printing integer: " << x << std::endl; }
void print(double x) { std::cout << "Printing double: " << x << std::endl; }int main() {print(10); // 调用 print(int)print(3.14); // 调用 print(double)return 0;
}
模板示例 (之前已经展示过): 编译器会为每种实际使用的类型生成特定的函数或类。
2.2.2. 抽象类和纯虚函数
包含至少一个纯虚函数的类被称为抽象类。抽象类不能被实例化。派生类必须重写基类中的所有纯虚函数才能成为非抽象类并被实例化。纯虚函数通过在虚函数声明后添加 = 0
来声明。
C++
#include <iostream>class Shape {
public:virtual void draw() const = 0; // 纯虚函数virtual double area() const = 0; // 纯虚函数virtual ~Shape() {}
};class Circle : public Shape {
private:double radius_;
public:Circle(double r) : radius_(r) {}void draw() const override { std::cout << "Drawing a circle." << std::endl; }double area() const override { return 3.14159 * radius_ * radius_; }
};// Shape shape; // 错误:Shape 是抽象类,不能实例化int main() {Circle circle(5.0);circle.draw();std::cout << "Circle area: " << circle.area() << std::endl;return 0;
}
2.2.3. 接口 (Interface) 的概念
在 C++ 中,没有显式的 interface
关键字(像在 Java 或 C# 中那样)。然而,可以通过只包含纯虚函数的抽象类来模拟接口的行为。接口定义了一组派生类必须实现的方法,从而实现“契约式编程”。
3. 异常处理
3.1. try
, catch
, throw
(进阶)
3.1.1. 标准异常类
<stdexcept>
头文件定义了一系列标准的异常类,可以用于报告不同类型的错误,例如 std::runtime_error
, std::logic_error
, std::out_of_range
等。
C++
#include <iostream>
#include <stdexcept>
#include <vector>int main() {std::vector<int> data = {1, 2, 3};int index = 5;try {if (index >= data.size()) {throw std::out_of_range("Index out of bounds");}std::cout << "Element at index " << index << ": " << data.at(index) << std::endl;} catch (const std::out_of_range& error) {std::cerr << "Error: " << error.what() << std::endl;}return 0;
}
3.1.2. 异常安全
编写异常安全的代码意味着即使在异常被抛出的情况下,程序也能保持其状态的完整性,并且不会发生资源泄漏(例如,内存泄漏)。有不同的异常安全级别:
- 基本保证: 如果异常被抛出,程序不会崩溃,并且所有对象都处于有效的状态(但可能不是原始状态)。
- 强保证: 如果异常被抛出,程序的状态不会发生改变(就像操作从未发生过一样)。这通常通过“先复制再交换”等技术实现。
- 无抛出保证: 操作永远不会抛出异常(通常使用
noexcept
标记)。
3.1.3. 自定义异常类
您可以创建自己的异常类,通常通过继承自 std::exception
或其派生类来提供更具体的错误信息。
C++
#include <iostream>
#include <exception>
#include <string>class MyCustomError : public std::runtime_error {
public:MyCustomError(const std::string& message) : std::runtime_error(message) {}
};void processData(int value) {if (value < 0) {throw MyCustomError("Value cannot be negative: " + std::to_string(value));}std::cout << "Processing data: " << value << std::endl;
}int main() {try {processData(-5);} catch (const MyCustomError& error) {std::cerr << "Custom Error: " << error.what() << std::endl;} catch (const std::runtime_error& error) {std::cerr << "Runtime Error: " << error.what() << std::endl;} catch (...) {std::cerr << "An unknown error occurred." << std::endl;}return 0;
}
3.1.4. noexcept
规范
noexcept
是一个函数规范,用于表明函数是否会抛出异常。如果一个声明为 noexcept
的函数抛出了异常,程序可能会立即终止。使用 noexcept
可以帮助编译器进行优化,并且对于某些操作(例如,移动构造函数和析构函数)非常重要。
C++
#include <iostream>
#include <stdexcept>void mightThrow() {throw std::runtime_error("This function might throw.");
}void doesNotThrow() noexcept {std::cout << "This function does not throw." << std::endl;
}int main() {try {mightThrow();} catch (const std::runtime_error& e) {std::cerr << "Caught exception: " << e.what() << std::endl;}doesNotThrow();// 如果 doesNotThrow 内部抛出异常,程序可能会终止// try {// throw std::runtime_error("Exception from noexcept function");// } catch (...) {// std::cerr << "Caught exception (should not happen): " << std::endl;// }return 0;
}
4. 模板
4.1. 函数模板 (进阶)
4.1.1. 多个模板参数
函数模板可以接受多个类型参数。
C++
#include <iostream>template <typename T1, typename T2>
void printPair(const T1& a, const T2& b) {std::cout << "First: " << a << ", Second: " << b << std::endl;
}int main() {printPair(10, "Hello");printPair(3.14, true);return 0;
}
4.1.2. 模板特化
模板特化允许您为特定的类型提供模板的不同实现。
C++
#include <iostream>
#include <string>template <typename T>
void print(const T& value) {std::cout << "Generic print: " << value << std::endl;
}// 为 int 类型提供特化版本
template <>
void print<int>(const int& value) {std::cout << "Specialized print for int: " << value * 2 << std::endl;
}// 为 std::string 类型提供特化版本
template <>
void print<std::string>(const std::string& value) {std::cout << "Specialized print for string: " << value.length() << std::endl;
}int main() {print(5); // 输出: Specialized print for int: 10print(3.14); // 输出: Generic print: 3.14print(std::string("World")); // 输出: Specialized print for string: 5return 0;
}
4.2. 类模板 (进阶)
4.2.1. 多个模板参数
类模板也可以接受多个类型参数。
C++
#include <iostream>template <typename T1, typename T2>
class Pair {
public:Pair(const T1& first, const T2& second) : first_(first), second_(second) {}void print() const {std::cout << "First: " << first_ << ", Second: " << second_ << std::endl;}
private:T1 first_;T2 second_;
};int main() {Pair<int, std::string> myPair(100, "Example");myPair.print();return 0;
}
4.2.2. 成员函数模板
类模板的成员函数也可以是模板。
C++
#include <iostream>template <typename T>
class MyArray {
private:T* data_;int size_;
public:MyArray(int size) : size_(size), data_(new T[size]()) {}~MyArray() { delete[] data_; }template <typename U>void fill(const U& value) {for (int i = 0; i < size_; ++i) {data_[i] = static_cast<T>(value);}}void print() const {for (int i = 0; i < size_; ++i) {std::cout << data_[i] << " ";}std::cout << std::endl;}
};int main() {MyArray<double> doubleArray(5);doubleArray.fill(3); // 使用 int 类型填充 double 数组doubleArray.print();return 0;
}
4.2.3. 类模板的特化
类似于函数模板,类模板也可以进行特化,为特定的类型提供不同的实现。可以特化整个类,也可以只特化类的某些成员函数。
C++
#include <iostream>
#include <string>template <typename T>
class Printer {
public:void print(const T& value) {std::cout << "Generic Printer: " << value << std::endl;}
};// 特化 Printer<std::string>
template <>
class Printer<std::string> {
public:void print(const std::string& value) {std::cout << "String Printer: Length = " << value.length() << ", Value = " << value << std::endl;}
};int main() {Printer<int> intPrinter;intPrinter.print(123); // 输出: Generic Printer: 123Printer<std::string> stringPrinter;stringPrinter.print("Hello"); // 输出: String Printer: Length = 5, Value = Helloreturn 0;
}
4.2.4. 模板元编程 (简介)
模板元编程是一种在编译时使用模板生成代码的技术。它允许在编译阶段执行一些计算和逻辑,可以用于提高程序的性能和灵活性。模板元编程通常涉及复杂的模板技巧和类型操作。
5. 智能指针
5.1. std::unique_ptr
(进阶)
5.1.1. 自定义删除器 (Custom Deleters)
std::unique_ptr
允许您指定一个自定义的删除器,当 unique_ptr
被销毁时,将调用该删除器而不是默认的 delete
运算符。这对于管理不是通过 new
分配的资源(例如,文件句柄、自定义的内存分配器分配的内存)非常有用。
C++
#include <iostream>
#include <memory>
#include <fstream>// 自定义删除器,用于关闭文件
struct FileDeleter {void operator()(std::ofstream* file) const {if (file && file->is_open()) {file->close();std::cout << "File closed." << std::endl;}delete file;}
};int main() {std::unique_ptr<std::ofstream, FileDeleter> logFile(new std::ofstream("log.txt"));if (logFile) {*logFile << "This is a log message." << std::endl;}// 当 logFile 离开作用域时,FileDeleter 会被调用,关闭文件。return 0;
}
5.1.2. unique_ptr
和数组
std::unique_ptr
可以用于管理动态分配的数组,需要使用 unique_ptr<T[]>
的形式,并且会自动使用 delete[]
来释放内存。
C++
#include <iostream>
#include <memory>int main() {std::unique_ptr<int[]> intArray(new int[5]);for (int i = 0; i < 5; ++i) {intArray[i] = i * 2;}for (int i = 0; i < 5; ++i) {std::cout << intArray[i] << " ";}std::cout << std::endl;// 当 intArray 离开作用域时,delete[] 会被调用。return 0;
}
5.2. std::shared_ptr
(进阶)
5.2.1. 循环引用和 std::weak_ptr
当两个或多个通过 std::shared_ptr
相互引用的对象形成一个环时,它们的引用计数永远不会降至零,导致内存泄漏。std::weak_ptr
是一种不增加引用计数的智能指针,可以用来打破这种循环引用。weak_ptr
可以指向由 shared_ptr
管理的对象,但它不会阻止该对象的销毁。在使用 weak_ptr
之前,需要先将其转换为 shared_ptr
(可以使用 lock()
方法),如果原始对象已经被销毁,则转换会失败。
C++
#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed" << std::endl; }
};class B {
public:std::weak_ptr<A> a_ptr; // 使用 weak_ptr 打破循环引用~B() { std::cout << "B destroyed" << std::endl; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a;// 如果 b_ptr 在 A 中也是 weak_ptr,或者 a_ptr 在 B 中是 shared_ptr,// 那么当 a 和 b 离开作用域时,A 和 B 的析构函数将不会被调用,导致内存泄漏。return 0;
}
5.2.2. std::shared_ptr
的线程安全性
std::shared_ptr
的控制块(包含引用计数和删除器)是线程安全的,允许多个线程安全地增加和减少引用计数。然而,通过同一个 shared_ptr
访问所管理的对象本身并不是线程安全的,需要额外的同步机制(例如,互斥锁)。
6. 调试技巧
6.1. 如何使用调试器 (更详细)
6.1.1. 常用调试器命令和操作 (以 GDB 和 Visual Studio 为例)
GDB (GNU Debugger):
break <file>:<line>
或b <file>:<line>
: 在指定文件和行号设置断点。run
或r
: 运行程序。next
或n
: 执行下一行代码(不进入函数调用)。step
或s
: 执行下一行代码(如果当前行是函数调用,则进入函数内部)。finish
: 执行完当前函数并返回。continue
或c
: 继续执行程序直到下一个断点或程序结束。print <variable>
或p <variable>
: 打印变量的值。watch <expression>
: 设置监视点,当表达式的值发生变化时暂停程序。backtrace
或bt
: 显示当前的函数调用堆栈。frame <number>
: 切换到指定堆栈帧。info locals
: 显示当前作用域的局部变量。quit
或q
: 退出 GDB。
Visual Studio Debugger:
- 设置断点: 在代码行号旁边的空白区域单击,或选中一行代码后按 F9。
- 启动调试: 按 F5 或选择“调试” -> “开始调试”。
- 单步执行:
- Step Over (F10): 执行当前行,然后跳到下一行。
- Step Into (F11): 如果当前行是函数调用,则进入该函数内部。
- Step Out (Shift + F11): 执行完当前函数并返回。
- Continue (F5): 继续执行程序。
- 查看变量: 在“局部变量”、“监视”窗口中查看变量的值。
- 调用堆栈: 在“调用堆栈”窗口中查看函数调用序列。
- 条件断点: 右键单击断点,选择“条件”,设置断点触发的条件。
- 数据断点: 可以在变量的值发生改变时触发断点(右键单击变量,选择“当值更改时中断”)。
6.1.2. 高级调试技巧
- 使用日志记录: 在关键代码路径中添加日志输出,以便在不使用调试器的情况下也能追踪程序行为。
- 二分查找法调试: 如果您不知道错误发生在哪里,可以尝试在代码中间设置断点,逐步缩小错误范围。
- 单元测试: 编写单元测试可以帮助您在早期发现和修复代码中的错误。
- 代码审查: 让其他开发人员检查您的代码,可以发现您可能忽略的错误。
- 使用静态分析工具: 静态分析工具可以在不运行代码的情况下检测潜在的错误和代码质量问题。
6.1.3. 使用断言 (assert
)
assert
是一个预处理宏,用于在运行时检查条件是否为真。如果条件为假,程序会终止并显示错误消息。断言通常用于在开发阶段检查代码中的假设是否成立。在发布版本中,断言通常会被禁用。
C++
#include <iostream>
#include <cassert>int divide(int a, int b) {assert(b != 0); // 断言:除数不能为零return a / b;
}int main() {int result = divide(10, 2);std::cout << "Result: " << result << std::endl;// divide(5, 0); // 这会触发断言,导致程序终止return 0;
}
7. 构建系统
7.1. CMake (简介) (更详细)
7.1.1. 更复杂的 CMakeLists.txt
示例
假设您的项目包含多个源文件和一个头文件目录。
CMake
cmake_minimum_required(VERSION 3.10)
project(MyAdvancedProject)# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 指定头文件搜索路径
include_directories(include)# 添加可执行文件,源文件包括 main.cpp 和 utils.cpp
add_executable(MyAdvancedProject main.cpp utils.cpp)# 如果需要链接库,可以使用 target_link_libraries
# target_link_libraries(MyAdvancedProject my_library)
7.1.2. 外部依赖管理
CMake 可以帮助管理外部库的依赖。常用的方法包括:
find_package()
: 用于查找系统中已安装的库。WorkspaceContent
模块: 允许在构建过程中下载和构建外部库。- 子模块 (Submodules): 将外部库作为 Git 子模块添加到项目中。
7.1.3. 构建类型 (Debug, Release)
CMake 支持不同的构建类型,通常包括 Debug 和 Release。Debug 构建包含调试信息,而 Release 构建会进行优化以提高性能。您可以在配置 CMake 时指定构建类型:
Bash
cmake -DCMAKE_BUILD_TYPE=Debug ..
# 或者
cmake -DCMAKE_BUILD_TYPE=Release ..
7.1.4. 其他构建系统简介
除了 CMake,还有其他一些流行的构建系统:
- Make: 一个经典的构建工具,使用 Makefile 来描述构建规则。
- Ninja: 一个小型且快速的构建系统,通常由其他构建系统(如 CMake)生成构建文件。
- Meson: 另一个跨平台的构建系统,旨在提供快速且用户友好的构建体验。
- Gradle 和 Maven: 主要用于 Java 项目,但也支持 C/C++ 项目。
相关文章:
C++零基础实践教程 文件输入输出
模块八:文件输入输出 (数据持久化) 在之前的模块中,我们学习了如何使用程序处理数据。然而,当程序结束运行时,这些数据通常会丢失。数据持久化 (Data Persistence) 指的是将程序中的数据存储到非易失性存储介质(如硬盘…...
SpringAI+DeepSeek大模型应用开发——1 AI概述
AI领域常用词汇 LLM(LargeLanguage Model,大语言模型) 能理解和生成自然语言的巨型AI模型,通过海量文本训练。例子:GPT-4、Claude、DeepSeek、文心一言、通义干问。 G(Generative)生成式: 根据上…...
数据中台进化史:从概念萌芽到价值变现的蜕变之路
在数字化转型的浪潮中,数据中台已成为企业驾驭数据、驱动业务创新的关键力量。回顾数据中台的发展历程,犹如一场从混沌到有序、从萌芽到成熟的精彩蜕变,它由湖仓一体、数据治理平台、数据服务平台三大核心要素逐步构建而成,每一个…...
【Java学习笔记】运算符
运算符 运算符的类型 算数运算符 赋值运算符 关系运算符(比较哦啊运算符) 逻辑运算符 三元运算符 位运算符(需要二进制基础) 一、算数运算符 运算符计算范例结果正号77-负号b11; -b-11加法9918-减法10-82*乘法7*856/除法9…...
【python】OpenCV—Tracking(10.6)—People Counting
文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、涉及到的库函数6、参考来自 更多有趣的代码示例,可参考【Programming】 1、功能描述 借助 opencv-python,用 SSD 人形检测模型和质心跟踪方法实现对人群的计数 基于质心的跟踪可以参考 【pyt…...
JavaSE学习(前端初体验)
文章目录 前言一、准备环境二、创建站点(创建一个文件夹)三、将站点部署到编写器中四、VScode实用小设置五、案例展示 前言 首先了解前端三件套:HTML、CSS、JS HTML:超文本标记语言、框架层、描述数据的; CSS…...
智慧城市像一张无形大网,如何紧密连接你我他?
智慧城市作为复杂巨系统,其核心在于通过技术创新构建无缝连接的网络,使物理空间与数字空间深度融合。这张"无形大网"由物联网感知层、城市数据中台、人工智能中枢、数字服务入口和安全信任机制五大支柱编织而成,正在重塑城市运行规…...
Linux常用命令
一、history 用于显示历史命令。 history 10显示最近10条历史命令。!200使用第200行的指令。history -c清空历史记录。 二、pwd 用于显示当前绝对路径。 pwd显示当前绝对路径。 三、ls 用于以行的形式显示当前文件夹下所有内容。 ls -a显示所有内容,包括隐藏文…...
【AI】SpringAI 第二弹:接入 DeepSeek 官方服务
一、接入 DeepSeek 官方服务 通过一个简单的案例演示接入 DeepSeek 实现简单的问答功能 1.添加依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId> </dependency> 2…...
QT的信号槽的直接触发,队列触发,自动触发
在Qt中,信号槽机制是一个非常强大的特性,它用于实现对象之间的通信。除了默认的直接触发方式之外,Qt还提供了队列触发等不同的触发方式。 1. 直接触发(Direct Connection) 直接触发是最常见的连接方式,信…...
typescript html input无法输入解决办法
input里加上这个: onkeydown:(e: KeyboardEvent) > {e.stopPropagation();...
工厂能耗系统智能化解决方案 —— 安科瑞企业能源管控平台
安科瑞顾强 政策背景与“双碳”战略驱动 2025年《政府工作报告》明确提出“单位国内生产总值能耗降低3%左右”的目标,要求通过产业结构升级(如高耗能行业技术革新或转型)、能源结构优化(提高非化石能源占比)及数字化…...
栅格数据处理
一、栅格数据的引入与基本操作 (一)加载栅格数据 在 ArcPy 中,栅格数据可以通过 arcpy.Raster 类来加载。例如,如果你有一个存储在本地路径下的栅格数据文件(如 GeoTIFF 格式),可以这样加载&a…...
C语言文件操作
本文重点: 什么是文件 文件名 文件类型 文件缓冲区 文件指针 文件的打开和关闭 文件的顺序读写 文件的随机读写 文件结束的判定 什么是文件 磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件 程序文件 包括源程序文…...
毛笔书体检测-hog+svm python opencv源码
链接:https://pan.baidu.com/s/1l-bw8zR9psv1HycmMqQBqQ?pwd2ibp 提取码:2ibp --来自百度网盘超级会员V2的分享 1、毛笔字检测运行流程 如果解压文件发现乱码,可以下载Bandizip 解压文件 数据集在百度网盘里面 将文件名字改成images c…...
基于YOLOV11的道路坑洼分析系统
基于YOLOV11的道路坑洼分析系统 【包含内容】 【一】项目提供完整源代码及详细注释 【二】系统设计思路与实现说明 【三】图形化界面与实时检测统计可视化功能 【技术栈】 ①:系统环境:Windows/MacOS/Linux多平台支持,推荐NVIDIA GPU加速 ②…...
【系统搭建】DPDK安装配置与helloworld运行
一,安装相关依赖 1. 安装依赖 sudo apt update && sudo apt install -y \build-essential libnuma-dev meson ninja-build pciutils#安装Python3与PIP3 sudo apt install python3-pip2. 升级 pip 和 setuptools sudo apt install python3-pip python3-de…...
Distortion, Animation Raymarching
这节课的主要目的是对uv进行操作,实现一些动画的效果,实际就是采样的动画 struct texDistort {float2 texScale(float2 uv, float2 scale){float2 texScale (uv - 0.5) * scale 0.5;return texScale;}float2 texRotate(float2 uv, float angle){float…...
架构风格(高软59)
系列文章目录 架构风格 文章目录 系列文章目录前言一、架构风格定义?二、架构风格分类总结 前言 本节讲明架构风格知识点。 一、架构风格定义? 二、架构风格分类 总结 就是高软笔记,大佬请略过!...
免费使用RooCode + Boomerang AI + Gemini 2.5 Pro开发套件
若您正在寻找利用免费AI工具简化应用开发的方法,这份指南将为您揭开惊喜。 我们将详解如何免费整合RooCode、Boomerang AI智能代理与Google Gemini 2.5 Pro API,在Visual Studio Code中实现自动化编程加速。 这套方案能让您在几分钟内从创意跃迁至可运行原型。 套件构成与…...
《MAmmoTH2: Scaling Instructions from the Web》全文翻译
《MAmmoTH2: Scaling Instructions from the Web》 MAmmoTH2:从网络规模化采集指令数据 摘要 指令调优提升了大语言模型(LLM)的推理能力,其中数据质量和规模化是关键因素。大多数指令调优数据来源于人工众包或GPT-4蒸馏。我们提…...
解决Ubuntu终端命令不能补全的问题
使用命令: sudo vi /etc/bash.bashr 把框出的部分取消注释,取消后截图如下,保存退出: 使用命令env -i bash --noprofile --norc, 进行测试,查看tab自动补全是否可以使用。 tab键可正常使用, env -i bash …...
知识图谱与其它知识库的关系
知识图谱与其它知识库的关系 知识图谱与传统知识库:解构数据连接的哲学知识图谱的商业价值:连接带来的革命选择知识图谱还是传统数据库?一个实用指南 知识图谱的出现,正在改变了我们组织和理解信息的方式。 这种技术不仅仅是一种数…...
STM32基础教程——DMA+ADC多通道
目录 前言 编辑 技术实现 连线图 代码实现 技术要点 实验结果 问题记录 前言 DMA(Direct Memory Access)直接存储器存取,用来提供在外设和存储器 之间或者存储器和存储器之间的高速数据传输。无需CPU干预,数据可以通过DMA快速地移动࿰…...
波束形成(BF)从算法仿真到工程源码实现-第十一节-非线性波束形成算法工程化
一、概述 本节我们对非线性波束形成算法进行工程化,运行在respeaker core v2平台上,算法实时率在0.046左右。更多资料和代码可以进入https://t.zsxq.com/qgmoN ,同时欢迎大家提出宝贵的建议,以共同探讨学习。 二、算法实现 2.1 …...
Windows安装Rust版本GDAL
前言 笔者想安装GDAL,这是一个开源的地理数据库, 笔者到处搜索,最后看到这位大佬写的这篇文章,终于成功了。 aliothor/Windows-Install-Rust-Gdal-Tutorial: Windows Install Rust Version Gdal Stepshttps://github.com/aliot…...
OpenCv高阶(六)——图像的透视变换
目录 一、透视变换的定义与作用 二、透视变换的过程 三、OpenCV 中的透视变换函数 1. cv2.getPerspectiveTransform(src, dst) 2. cv2.warpPerspective(src, H, dsize, dstNone, flagscv2.INTER_LINEAR, borderModecv2.BORDER_CONSTANT, borderValue0) 四、文档扫描校正&a…...
常用正则化技术dropout
在深度学习中,Dropout 是一种常用的正则化技术,用于防止神经网络过拟合。它的核心思想是随机丢弃(临时关闭)网络中的部分神经元,迫使模型不依赖单一神经元,从而提升泛化能力。 1. Dropout…...
66.加1
目录 一、问题描述 二、解题思路 三、代码 四、复杂度分析 一、问题描述 给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#…...
Tecnomatix Plant Simulation 2302安装教程
Tecnomatix Plant Simulation 2302安装教程,这个比较简单,只有4步即可完成。 第1步:获取并下载安装包 Follow WX account and reply: 2302, get the installation package link. 下载安装包至电脑本地,打开安装包文件如下图所示…...
Flutter 与原生通信
Flutter 与原生之间的通信主要基于通道机制,包括 MethodChannel、EventChannel 和 BasicMessageChannel。 MethodChannel:用于 Flutter 与原生之间的方法调用,实现双向通信,适合一次性的方法调用并获取返回值,如 Flut…...
关于postman的使用(一)
postman创建被测系统结构 改为被测系统名称 添加一级功能 添加接口测试 请求发起前脚本和请求发起后脚本 请求前运行脚本(需要一个随机的岗位名称): 上述脚本功能是自动生成一个岗位名称并且配置它为postman的变量下面是调用 请求后运行脚本…...
【c语言】深入理解指针1
深入理解指针1 一、数组名的理解二、使用指针访问数组三、一维数组传参本质四、二级指针 一、数组名的理解 数组名就是数组首元素的地址,类型是指针类型,但是存在两个例外: sizeof(arr) : 整个数组在内存中的大小 &arr : 整个数组的地址…...
leetcode14.最长公共前缀
暴力逐个比对最长前缀 class Solution {public String longestCommonPrefix(String[] strs) {String prefix strs[0];for (int i 1; i < strs.length; i) {prefix longestCommonPrefix(prefix, strs[i]);}return prefix;}private String longestCommonPrefix(String st…...
云服务器X86计算和Arm计算架构有什么区别?
阿里云服务器架构X86计算和ARM计算有什么区别?x86架构是最常见的,CPU采用Intel或AMD处理器;ARM架构具有低功耗的特性,CPU采用Ampere Altra / AltraMax或阿里自研倚天710处理器。如何选择?阿里云服务器网aliyunfuwuqi.com建议根据实际使用场景选择,X86架构兼容性更广,适合…...
leetcode0079. 单词搜索-medium
1 题目: 单词搜索 官方标定难度:中 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字…...
ShellScript脚本编程
语法基础 脚本结构 我们先从这个小demo程序来窥探一下我们shell脚本的程序结构 #!/bin/bash# 注释信息echo_str"hello world"test(){echo $echo_str }test echo_str 首先我们可以通过文本编辑器(在这里我们使用linux自带文本编辑神器vim),新建一个文件…...
【leetcode100】整数拆分
1、题目描述 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k > 2 ),并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 36…...
leetcode:2899. 上一个遍历的整数(python3解法)
难度:简单 给你一个整数数组 nums ,其中 nums[i] 要么是一个正整数,要么是 -1 。我们需要为每个 -1 找到相应的正整数,我们称之为最后访问的整数。 为了达到这个目标,定义两个空数组:seen 和 ans。 从数组 …...
Mysql读写分离(2)-中间件mycat和实践方案
系统环境要求 Mysql版本5.5版本以上jdk1.7Mycat1.6 mycat使用Java开发,因为用到了JDK 7的部分功能,所以在使用前请确保安装了JDK 7.0,并设置了正确的Java环境变量(可在命令行窗口输入:“java –version”获知是否安装…...
QT之在多线程中如何优雅的处理资源泄漏
概述 在多线程编程中,资源泄漏是一个常见且需要特别关注的问题。资源泄漏通常指的是程序未能正确释放分配给它的资源(如内存、文件句柄、数据库连接等),这可能导致系统性能下降甚至崩溃。尤其是在多线程环境中,由于多个线程可能同时访问相同的资源,增加了管理这些资源的…...
SPA 收入支出/技师提成自动统计系统——仙盟共创平台——未来之窗
支出 spa服务 使用开始:https://mp.weixin.qq.com/s/Ok3wuSYAPhd-6N8DrK7jwg 收入清晰呈现:自动整合 SPA 门店各类服务项目收入数据,包括面部护理、身体按摩、特色疗程等。通过对接收银系统,实时记录每笔消费金额,按不…...
Linux的应用领域,测试与Linux,Linux的介绍,VirtualBox和Ubuntu的安装,VMware的安装和打开虚拟机CentOS
目录 Linux的应用领域 测试人员在Linux的工作 测试人员需要掌握Linux的程度 Linux的介绍 Linux的介绍 Linux发行版 Unix和Linux的渊源 虚拟机和Linux的安装 VirtualBox和Ubuntu的安装 安装VirtualBox 安装Ubuntu 下载Ubuntu操作系统的镜像文件 创建虚拟机 虚拟机…...
《Not All Tokens Are What You Need for Pretraining》全文翻译
《Not All Tokens Are What You Need for Pretraining》 不是所有的词元都是预训练所需 摘要 先前的语言模型预训练方法通常对所有训练词元均匀地应用下一词预测损失。对此常规做法提出挑战,我们认为“语料库中的并非所有词元对于语言模型训练同等重要”。我们的…...
vscode终端运行windows服务器的conda出错
远程windows服务器可以运行,本地vscode不能。 打开vscode settings.json文件 添加conda所在路径...
使用基数树优化高并发内存池(替代加锁访问的哈希表和红黑树)
前言: 本篇旨在熟悉 基于tcmalloc的高性能并发内存池项目之后,对于最后的优化进行的笔记梳理,项目完整代码 可点击:项目代码 进行查看。 优化思想风暴: 为了方便根据页号查找到对应的span, 这里我们可以使用红黑树或…...
【bash】.bashrc
查看当前路径文件数量 alias file_num"ls -l | grep ^- | wc -l"查看文件大小 alias file_size"du -sh"alias ll alias ll"ls -ltrh"cd的同时执行ll alias cdcdls; function cdls() {builtin cd "$1" && ll }自定义prompt…...
自我生成,自我训练:大模型用合成数据实现“自我学习”机制实战解析
目录 自我生成,自我训练:大模型用合成数据实现“自我学习”机制实战解析 一、什么是自我学习机制? 二、实现机制:如何用合成数据实现自我训练? ✅ 方式一:Prompt强化生成 → 自我采样再训练 ✅ 方式二…...
Linux的命令格式,运行级别,找回root密码,Linux用户的分类,绝对路径和相对路径,硬链接和软链接,实用按键
目录 Linux的命令格式 运行级别 运行级别说明 切换运行级别 指定默认的运行级别 找回root密码 找回root密码 可能会出现的问题 Linux的用户分类 绝对路径和相对路径 硬链接和软链接 实用按键 Linux的命令格式 Linux的命令格式: command [-options] [par…...
C# JSON
在C#中,你可以使用System.Text.Json或Newtonsoft.Json库来解析JSON字符串。以下是使用这两种库分别解析你提供的JSON字符串的示例。 1. 使用 System.Text.Json System.Text.Json 是 .NET Core 3.0 及以上版本中包含的内置JSON库。以下是如何使用它来解析你的JSON字…...