目录
第7章 C++ 高级
1. 内存管理
1.1. 基础
参考:
- C++多线程编程之常见面试问题
1.1.1. 程序的内存分配
一个由**C/C++**编译的 程序占用的内存分为以下几个部分:
- 栈区(stack)— 由编译器自动分配释放
,存放函数的参数名,局部变量的名等。其操作方式类似于数据结构中的栈。 - 堆区(heap)— 由程序员分配释放,
若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 - 全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。 - 文字常量区—常量字符串就是放在这里的,程序结束后由系统释放 。
- 程序代码区— 存放函数体的二进制代码。
1.2. 释放 与 销毁
1.2.1. 部分
1.2.2. MFC
参考:
- MFC创建非模态对话框和销毁过程 //命令和消息的传递
1.2.3. 非模态窗口 关闭
- MFC应用程序关闭窗口的顺序(非模态窗口)
- OnClose()
消息响应函数,响应窗口的WM_CLOSE消息,当关闭按钮被单击的时候发送此消息 - OnDestroy()
消息响应函数,响应窗口的WM_DESTROY消息,当一个窗口将被销毁时,发送此消息 - OnNcDestroy()
消息响应函数,响应窗口的WM_NCDESTROY消息,当一个窗口被销毁后发送此消息 - PostNcDestroy() 重载函数,作为处理OnNcDestroy()函数的最后动作,被CWnd调用。
- OnClose()
2. 文件处理 // 文件
2.1. 文件和流
- 从文件读取流和向文件写入流需要用到 C++ 中另一个标准库fstream,
- 它定义了三个新的数据类型:
- ofstream:表示输出文件流,用于创建文件并向文件写入信息。
- ifstream:表示输入文件流,用于从文件读取信息。
- fstream:通常表示文件流,且同时具有 ofstream 和 ifstream
两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。
//注:- i fstream 即i(n) f //输入到程序 读取 //程序是主体
- o fstream 即o(ut) f //输出到程序 写入
注:
- 在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件
<iostream>
和<fstream>
。
2.1.1. 创建 删除 文件 文件夹
//“创建文件夹” “删除文件夹”
2.1.2. 创建文件夹
-
使用 WINAPI 函数
调用Windows API函数 *CreateDirectory()*和 *RemoveDirectory()* *成功返回0,否则返回非零* bool flag = CreateDirectory(path.c_str(), NULL); *判断文件夹是否存在 再创建* # include <windows.h> # include <iostream> using namespace *std*; int main() { *string* defaultPath = "E:database"; *string* folderPath = defaultPath + "testFolder"; if (!*GetFileAttributesA*(folderPath.*c_str*()) & *FILE_ATTRIBUTE_DIRECTORY*) { bool flag = *CreateDirectory*(folderPath.*c_str*(), *NULL*); // flag 为 true 说明创建成功 } else { *cout* << "Directory already exists." << *endl*; } return 0; }
-
使用 dos 命令
参考:
2.1.3. 删除文件夹
- WINAPI函数
成功返回0,否则返回非零
bool flag = RemoveDirectory(path.c_str());
2.1.4. 判断是否存在 文件、文件夹
//“创建文件夹”、“创建文件”、“文件是否重复”、‘文件重复’
-
GetFileAttributesA( ) 函数 //文件夹
DWORD d = GetFileAttributesA(const char* filename); #include <windows.h> // windows系统函数, 判断**文件夹**是否存在; // 见 *参考2*
-
_access( ) 函数 //文件
//参考 2 -
Visual Studio 2015 //文件
//文件是否存在
-
使用 .open 函数来打开文件,看是否存在
// 但存在则已经打开,需要关闭 不推荐使用
参考:
- 在C++中检查一个文件是否存在的几种方法
- C++ 判断文件夹(folder)是否存在(exist) //推荐
- C++中如何判断文件是否存在 //.open 不推荐
2.1.5. 打开与关闭
-
打开文件
在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstream 和 fstream
对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream
对象。 -
语句格式
void open(const char *filename, ios::openmode mode); //open() 成员函数的第一参数指定要打开的文件的名称和位置 //第二个参数定义文件被打开的模式 //open() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
-
代码示例:
ofstream outfile; outfile.open("file.dat", ios::out | ios::trunc); //将两种方式结合使用
-
关闭文件
当 C++程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开的文件。但程序员应该养成一个好习惯,在程序终止前关闭所有打开的文件。 -
语句格式
void close(); //close() 函数是 fstream、ifstream 和 ofstream 对象的一个成员
2.1.6. 读取和写入
在 C++编程中,我们使用流提取运算符(>>)从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是ifstream或fstream对象,而不是cin对象。
//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; }
2.1.7. 文件位置指针
2.2. 帮助文档
3. 多线程
参考:
- 为什么要多线程开发
- 多线程笔试面试题汇总
//主要
3.1. 多线程 概述
做到:主线程与子线程之间的同步;各子线程间的互斥。
参考:
- 秒杀多线程系列
多线程笔试面试题汇总 共16篇 //主要 讲解详细 - C++ 多线程 面试题详解 //很少
- C++多线程编程之常见面试问题
- C++ 11 多线程–线程管理
- 程序员的自我修养(五):C++多线程编程初步
3.1.1. 堆和栈
-
堆:
是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。 -
栈:
是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread
safe的。每个C ++对象的数据成员也存在在栈中,每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
3.1.2. 进程和线程有什么区别?
这个一个最常见,却最不好回答的问题,csdn上面一位博主给出的解答和另一位cnblog博主的解答稍微清晰些一些,
-
总结起来,就是一下的几个区别:
- 进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。在Mac、Windows
NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。 - 进程有独立的地址空间,比如在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种非常昂贵的多任务工作方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,因此启动一个线程,切换一个线程远比进程操作要快,花费也要小得多。当然,线程是拥有自己的局部变量和堆栈(注意不是堆)的,比如在windows中用_beginthreadex创建一个新进程就会在调用CreateThread的同时申请一个专属于线程的数据块(_tiddata)。
- 线程之间的通信比较方便。统一进程下的线程共享数据(比如全局变量,静态变量),通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问的同步与互斥正是编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。
- 多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。
- 线程的执行与进程是有区别的。每个独立的线程有有自己的一个程序入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依附与程序之中,由应用程序提供多个线程的并发控制。
- 进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。在Mac、Windows
3.1.3. 线程同步与互斥
参考:
- 详解
-
线程(进程)同步的主要任务:
在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。 -
线程(进程)之间的制约关系?
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程A操作完毕,这其实就是线程的同步了。
因此同步包括互斥,互斥其实是一种特殊的同步。 -
临界资源和临界区
在一段时间内只允许一个线程访问的资源就称为临界资源或独占资源,计算机中大多数物理设备,进程中的共享变量等待都是临界资源,
它们要求被互斥的访问。每个进程中访问临界资源的代码称为临界区。
-
3.2. 线程的基本操作
3.2.1. 基本函数
- WaitForSingleObject 与 WaitForMultipleObjects
To enter an alertable wait state, use the WaitForSingleObjectEx function.
To wait for multiple objects, useWaitForMultipleObjects. - WaitForSingleObject
Waits until the specified object is in the signaled state or the time-out
interval elapses. - WaitForMultipleObjects
参考:
3.2.2. 线程的创建
//4种创建方法;
3.2.3. 线程的挂起、唤醒和终止
3.3. 线程同步
参考:
- 秒杀多线程系列
多线程笔试面试题汇总 //主要 讲解详细 - VC++ 从入门到精通
-
概述
-
定义:
线程同步:- 即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,
- 而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
//百度百科
// 单线程的 对 内存 的有序操作;
-
目的:
解决线程对于 资源访问冲突 的问题,例如,同时访问;访问后更改,不同步的问题;
-
-
方法:
- 关键段CS //秒杀多线程第五篇 经典线程同步
CRITICAL_SECTION - 事件Event //秒杀多线程第六篇 经典线程同步
- 互斥量Mutex //秒杀多线程第七篇 经典线程同步
- 信号量Semaphore //秒杀多线程第八篇 经典线程同步
- 关键段CS //秒杀多线程第五篇 经典线程同步
-
线程同步方式比较:
- 用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
- 内核模式下的方法有:事件,信号量,互斥量。
- 概述:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4YJ5uiK-1591631942702)(media/43f353835e351274b95e391e8e8401a3.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kjcxjEOv-1591631942705)(media/b92928decd7be281afe8645f4998369b.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dQMytPrK-1591631942707)(media/8eef1efa5ba4282823b1b836659b3096.png)]
3.3.1. 关键段CS CRITICAL_SECTION
//可以解决互斥;不能解决同步问题;
-
总结:
- 关键段共初始化化、销毁、进入和离开关键区域四个函数。
- 关键段可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
- 推荐关键段与旋转锁配合使用。
-
关键段CRITICAL_SECTION一共就四个函数,使用很是方便。
- 初始化:
void InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:定义关键段变量后必须先初始化。 - 销毁:
void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:用完之后记得销毁。 - 进入关键区域:
void EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。 - 离开关键区域:
void LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
- 初始化:
3.3.2. 事件 Event
-
总结:
- 事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
- 事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
- 事件可以解决线程间同步问题,因此也能解决互斥问题。
-
事件对象属于系统内核对象之一;分为手动置位事件和自动置位事件;
- 手动置位事件
等待到事件对象后,可同时使多个线程成为可调度线程; - 自动置位事件
等待到事件对象后,多个线程中只有一个成为可调度线程;并将事件对象设置为未通知状态;
//2个动作
- 函数:
HANDLE CreateEvent() 创建事件; HANDLE OpenEvent() 根据名称获得一个事件的句柄; BOOLSetEvent(HANDLEhEvent); BOOLResetEvent(HANDLEhEvent); CloseHandle(); 清理和销毁;
- 手动置位事件
3.3.3. 互斥量 Mutex
-
总结:
- 互斥量是内核对象,它与关键段都有“线程所有权”;所以不能用于线程的同步。
- 互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某进程意外终止所造成的**“遗弃”**问题。
-
函数:
HANDLE CreateMutex(); HANDLE OpenMutex(); BOOL ReleaseMutex (HANDLEhMutex);
3.3.4. 信号量 Semaphore
- 总结:
- 信号量也可以解决线程之间的同步问题。
- 由于信号量可以计算资源当前剩余量并根据当前剩余量与零比较来决定信号量是处于触发状态或是未触发状态,因此信号量的应用范围相当广泛。
- 函数:
HANDLE CreateSemaphore(); HANDLE OpenSemaphore(); BOOL ReleaseSemaphore(); CloseHandle(); //清理和销毁;通用;
3.4. 线程间通信
参考: