C++面试题

1)为什么要将析构函数设置成虚函数,有什么好处?

面向对象编程的3个基本概念:数据抽象、继承、动态绑定。

我们在设计类似时,一般讲析构函数设计成visual,因为这样有继承时,不会造成内存泄露问题,举例如下:

class CA
{
public:
 CA(){cout<<"CA constructor"<<endl;}
 
    ~CA(){cout<<"CA desstructor"<<endl;}

};

class CB:public CA
{
public:
 CB(){cout<<"CB constructor"<<endl;}
 
 ~CB(){cout<<"CB desstructor"<<endl;}
};

class CC:public CB
{
public:
 CC(){cout<<"CC constructor"<<endl;}
 
 ~CC(){cout<<"CC desstructor"<<endl;}
};


如果只是定义一个CC的对象,那么调用的顺序如下:

(1) int main()
{
   CC p ;
}
这个程序运行结果是
CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor


那么如果是多态呢,即定义了基类的指针指向派生类的对象;

~CA(){cout<<"CA desstructor"<<endl;}  ----->>>
virtual  ~CA(){cout<<"CA desstructor"<<endl;}

修改main 代码如下
int main()
{
   CA * p = new CC();

   delete p;

   return 0;
}

结果
CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor

但是如果把virtual  ~CA(){cout<<"CA desstructor"<<endl;}
的virtual 去掉

那么运行结果为:
CA constructor
CB constructor
CC constructor

CA desstructor

只调了CA的析构函数,


将基类的析构函数设为虚函数后,派生类CB会继承基类的虚函数,表现如下:

保留CA中的虚析构函数

修改main 代码如下
int main()
{
   CB * p = new CC();

   delete p;

   return 0;
}

运行结果
CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor


结果也是正确的。我们一般将析构函数设置成虚函数,如果只将CB的析构函数设置成虚函数,而CA不设置成虚函数,那么有什么表现呢:

CA,CB,CC中,只有CB是虚析构函数
3中代码运行如下

CA constructor
CB constructor
CC constructor

CC desstructor
CB desstructor
CA desstructor


所以,如果是CB指向派生类,只要CB或者其基类中存在虚析构函数,那么也是所有的析构函数都调用的了。


但是如果CA,CB,CC都不是虚析构函数,结果如下:

代码运行结果如下

CA constructor
CB constructor
CC constructor

CB desstructor
CA desstructor


结论:

在delete p的时候,那么有以下几种情况:
1) C1或者C1的祖先(基类)中,含有虚析构函数,那么调用的析构函数的顺序是从C2一直到C2的第一个祖先
2)如果C1或者C1的祖先中,没有一个是类是含有虚析构函数的,那么调用的是从C1一直到C1(也是C2的)的第一个祖先的
3)如果C1是void,那就什么析构都不调用了。

如果一个类,作为多态的基类,那么尽量把析构函数声明成虚拟的。


2)C++多态是怎么理解;

关于对C++多态的理解,有一篇很好的博客:名字叫做:关于C++多态的理解

http://blog.csdn.net/echo779/article/details/8009332

 

 

3)C++基本概念

#include "stdio.h"

 

class CA

{

        

public:

         CA(intiValue=3)

         {

                   value=iValue;

         }

 

         intGetValue(void)

         {

                   returnvalue;

         }

 

         SetValue(intiValue)

         {

                   value=iValue;

         }

 

private:

         intvalue;

 

};

 

int main()

{

   CA ca;

         ca.GetValue();

         printf("value%d\n",ca.GetValue());

 

         intvalue=ca.value;            //error  CA的对象不能访问类的私有成员,如果ca.value为保护的,结果也一样;

         return0;

}

 

1)  类的私有成员变量或私有成员函数只能被类的成员函数或友元函数访问;

2)  类的保护成员变量或保护成员函数只能被类的成员函数或友元函数访问;

3)  类的共有的成员变量或成员函数可以被类的对象访问;

4)  Protected成员在基类和私有成员变量一样,但是它可以被派生类的成员函数访问,不能被基类或派生类的对象访问;

5)  类的公有继承,基类成员保持自己的访问级别:基类的public成员为派生类的public成员,基类的protected成员为派生类的protected成员;

6)  Protected继承,基类的public和protected成员在派生类中为protected成员;

7)  Private继承,基类的所有成员在派生类中为private成员;

8)  派生类虚函数调用基类的版本时,必须显示的使用作用域操作符,不然会导致无穷递归。

9)  多态的关键是动态绑定,决定是调用基类还是派生类,有实参或基类指针指向的对象决定;要触发动态绑定,必须满足2个条件:1)只有指定为虚函数的成员函数才能进行动态绑定,2)必须通过基类类型的引用或指针进行函数的调用;

10)基类或派生类可以使其它类或函数为友元,友元可以访问类的private和protected数据,但是友元不能继承,即在派生类中不能使用基类的友元函数。

11) 重载(overload)和覆盖(overide)的区别:overload指函数名相同,但是它的参数表列个数或顺序,类型不同;override指函数原型一样,但函数体不同,主要在继承中体现,比如虚函数;

 重载与覆盖
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

 

隐藏规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

12)派生类如何访问基类的静态成员函数?

通过类作用域操作符::也可以通过.->成员访问操作符。Static成员在整个继承层次中只有一个这样的成员,无论基类派生出多少个派生类,每个static成员只有一个这样的实例,如果static在基类中是private的,那么在派生类中不能访问。此时通过对象访问static成员函数,只要使用对象的类型而已,因为static成员函数是属于类的,而不是属于对象的,切记。

13)静态成员函数和非静态成员函数的区别?

静态成员函数与非静态成员函数的根本区别是: 非静态成员函数有this指针,静态成员函数并不属于某一对象,它与任何对象都无关,静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。

14)虚函数和纯虚函数的区别?

1)含有纯虚函数的类叫做抽象类,而只含有虚函数的类不能称为抽象类;2)虚函数可以直接使用,也可被子类重载后使用;3)而纯虚函数只有在子类中实现该函数才能使用;4)含有纯虚函数的类不能实例化。




3)工作者线程和用户界面线程之间的区别?

 MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
  工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
  在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:

  (1)

[cpp]  view plain copy
  1. CWinThread* AfxBeginThread(  
  2.   
  3.         AFX_THREADPROC pfnThreadProc,  
  4.             LPVOID pParam,  
  5.             int nPriority = THREAD_PRIORITY_NORMAL,  
  6.             UNT nStackSize = 0,  
  7.             DWORD dwCreateFlags = 0,  
  8.             LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  
  9.            );//用于创建工作者线程  


  PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:

  UINT ExecutingFunction(LPVOID pParam);

  请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。

  • pParam:      一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
  • nPriority:     线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
  • nStackSize:   线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
  • dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
  • lpSecurityAttrs:线程的安全属性指针,一般为NULL;

  (2)

[cpp]  view plain copy
  1. CWinThread* AfxBeginThread(  
  2.   
  3.        CRuntimeClass* pThreadClass,  
  4.             int nPriority = THREAD_PRIORITY_NORMAL,  
  5.             UNT nStackSize = 0,  
  6.             DWORD dwCreateFlags = 0,  
  7.             LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  
  8.            );   


  pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。

  下面我们对CWinThread类的数据成员及常用函数进行简要说明。

  • m_hThread:     当前线程的句柄;
  • m_nThreadID:   当前线程的ID;
  • m_pMainWnd: 指向应用程序主窗口的指针
  BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,UINT nStackSize=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

  该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。
  一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。

  virtual BOOL CWinThread::InitInstance();

  重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。

  virtual int CWinThread::ExitInstance();

  在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。


(4)堆栈内存什么时候会越界?

堆内存越界的原因有以下:

1)被其它内存越界,即我们的内存的被其它内存踩坏了;

2)我们的内存踩坏了别人的内存;

3)以上两种情况;


以下有篇博客对堆越界讲解的很清晰:

http://blog.chinaunix.net/uid-27629626-id-3312297.html


5)windows内核,分页内存和非分页内存的区别?

1)中断优先级;2)内存调度;3)缺页中断


虚拟内存如果能交换到文件中,这些内存被称为分页内存,如果虚拟内存永远不会交换到文件中,这些内存被称为非分页内存。

当程序的中断请求级在Dispatch_level之上时,(包括dispatch_level层),程序只能使用非分页内存,否则将导致蓝屏死机。


操作系统实现虚拟内存的主要方法就是通过分页机制。在Win32中,物理地址空间,二维虚拟地址空间和实际内存地址是三个不同的概念。操作系统通过段选择子构成二维虚拟地址空间,每个进程有一个4G的地址空间,然后操作系统的内存管理器件把每个进程映射到一维物理地址空间的不同部分,但是因为我们实际机器上大都没有4G内存,所以,实际内存空间是物理地址空间的子集。

分页管理器把地址空间划分成4K大小的页面(非Intel X86体系与之不同),当进程访问某个页面时,操作系统首先在Cache中查找页面,如果该页面不在内存中,则产生一个缺页中断(Page Fault),进程就会被阻塞,直至要访问的页面从外存调入内存中。
我们知道,在处理低优先级的中断时,仍可以发生高优先级的中断。既然缺页过程也是一个中断过程,那么就产生一个问题,即,缺页中断和其他中断的优先级的问题。如果在高于缺页中断的中断优先级上再发生缺页中断,内核就会崩溃。所以在DISPATCH_LEVEL级别以上,绝对不能使用分页内存,一旦使用分页内存,就有发生缺页中断的可能,前面说过,这样会导致内核崩溃。




6)window 多线程同步的机制有哪些,哪个效率最高,为什么?

多线程同步机制主要有:1)临界区,2)互斥;3)信号量,4)事件

1)临界区属于用户态的同步方法,效率比其它3个高,因为其它3个是内核态的同步方法,需要用户态到内核态的切换,这样需要耗尽千个cpu的时钟周期;

2)临界区只能用于进程内的线程同步,而其它3个可以用于进程之间的同步;

3)临界区保护的内容,不能占用较长的时间,因为这样会造成其它线程等待;


详细的解释如下:

在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。
  如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
  为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。
  线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。
  内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。

1.临界区
 临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
  临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
代码:

[cpp] viewplaincopy

1.  CRITICAL_SECTION g_cs;              // 临界区结构对象  

2.  char g_cArray[10];                  // 共享资源   

3.    

4.    

5.  UINT ThreadProc10(LPVOID pParam)  

6.  {  

7.   EnterCriticalSection(&g_cs);     // 进入临界区  

8.   for (int i = 0; i < 10; i++)      // 对共享资源进行写入操作  

9.   {  

10.    g_cArray[i] = 'a';  

11.    Sleep(1);  

12.   }  

13.    

14.   LeaveCriticalSection(&g_cs);      // 离开临界区  

15.   return 0;  

16.  }  

17.  UINT ThreadProc11(LPVOID pParam)  

18.  {  

19.     

20.   EnterCriticalSection(&g_cs);  

21.    

22.   for (int i = 0; i < 10; i++)  

23.   {  

24.    g_cArray[10 - i - 1] = 'b';  

25.    Sleep(1);  

26.   }  

27.    

28.   LeaveCriticalSection(&g_cs);  

29.   return 0;  

30.  }  

31.  ……  

32.  void CSample08View::OnCriticalSection()   

33.  {  

34.   InitializeCriticalSection(&g_cs);       // 初始化临界区  

35.     

36.   AfxBeginThread(ThreadProc10, NULL);      // 启动线程  

37.   AfxBeginThread(ThreadProc11, NULL);  

38.    

39.   Sleep(300);  

40.    

41.   CString sResult = CString(g_cArray);  

42.   AfxMessageBox(sResult);  

43.  }  


在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下:

代码:

[cpp] viewplaincopy

1.  Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->CCriticalSection g_clsCriticalSection;// MFC临界区类对象  

2.  char g_cArray[10];              // 共享资源   

3.    

4.  UINT ThreadProc20(LPVOID pParam)  

5.  {  

6.   g_clsCriticalSection.Lock();        // 进入临界区  

7.    

8.   for (int i = 0; i < 10; i++)       // 对共享资源进行写入操作  

9.   {  

10.    g_cArray[i] = 'a';  

11.    Sleep(1);  

12.   }  

13.   g_clsCriticalSection.Unlock();      // 离开临界区  

14.   return 0;  

15.  }  

16.  UINT ThreadProc21(LPVOID pParam)  

17.  {  

18.   g_clsCriticalSection.Lock();  

19.    

20.   for (int i = 0; i < 10; i++)  

21.   {  

22.    g_cArray[10 - i - 1] = 'b';  

23.    Sleep(1);  

24.   }  

25.    

26.   g_clsCriticalSection.Unlock();  

27.   return 0;  

28.  }  

29.  ……  

30.  void CSample08View::OnCriticalSectionMfc()   

31.  {  

32.   AfxBeginThread(ThreadProc20, NULL);  

33.   AfxBeginThread(ThreadProc21, NULL);  

34.    

35.   Sleep(300);  

36.    

37.   CString sResult = CString(g_cArray);  

38.   AfxMessageBox(sResult);  

39.  }  


2.事件内核对象

  在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:

[cpp] viewplaincopy

1.  代码   

2.    

3.  HANDLE hEvent = NULL;              // 事件句柄  

4.    

5.  char g_cArray[10];                 // 共享资源   

6.    

7.  UINT ThreadProc12(LPVOID pParam)  

8.  {  

9.   WaitForSingleObject(hEvent, INFINITE);  // 等待事件置位  

10.    

11.   for (int i = 0; i < 10; i++)  

12.   {  

13.    g_cArray[i] = 'a';  

14.    Sleep(1);  

15.   }  

16.     

17.   SetEvent(hEvent);              // 处理完成后即将事件对象置位  

18.   return 0;  

19.  }  

20.  UINT ThreadProc13(LPVOID pParam)  

21.  {  

22.   WaitForSingleObject(hEvent, INFINITE);  

23.    

24.   for (int i = 0; i < 10; i++)  

25.   {  

26.    g_cArray[10 - i - 1] = 'b';  

27.    Sleep(1);  

28.   }  

29.    

30.   SetEvent(hEvent);  

31.   return 0;  

32.  }  

33.  ……  

34.  void CSample08View::OnEvent()   

35.  {  

36.   hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);     // 创建事件  

37.    

38.   SetEvent(hEvent);                       // 事件置位  

39.    

40.   AfxBeginThread(ThreadProc12, NULL);            // 启动线程  

41.   AfxBeginThread(ThreadProc13, NULL);  

42.    

43.   Sleep(300);  

44.    

45.   CString sResult = CString(g_cArray);  

46.   AfxMessageBox(sResult);  

47.  }  


在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。
  使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:

[cpp] viewplaincopy

1.  HANDLE OpenEvent(  

2.   DWORD dwDesiredAccess,  // 访问标志  

3.   BOOL bInheritHandle,    // 继承标志  

4.   LPCTSTR lpName          // 指向事件对象名的指针  

5.  );  

如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。
  如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:

[cpp] viewplaincopy

1.  DWORD WaitForMultipleObjects(  

2.   DWORD nCount,              // 等待句柄数  

3.   CONST HANDLE *lpHandles,   // 句柄数组首地址  

4.   BOOL fWaitAll,             // 等待标志  

5.   DWORD dwMilliseconds       // 等待时间间隔  

6.  );  


参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。 下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:

[cpp] viewplaincopy

1.  代码   

2.    

3.  HANDLE hEvents[2];                                // 存放事件句柄的数组  

4.    

5.  UINT ThreadProc14(LPVOID pParam)  

6.  {   

7.   DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); // 等待开启事件  

8.     

9.   if (dwRet1 == WAIT_OBJECT_0)                       // 如果开启事件到达则线程开始执行任务  

10.   {  

11.    AfxMessageBox("线程开始工作!");  

12.    while (true)  

13.    {  

14.     for (int i = 0; i < 10000; i++);  

15.       

16.     DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);   // 在任务处理过程中等待结束事件   

17.       

18.     if (dwRet2 == WAIT_OBJECT_0 + 1)                   // 如果结束事件置位则立即终止任务的执行  

19.      break;  

20.    }  

21.   }  

22.   AfxMessageBox("线程退出!");  

23.   return 0;  

24.  }  

25.  ……  

26.  void CSample08View::OnStartEvent()   

27.  {  

28.     for (int i = 0; i < 2; i++)                      // 创建线程  

29.    hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);  

30.    AfxBeginThread(ThreadProc14, NULL);                  // 开启线程  

31.    SetEvent(hEvents[0]);                          // 设置事件0(开启事件)  

32.    

33.  }  

34.  void CSample08View::OnEndevent()   

35.  {  

36.    SetEvent(hEvents[1]);                          // 设置事件1(结束事件)  

37.    

38.  }  


MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数创建事件对象的职责,其函数原型为:
  CEvent(BOOLbInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL,LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

 

3.信号量内核对象

  信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
  使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:

[cpp] viewplaincopy

1.  HANDLE CreateSemaphore(  

2.   LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针  

3.   LONG lInitialCount,               // 初始计数  

4.   LONG lMaximumCount,               // 最大计数  

5.   LPCTSTR lpName                  // 对象名指针  

6.  );    


参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:

[cpp] viewplaincopy

1.  HANDLE OpenSemaphore(  

2.   DWORD dwDesiredAccess,   // 访问标志  

3.   BOOL bInheritHandle,    // 继承标志  

4.   LPCTSTR lpName        // 信号量名  

5.  );   


 在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:

[cpp] viewplaincopy

1.  BOOL ReleaseSemaphore(  

2.   HANDLE hSemaphore,    // 信号量句柄  

3.   LONG lReleaseCount,   // 计数递增数量  

4.   LPLONG lpPreviousCount  // 先前计数  

5.  );  


 该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。
  信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:

[cpp] viewplaincopy

1.  代码   

2.    

3.  HANDLE hSemaphore;                 // 信号量对象句柄  

4.    

5.  UINT ThreadProc15(LPVOID pParam)  

6.  {   

7.   WaitForSingleObject(hSemaphore, INFINITE);  // 试图进入信号量关口  

8.     

9.   AfxMessageBox("线程一正在执行!");        // 线程任务处理  

10.     

11.   ReleaseSemaphore(hSemaphore, 1, NULL);    // 释放信号量计数  

12.   return 0;  

13.  }  

14.  UINT ThreadProc16(LPVOID pParam)  

15.  {   

16.    

17.   WaitForSingleObject(hSemaphore, INFINITE);  

18.    

19.   AfxMessageBox("线程二正在执行!");  

20.    

21.   ReleaseSemaphore(hSemaphore, 1, NULL);  

22.   return 0;  

23.  }  

24.  UINT ThreadProc17(LPVOID pParam)  

25.  {   

26.    

27.   WaitForSingleObject(hSemaphore, INFINITE);  

28.    

29.   AfxMessageBox("线程三正在执行!");  

30.    

31.   ReleaseSemaphore(hSemaphore, 1, NULL);  

32.   return 0;  

33.  }  

34.  ……  

35.  void CSample08View::OnSemaphore()   

36.  {  

37.   hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);  // 创建信号量对象  

38.    

39.   AfxBeginThread(ThreadProc15, NULL);         // 开启线程  

40.   AfxBeginThread(ThreadProc16, NULL);  

41.   AfxBeginThread(ThreadProc17, NULL);  

42.  }  


MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:

   CSemaphore( LONG lInitialCount = 1, LONGlMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes =NULL );

  在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:

[cpp] viewplaincopy

1.  代码   

2.    

3.    

4.  Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->// MFC信号量类对象  

5.  CSemaphore g_clsSemaphore(2, 2);  

6.  UINT ThreadProc24(LPVOID pParam)  

7.  {   

8.   // 试图进入信号量关口  

9.   g_clsSemaphore.Lock();  

10.   // 线程任务处理  

11.   AfxMessageBox("线程一正在执行!");  

12.   // 释放信号量计数  

13.   g_clsSemaphore.Unlock();  

14.   return 0;  

15.  }  

16.  UINT ThreadProc25(LPVOID pParam)  

17.  {  

18.   // 试图进入信号量关口  

19.   g_clsSemaphore.Lock();  

20.   // 线程任务处理  

21.   AfxMessageBox("线程二正在执行!");  

22.   // 释放信号量计数  

23.   g_clsSemaphore.Unlock();  

24.   return 0;  

25.  }  

26.  UINT ThreadProc26(LPVOID pParam)  

27.  {  

28.   // 试图进入信号量关口  

29.   g_clsSemaphore.Lock();  

30.   // 线程任务处理  

31.   AfxMessageBox("线程三正在执行!");  

32.   // 释放信号量计数  

33.   g_clsSemaphore.Unlock();  

34.   return 0;  

35.  }  

36.  ……  

37.  void CSample08View::OnSemaphoreMfc()   

38.  {  

39.   // 开启线程  

40.   AfxBeginThread(ThreadProc24, NULL);  

41.   AfxBeginThread(ThreadProc25, NULL);  

42.   AfxBeginThread(ThreadProc26, NULL);  

43.  }  


4.互斥内核对象

    互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。

     以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为:

     HANDLECreateMutex(
      LPSECURITY_ATTRIBUTESlpMutexAttributes, //安全属性指针
      BOOL bInitialOwner, // 初始拥有者
      LPCTSTR lpName // 互斥对象名
     );

     参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄。OpenMutex()函数原型为:

    HANDLE OpenMutex(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 互斥对象名
    );

     当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:

    BOOLReleaseMutex(HANDLE hMutex);

    其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。
  在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:

[cpp] viewplaincopy

1.  代码   

2.    

3.    

4.  Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->// 互斥对象  

5.  HANDLE hMutex = NULL;  

6.  char g_cArray[10];  

7.  UINT ThreadProc18(LPVOID pParam)  

8.  {  

9.   // 等待互斥对象通知  

10.   WaitForSingleObject(hMutex, INFINITE);  

11.   // 对共享资源进行写入操作  

12.   for (int i = 0; i < 10; i++)  

13.   {  

14.    g_cArray[i] = 'a';  

15.    Sleep(1);  

16.   }  

17.   // 释放互斥对象  

18.   ReleaseMutex(hMutex);  

19.   return 0;  

20.  }  

21.  UINT ThreadProc19(LPVOID pParam)  

22.  {  

23.   // 等待互斥对象通知  

24.   WaitForSingleObject(hMutex, INFINITE);  

25.   // 对共享资源进行写入操作  

26.   for (int i = 0; i < 10; i++)  

27.   {  

28.    g_cArray[10 - i - 1] = 'b';  

29.    Sleep(1);  

30.   }  

31.   // 释放互斥对象  

32.   ReleaseMutex(hMutex);  

33.   return 0;  

34.  }  

35.  ……  

36.  void CSample08View::OnMutex()   

37.  {  

38.   // 创建互斥对象  

39.   hMutex = CreateMutex(NULL, FALSE, NULL);  

40.   // 启动线程  

41.   AfxBeginThread(ThreadProc18, NULL);  

42.   AfxBeginThread(ThreadProc19, NULL);  

43.   // 等待计算完毕  

44.   Sleep(300);  

45.   // 报告计算结果  

46.   CString sResult = CString(g_cArray);  

47.   AfxMessageBox(sResult);  

48.  }  


互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:
     CMutex(BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTESlpsaAttribute = NULL );   该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的,但要简洁的多,下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单:

[cpp] viewplaincopy

1.  代码   

2.    

3.    

4.  Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->  

[cpp] viewplaincopy

1.  CMutex g_clsMutex(FALSE, NULL);     // MFC互斥类对象  

2.    

3.  UINT ThreadProc27(LPVOID pParam)  

4.  {  

5.   g_clsMutex.Lock();                // 等待互斥对象通知  

6.    

7.   for (int i = 0; i < 10; i++)      // 对共享资源进行写入操作  

8.   {  

9.    g_cArray[i] = 'a';  

10.    Sleep(1);  

11.   }  

12.    

13.   g_clsMutex.Unlock();              // 释放互斥对象  

14.   return 0;  

15.  }  

16.  UINT ThreadProc28(LPVOID pParam)  

17.  {  

18.   g_clsMutex.Lock();  

19.    

20.   for (int i = 0; i < 10; i++)  

21.   {  

22.    g_cArray[10 - i - 1] = 'b';  

23.    Sleep(1);  

24.   }  

25.    

26.   g_clsMutex.Unlock();  

27.   return 0;  

28.  }  

29.  ……  

30.  void CSample08View::OnMutexMfc()   

31.  {  

32.   AfxBeginThread(ThreadProc27, NULL);  

33.   AfxBeginThread(ThreadProc28, NULL);  

34.    

35.   Sleep(300);  

36.    

37.   CString sResult = CString(g_cArray);  

38.   AfxMessageBox(sResult);  

39.  }  

 

7)拷贝构造函数和赋值构造函数的作用,及区别?

        在普通变量的赋值中,可以利用另外一个同类型的变量进行赋值,而用一个实例构造另外一个实例的方法有2种,1)先建立一个实例,然后将已经存在的实例值一一赋值给新的实例,另一种方法就是使用类的拷贝构造函数,类的拷贝构造函数的作用是将一个已经存在的实例去初始化另外一个新的同类实例。原形如下:

      类名(类名&实例名)

 

(一)当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。
    如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。
    自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
例子:
String a("hello");
String b("world");
String c = a; // 调用了拷贝构造函数,最好写成 c(a);
c = b; // 调用了赋值构造函数。!
本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。

赋值构造函数:

拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
不同点:
拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。 
operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
还要注意的是拷贝构造函数是构造函数,不返回值;而赋值函数需要返回一个对象自身的引用,以便赋值之后的操作。

   


 


8)构造函数可以是私有的吗?析构函数可以是私有的吗?

构造函数一般都是公有的,但是有例外,就是不需要创建对象的时候可以设为私有的,单例设计模式就是这样操作的。

析构函数只能是公有的。


(9)静态成员变量初始化?

静态成员变量只能在类外进行初始化。

 

(10)浅拷贝和深拷贝的区别

默认的copy构造函数无法对实例资源进行copy,如动态内存,如果在实例中的数据成员拥有资源,copy构造函数只会建立该数据成员的一个拷贝,而不会自动为它分配资源,这样两个 实例中就拥有同一个资源,而在析构函数中会释放2次资源,导致程序出错。

当对实例进行 拷贝 时,未对资源进行拷贝的过程称为浅拷贝。

浅拷贝和深拷贝的区别可以参考下面一个博客的内容。

 http://www.cnblogs.com/lionfight/archive/2012/05/25/2518631.html

在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
    深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。
    浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错。

下面是例子,注意看两种函数怎么写,什么地方用到:

#include <iostream>
using namespace std;
class CA
{
  public:
    CA(int b,char* cstr)
    {
      a=b;
      str=new char[b];
      strcpy(str,cstr);
    }
    CA(const CA& Other)                    //拷贝构造函数
    {
      a=Other.a;
      str=new char[a]; //深拷贝
      if(str!=0)
        strcpy(str,Other.str);
    }
 
    CA & operator = (const CA& Other)    //赋值符重载
    {
        a=Other.a;    //复制常规成员
        // (1) 检查自赋值
        if(this == &Other)
        return *this;
        // (2) 释放原有的内存资源
        delete [] str;
        // (3) 分配新的内存资源,并复制内容
        str = new char[a];
        strcpy(str,Other.str);
        // (4) 返回本对象的引用
        return *this;
    }
    void Show()
    {
      cout<<str<<endl;
    }
    ~CA()
    {
      delete str;
    }
  private:
    int a;
    char *str;
};
int main()
{
  CA A(10,"Hello!");
  CA B=A;
  B.Show();
  CA C(9,"world!");
  C = B;
  return 0;
} 

下面是另外一个例子,修改了下里面的赋值构造函数,把temp变量去掉了,这样更省空间吧?

class CExample
{
    ...
    CExample(const CExample&); //拷贝构造函数
    CExample& operator = (const CExample&); //赋值符重载
    ...
};
 
//拷贝构造函数使用赋值运算符重载的代码。
CExample::CExample(const CExample& RightSides)
{
    pBuffer=NULL;
    *this=RightSides     //调用重载后的"="
}
 
//赋值操作符重载
CExample & CExample::operator = (const CExample& RightSides)
{
    nSize=RightSides.nSize; //复制常规成员
    char *temp=new char[nSize]; //复制指针指向的内容 
    memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
 
    delete []pBuffer; //删除原指针指向内容  (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
    pBuffer=temp;   //建立新指向
    return *this
}
//其实也没啥改进,但觉得这样的写法是不是更好?省了一个temp临时变量。
CExample & CExample::operator = (const CExample& RightSides)
{
    nSize=RightSides.nSize; //复制常规成员
 
    delete []pBuffer; //删除原指针指向内容  (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
    pBuffer=new char[nSize];   //建立新指向
    memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
    return *this
}

 

(11)结构体变量之间可以直接赋值吗

结构变量之间支持赋值,但是这种赋值是按默认的按位拷贝的,这种方法具有浅拷贝类似的缺陷;可以参考这篇博客:

http://blog.sina.com.cn/s/blog_80435476010166o3.html

 

12)C++面试常见问题

面试常见问题:

1)Debug下和Release下变量初始化的问题?
debug版初始化成0xcc是因为0xcc在x86下是一条int 3单步中断指令,这样程序如果跑飞了遇到0xcc就会停下来,
这和单片机编程时一般将没用的代码空间填入jmp 0000语句是一样地; Release下赋值为随机数,所以变量定义后,尽量初始化;代码存在错误在debug方式下可能会忽略而不被察觉到,如debug方式下数组越界也大多不会出错,在 release中就暴露出来了,这个找起来就比较难了。


2)memcpy内存越界时,debug怎么处理,Release下怎么处理;
Debug下可能不报错;Release下就会内存错误,这一点也值得注意,因为VC对于Debug和Release版本生成的指令有些差异。

在Debug下,栈空间
SUB ESP了一个偏大的空间,导致你有时候栈上变量越界溢出也不会出现死机。
而Release版本不是这样的。

 

3)如何打印当前文件名和行号;
_LINE_;_FILE_

4)内存DC的理解

http://bbs.csdn.net/topics/310058859


5)windows 刷新窗口的方法有哪些?
调用UpdateWindow(),Invalidate()

6)节点数为N的完全二叉树有多少个叶子节点?
,假设n0是度为0的结点总数(即叶子结点数),n1是度为1的结点总数,n2是度为2的结点总数,由二叉树的性质可知:n0=n2+1,则n= n0+n1+n2(其中n为完全二叉树的结点总数),由上述公式把n2消去得:n= 2n0+n1-1,由于完全二叉树中度为1的结点数只有两种可能0或1,由此得到n0=(n+1)/2或n0=n/2。
总结起来,就是 n0=[n/2],其中[]表示上取整。可根据完全二叉树的结点总数计算出叶子结点数。


7)windows的入口函数是什么,windows的消息机制流程?

入口点是WinMain函数.

1.Windows中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。应用程序中含有一段称作“消息循环”的代码,用来从消息队列中检索这些消息并把它们分发到相应的窗口函数中。

2.Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」并将消息放入程序的消息队列中。程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息:

while(GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

TranslateMessage(&msg);将msg结构传给Windows,进行一些键盘转换。

DispatchMessage (&msg);又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。

 

7.1)SendMessage()与PostMessage()之间的区别是什么?

它们两者是用于向应用程序发送消息的。PostMessagex()将消息直接加入到应用程序的消息队列中,不等程序返回就退出;而SendMessage()则刚好相反,应用程序处理完此消息后,它才返回。

7.2)GetMessage和PeekMessage的区别?

http://blog.csdn.net/npjocj/article/details/6611029

 

7.3)工作者线程和用户界面线程的区别?

http://blog.csdn.net/wendysuly/article/details/3698045

UI thread : 派生CWinThread,//CWinThread::Run里有一个消息循环,所以派生这个类,
            //UI thread ,不仅要执行线程函数,还要处理消息
              然后调用afxBeginThread.
worker thread: 为它准备一个执行线程函数,然后调用AfxBeginThread;

 

8)在非静态成员函数中可以访问静态成员吗?
 
通过类名访问;
 
9)进程和线程概念的理解

什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:

       进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。
一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。
同一进程中的两段代码不能够同时执行,除非引入线程。
线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。
线程占用的资源要少于进程所占用的资源。
进程和线程都可以有优先级。
在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。


进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行


多线程共存于应用程序中是现代操作系统中的基本特征和重要标志。用过UNIX操作系统的读者知道进程,在UNIX操作系统中,每个应用程序的执行都在操作系统内核中登记一个进程标志,操作系统根据分配的标志对应用程序的执行进行调度和系统资源分配,但进程和线程有什么区别呢?

       进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于:

       线程的划分尺度小于进程,使得多线程程序的并发性高。

      另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

10)CreateThread函数和_beginThread()函数的区别?
 beginthread是_beginthreadex的功能子集,_beginthreadex是微软的C/C++运行时库函数,CreateThread是操作系统的函数。虽然_beginthread内部是调用_beginthreadex但他屏蔽了象安全特性这样的功能,所以_beginthread与CreateThread不是同等级别,_beginthreadex和CreateThread在功能上完全可替代,我们就来比较一下_beginthreadex与CreateThread! 

<<Windows核心编程>>中有很详细地介绍:

注意:若要创建一个新线程,绝对不要使用CreateThread,而应使用_beginthreadex.  
  Why?考虑标准C运行时库的一些变量和函数,如errno,这是一个全局变量。全局变量用于  
  多线程会出什么事,你一定知道的了。故必须存在一种机制,使得每个线程能够引用它自己的  
  errno变量,又不触及另一线程的errno变量._beginthreadex就为每个线程分配自己的  
  tiddata内存结构。该结构保存了许多像errno这样的变量和函数的值、地址。


11)线程结束的方法?
TerminateThread()函数,不推荐使用?
ExitThread()函数,用于线程自我终止的情况,主要在线程内被执行调用;
使用全局变量

12)进程间通信:消息机制和共享内存?
 1)管道;2)共享内存;3)socket;4)动态链接库(dll);5)串口;6)消息机制,比如postmessage;7)Event,Mutex,Semaphore,8)远程过程调用;
 

13)C++是类型安全的吗?

14)如何保证只有一个程序在运行; CreateMutex;

15)为什么需要将析构函数设置成虚函数;

16)多态的理解?纯虚函数和虚函数的区别?

首先讲解纯虚函数和虚函数的区别:

16.1)函数纯虚函数的类叫抽象类,而只含有虚函数的类不能称为抽象类;

16.2)虚函数可以直接使用,也可以被子类重载后使用,也可以被子类重载后使用;

16.3)而纯虚函数只有在子类中实现该函数才能使用;

16.4)函数纯虚函数的类不能实例化;

 

多态的重要特征就是动态绑定:它有两个要素;1)只有虚函数才能实现多态;2)必须通过引用或指针根据赋值的对象的不同调用不同的函数;

 

如何共享全局变量?


linux 下socket模型和windows socket模型的区别?

 

17)自己编程实现memcpy函数

void MyMemcpy(void *dest,const void*src ,int len)
{
 if(dest==NULL||src==NULL)
 {
  return;
 }

 char *Result=(char *)dest;
 const char *Source=(const char*)src;
 
 //dest>src 说明两段内存有交集,且dest-src<len

 if((&Result>&Source)&&((&Result-&Source)<len))
 {

  //
  for(int i=len-1;i>=0;i--)
  {
   *(Result+i)=*(Source+i);
  }

 }else
 {
  for(int i=0;i<len;i++)
  {
   *(Result+i)=*(Source+i);
  }
 }

}

 

12)什么是静态工厂?函数纯虚函数的接口类,怎么产生一个实例?

 

13)含有虚函数的基类在派生类中的函数没有用virtual,默认是虚函数,还是?如果再从派生类的非virtual函数继承,还是动态绑定吗?

 


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值