《C++程序员面试笔试知识点总结.docx》由会员分享,可在线阅读,更多相关《C++程序员面试笔试知识点总结.docx(93页珍藏版)》请在三一办公上搜索。
1、字符串反转的实现char* ReverseStr(char *str)char *p1 = str;char *p2 = str;char tmp;while (*(p2 + 1) != 0) p2+; /* 移动 p2 到最后一个字符(0字符前一个)*/while (p1 p2) /*交换字符*/tmp = *p1;*p1 = *p2;*p2 = tmp;p1+;p2-;return str;void Swap(char *m, char *n)/ 交换函数char t;t = *m;*m = *n;*n = t;void reverse_string(char *string)/ 字符反向
2、排列处理函数int len = 0;int i;char *p = string;while(*(p+) != 0) /计算字符串的实际长度len+;for(i = 0;i len / 2; i+) Swap(string + i, string + len - i - 1);/ 逆序*(string + len) = 0;/ 字符串以0结束C+中指针和引用的区别相同点:都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。区别:1. 指针是一个实体,而引用仅是个别名;2. 引用使用时无需解引用(*),指针需要解引用;3. 引用只能在定义时被初始化一次,之后不可变;
3、指针可变;引用“从一而终”4. 引用没有const,指针有const,const的指针不可变;5. 引用不能为空,指针可以为空;6. “sizeof引用”得到的是所指向的变量(对象)的大小,而“sizeof指针”得 到的是指针本身(所指向的变量或对象的地址)的大小;typeid(T) = typeid(T&)恒为真,sizeof(T) = sizeof(T&)恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。7. 指针和引用的自增(+)运算意义不一样;实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这 东西?答案是“用适当的工具做恰如其分的工作”。指针
4、能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用? 如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指 针”,以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的 印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。你如 何决定在什么时候使用指针,在什么时候使用引用呢?首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向 某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时 候也可能不指向任何对象,这时你应该把变量声明为指针,因为这
5、样你可以赋空 值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空, 这时你就可以把变量声明为引用。总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的 可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指 向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象 并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是 操作符。这个操作符典型的用法是返回一个目标对象,其能被赋值。当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防 止不
6、必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应 使用指针。有关多线程的一些技术问题:1、何时使用多线程?2、线程如何同步?3、线程之间如何通讯?4、进程之间如何通讯?先来回答第一个问题,线程实际主要应用于四个主要领域,当然各个领域之间不 是绝对孤立的,他们有可能是重叠的,但是每个程序应该都可以归于某个领域:1、offloading time-consuming task。由辅助线程来执行耗时计算,而使GUI有更好的反应。我想这应该是我们考虑使用线程最多的一种情况吧。2、Scalability。服务器软件最常考虑的问题,在程序中产生多个线程,每个线程做一份小的工作,使每个CP
7、U都忙碌,使CPU (一般是多个)有最佳的使 用率,达到负载的均衡,这比较复杂,我想以后再讨论这个问题。3、 Fair-share resource allocation。当你向一个负荷沉重的服务器发出请求, 多少时间才能获得服务。一个服务器不能同时为太多的请求服务,必须有一个请 求的最大个数,而且有时候对某些请求要优先处理,这是线程优先级干的活了。4、Simulations。线程用于仿真测试。我把主要的目光放在第一个领域,因为它正是我想要的。第二和第三个领域比较 有意思,但是目前不在我的研究时间表中。线程的同步机制:1、Event用事件(Event)来同步线程是最具弹性的了。一个事件有两种状
8、态:激发状态 和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事 件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程, 而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件 被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。 所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为CEvent.。 CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。共有 三个函数来改变事件的状态:SetEvent,ResetEvent和PulseEvent。用事件来同 步线程是一种比较理想的做法,但在实际的
9、使用过程中要注意的是,对自动重置 事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。2、Critical Section使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对 的,视不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程 序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不 释放的话,会如何?答案是不会怎样。如果是主线程GUI线程)要进入一个没 有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临 界区的线程
10、挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界 资源。这个缺点在互斥器(Mutex)中得到了弥补。Critical Section在MFC中的相 应实现类是 CcriticalSection。CcriticalSection: Lock()进入临界区, CcriticalSection: UnLock()离开临界区。3、Mutex互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定TIMEOUT,不会像Critic
11、al Section那样无法得知临界区域的情况,而一直死等。MFC中的对应类为 CMutex oWin32函数有:创建互斥体CreateMutex(),打开互斥体OpenMutex(), 释放互斥体ReleaseMutex()o Mutex的拥有权并非属于那个产生它的线程,而 是最后那个对此Mutex进行等待操作(WaitForSingleObject等等)并且尚未进 行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入Critical Section 样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之 前没有调用ReleaseMutex(),那么这
12、个Mutex就被舍弃了,但是当其他线程等 待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个 WAIT_ABANDONED_O返回值。能够知道一个Mutex被舍弃是Mutex特有的。4、Semaphore信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键 要素。对应的 MFC 类是 Csemaphore。Win32 函数 CreateSemaphore ()用 来产生信号量。ReleaseSemaphore ()用来解除锁定。Semaphore的现值代 表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个
13、锁 定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用 Wait等函数要求锁定,如果Semaphore现值不为0, Wait马上返回,资源 数减1。当调用ReleaseSemaphore ()资源数加1,当时不会超过初始设定的 资源总数。线程之间的通讯:线程常常要将数据传递给另外一个线程。Worker线程可能需要告诉别人说它的 工作完成了,GUI线程则可能需要交给Worker线程一件新的工作。通过PostThreadMessage (),可以将消息传递给目标线程,当然目标线程必 须有消息队列。以消息当作通讯方式,比起标准技术如使用全局变量等,有很大 的好处。如果对象是同一进程
14、中的线程,可以发送自定义消息,传递数据给目标 线程,如果是线程在不同的进程中,就涉及进程之间的通讯了。下面将会讲到。进程之间的通讯:当线程分属于不同进程,也就是分驻在不同的地址空间时,它们之间的通讯需要 跨越地址空间的边界,便得采取一些与同一进程中不同线程间通讯不同的方法。1、 Windows专门定义了一个消息:WM_COPYDATA,用来在线程之间搬移数据,-不管两个线程是否同属于一个进程。同时接受这个消息的线程必须有一 个窗口,即必须是UI线程。WM_COPYDATA 必须由SendMessage ()来发 送,不能由PostMessage ()等来发送,这是由待发送数据缓冲区的生命期决
15、定的,出于安全的需要。2、WM_COPYDATA效率上面不是太高,如果要求高效率,可以考虑使用共 享内存(Shared Memory)。使用共享内存要做的是:设定一块内存共享区域;使用共享内存;同步处理共享内存。第一步:设定一块内存共享区域。首先,CreateFileMapping()产生一个 file-mapping核心对象,并指定共享区域的大小。MapViewOfFile()获得一个 指针指向可用的内存。如果是C/S模式,由Server端来产生file-mapping,那 么 Client 端使用 OpenFileMapping(),然后调用 MapViewOfFile()。第二步:使用共
16、享内存。共享内存指针的使用是一件比较麻烦的事,我们需要借 助based属性,允许指针被定义为从某一点开始起算的32位偏移值。第三步:清理。UnmapViewOfFile()交出由MapViewOfFile()获得的指针, CloseHandle()交出 file-mapping 核心对象的 handle。第四步:同步处理。可以借助Mutex来进行同步处理。3、IPC1) Anonymous Pipes。Anonymous Pipes只被使用于点对点通讯。当一个进 程产生另一个进程时,这是最有用的一种通讯方式。2) Named Pipes。Named Pipes可以是单向,也可以是双向,并且可以
17、跨越网 络,步局限于单机。3) Mailslotso Mailslots为广播式通讯。Server进程可以产生Mailslots,任何 Client进程可以写数据进去,但是只有Server进程可以取数据。4) OLE Automationo OLE Automation和UDP都是更高阶的机制,允许通讯 发生于不同进程间,甚至不同机器间。5) DDE。DDE动态数据交换,使用于16位Windows,目前这一方式应尽量避 免使用。下面的是源代码:* DataAnalyzer.h *class DataAnalyzerpublic:int Run();private:void AnalyzingD
18、ata();static DWORD WINAPI AnalyzingThread(LPVOID lpParameter);static DWORD WINAPI ManagerThread(LPVOID lpParameter);static char* datafilepath; / 数据输入文件名static const int maxthread; / 最大线程数 static HANDLE m_hEvent;typedef structHANDLE hEvent; /线程对应的事件句柄int number; / 数据 threaddata;* DataAnalyzer.cpp *#d
19、efine WIN32_LEAN_AND_MEAN#include #include using namespace std;#include dataanalyzer.h char* DataAnalyzer:datafilepath = NULL;const int DataAnalyzer:maxthread = 5; / 最大线程数 HANDLE DataAnalyzer:m_hEvent = NULL;int DataAnalyzer:Run()MSG msg;/打开事件句柄m_hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, syn_eve
20、 nt );if(m_hEvent = NULL)cout 没有找到需要的事件。n DataAnalyzer只能够由DataMaker调用,请勿直接使用.n ;cout ”按 ENTER 键退出.;cin.get();return -1;cout DataAnalyzer正在启动.;Sleep(1000);cout OKnnn ;/设置事件为有信号,使得正在等待的DataMaker可以继续运行SetEvent(m_hEvent);while(GetMessage(&msg, NULL, NULL, NULL)switch(msg.message)case WM_NOTIFY: / DataMa
21、ker 数据包写入完毕后收到这个消 息if(msg.wParam =1)cout # 收到 DataMaker 发送的通知,可以获取数据文件名了 .n ;HANDLE hfilemap = OpenFileMapping(FILE_MAP_READ, FALSE, demo_filemapping );LPVOID lpview = MapViewOfFile(hfilemap, FILE_MAP_ READ, 0, 0, 0);cout hfilemap = hex hfilemap , lpview = lpview endl;int nlen = *(int*)lpview);dataf
22、ilepath = new charnlen;memcpy(datafilepath, (BYTE*)lpview + 4, nlen);UnmapViewOfFile(lpview);CloseHandle(hfilemap);cout datafilepath : datafilepath endl endl;/将事件变为有信号,以此通知 DataMaker已经准备好了。SetEvent(m_hEvent);elsecout # 收到 DataMaker 发送的消息,数据已经写入完毕.n ;AnalyzingData();break;case WM_CLOSE: / 任务完成时收到这个消息
23、cout nnn# 收到 DataMaker 发送的消息,可以退出运行了、;delete datafilepath;PostQuitMessage(O);break;/ 让事件变为有信号,通知 DataMaker, DataAnalyzer进程即将结束 SetEvent(m_hEvent);CloseHandle(m_hEvent);cout 按 ENTER 键关闭窗口.;cin.get();return 0;void DataAnalyzer:AnalyzingData() cout # 建立管理者线程.n ;DWORD dwThreadID;HANDLE hManagerThread =
24、CreateThread(NULL, 0, ManagerThread, 0, CREATE_SUSPENDED, &dwThreadID);cout # 等待管理者线程结束.n ;ResumeThread(hManagerThread);WaitForSingleObject(hManagerThread, INFINITE);CloseHandle(hManagerThread);/让事件变为有信号,通知 DataMaker 分析工作已经结束 cout # 分析工作已经全部完成.nnn ;SetEvent(m_hEvent);DWORD DataAnalyzer:ManagerThread
25、(LPVOID lpParameter)HANDLE* hThreads = new HANDLEmaxthread; / 保存句柄 HANDLE*hEvents= new HANDLEmaxthread; / 保存事件threaddata* tds = new threaddatamaxthread; / 线程需要的数据 cout 管理线程开始.n ;/ 建立事件 for(int i = 0; i maxthread; i+) hEventsiCreateEvent(NULL, FALSE, TRUE, NULL);DWORD dwThreadID;SYSTEMTIME stime;GetS
26、ystemTime(&stime);for(int nCount = 0; nCount stime.wMilliseconds %10 +1; nCount+)/等待一个分析线程结束i = WaitForMultipleObjects(maxthread, hEvents, FALSE, INFINI TE) - WAIT_OBJECT_0;/关闭句柄CloseHandle(hThreadsi);/为分析线程准备数据Sleep(350);tdsi.hEvent = hEventsi;/模拟读入的数据 tdsi.number = 0;/建立分析线程但不立刻启动hThreadsi = Creat
27、eThread(NULL, 0, AnalyzingThread, (LPVOID)&tdsi, 0, &dwThreadID);/等待所有的分析线程结束for(i = 0; i maxthread; i+)WaitForSingleObject(hThreadsi, INFINITE);CloseHandle(hEventsi);delete hThreads;delete hEvents;delete tds;cout 管理线程结束.n ;return 0;DWORD DataAnalyzer:AnalyzingThread(LPVOID IpParameter)threaddata* p
28、td = (threaddata*)lpParameter;DWORD dwid = GetCurrentThreadId();cout hex dwid number);/将事件设置为有信号后,前面 ManagerThread 中的/ i = WaitForMultipleObjects(maxthread,hEvents, FALSE, INFINITE) -WAIT_OBJECT_0/将返回一个已经空闲的句柄的索引SetEvent(ptd- hEvent);cout hex dwid 分析线程结束.ENDn ;return 0; int main(int argc, char* argv
29、) DataAnalyzer theObj;return theObj.Run();* DataMaker.h *class DataMakerpublic:int Run();private:void MakeData(); / 准备数据包void SaveData(); / 写入数据包void StartDataAnalyzer(); / 启动 DataAnalyzerDWORD m_idDataAnalyzer; / DataAnalyzer 的线程 ID HANDLE m_hEvent;static const char* const datafilepath; /数据文件;* Dat
30、aMaker.cpp *#define WIN32_LEAN_AND_MEAN#include #include using namespace std;#include datamaker.h const char* const DataMaker:datafilepath = c:Xdatafile.txt ;void DataMaker:StartDataAnalyzer()STARTUPINFO si;PROCESS_INFORMATION pi;ZeroMemory(&si, sizeof(si);ZeroMemory(&pi, sizeof(pi);si.cb = sizeof(s
31、i);/建立事件m_hEvent = CreateEvent(NULL, TRUE, FALSE, syn_event );/ 建立进程 DataAnalyzerif(CreateProcess(TEXT( DataAnalyzer.exe ),NULL,NULL,NULL,FALSE,CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi) = FALSE)cout GetLastError() endl;throw 0; /如果建立进程失败抛出异常cout ”等待DataAnalyzer 做好准备.;WaitForS
32、ingleObject(m_hEvent, INFINITE);cout OKn ;CloseHandle(pi.hProcess);m_idDataAnalyzer = pi.dwThreadId; / 得到 DataAnalyzer 的线程IDvoid DataMaker:MakeData()cout DataMaker 正在准备数据.;SYSTEMTIME stime;GetSystemTime(&stime);Sleep(stime.wMilliseconds % 3000);cout OKn ;void DataMaker:SaveData()/模拟写数据cout 正在写入数据.;S
33、YSTEMTIME stime;GetSystemTime(&stime);Sleep(stime.wMilliseconds %1000);cout OKn ;int DataMaker:Run()cout DataMaker 开始运行.n ;/ 启动 DataAnalyzertryStartDataAnalyzer();catch(.) cout ”启动 DataAnalyzer 失败.n ;return -1;cout ”建立页面文件文件映射来和DataAnalyzer 共享数据.n ;HANDLE hfilemap = CreateFileMapping(HANDLE)0xffffff
34、fff, NULL, PAGE_READWRITE, 0, lstrlen(datafilepath) +5,demo_filemapping );LPVOID lpview = MapViewOfFile(hfilemap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);cout hfilemap = hex hfilemap , lpview = lpview endl;*(int*)lpview) = lstrlen(datafilepath) +1;memcpy(BYTE*)lpview + 4, datafilepath, lstrlen(data
35、filepath) +1);UnmapViewOfFile(lpview);cout ”通知 DataAnalyzer 接收数据文件名,;ResetEvent(m_hEvent);PostThreadMessage(m_idDataAnalyzer, WM_NOTIFY, (WPARAM)1, N ULL);cout ”并等待其完成处理.;WaitForSingleObject(m_hEvent, INFINITE);cout OKn ;CloseHandle(hfilemap);for(intcoutnCount0; nCount 10; nCount+) nn*nn 进行第 dec nCo
36、unt +1次数据数据准备工作.n ;/准备数据包MakeData();/等待事件变为有信号/ DataAnalyzer会在数据处理完毕后将事件变为有信号 cout ”等待 DataAnalyzer 的分析工作完成.; WaitForSingleObject(m_hEvent, INFINITE);cout OKn ;/保存数据包SaveData();/ 通知 DataAnalyzer 数据包已经写入完毕cout ”通知 DataAnalyzer 新的数据包已经写入到文件中了、;ResetEvent(m_hEvent);PostThreadMessage(m_idDataAnalyzer, W
37、M_NOTIFY, NULL, NUL L);cout nn没有数据需要分析了, 等待 DataAnalyzer 完成分析任务.;/ 等待 DataAnalyzer 完成任务WaitForSingleObject(m_hEvent, INFINITE);cout OKn ;/ 通知 DataAnalyzer 任务完成,可以退出了 cout OK,大家可以休息了.侦; PostThreadMessage(m_idDataAnalyzer, WM_CLOSE, NULL, NULL);/释放事件CloseHandle(m_hEvent);cout DataMaker运行结束.n 按 ENTER 键
38、关闭窗口.;cin.get();return 0;int main(int argc, char* argv) DataMaker theObj;return theObj.Run();* 说明 *zero实例包含两个文件DataMaker.exe和DataAnalyzer.exe,都是控制台程序。 我们启动程序DataMaker后,DataMaker会自动的创建一个新进程DataAnaly 之后DataMaker开始准备一系列由不同种类的数据组成的数据包,准备完成后, DataMaker打开一个文件,将这个数据包添加到文件中。添加完成后,DataMaker 将通知DataAnalyzer新的
39、数据包已经添加完毕,然后接着继续准备新的数据包。 而同一时刻,DataAnalyzer 一直在等待DataMaker添加完数据包后发送的通知。 在收到通知后,DataAnalyzer会打开文件开始处理数据包,并且创建一些线程来 分析数据,但同一时刻运行的线程数量不能超过上限。DataAnalyzer在分析工作 完毕后会通知DataMaker。而DataMaker在没有收到DataAnalyzer发送的处理完 毕通知前不会再次向文件中添加新的数据包。在这个过程进行10次后,DataMaker将通知DataAnalyzer工作已经结束,可以 退出了。然后DataMaker和DataAnalyzer
40、都将结束运行。当然,为了缩小实例的长度和降低复杂性,所有针对文件的操作都是模拟的。单态设计模式(Singleton Pattern) 一、初识单态 单态设计模式(Singleton Pattern),又称单件,单个等设计模式。也许,在 java方面的材料中你会发现叫法多为单态,而在c#方面的材料中的叫法多为单 件。无论是那种叫法,都是指的同一种设计模式Singleton pattern,笔者习惯 叫单态,在本文中以单态称呼。二、单态的特点和定义单态,简单的讲,就是保证程序在运行的过程中只允许有一个实例对象产生。期 特点简单的描述为:1. 单态类只能有一个实例。2. 单态类必须自己创建自己的一个
41、唯一的实例,3. 单态类必须给客户端使用次实例的方式,即通过一定的方式返回三、单态的原型使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才 应当使用单例模式。反过来,如果一个类可以有几个实例共存,就不要使用单例 模式。单态的社会原型非常多,像美国的总统,只能有一个,是通过选举得来的;像打 印机的当前的打印作业也只能有一个,等等。注意:不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对 应类的静态成员中。不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在 有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员
42、存储类实例,所以可能会 造成资源无法及时释放,带来问题。四、单态的实现单态的实现,用一句比较诙谐的话来说就是:“只需周官放火,不许百姓点灯”。概要的讲单态的设计就是上面那句话,在类中把构造函数私有化,这样就不能在 类的外部(客户端)用new来创建类的实例了(这就是不许百姓点灯),然后 在类的内部来实例化一份类的实例,通过某种方式返回给客户端(这就是所谓的 只需州官防火),java中通常是通过一个静态方法返回,C#中有时候也是会使 用属性来返回的。具体的讲,单态的实现主要是在对象实例化的时候有些区别,问题在于什么时候 来实例化这个对象,分两种方式。给个例子吧,两种方式:硬汉和懒汉方式。模式1:p
43、ublic class SingletonI (private static SingletonI sin = null;private int x = 5;private SingletonI() (public int getX() (return x;public void setX(int x) (this.x = x;public static SingletonI newInstance() ( if (sin = null) (sin = new SingletonI();return sin;模式2:public class SingletonII (private stati
44、c SingletonII singletonII = new SingletonII(); private int x = 5;private SingletonII() (public int getX() ( return x;public void setX(int x) ( this.x = x;public static SingletonII newInstance() ( return singletonII;测试:public class Test (public static void main(String args) (SingletonII instance = SingletonII.newInstance();System.out.println(instance.getX