C++程序员面试笔试知识点总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
字符串反转的实现
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)//字符反向排列处理函数
{
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. 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”
4. 引用没有const,指针有const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,
但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;
实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这东西?
答案是“用适当的工具做恰如其分的工作”。
指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。
就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?
如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,以免发生意外。
比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。
你如何决定在什么时候使用指针,在什么时候使用引用呢?
首先,要认识到在任何情况下都不能用指向空值的引用。
一个引用必须总是指向某些对象。
因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。
相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。
如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
还有一种情况,就是当你重载某个操作符时,你应该使用引用。
最普通的例子是操作符[]。
这个操作符典型的用法是返回一个目标对象,其能被赋值。
当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。
而在除此之外的其他情况下,则应使用指针。
有关多线程的一些技术问题:
1、何时使用多线程?
2、线程如何同步?
3、线程之间如何通讯?
4、进程之间如何通讯?
先来回答第一个问题,线程实际主要应用于四个主要领域,当然各个领域之间不是绝对孤立的,他们有可能是重叠的,但是每个程序应该都可以归于某个领域:
1、offloading time-consuming task。
由辅助线程来执行耗时计算,而使GUI 有更好的反应。
我想这应该是我们考虑使用线程最多的一种情况吧。
2、Scalability。
服务器软件最常考虑的问题,在程序中产生多个线程,每个线程做一份小的工作,使每个CPU都忙碌,使CPU(一般是多个)有最佳的使用率,达到负载的均衡,这比较复杂,我想以后再讨论这个问题。
3、Fair-share resource allocation。
当你向一个负荷沉重的服务器发出请求,多少时间才能获得服务。
一个服务器不能同时为太多的请求服务,必须有一个请求的最大个数,而且有时候对某些请求要优先处理,这是线程优先级干的活了。
4、Simulations。
线程用于仿真测试。
我把主要的目光放在第一个领域,因为它正是我想要的。
第二和第三个领域比较有意思,但是目前不在我的研究时间表中。
线程的同步机制:
1、Event
用事件(Event)来同步线程是最具弹性的了。
一个事件有两种状态:激发状态和未激发状态。
也称有信号状态和无信号状态。
事件又分两种类型:手动重置事件和自动重置事件。
手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。
自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。
所以用自动重置事件来同步两个线程比较理想。
MFC中对应的类为CEvent.。
CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。
共有三个函数来改变事件的状态:SetEvent,ResetEvent和PulseEvent。
用事件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重置事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。
2、Critical Section
使用临界区域的第一个忠告就是不要长时间锁住一份资源。
这里的长时间是相对的,视不同程序而定。
对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说,可以长达数分钟。
但进入临界区后必须尽快地离开,释放资源。
如果不释放的话,会如何?答案是不会怎样。
如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。
这个缺点在互斥器(Mutex)中得到了弥补。
Critical Section在MFC中的相应实现类是CcriticalSection。
CcriticalSection::Lock()进入临界区,CcriticalSection::UnLock()离开临界区。
3、Mutex
互斥器的功能和临界区域很相似。
区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进
程使用,而且等待一个被锁住的Mutex可以设定TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。
MFC中的对应类为CMutex。
Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。
Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。
线程拥有Mutex就好像进入Critical Section一样,一次只能有一个线程拥有该Mutex。
如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个
WAIT_ABANDONED_0返回值。
能够知道一个Mutex被舍弃是Mutex特有的。
4、Semaphore
信号量是最具历史的同步机制。
信号量是解决producer/consumer问题的关键要素。
对应的MFC类是Csemaphore。
Win32函数CreateSemaphore()用来产生信号量。
ReleaseSemaphore()用来解除锁定。
Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。
如果现值为5,就表示还有五个锁定动作可以成功。
当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。
当调用ReleaseSemaphore()资源数加1,当时不会超过初始设定的资源总数。
线程之间的通讯:
线程常常要将数据传递给另外一个线程。
Worker线程可能需要告诉别人说它的工作完成了,GUI线程则可能需要交给Worker线程一件新的工作。
通过PostThreadMessage(),可以将消息传递给目标线程,当然目标线程必须有消息队列。
以消息当作通讯方式,比起标准技术如使用全局变量等,有很大的好处。
如果对象是同一进程中的线程,可以发送自定义消息,传递数据给目标线程,如果是线程在不同的进程中,就涉及进程之间的通讯了。
下面将会讲到。
进程之间的通讯:
当线程分属于不同进程,也就是分驻在不同的地址空间时,它们之间的通讯需要跨越地址空间的边界,便得采取一些与同一进程中不同线程间通讯不同的方法。
1、Windows专门定义了一个消息:WM_COPYDATA,用来在线程之间搬移数据,――不管两个线程是否同属于一个进程。
同时接受这个消息的线程必须有一个窗口,即必须是UI线程。
WM_COPYDATA必须由SendMessage()来发送,不能由PostMessage()等来发送,这是由待发送数据缓冲区的生命期决定的,出于安全的需要。
2、WM_COPYDATA效率上面不是太高,如果要求高效率,可以考虑使用共享内存(Shared Memory)。
使用共享内存要做的是:
设定一块内存共享区域;
使用共享内存;
同步处理共享内存。
第一步:设定一块内存共享区域。
首先,CreateFileMapping()产生一个
file-mapping核心对象,并指定共享区域的大小。
MapViewOfFile()获得一个指针指向可用的内存。
如果是C/S模式,由Server端来产生file-mapping,那么Client端使用OpenFileMapping(),然后调用MapViewOfFile()。
第二步:使用共享内存。
共享内存指针的使用是一件比较麻烦的事,我们需要借助_based属性,允许指针被定义为从某一点开始起算的32位偏移值。
第三步:清理。
UnmapViewOfFile()交出由MapViewOfFile()获得的指针,CloseHandle()交出file-mapping核心对象的handle。
第四步:同步处理。
可以借助Mutex来进行同步处理。
3、IPC
1)Anonymous Pipes。
Anonymous Pipes只被使用于点对点通讯。
当一个进程产生另一个进程时,这是最有用的一种通讯方式。
2)Named Pipes。
Named Pipes可以是单向,也可以是双向,并且可以跨越网络,步局限于单机。
3)Mailslots。
Mailslots为广播式通讯。
Server进程可以产生Mailslots,任何Client进程可以写数据进去,但是只有Server进程可以取数据。
4)OLE Automation。
OLE Automation和UDP都是更高阶的机制,允许通讯发生于不同进程间,甚至不同机器间。
5)DDE。
DDE动态数据交换,使用于16位Windows,目前这一方式应尽量避免使用。
下面的是源代码:
**** DataAnalyzer.h ****
class DataAnalyzer
{
public:
int Run();
private:
void AnalyzingData();
static DWORD WINAPI AnalyzingThread(LPVOID lpParameter);
static DWORD WINAPI ManagerThread(LPVOID lpParameter);
static char* datafilepath; // 数据输入文件名
static const int maxthread; // 最大线程数
static HANDLE m_hEvent;
typedef struct
{
HANDLE hEvent; // 线程对应的事件句柄
int number; // 数据
}threaddata;
};
**** DataAnalyzer.cpp ****
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
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 nt ");
if(m_hEvent == NULL)
{
cout < < "没有找到需要的事件。
\n "
< < "DataAnalyzer只能够由DataMaker调用,请勿直接使用.\n ";
cout < < "按ENTER 键退出..... ";
cin.get();
return -1;
}
cout < < "DataAnalyzer 正在启动.... ";
Sleep(1000);
cout < < "OK\n\n\n ";
// 设置事件为有信号,使得正在等待的DataMaker 可以继续运行
SetEvent(m_hEvent);
while(GetMessage(&msg, NULL, NULL, NULL))
{
switch(msg.message)
{
case WM_NOTIFY: // DataMaker 数据包写入完毕后收到这个消息
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);
datafilepath = new char[nlen];
memcpy(datafilepath, (BYTE*)lpview + 4, nlen);
UnmapViewOfFile(lpview);
CloseHandle(hfilemap);
cout < < "datafilepath : " < < datafilepath <
< endl < < endl;
// 将事件变为有信号,以此通知DataMaker 已经准备好了。
SetEvent(m_hEvent);
}
else
{
cout < < "#### 收到DataMaker 发送的消息,数据已经写入完毕.\n ";
AnalyzingData();
}
break;
case WM_CLOSE: // 任务完成时收到这个消息
cout < < "\n\n\n#### 收到DataMaker 发送的消息,可以退出运行了.\n ";
delete datafilepath;
PostQuitMessage(0);
break;
}
}
// 让事件变为有信号,通知DataMaker, DataAnalyzer进程即将结束SetEvent(m_hEvent);
CloseHandle(m_hEvent);
cout < < "按ENTER 键关闭窗口. ";
cin.get();
return 0;
}
void DataAnalyzer::AnalyzingData()
{
cout < < "#### 建立管理者线程.\n ";
DWORD dwThreadID;
HANDLE hManagerThread = CreateThread(NULL, 0,
ManagerThread, 0, CREATE_SUSPENDED,
&dwThreadID);
cout < < "#### 等待管理者线程结束.\n ";
ResumeThread(hManagerThread);
WaitForSingleObject(hManagerThread, INFINITE);
CloseHandle(hManagerThread);
// 让事件变为有信号,通知DataMaker 分析工作已经结束
cout < < "#### 分析工作已经全部完成.\n\n\n ";
SetEvent(m_hEvent);
}
DWORD DataAnalyzer::ManagerThread(LPVOID lpParameter)
{
HANDLE* hThreads = new HANDLE[maxthread]; // 保存句柄HANDLE* hEvents = new HANDLE[maxthread]; // 保存事件threaddata* tds = new threaddata[maxthread]; // 线程需要的数据
cout < < "@@@@ 管理线程开始.\n ";
// 建立事件
for(int i = 0; i < maxthread; i++)
{
hEvents[i] = CreateEvent(NULL, FALSE, TRUE, NULL);
}
DWORD dwThreadID;
SYSTEMTIME stime;
GetSystemTime(&stime);
for(int nCount = 0; nCount < stime.wMilliseconds % 10 + 1; nCount++)
{
// 等待一个分析线程结束
i = WaitForMultipleObjects(maxthread, hEvents, FALSE, INFINI TE) - WAIT_OBJECT_0;
// 关闭句柄
CloseHandle(hThreads[i]);
// 为分析线程准备数据
Sleep(350);
tds[i].hEvent = hEvents[i];
// 模拟读入的数据
tds[i].number = 0;
// 建立分析线程但不立刻启动
hThreads[i] = CreateThread(NULL, 0,
AnalyzingThread, (LPVOID)&tds[i],
0,
&dwThreadID);
}
// 等待所有的分析线程结束
for(i = 0; i < maxthread; i++)
{
WaitForSingleObject(hThreads[i], INFINITE);
CloseHandle(hEvents[i]);
}
delete hThreads;
delete hEvents;
delete tds;
cout < < "@@@@ 管理线程结束.\n ";
return 0;
}
DWORD DataAnalyzer::AnalyzingThread(LPVOID lpParameter)
{
threaddata* ptd = (threaddata*)lpParameter;
DWORD dwid = GetCurrentThreadId();
cout < < " " < < hex < < dwid < < " 分析线程开
始. START\n ";
// 模拟数据分析过程
Sleep(ptd-> number);
// 将事件设置为有信号后,前面ManagerThread 中的
// i = WaitForMultipleObjects(maxthread, hEvents, FALSE, INFIN ITE) - WAIT_OBJECT_0
// 将返回一个已经空闲的句柄的索引
SetEvent(ptd-> hEvent);
cout < < " " < < hex < < dwid < < " 分析线程结
束. END\n ";
return 0;
}
int main(int argc, char* argv[])
{
DataAnalyzer theObj;
return theObj.Run();
}
**** DataMaker.h ****
class DataMaker
{
public:
int Run();
private:
void MakeData(); // 准备数据包
void SaveData(); // 写入数据包
void StartDataAnalyzer(); // 启动DataAnalyzer
DWORD m_idDataAnalyzer; // DataAnalyzer 的线程ID
HANDLE m_hEvent;
static const char* const datafilepath; // 数据文件
};
**** DataMaker.cpp ****
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
using namespace std;
#include "datamaker.h "
const char* const DataMaker::datafilepath = "c:\\datafile.txt ";
void DataMaker::StartDataAnalyzer()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
// 建立事件
m_hEvent = CreateEvent(NULL, TRUE, FALSE, "syn_event ");
// 建立进程DataAnalyzer
if(CreateProcess(TEXT( "DataAnalyzer.exe "),
NULL,
NULL,
NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOL E,
NULL,
NULL,
&si,
&pi) == FALSE)
{
cout < < GetLastError() < < endl;
throw 0; // 如果建立进程失败抛出异常
}
cout < < "等待DataAnalyzer 做好准备..... ";
WaitForSingleObject(m_hEvent, INFINITE);
cout < < "OK\n ";
CloseHandle(pi.hProcess);
m_idDataAnalyzer = pi.dwThreadId; // 得到DataAnalyzer 的线程ID
}
void DataMaker::MakeData()
{
cout < < "DataMaker 正在准备数据.... ";
SYSTEMTIME stime;
GetSystemTime(&stime);
Sleep(stime.wMilliseconds % 3000);
cout < < "OK\n ";
}
void DataMaker::SaveData()
{
// 模拟写数据
cout < < "正在写入数据.... ";
SYSTEMTIME stime;
GetSystemTime(&stime);
Sleep(stime.wMilliseconds % 1000);
cout < < "OK\n ";
}
int DataMaker::Run()
{
cout < < "DataMaker 开始运行.\n ";
// 启动DataAnalyzer
try
{
StartDataAnalyzer();
}
catch(...)
{
cout < < "启动DataAnalyzer 失败.\n ";
return -1;
}
cout < < "建立页面文件文件映射来和DataAnalyzer 共享数据.\n ";
HANDLE hfilemap = CreateFileMapping((HANDLE)0xfffffffff, 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(datafilepath) + 1 );
UnmapViewOfFile(lpview);
cout < < "通知DataAnalyzer 接收数据文件名, ";
ResetEvent(m_hEvent);
PostThreadMessage(m_idDataAnalyzer, WM_NOTIFY, (WPARAM)1, N ULL);
cout < < "并等待其完成处理..... ";
WaitForSingleObject(m_hEvent, INFINITE);
cout < < "OK\n ";
CloseHandle(hfilemap);
for(int nCount = 0; nCount < 10; nCount++)
{
cout < < "\n\n*****************************\n\n "
< < "进行第" < < dec < < nCount + 1 <
< " 次数据数据准备工作.\n ";
// 准备数据包
MakeData();
// 等待事件变为有信号
// DataAnalyzer 会在数据处理完毕后将事件变为有信号
cout < < "等待DataAnalyzer 的分析工作完成..... ";
WaitForSingleObject(m_hEvent, INFINITE);
cout < < "OK\n ";
// 保存数据包
SaveData();
// 通知DataAnalyzer 数据包已经写入完毕
cout < < "通知DataAnalyzer 新的数据包已经写入到文件中了.\n ";
ResetEvent(m_hEvent);
PostThreadMessage(m_idDataAnalyzer, WM_NOTIFY, NULL, NUL L);
}
cout < < "\n\n没有数据需要分析了,"
< < "等待DataAnalyzer 完成分析任务.... ";
// 等待DataAnalyzer 完成任务
WaitForSingleObject(m_hEvent, INFINITE);
cout < < "OK\n ";
// 通知DataAnalyzer 任务完成,可以退出了
cout < < "OK,大家可以休息了.\n ";
PostThreadMessage(m_idDataAnalyzer, WM_CLOSE, NULL, NULL);
// 释放事件
CloseHandle(m_hEvent);
cout < < "DataMaker 运行结束.\n按ENTER 键关闭窗口. ";
cin.get();
return 0;
}
int main(int argc, char* argv[])
{
DataMaker theObj;
return theObj.Run();
}
**** 说明****
实例包含两个文件DataMaker.exe和DataAnalyzer.exe,都是控制台程序。
我们启动程序DataMaker后,DataMaker会自动的创建一个新进程DataAnalyzer。
之后DataMaker开始准备一系列由不同种类的数据组成的数据包,准备完成后,DataMaker打开一个文件,将这个数据包添加到文件中。
添加完成后,DataMaker 将通知DataAnalyzer新的数据包已经添加完毕,然后接着继续准备新的数据包。
而同一时刻,DataAnalyzer一直在等待DataMaker添加完数据包后发送的通知。
在收到通知后,DataAnalyzer会打开文件开始处理数据包,并且创建一些线程来分析数据,但同一时刻运行的线程数量不能超过上限。
DataAnalyzer在分析工作完毕后会通知DataMaker。
而DataMaker在没有收到DataAnalyzer发送的处理完毕通知前不会再次向文件中添加新的数据包。
在这个过程进行10次后,DataMaker将通知DataAnalyzer工作已经结束,可以退出了。
然后DataMaker和DataAnalyzer都将结束运行。
当然,为了缩小实例的长度和降低复杂性,所有针对文件的操作都是模拟的。
单态设计模式(Singleton Pattern)
一、初识单态
单态设计模式(Singleton Pattern),又称单件,单个等设计模式。
也许,在java方面的材料中你会发现叫法多为单态,而在c#方面的材料中的叫法多为单件。
无论是那种叫法,都是指的同一种设计模式Singleton pattern,笔者习惯叫单态,在本文中以单态称呼。
二、单态的特点和定义
单态,简单的讲,就是保证程序在运行的过程中只允许有一个实例对象产生。
期特点简单的描述为:
1. 单态类只能有一个实例。
2. 单态类必须自己创建自己的一个唯一的实例,
3. 单态类必须给客户端使用次实例的方式,即通过一定的方式返回
三、单态的原型
使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。
反过来,如果一个类可以有几个实例共存,就不要使用单例模式。
单态的社会原型非常多,像美国的总统,只能有一个,是通过选举得来的;像打印机的当前的打印作业也只能有一个,等等。
注意:不要使用单例模式存取全局变量。
这违背了单例模式的用意,最好放到对应类的静态成员中。
不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽
可能及时释放连接。
Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。
四、单态的实现
单态的实现,用一句比较诙谐的话来说就是:“只需周官放火,不许百姓点灯”。
概要的讲单态的设计就是上面那句话,在类中把构造函数私有化,这样就不能在类的外部(客户端)用 new来创建类的实例了(这就是不许百姓点灯),然后在类的内部来实例化一份类的实例,通过某种方式返回给客户端(这就是所谓的只需州官防火),java中通常是通过一个静态方法返回,C#中有时候也是会使用属性来返回的。
具体的讲,单态的实现主要是在对象实例化的时候有些区别,问题在于什么时候来实例化这个对象,分两种方式。
给个例子吧,两种方式:硬汉和懒汉方式。
模式1:
public 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 static 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());
instance.setX(50);
SingletonII newInstance = SingletonII.newInstance(); System.out.println(newInstance.getX());
}
}
有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。
问题:
1、如何判断一个链表是不是这类链表?
2、如果链表为存在环,如果找到环的入口点?
思考:
一、判断链表是否存在环,我们可以用循环实现(如算法二提到的),但是那样效率较低为:
设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。
(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:
01 bool IsExitsLoop(slist *head)
02 {
03 slist *slow = head, *fast = head;
04
05 while ( fast && fast->next )
06 {
07 slow = slow->next;
08 fast = fast->next->next;
09 if ( slow == fast ) break;
10 }
11
12 return !(fast == NULL || fast->next == NULL);
13 }
二、找到环的入口点
(1)我们首先想到一个简单的方法,算法复杂度为O(N^2)的双循环的方法,如下
01 LinkedList* IsCyclicLinkedList(LinkedList *pHead)
02 {
03 if(pHead==NULL || pHead->pNext == NULL) return NULL; //
返回空说明无环
04
05 LinkedList *pCur;
06 LinkedList *pStart;
07
08 pCur = pStart = pHead->pNext; //头指针不用,指向头结
点
09
10 while(pCur != NULL)
11 {
12 for(pStart = pCur;pStart != NULL;) //加上控制
条件
13 {
14 if(pStart->pNext == pCur)
15 return pCur; //返回环的起始
位置
16 pStart = pStart->pNext;
17 }
18 pCur = pCur->pNext;
19 }
20 return pStart;
21
22 }
2.我们还是延续上面的算法,把这个计算出来:
当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(n>=1)。
假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:
2s = s + nr
s= nr
设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L – a
a = (n-1)r + (L – a – x)
(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
程序描述如下:
(思考了一下,觉得胡满超分析的没有表达很明白,蓝色标示,如果存在环,相遇点和入口点必定两条路S1和S2,如果我们认定为S1=x的话,那么S2L-a-x,这样就更严谨了,呵呵)
01 slist* FindLoopPort(slist *head)
02 {
03 slist *slow = head, *fast = head;
04
05 while ( fast && fast->next )
06 {
07 slow = slow->next;
08 fast = fast->next->next;
09 if ( slow == fast ) break;
10 }
11
12 if (fast == NULL || fast->next == NULL)
13 return NULL;
14
15 slow = head;
16 while (slow != fast)
17 {
18 slow = slow->next;
19 fast = fast->next;
20 }
21
22 return slow;
23 }
扩展问题:
判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。
比较好的方法有两个:
一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。
二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。
这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。
--------------------------------------------------------------------- 1.链表:单链表,双链表,循环链表。
常考的东西:单链表反转,判断单链表
中是否有循环,双链表中删除一个节点。
//----------------------------------------------
// Note: This is a template class for chain
// Author: Kevin
// Date: 2010.4.26
//----------------------------------------------
#ifndef T_CHAIN_H
#define T_CHAIN_H
#include<iostream>
template <class T> class Chain;
template <class T> class ChainIterator;
template <class T>
class ChainNode {
friend Chain<T>;
friend ChainIterator<T>;
T data;
ChainNode<T> *link;
};
template<class T>
class Chain {
friend ChainIterator<T>;
public:
Chain() {first = 0;}
~Chain();
bool IsEmpty() const {return first == 0;} int Length() const;
bool Find(int k, T& x) const;
int Search(const T& x) const;
Chain<T>& Delete(int k, T& x);
Chain<T>& Insert(int k, const T& x);
Chain<T>& append(const T& x);
void Invert();
bool IsExitsLoop() ;
void Output() const;
ChainNode<T> *first; // pointer to first node };
template<class T>
Chain<T>::~Chain()
{// Chain destructor. Delete all nodes in chain.
ChainNode<T> *next; // next node
while (first) {
next = first->link;
delete first;
first = next;
}
}
template<class T>
int Chain<T>::Length() const
{// Return the number of elements in the chain.
ChainNode<T> *current = first;
int len = 0;
while (current) {
len++;
current = current->link;
}
return len;
}
template<class T>
bool Chain<T>::Find(int k, T& x) const
{// Set x to the k'th element in the chain.
// Return false if no k'th; return true otherwise.
if (k < 1) return false;
ChainNode<T> *current = first;
int index = 1; // index of current
while (index < k && current) {
current = current->link;
index++;
}
if (current) {x = current->data;
return true;}
return false; // no k'th element
}
template<class T>
int Chain<T>::Search(const T& x) const
{// Locate x. Return position of x if found.
// Return 0 if x not in the chain.
ChainNode<T> *current = first;
int index = 1; // index of current
while (current && current->data != x) {
current = current->link;
index++;
}
if (current) return index;
return 0;
}
template<class T>
Chain<T>& Chain<T>::Delete(int k, T& x)
{// Set x to the k'th element and delete it.
// Throw OutOfBounds exception if no k'th element.
if (k < 1 || !first)
{
cout << "delete error!" << endl;
return *this;
}
// p will eventually point to k'th node
ChainNode<T> *p = first;
// move p to k'th & remove from chain
if (k == 1) // p already at k'th
first = first->link; // remove
else { // use q to get to k-1'st
ChainNode<T> *q = first;
for (int index = 1; index < k - 1 && q; i ndex++) q = q->link;
if (!q || !q->link)
{
cout << "delete error!" << endl;
return *this;
}
p = q->link; // k'th
q->link = p->link;} // remove from chain
// save k'th element and free node p
x = p->data;
delete p;
return *this;
}
template<class T>
Chain<T>& Chain<T>::Insert(int k, const T& x)
{// Insert x after the k'th element.
// Throw OutOfBounds exception if no k'th element.
// Pass NoMem exception if inadequate space.
if (k < 0)
{
cout << "insert error!" << endl;
return *this;
}
// p will eventually point to k'th node
ChainNode<T> *p = first;
for (int index = 1; index < k && p; index++) // move p to k'th p = p->link;
if (k > 0 && !p)
{
cout << "insert error!" << endl;
return *this;
}
// insert
ChainNode<T> *y = new ChainNode<T>;
y->data = x;
if (k) {// insert after p
y->link = p->link;
p->link = y;}
else {// insert as first element
y->link = first;
first = y;}
return *this;
}
template<class T>
Chain<T>& Chain<T>::append(const T& x) {
int len = Length();
Insert(len, x);
return *this;
}
template <class T>
void Chain<T>::Invert()
{
ChainNode<T> *p = first, *q = 0;//first是链表的头节点while(p){
ChainNode<T> *r = q;
q = p;
p = p->link;
q->link = r;
}
first = q;
}
template <class T>
bool Chain<T>::IsExitsLoop()
{
ChainNode<T> *slow = first, *fast = first;
while ( fast && fast->link )
{
slow = slow->link;
fast = fast->link->link;
if ( slow == fast )
break;//have loop in there
}
return !(fast == NULL || fast->link == NULL);
}
template<class T>
void Chain<T>::Output() const
{// Insert the chain elements into the stream out.
ChainNode<T> *current;
for (current = first; current; current = current->link)
cout << current->data << " ";
}
#endif
堆栈和队列:深度遍历,广度遍历二叉树或者图就是用堆栈和队列。
//----------------------------------------------
// Note: This is a template class for sorting
// Author: Kevin
// Date: 2010.4.27
//----------------------------------------------。