MFC线程(一):简单介绍
线程简介
我们知道一般情况程序中的代码都是按顺序从头开始一行一行的执行以最后.中间不能出现同时执行的情况.比如一段代码调用两个函数
FunOne();
FunTwo();
只要当函数FunOne中的代码执行完才返回来执行FunTwo.假如逻辑上是有先后顺序那还真只能这样按顺序执行下来.不过有假如FunOne与FunTwo没有逻辑先后顺序,是相互独立的.比如两个函数分别处理两不同的文件one.text与two.txt.
这种情形就可以用到线程,弄两个线程去执行这两函数.这样两函数同时执行,提高了效率(如果单核的CPU可能没有真正的并行效果不明显,那多核CPU执行多线程那是能够真正达到并行执行,效果很明显的).
实际上可以这样简单的理解线程,它是CPU的调度单位.而一个线程是对应一个函数.所以别把一个线程想得太复杂,就只是执行个函数而已.只不过执行的时候是并行执行罢了.如果只是简单的几个线程不涉及使用共同的资源,没其他啥关联.就完全跟简单的执行一个函数类似.只是如果多个线程间关系复杂就会涉及到啥同步问题,那样就有很多复杂的细节性问题.
线程与函数
线程函数必须是全局函数,或者是类的静态成员函数,因为非静态成员函数有this指针,而在进程中无法访问此指针。
但是静态成员函数只能访问静态成员,解决此问题途径:
1. 就是在调用静态成员函数时将this指针作为参数传入,通过该指针访问非静态成员。
2. 不将线程函数定义为类的静态成员函数,而是定义为类的友元函数,这样函数线程也可以有类成员函数相同的权限。
最简单示例
线程分工作线程与界面线程.这里就以工作线程为例
1.先来看个MFC中的创建线程的简单例子.
UINT ThreadFun(LPVOID pParam){ //线程要调用的函数
MessageBox(NULL,_T("i am called by a thread."), _T("thread func"),MB_OK);
}
::AfxBeginThread(ThreadFun, NULL); //这就是创建一个线程并执行了,调用上面的函数弹出一个对话框.
2.示例分析
上面的线程是简单的不能再简单了吧.下面从两个来分析下.
a.首先是被调用的函数有啥讲究不? 当然有,被线程用到的函数格式必须是统一的,返回类型必须是UINT,函数只能有一个参数LPVOID.其中UINT就是个无符号的整形,LPVOID是void*,所以这个参数表示可以传任何类型的指针过来的.
b.函数AfxBeginThread的分析.
这个函数还有返回值CWinThread*的,如果你只是简单的创建一个线程并执行,就不用管了.但如果想要对创建的线程做其他操作就必须这样写.
CWinThread* pThread = ::AfxBeginThread(ThreadFun, NULL); //接下来做啥就直接调用pThead就行.
另外函数AfxBeginThread的参数有很多个,但很多都有默认值.下面是完整的参数CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc, //一个函数指针
LPVOID pParam, //void*类型的指针,可以传任何种类指针过来.
int nPriority = THREAD_PRIORITY_NORMAL, //线程优先级
UNT nStackSize = 0, //分配堆栈大小
DWORD dwCreateFlags = 0, //表示线程创建后是立即执行还是等会执行LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程安全属性指针
);//用于创建工作者线程
上面的参数我们用的最多的是3个,其他一盘都默认值.
AFX_THREADPROC pfnThreadProc //函数指针肯定是必须要指定的,不然线程执行哪个函数去啊
LPVOID pParam //这是传给上面指定函数的参数.如果被调用的函数需要啥参数就只能在这里指定了.
DWORD dwCreateFlags //默认值为0表示创建线程后立即执行.如果是CREATE_SUSPEND 则表示创建好后先挂起.必须通过ResumeThread来执行.
稍复杂点的例子
扩充下上面的例子,给函数传入参数,并且休眠和挂起线程.
UINT ThreadFun(LPVOID pParam){ //线程要调用的函数
int* pNum = (int*)pParam; //假如会传入一个整形指针参数
MessageBox(NULL,_T("i am called by a thread."), _T("thread func"),MB_OK);
}
CWinThread* pThread; //定义一个线程指针
void CreateThread(){//创建一个线程并挂起
int* pNum = new int(88); //传入的参数
pThread
= ::AfxBeginThread(ThreadFun, pNum,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDE D);
}
void StartThread(){ //运行线程
pThread->ResumeThread();//唤醒线程
}
线程休眠与挂起的区别
上面示例中先创建一个线程,并让它挂起.(suspend),被挂起的线程只有通过ResumeThread才能开始执行.
而休眠则一般是这样使用.
UINT ThreadFun(LPVOID pParam){ //线程要调用的函数
::Sleep(1000); //表示函数执行到这里先休息1000微秒,也就是1秒.然后再接着执行下面的语句.
MessageBox(NULL,_T("i am called by a thread."), _T("thread func"),MB_OK);
}
所以休眠一般是会在线程调用的那个函数中指定.休眠在指定的时间后会自动再次执行,相当于暂停一断时间然后又自动活过来了,不用像挂起还必须得显式去启动才行.
内核对象进程
内核对象
这里的对象不不是指一个类的实例化,不过实际上也可以类似的等同.因为内核对象是内核分配的一个内存块,这种内存块就是一个结构体(struct).应用程序若需要访问内核对象需要
通过一些API函数,不能直接访问(基于安全的考虑).内核对象的拥有者是内核,所以何时释放对象的内存是由内核决定的.我们使用内核对象时一般是通过一个句柄支间接的使用,于是每有一个句柄与对象关联则对象的引用计数加1,当系统发现内核对象的引用计数为0时则释放内核对象内存.(看起来是不是有点像智能指针的用法了啊?)
进程与内核对象
每个进程在初始化时被分配一个句柄表,表中保存进程能访问的所有内核对象的句柄(进程是不能直接访问内核对象,只能先在找到句柄表中的句柄,然后再使用内核对象.)
当然进程还能通过CreateObject来创建一些内核对象,然后不使用时使用CloseHandle来关闭内核对象.
如果某个进程创建内核对象时指定SECURITY_ATTRIBUTES中的bInheritHandle为TRUE,创建子进程时(CreateProcess也设bInheritHandle为TRUE)则子进程也能拥有那个内核对象的访问权限(此时子进程的句柄表会复制该内核对象句柄过来,内核对象引用计数加1).当然如果父进程在创建了子进程之后再生成一些内核对象,则子进程是不会继承那访问权限的.
除了通过继承可以获得某个内核对象的访问权限外还可以通过同名共享(不过需要内核对象支持这种共享方式,不是所有种类的内核对象支持).当然通过CreateObject来创建好一个名为test1的内核对象后,此时如果有另外的进程再创建一个名为test1的内核对象那不会真的创建,而只是返回之前已创建好的test的句柄(看起来有点像是单例模式的应用啊)
另外还可以通过复制内核对象的句柄,通过DuplicateHandle,当然了前提是进程要有对那个句柄的访问权限先.(在句柄表中有)
线程与进程
进程只是个容器,不会执行任何操作.它里面有很多线程(至少必须有一个主线程).进程内的所有线程共享进程的内核对象.
当一个进程中止时所以线程自然中止.
///////////////////////
MFC启动和关闭线程
1、启动线程:
CWinThread* AfxBeginThread( 线程函数,this );
2、通常导致线程终止的两种情况是:控制函数退出或不允许线程完成运行。如果字处理器使用后台打印线程,若成功完成打印,则控制函数将正常终止。但是,如果用户要取消打印,后台打印线程则不得不提前终止。本主题介绍如何实现每一种情况,以及在终止后如何获取线程的退出代码。
(1)正常线程终止
对于辅助线程,正常线程终止很简单:退出控制函数并返回表示终止原因的值。可以使用函数或return 语句。一般情况下,0 表示成功完成,但这取决于您自己。
对于用户界面线程,该过程也很简单:从用户界面线程内调用Platform SDK 中的。PostQuitMessage 采用的唯一参数是线程的退出代码。对于辅助线程,0 通常表示成功完成。
(2)过早的线程终止
过早终止线程几乎一样简单:从线程内调用。将所需的退出代码作为唯一参数传递。这将停止执行线程、解除对线程堆栈的分配、分离附加到线程的所有DLL 并从内存中删除线程对象。
必须从要终止的线程内调用AfxEndThread。如果要从其他线程终止线程,必须设置两个线程间的通信方法。
举一个例子:
可以创建一个信号量,用WaitForSingleObject函数来检测该信号量的状态。
成员变量m_hThreadEvent;
m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
线程的执行函数:
for( ; ;)
{
DWORD dwRetVal;
dwRetVal = WaitForSingleObject( m_hThreadEvent, 100 );
if ( dwRetVal == WAIT_TIMEOUT )
{
// TODO:
}
else
{
// stop receive text thread.
DWORD dwExitCode;
GetExitCodeThread( m_pThreadRecv->m_hThread, &dwExitCode ); AfxEndThread( dwExitCode, TRUE );
}
}
要结束线程时,使用SetEvent,将信号量置为有信号。
该线程是在信号量有信号时,退出。
(3)TerminateThread
在CWinThread对象中有线程的句柄,可以使用该句柄强行杀死线程。但是不推荐使用这种方式,当可以正常结束的时候,选择前两种方法较好。
检索线程的退出代码
若要获取辅助线程或用户界面线程的退出代码,请调用函数。有关此函数的信息,请参见Platform SDK。此函数获取线程(存储在CWinThread 对象的m_hThread数据成员中)的句柄和DWORD 的地址。
如果线程仍然是活动的,GetExitCodeThread 将STILL_ACTIVE 放置在提供的DWORD 地址中;否则将退出代码放置在该地址中。
检索对象的退出代码还需要一步。默认情况下,当CWinThread 线程终止时,删除该线程对象。这意味着不能访问m_hThread数据成员,因为CWinThread 对象不再存在。若要避免出现这种情况,请执行以下操作之一:
?将m_bAutoDelete数据成员设置为FALSE。这使CWinThread 对象在线程终止后仍可以继续存在。然后可以在线程终止后,访问m_hThread数据成员。但是,如果使用此方法,就得销毁CWinThread 对象,因为框架不会自动删除该对象。这是首选方法。
?单独存储线程的句柄。创建线程后,(使用::DuplicateHandle)将其m_hThread数据成员复制到其他变量,并通过该变量访问该成员。这样,终止后即会自动删除对象,并且仍然可以找到线程终止的原因。请注意:在可以复制句柄之前,线程不终止。执行此操作的最安全的方式是将
CREATE_SUSPENDED 传递到,存储句柄,然后通过调用继续执行线程。
任一方法都可以使您确定CWinThread 对象终止的原因。
////////////////////////////////
线程中CreateEvent和SetEvent及WaitForSingleObject的用法
首先介绍CreateEvent是创建windows事件的意思,作用主要用在判断线程退出,线程锁定方面.
CreateEvent
函功能描述:创建或打开一个命名的或无名的事件对象.
EVENT有两种状态:发信号,不发信号。
SetEvent/ResetEvent分别将EVENT置为这两种状态分别是发信号与不发信号。
WaitForSingleObject()等待,直到参数所指定的OBJECT成为发信号状态时才返回,OBJECT 可以是EVENT,也可以是其它内核对象。
当你创建一个线程时,其实那个线程是一个循环,不像上面那样只运行一次的。这样就带来了一个问题,在那个死循环里要找到合适的条件退出那个死循环,那么是怎么样实现它的呢?在Windows里往往是采用事件的方式,当然还可以采用其它的方式。在这里先介绍
采用事件的方式来通知从线程运行函数退出来,它的实现原理是这样,在那个死循环里不断地使用WaitForSingleObject函数来检查事件是否满足,如果满足就退出线程,不满足就继续运行。当在线程里运行阻塞的函数时,就需要在退出线程时,先要把阻塞状态变成
非阻塞状态,比如使用一个线程去接收网络数据,同时使用阻塞的SOCKET时,那么要先关闭SOCKET,再发送事件信号,才可以退出线程的。
当然我感觉重要应用方面还是用来锁定,实现所谓的pv功能。
下面介绍函数功能,参数等
1.CreateEvent
2. WaitForSingleObject的用法
WaitForSingleObject的用法
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,// 句柄
__in DWORD dwMilliseconds// 时间间隔
);
hHandle[in]对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。
dwMilliseconds[in]定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。
WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。
WAIT_OBJECT_0 0x00000000 :指定的对象出有有信号状态
WAIT_TIMEOUT 0x00000102:等待超时
WAIT_FAILED 0xFFFFFFFF :出现错误,可通过GetLastError得到错误代码
在这里举个例子:
先创建一个全局Event对象g_event:
CEvent g_event;
在程序中可以通过调用CEvent::SetEvent设置事件为有信号状态。
下面是一个线程函数MyThreadProc()
UINT CFlushDlg::MyThreadProc( LPVOID pParam )
{
WaitForSingleObject(g_event,INFINITE);
For(;;)
{
………….
}
return0;
}
在这个线程函数中只有设置g_event为有信号状态时才执行下面的for循环,因为g_event是全局变量,所以我们可以在别的线程中通过g_event. SetEvent控制这个线程。
还有一种用法就是我们可以通过WaitForSingleObject函数来间隔的执行一个线程函数的函数体
UINT CFlushDlg::MyThreadProc( LPVOID pParam )
{
while(WaitForSingleObject(g_event,MT_INTERVAL)!=WAIT_OBJECT_0)
{
//……
}
return0;
}
在这个线程函数中可以通过设置MT_INTERVAL来控制这个线程的函数体多久执行一次,当事件为无信号状态时函数体隔MT_INTERVAL执行一次,当设置事件为有信号状态时,线程就执行完毕了。
//////////////////
安全终止MFC线程
终止线程
有两种情况可以使线程结束:控制函数结束或者根本就不允许线程完成,而提前终止它。我们可以想象在WORD中进行后台打印,如果打印结束了,那线程就可以结束了。如果用户中止了打印,那后台打印线程也要终止了。本文将主要介绍对这两种情况的实现,并且介绍如何获得线程的结束代码。
1.对于工作线程,结束它是比较容易的:退出线程函数然后返回一个结束原因的代码就是了。用户可以使用AfxEndThread函数或直接利用return返回。通常0代表成功返回,这不是硬性规定,一切要取决于你了。对于用户界面线程,调用::PostQuitMessage,它所要的唯一的参数就是返回代码,也就是工作线程中的那个码,性质是一样的。0通常代表成功。
2.提前终止一个线程也不难:在线程函数中调用AfxEndThread就是了,其中要传入的参数就是返回代码。这会停止线程的执行,释放线程栈,及与线程相关的DLL,并从内存中删除线程对象。AfxEndThread必须在线程函数内调用,如果用户希望从一个线程结束另一个线程,则需要在两个线程间建立通信机制。
如果需要获得线程返回代码,只需要调用::GetExitCodeThread就可以了。这个函数的具体作用就看大家具体去查帮助了。它传入的是线程的句柄,和一个提向返回代码的指针。将来就从那个指针得到返回代码。如果线程仍然处于活动状态,那么::GetExitCodeThread得到的返回代码为STILL_ACTIVE,如果已经退出则得到的是返回代码的地址。获得CWinThread对象的返回代码还需要一点麻烦,通常,当CWinThread线程结束时,线程对象就删除了,因为这个对象不存在了,也就没有办法访问对象的m_hThread变量了,为了避免这种情况,可以有两种方法:
将m_bAutoDelete设置为FALSE,这使得线程结束后CWinThread对象仍然存在,这样用户就可以访问m_hThread了,但是如果用户使用这种方法,用户需要自己析构CWinThread对象。这种方法是推荐的方法。
下一个方法是另外保存线程的句柄。在线程创建后,将m_hThread保存在另一个变量中,以后访问这个变量就是了。但是要小心,在复制句柄以前线程并没有结束,最安全的方法是在AfxBeginThread中传入CREATE_SUSPENDED,保存句柄,然后通过调用ResumeThread,重新开始线程。这两种方法都可以帮助用户得到CWinThread对象的返回代码。
对于Worker线程,终止线程可以使用线程的退出码作为返回值从线程函数返回。
对于UI线程,因为有消息循环,需要发送一个WM_QUIT消息到线程的消息队列,当线程接收到WM_QUIT消息时退出消息循环。因此,结束线程可以在线程内部调用SDK的PostQuitMessage函数,发送WM_QUIT消息。
PostQuitMessage函数的定义如下:
void PostQuitMessage(int nExitCode);
其中:
nExitCode:线程的退出码。
MFC还提供了AfxEndThread函数,Worker线程和UI线程都可以通过在线程内部调用AfxEndThread函数结束线程。
AfxEndThread函数的定义如下:
void AfxEndThread(UINT nExitCode, BOOL bDelete = TRUE);
其中:
nExitCode:线程的退出码。
在MFC的THRDCORE.CPP中,AfxEndThread函数的相关代码如下:
// THRDCORE.CPP
void AFXAPI AfxEndThread(UINT nExitCode, BOOL bDelete)
{
// remove current CWinThread object from memory
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
CWinThread* pThread = pState->m_pCurrentWinThread;
if (pThread != NULL)
{
ASSERT_VALID(pThread);
ASSERT(pThread != AfxGetApp());
// cleanup OLE if required
if (pThread->m_lpfnOleTermOrFreeLib != NULL)
(*pThread->m_lpfnOleTermOrFreeLib)(TRUE, FALSE);
if (bDelete)
pThread->Delete();
pState->m_pCurrentWinThread = NULL;
}
// allow cleanup of any thread local objects
AfxTermThread();
// allow C-runtime to cleanup, and exit the thread
_endthreadex(nExitCode);
}
从MFC代码中可以看出,AfxEndThread函数通过调用_endthreadex函数终止线程。此外,函数还进行释放线程的堆栈、删除线程对象等工作。
如果在其它线程中终止该线程,必须采用线程通信的方法实现。其中一种简单的方法是建立一个变量,让线程监视该变量,当该变量为某个值时,则终止线程。
(1)创建1个基于对话框的应用程序,名称为Demo。
(2)在IDD_DEMO_DIALOG对话框资源中添加控件,如表所示。
(3)在文件中定义线程传递参数的数据结构,代码如下:
// DemoDlg.h
typedef struct THREAD_PARAM
{
HWND hWnd;
int nData;
BOOL bExit;
}_THREAD_PARAM;
(4)在CDemoDlg类中添加成员变量,代码如下:
// DemoDlg.h
protected:
CWinThread* m_pThread;
THREAD_PARAM m_ThreadParam;
(5)在CDemoDlg类的构造函数中初始化成员变量,代码如下:
// DemoDlg.cpp
CDemoDlg::CDemoDlg(CWnd* pParent /*=NULL*/)
: CDialog(CDemoDlg::IDD, pParent)
{
// ...
m_pThread = NULL;
m_ThreadParam.nData = 0;
}
(6)在CDemoDlg类的OnInitDialog函数中添加如下代码:
// DemoDlg.cpp
BOOL CDemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// …
SetDlgItemInt(IDC_DATA, m_nData);
return TRUE;
}
(7)在文件中定义线程消息,代码如下:
// DemoDlg.h
#define WM_THREADMSG WM_USER+1
(8)在文件中定义线程函数,代码如下:
// DemoDlg.h
UINT ThreadProc(LPVOID pParam);
// DemoDlg.cpp
UINT ThreadProc(LPVOID pParam)
{
//线程参数
THREAD_PARAM* pThreadParam = (THREAD_PARAM*)pParam;
while (!pThreadParam->bExit)
{
Sleep(100);
pThreadParam->nData++;
//向主线程窗口发送消息
::PostMessage(pThreadParam->hWnd, WM_THREADMSG, 0, 0);
}
return0;
}
(9)在CDemoDlg类中分别为Button控件添加BN_CLICKED添加消息处理函数,代码如下:
// DemoDlg.cpp
void CDemoDlg::OnBeginThread()
{
if (m_pThread != NULL)
{
AfxMessageBox(_T("线程已经启动。"));
return;
}
m_ThreadParam.hWnd = m_hWnd;
m_ThreadParam.bExit = FALSE;
//启动线程,初始为挂起状态
m_pThread = AfxBeginThread(ThreadProc, &m_ThreadParam, THREAD_PRIORITY_ABOVE_NORMAL, 0, CREATE_SUSPENDED);
//线程结束时不自动删除
m_pThread->m_bAutoDelete = FALSE;
//恢复线程运行
m_pThread->ResumeThread();
}
void CDemoDlg::OnEndThread()
{
if (m_pThread == NULL)
{
AfxMessageBox(_T("线程已经终止。"));
return;
}
m_ThreadParam.bExit = TRUE;
//等待线程结束
::WaitForSingleObject(m_pThread->m_hThread, INFINITE);
delete m_pThread;
m_pThread = NULL;
}
(10)在CDemoDlg类中添加自定义消息处理函数,代码如下:
// DemoDlg.h
afx_msg LRESULT OnMsgFunc();
// DemoDlg.cpp
BEGIN_MESSAGE_MAP(CDemoDlg, CDialog)
ON_MESSAGE(WM_THREADMSG, OnMsgFunc)
END_MESSAGE_MAP()
LRESULT CDemoDlg::OnMsgFunc()
{
SetDlgItemInt(IDC_DATA, m_ThreadParam.nData);
return1;
}
//////////////////////////////////////////////////////////////////////////////////////// 指针
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。[1]在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。作个比喻,假设将电脑存储器当成一本书,一张内容记录了某个页码加上行号的便利贴,可以被当成是一个指向特定页面的指针;根据便利粘贴面的页码与行号,翻到那个页面,把那个页面的那一行文字读出来,就相当于是对这个指针进行反参考的动作。[2]
在信息工程中指针是一个用来指示一个内存地址的计算机语言的变量或中央处理器(CPU)中寄存器(Register)【用来指向该内存地址所对应的变量或数组】。指针一般出现在比较接近机器语言的语言,如汇编语言或C语言。面向对象的语言如Java一般避免用指针。
指针一般指向一个函数或一个变量。在使用一个指针时,一个程序既可以直接使用这个指针所储存的内存地址,又可以使用这个地址里储存的函数的值。
另外,指针也指钟表中用来指示对应时间的部件。
中文名
指针
外文名
pointer
类别
指示测量的数据的装置
适用范围
计算机
作用
通过它找到以它为地址的内存单元
目录
1. 1简介
2. 2信息工程
3. 3按值传递
4. 4*和&运算
1. 5另类*和&
2. 6双级指针
3. 7指针的初始化
4. 8与数组关系
1. 9与“引用”的区别
2. 10其他
简介
使用指针来读取数据,在重复性操作的状况下,可以明显改善程序性能,例如在遍历字符串,查取表格,控制表格及树状结构上。对指针进行复制,之后再解引用指针以取出数据,无论在时间或空间上,都比直接复制及访问数据本身来的经济快速。[2]
指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用(reference)数据形别。许多编程语言中都支持某种形式的指针,最著名的是C语言,但是有些编程语言对指针的运用采取比较严格的限制,如Java一般避免用指针,改为使用引用。[2]有两种含义,一是作为数据类型,二是作为实体。[2]
指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量。指针一般出现在比较底层的程序设计语言中,如C语言。高层的语言如Java一般避免用指针,而是引用。
[2]
指针作为数据类型,可以从一个函数类型、一个对象类型或者一个不完备类型中导出。
从中导出的数据类型称之为被引用类型(referenced type)。指针类型描述了一种对象,其值为对被引用类型的实体的引用。[2]
C++标准中规定,“指针”概念不适用于成员指针(不包含指向静态成员的指针)。C++标准规定,指针分为两类:[2]
?object pointer type:指向void或对象类型,表示对象在内存中的字节地址或空指针。
[2]
?function pointer type:指代一个函数[2]
信息工程
指针与C语言
大家都认为,c语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。
因此,说指针是c语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针。basic不支持指针,在此不论。其实,pascal语言本身也是支持指针的。从最初的pascal发展至今的object pascal,可以说在指针运用上,丝毫不会逊色于c语言的指针。
内存分配表
计算机中的内存都是编址的,就像你家的地址一样。在程序编译或者运行的时候,系统(可以不关心具体是什么,可能是编译器,也可能是操作系统)开辟了一张表。每遇到一
次声明语句(包括函数的传入参数的声明)都会开辟一个内存空间,并在表中增加一行纪录。记载着一些对应关系。(如图1所示)
图1
Declaration | ID Name Address Length
int nP; | 1 nP 2000 2B
char myChar; | 2 myChar 2002 1B
int *myPointer; | 3 myPointer 2003 2B
char *myPointer2; | 4 myPointer2 2005 2B
指针,是一个无符号整数(unsigned int),它是一个以当前系统寻址范围为取值范围的整数。32位系统下寻址能力(地址空间)是4G Bytes(0~2^32-1)二进制表示长度为32bits(也就是4Bytes),unsigned int类型也正好如此取值。
例证(一)
例证就是程序1得到的答案和程序2的答案一致。(不同机器可能需要调整一下pT的取值)
程序1
#include
main()
{
char *pT;
char t='h';
pT=&t;
putchar(*pT);
}
程序2
#include
main()
{
char *pT;
char t='h';
pT=(char *)1245048;
putchar(*pT);
}
间。(如图2所示)
图2
在32 位系统下,内存里面做如下分配(单位:H,16 进制);(如图3所示)
图3