2026《数据结构》考研复习笔记三(C++高级教程)
C++高级教程
- 一、文件和流
- 二、异常处理
- 三、命名空间
- 四、模板
- 五、信号处理
- 六、多线程
一、文件和流
iostream | 用于标准输入/输出(控制台I/O),处理与终端(键盘输入和屏幕输出)的交互 包含以下全局流对象:
|
---|---|
fstream | 用于文件输入/输出(文件I/O),主要操作磁盘文件 包含以下类:
|
1.1打开文件
open()函数是fstream、ifstream和ofstream共同成员
void open(const char *filename,openmode mode);
第一个参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式
模式标志 | 描述 |
---|---|
app | 追加模式,写入追加到文件末尾 |
ate | 文件打开后定位到文件末尾 |
in | 打开文件用于读取 |
out | 打开文件用于写入 |
trunc | 如果文件已经存在,则在文件打开之前将文件清空 |
可以将上述模式结合使用,例如:
- 写入模式打开文件,以防文件存在并清空文件:
ofstream outfile;
outfile.open(“file.dat”,out|trunc); - 打开文件用于读写:
ifstream afile;
aflie.open(“file.dat”,out|in);
1.2关闭文件
close()函数是fstream、ifstream和ofstream共同成员
void close();
1.3写入文件
使用流插入运算符<<向文件写入信息(类似输出信息到屏幕上),但是这里使用的是ofstream或fstream对象,不是cout对象
1.4读取文件
使用流提取运算符>>从文件读取信息(类似使用键盘输入信息),但是这里使用的是ifstream或fstream对象,不是cin对象
读取&写入实例:
#include <fstream>
#include <iostream>
using namespace std;int main() {char data[100];// 以写模式打开文件ofstream outfile;outfile.open("afile.dat");cout << "Writing to the file" << endl;cout << "Enter your name: "; cin.getline(data, 100);// 向文件写入用户输入的数据outfile << data << endl;cout << "Enter your age: "; cin >> data;cin.ignore();// 再次向文件写入用户输入的数据outfile << data << endl;// 关闭打开的文件outfile.close();// 以读模式打开文件ifstream infile; infile.open("afile.dat"); cout << "Reading from the file" << endl; infile >> data; // 在屏幕上写入数据cout << data << endl;// 再次从文件读取数据,并显示它infile >> data; cout << data << endl; // 关闭打开的文件infile.close();return 0;
}
//当上面的代码被编译和执行时,它会产生下列输入和输出:
/*
$./a.out
Writing to the file
Enter your name: Zara
Enter your age: 9
Reading from the file
Zara
9
*/
1.5文件位置指针
istream和ostream都提供了用于重新定位文件位置指针的成员函数,包括istream的seekg(seek get)和ostream的seekp(seek put)
第一个参数是一个长整型,第二个参数可以用于指定查找方向。有beg(默认,从流的开头定位),也可以是cur(从流的当前位置开始定位),还可以是end(从流的末尾开始定位)
关于get文件位置指针实例:
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
二、异常处理
异常是程序在执行期间产生的问题。C++异常处理提供一种转移程序控制权的方式,涉及到三个关键字:try、catch、throw
- throw:当问题出现时,程序会抛出一个异常
- catch:在想要处理问题的地方,通过异常处理程序捕获异常
- try:try块中的代码标识将被激活的特定异常,后面通常跟着一个或多个catch块
2.1抛出异常
throw语句可以在代码块的任何地方抛出异常。throw操作数可以是任意表达式,表达式的结果类型决定了抛出异常的类型
double division(int a,int b){if(b==0){throw "Division by zero condition!";}return (a/b);
}
2.2捕获异常
catch块跟在try块后面,用于捕获异常。可以指定想要捕获的异常,也可以使用省略号…表示catch块能够处理try块抛出的任何类型的异常
try{
//保护代码
}catch(ExceptionName e){
//处理ExceptionName异常的代码
}catch(…){
//能够处理任何异常代码
}
注:建议实践下述代码,并且自主探索几个问题:
- try块throw异常后还会执行throw块后面的代码吗
- try块throw异常后还会执行try块后面的代码吗
- catch到异常后是否直接终止程序运行(还会执行try-catch块后的代码吗
实例:
#include<iostream>
using namespace std;double division(int a, int b) {if (b == 0) {throw "Division by zero condition!";cout << "Code behind throw is running" << endl;}return a / b;
}int main() {int x = 50;int y = 0;double z = 0;try {z = division(x, y);cout << "Code behind try is running" << endl;}catch (const char* msg) {cerr << msg << endl;}cout << "Code behind try-catch is running" << endl;return 0;
}
//当上面代码被编译和执行时,产生以下结果:
//Division by zero condition!
//Code behind try-catch is running
C++标准异常和定义新异常的方法请参见【菜鸟教程】——异常。此处不再介绍
三、命名空间
当多个不同的文件分别出现相同的函数(函数体不同)时,为了区分使用的是哪个函数,于是引入了命名空间的概念。例如,一个文件夹可以包含很多文件夹,每个文件夹不能有相同的文件名,但不同的文件夹中的文件可以重名
3.1定义命名空间
命名空间的定义使用关键字namespace,后跟命名空间的名称
namespace namespace_name{
//代码声明
}
为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如:name::code;//code可以是变量或是函数
#include <iostream>
using namespace std;// 第一个命名空间
namespace first_space{void func(){cout << "Inside first_space" << endl;}
}
// 第二个命名空间
namespace second_space{void func(){cout << "Inside second_space" << endl;}
}
int main ()
{// 调用第一个命名空间中的函数first_space::func();// 调用第二个命名空间中的函数second_space::func(); return 0;
}
3.2using指令
使用using namespace指令,使用命名空间是不需要在前面加上命名空间的名称
#include <iostream>
using namespace std;// 第一个命名空间
namespace first_space{void func(){cout << "Inside first_space" << endl;}
}
// 第二个命名空间
namespace second_space{void func(){cout << "Inside second_space" << endl;}
}
using namespace first_space;
int main ()
{// 调用第一个命名空间中的函数func();return 0;
}
using指令也可以用来指定命名空间中的特定项目,例如只打算使用std命名空间中的cout部分,可以使用:
using std::cout
3.3不连续的命名空间
一个命名空间的各个组成部分可以分散在多个文件中。因此,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,仍需要声明该名称
3.4嵌套的命名空间
可以在一个命名空间内定义另一个命名空间,如:
namespace namespace_name1{//代码声明namespace namespace_name2{//代码声明}
}
可以通过::运算符来访问嵌套的命名空间的成员
//访问namespace_name2中的成员
using namespace namespace_name1::namespace_name2;//访问namespace_name1中的成员
using namespace namespace_name1;//如果使用namespace_name1,那么在该范围内namespace_name2的元素也是可用的
四、模板
4.1函数模板
函数模板的一般形式:
temlpate < typename type>
return-type func-name(parameter list){
//函数主体
}
此处的type是函数所使用的数据类型的占位符名称,用来作为数据类型(int、double等)的统一表示
实例:
#include <iostream>
#include <string>using namespace std;template <typename T>
inline T const& Max (T const& a, T const& b)
{ return a < b ? b:a;
}
int main ()
{int i = 39;int j = 20;cout << "Max(i, j): " << Max(i, j) << endl; double f1 = 13.5; double f2 = 20.7; cout << "Max(f1, f2): " << Max(f1, f2) << endl; string s1 = "Hello"; string s2 = "World"; cout << "Max(s1, s2): " << Max(s1, s2) << endl; return 0;
}
4.2类模板
类模板的一般形式:
template < class type>
class class-name{
//类主体
}
实例:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>using namespace std;template <class T>
class Stack { private: vector<T> elems; // 元素 public: void push(T const&); // 入栈void pop(); // 出栈T top() const; // 返回栈顶元素bool empty() const{ // 如果为空则返回真。return elems.empty(); }
}; template <class T>
void Stack<T>::push (T const& elem)
{ // 追加传入元素的副本elems.push_back(elem);
} template <class T>
void Stack<T>::pop ()
{ if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); }// 删除最后一个元素elems.pop_back();
} template <class T>
T Stack<T>::top () const
{ if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); }// 返回最后一个元素的副本 return elems.back();
} int main()
{ try { Stack<int> intStack; // int 类型的栈 Stack<string> stringStack; // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout << intStack.top() <<endl; // 操作 string 类型的栈 stringStack.push("hello"); cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const& ex) { cerr << "Exception: " << ex.what() <<endl; return -1;}
}
五、信号处理
5.1signal()函数
语法格式:signal(registered signal,signal handler)
- 第一个参数:设置的信号的标识符;
- 第二个参数:指向信号处理函数的指针
如果没有设置信号处理函数,则返回值为SIG_DFL;如果设置信号处理函数为SIG_IGN,则返回值为SIG_IGN
#include <iostream>
#include <csignal>
#include <unistd.h>using namespace std;void signalHandler( int signum )
{cout << "Interrupt signal (" << signum << ") received.\n";// 清理并关闭// 终止程序 exit(signum); }int main ()
{// 注册信号 SIGINT 和信号处理程序signal(SIGINT, signalHandler); while(1){cout << "Going to sleep...." << endl;sleep(1);}return 0;
}
/*
编译执行后按 Ctrl+C 来中断程序,您会看到程序捕获信号,程序打印如下内容并退出
Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.
*/
注释:signal(SIGINT,signalHandler);的作用是注册信号处理函数,当程序收到SIGINT信号时,调用signalHandler函数来处理该信号
- SIGINT是一个标准的POSIX信号,通常由用户按下Ctrl+C在终端触发
- signal(SIGINT,signalHandler)告诉操作系统,当程序收到SIGINT信号时,不要执行默认行为(直接终止),而是调用sinalHandler函数
5.2raise()函数
语法形式:int raise(signal sid);
sig是要发送的信号的编号,包括:SIGINT,SIGABRT,SIGFPE,SIGILL,SIGSEGV,SIGTERM,SIGHUP
实例:
#include <iostream>
#include <csignal>
#include <unistd.h>using namespace std;void signalHandler( int signum )
{cout << "Interrupt signal (" << signum << ") received.\n";// 清理并关闭// 终止程序 exit(signum); }int main ()
{int i = 0;// 注册信号 SIGINT 和信号处理程序signal(SIGINT, signalHandler); while(++i){cout << "Going to sleep...." << endl;if( i == 3 ){raise( SIGINT);}sleep(1);}return 0;
}
/*
当上面的代码被编译和执行时,它会产生下列结果,并会自动退出:
Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.
*/
六、多线程
多任务处理的类型:
- 基于进程的多任务处理是不同程序的并发执行
- 基于线程的多任务处理是同一程序不同片段的并发执行
6.1概念说明
-
线程(Thread):线程共享进程的地址空间、文件描述符、堆和全局变量等资源,但每个线程有自己的栈、寄存器和程序计数器
-
并发:多个任务在时间片段内交替执行,表现出同时进行的效果
并行:多个任务在多个处理器或处理器核上同时执行 -
多线程核心组件:
- thread:用于创建和管理线程
- mutex:用于进程之间的互斥,放置多个线程同时访问共享资源
- lock_guard和unique_lock:用于管理锁的获取和释放
- condition_variable:用于线程之间的条件变量、协调线程间的等待和通知
- future和promise:用于实现线程间的值传递和任务同步
6.2创建线程
标准线程库头文件#include<thread> std::thread thread_object(callable,args…);
其中,callable:可调用对象(函数指针,函数对象,Lambda表达式等);args…:传递给callable的参数列表
【使用函数指针】
#include <iostream> #include <thread>void printMessage(int count) {for (int i = 0; i < count; ++i) {std::cout << "Hello from thread (function pointer)!\n";} }int main() {std::thread t1(printMessage, 5); // 创建线程,传递函数指针和参数t1.join(); // 等待线程完成return 0; }
【使用函数对象】
#include <iostream> #include <thread>class PrintTask { public:void operator()(int count) const {for (int i = 0; i < count; ++i) {std::cout << "Hello from thread (function object)!\n";}} };int main() {std::thread t2(PrintTask(), 5); // 创建线程,传递函数对象和参数t2.join(); // 等待线程完成return 0; }
【使用Lambda表达式】
#include <iostream> #include <thread>int main() {std::thread t3([](int count) {for (int i = 0; i < count; ++i) {std::cout << "Hello from thread (lambda)!\n";}}, 5); // 创建线程,传递 Lambda 表达式和参数t3.join(); // 等待线程完成return 0; }
6.3线程管理
join():用于等待线程完成执行,如果不调用join()或detach()而直接销毁线程对象,会导致程序崩溃
t.jion()
detach():用于将线程与主线程分离,线程在后台独立运行,主线程不再等待它
t.detach():
6.4线程的传参
值传递:
thread t(func,arg1,arg2);
引用传递:
thread t(increment,ref(num));
综合实例:
#include <iostream> #include <thread> using namespace std;// 一个简单的函数,作为线程的入口函数 void foo(int Z) {for (int i = 0; i < Z; i++) {cout << "线程使用函数指针作为可调用参数\n";} }// 可调用对象的类定义 class ThreadObj { public:void operator()(int x) const {for (int i = 0; i < x; i++) {cout << "线程使用函数对象作为可调用参数\n";}} };int main() {cout << "线程 1 、2 、3 独立运行" << endl;// 使用函数指针创建线程thread th1(foo, 3);// 使用函数对象创建线程thread th2(ThreadObj(), 3);// 使用 Lambda 表达式创建线程thread th3([](int x) {for (int i = 0; i < x; i++) {cout << "线程使用 lambda 表达式作为可调用参数\n";}}, 3);// 等待所有线程完成th1.join(); // 等待线程 th1 完成th2.join(); // 等待线程 th2 完成th3.join(); // 等待线程 th3 完成return 0; }
注:关于线程的互斥笔者打算在复习操作系统时总结,有兴趣的朋友可以访问【菜鸟教程】——多线程
注:关于Web编程此处也不再总结,笔者打算复习计算机网络时再总结,如果有兴趣,可以访问菜鸟教程——Web编程
目标与计划:正是复习完C++编程语言知识点,之前粗略复习完了《数据结构》的课本知识,接下来开始根据课本知识点刷leetcode题目,然后再做课后题