总结2

1、什么是析构函数? 与构造函数的区别是什么?

析构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。 
以C++语言为例,析构函数名也应与类名相同,只是在函数名前面加一个波浪符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数,它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。
区别:构造函数是用来完成成员初始化的,析构是运行完程序释放掉变量的存储空间。析构函数释放对象所占用的资源。

2、什么时候调用构造函数和析构函数  
(1) 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。
(2) 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
(3) 如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

3、How many types of polymorphisms are supported by C++?

Types of Polymorphism:
1. Static / Compile time / Early binding polymorphism : 
a. Function overloading
b. Operator overloading

2. Dynamic / Run time / Late binding:
a. Virtual function

4、The difference between the call by value and call by reference are :The original value is not affected in call by value. That is only copies the value. Original value is affected in call by reference. That is value is changed after calling it.

5、构造函数中如何处理构造失败?为什么?

由于构造函数没有返回值,有两种方法来报告在构造对象期间发生的错误:

一、设置一个非局部的标记,并且希望用户检查它。

二、返回一个未完成的创建对象,并且希望用户检查它。

在构造函数中抛出异常,但是对象的析构函数不会被调用。在抛出异常的同时还需要对已经执行的动作(如分配了内存、打开了文件、锁定了信号量等等)进行清理,将这些资源释放掉。

6、什么情况下会产生野指针?举例说明

一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。 
二、指针p被free或者delete之后,没有置为NULL,因为p不是NULL指针,它不指向合法的内存块。

void function( void ) 

char* str = new char[100]; 
delete[] str; 
// Do something 
strcpy( str, "Dangerous!!" ); 

7、死锁什么情况下会发生?

产生死锁的必要条件是:1、互斥条件;2、不可剥夺条件(不可抢占);3、请求与保持;4、循环等待。  
如何预防死锁?  
答:根据产生死锁的四个必要条件,只要使其中之一不能成立,死锁就不会出现。为此,可以采取下列三种预防措施:  
1、采用资源静态分配策略,破坏"请求与保持"条件;  
2、允许进程剥夺使用其他进程占有的资源,从而破坏"不可剥夺"条件;  
3、采用资源有序分配法,破坏"环路"条件

8、程序输出:

[cpp]  view plain copy
  1. class A{  
  2. public:  
  3.     A(){  
  4.         cout<<"A()"<<endl;  
  5.     }  
  6.     ~A(){  
  7.         cout<<"~A()"<<endl;  
  8.     }  
  9. };  
  10. class B{  
  11. public:  
  12.     B(){  
  13.         cout<<"B()"<<endl;  
  14.     }  
  15.     ~B(){  
  16.         cout<<"~B()"<<endl;  
  17.     }  
  18. };  
  19. class X{  
  20. public:  
  21.     X(){  
  22.         cout<<"X()"<<endl;  
  23.     }  
  24.      ~X(){  
  25.         cout<<"~X()"<<endl;  
  26.     }  
  27.     virtual void test(int i=55){  
  28.         cout<<"test in X ()"<<endl;  
  29.     }  
  30. };  
  31. class C:public X{  
  32. private:  
  33.     A a;  
  34.     B b;  
  35. public:  
  36.     C():b(),a(){  
  37.         cout<<"C()"<<endl;  
  38.     }  
  39.     ~C(){  
  40.         cout<<"~C()"<<endl;  
  41.     }  
  42.     virtual void test(int i=99){  
  43.         cout<<"test in C()"<<i<<endl;  
  44.     }  
  45.   
  46. };  
  47. int main()  
  48. {  
  49.     C c;  
  50.     X* p=&c;  
  51.     p->test();  
  52.     //delete p;  
  53.     cout<<endl;  
  54.     return 0;  
  55. }  
  56. 输出:  
  57. X()  
  58. A()  
  59. B()  
  60. C()  
  61. test in C()55  
  62.   
  63. ~C()  
  64. ~B()  
  65. ~A()  
  66. ~X()  

[cpp]  view plain copy
  1. int main()  
  2. {  
  3.     X* p=new C;  
  4.     p->test();  
  5.     delete p;  
  6.     cout<<endl;  
  7.     return 0;  
  8. }  
  9. 输出:  
  10. X()  
  11. A()  
  12. B()  
  13. C()  
  14. test in C()55  
  15. ~X()  

[cpp]  view plain copy
  1.     virtual ~X(){  
  2.         cout<<"~X()"<<endl;  
  3.     }  
  4.   
  5. int main()  
  6. {  
  7.     X* p=new C;  
  8.     p->test();  
  9.     delete p;  
  10.     cout<<endl;  
  11.     return 0;  
  12. }  
  13.   
  14. 输出:  
  15. X()  
  16. A()  
  17. B()  
  18. C()  
  19. test in C()55  
  20. ~C()  
  21. ~B()  
  22. ~A()  
  23. ~X()  

9、指针常量与常量指针

int a;
int * const p = &a    //指针常量,*p可以修改*p = 8;(OK)
                                                   p不可以修改 p++(ERROR)

int a,b;
const int *p = &a;//常量指针    *p不可修改 *p = 8;(ERROR)
                                                  p 可以修改 p = &b  (OK)

还有一种
const int * const p = &a;    // *p 和 p均不可改变了

10、

[cpp]  view plain copy
  1. class C{  
  2.     int i;  
  3. public:  
  4.     C(){  
  5.         cout<<"C()"<<endl;  
  6.     }  
  7.     ~C(){  
  8.         cout<<"~C()"<<endl;  
  9.     }  
  10.     C(const C& ){  
  11.         cout<<"C(const &)"<<endl;  
  12.     }  
  13.     C& operator=( const C& c){  
  14.         if (this!=&c)  
  15.         {  
  16.             i=c.i;  
  17.         }  
  18.         cout<<"operator ="<<endl;  
  19.         return *this;  
  20.     }  
  21.   
  22. };  
  23.   
  24. void fun(C &c)  
  25. {  
  26.     cout<<"fun"<<endl;  
  27. }  
  28. int main()  
  29. {  
  30.     C c;  
  31.     fun(c);  
  32.     return 0;  
  33. }  
  34. 输出:  
  35. C()  
  36. fun  
  37. ~C()  

[cpp]  view plain copy
  1. void fun(C c)  
  2. {  
  3.     cout<<"fun"<<endl;  
  4. }  
  5. int main()  
  6. {  
  7.     C c;  
  8.     fun(c);  
  9.     return 0;  
  10. }  
  11. 输出:  
  12. C()  
  13. C(const &)  
  14. fun  
  15. ~C()  
  16. ~C()  

[cpp]  view plain copy
  1. C& fun(C c)  
  2. {  
  3.     cout<<"fun"<<endl;  
  4.     return c;  
  5. }  
  6. int main()  
  7. {  
  8.     C c;  
  9.     C b;  
  10.     b=fun(c);  
  11.     return 0;  
  12. }  
  13. 输出:  
  14. C()  
  15. C()  
  16. C(const &)  
  17. fun  
  18. ~C()  
  19. operator =  
  20. ~C()  
  21. ~C()  

[cpp]  view plain copy
  1. C& fun(C &c)  
  2. {  
  3.     cout<<"fun"<<endl;  
  4.     return c;  
  5. }  
  6. int main()  
  7. {  
  8.     C c;  
  9.     C b;  
  10.     b=fun(c);  
  11.     return 0;  
  12. }  
  13. 输出:  
  14. C()  
  15. C()  
  16. fun  
  17. operator =  
  18. ~C()  
  19. ~C()  

[cpp]  view plain copy
  1. C fun(C &c)  
  2. {  
  3.     cout<<"fun"<<endl;  
  4.     return c;  
  5. }  
  6. int main()  
  7. {  
  8.     C c;  
  9.     C b;  
  10.     b=fun(c);  
  11.     return 0;  
  12. }  
  13. 输出:  
  14. C()  
  15. C()  
  16. fun  
  17. C(const &)  
  18. operator =  
  19. ~C()  
  20. ~C()  
  21. ~C()  

[cpp]  view plain copy
  1. C fun(C c)  
  2. {  
  3.     cout<<"fun"<<endl;  
  4.     return c;  
  5. }  
  6. int main()  
  7. {  
  8.     C c;  
  9.     C b;  
  10.     b=fun(c);  
  11.     return 0;  
  12. }  
  13. 输出:  
  14. C()  
  15. C()  
  16. C(const &)  
  17. fun  
  18. C(const &)  
  19. ~C()  
  20. operator =  
  21. ~C()  
  22. ~C()  
  23. ~C()  

11、互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源。可以保证以下三点:
原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。
唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。
非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

从以上三点,我们看出可以用互斥量来保证对变量(关键的代码段)的排他性访问。

12、n个进程实现互斥的一般形式。假定mutex是一个互斥信号量,由于每次只允许一个进程进入临界区执行,若把临界区抽象成资源,显然它的可用单位数为1,由信号量的物理含义可知,mutex初值应为1。这样各并发进程的程序描述大致如下:
semaphore mutex;
mutex = 1;
……
process Pi
{
 ……
 P(mutex);
 进程Pi的临界区代码;
 V(mutex);
 ……
}
下面我们进一步分析各并发进程的执行过程和正确性。开始时,信号量mutex的值为1。当有一个进程Pj执行P(mutex)时,mutex的值变为0。这时若有其它进程再执行P(mutex)时,mutex的值将变为小于0,它们均会阻塞在该信号量的等待队列中。当进程Pj执行完其临界区代码,并执行V(mutex)时,若发现mutex的值小于等于0,它会唤醒在该信号量的等待队列中的一个进程,使它能进入其临界区代码执行,之后执行V(mutex)。同理,又唤醒在该信号量的等待队列中的另一个进程,使它能进入其临界区代码执行。因此,一定能保证各并发进程对其临界区的互斥执行。所以,用此框架实现多个并发进程对其临界区的互斥执行是正确的。

13、循环队列中元素的个数:

队列头指针为front,队列尾指针为rear,队列容量为M,则元素个数为|rear-front+M|%M,注意,这个%是求余运算。
整理如下:
队空:front==rear
队满: (rear+1) % M ==front
队中元素个数n=(rear-front+M )mod M 
入队:rear=(rear+1) % M ;
出队:front=(front+1) % M ;

14、 char s[5];   s="hua";  编译错误;

char s[5]={'d', 'f' , 'a' , };// right

15、void GetMemory(char **p,int num)
{
*p=(char *)malloc(num);
}


int main()
{
char *str=NULL;
GetMemory(&str,100);
strcpy(str,"hello");
printf(str);
cout<<endl;
return 0;
}

输出:hello

void GetMemory(char *p,int num)
{
p=(char *)malloc(num);
}


int main()
{
char *str=NULL;
GetMemory(str,100);
strcpy(str,"hello");
printf(str);
cout<<endl;
return 0;
}

编译正确,运行出错。

16、

[cpp]  view plain copy
  1. class A  
  2.     {  
  3.         public:  
  4.             virtual int test();  
  5.             virtual double test2();  
  6.             int test3();  
  7.         protected:  
  8.             double test4();  
  9.         private:  
  10.             int a,b,c;定义了三个变量,加上一个虚函数表指针。大小为16  
  11.     };  
  12.   
  13.     请问sizeof(A)=16  

成员变量+虚函数表指针(4个字节,多个虚函数也只有一个该指针)。

所有的虚函数保存在虚函数表中,然后类中会有一个指针指向该表;这个指针需要占用空间,所以需要 +4

17 、列举析构函数与普通类成员函数的不同点。
答:1、析构函数名也应与类名相同,只是在函数名前面加一个波浪符~,例如~stud( )
2、它不能带任何参数,也没有返回值(包括void类型)。
3、只能有一个析构函数,不能重载
4、析构函数在对象生存期即将结束的时刻被自动调用

18、在C++语言中使用宏定义经常会引起一些错误(如少打括号引起表达式值与预期不符等),列举一些可以代替宏定义的方法

一、const定义常量 

二、inline函数

三、typedef定义别名

四、类中编译期间的常量:
enum { NumTurns = 5};
static const int a=10;

19、进程与线程的区别:

A.操作系统只调度进程,不调度线程

B.线程共享内存地址空间,进程不共享

C.线程间可共享内存数据,但进程不可以

D.进程可以通过IPC通信,但线程不可以

20、进程和线程的区别:

主要差别在于它们是不同的操作系统资源管理方式。

一、进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,

二、但在进程切换时,耗费资源较大,效率要差一些。对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

三、线程的划分尺度小于进程,使得多线程程序的并发性高。
四、进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

20_1进程和线程的区别?
答:线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源. (4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。


20_2.网络编程中设计并发服务器,使用多进程 与 多线程 ,请问有什么区别?
答:用多进程时每个进程有自己的地址空间,线程则共享地址空间。所有其他区别都是由此而来的:(1)速度:线程产生的速度快,线程间的通信快,切换快等,因为它们在同一个地址空间内。(2)资源利用率:线程的资源利用率比较好也是因为它们在同一个地址空间内。(3)同步问题:线程使用公共变量/内存时需要使用同步机制,还是因为它们在同一个地址空间内。

21、内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。局部变量若定义为static,则存储在静态存储区,否则存储在函数的栈内,生命周期为本函数内。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元由编译器自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

22、堆和栈的区别: 
1 、管理方式不同; 
2 、空间大小不同; 
3 、能否产生碎片不同; 
4 、生长方向不同; 
5 、分配方式不同; 
6 、分配效率不同; 
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生 内存泄露。 


空间大小:一般来讲在 32 位系统下,堆内存可以达到 4G 的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在 VC6 下面,默认的栈空间大小是 1M 。当然,这个值可以修改。 


碎片问题:对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。


生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长


分配方式:堆都是动态分配的 ,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloca 函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现 。 


分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C++ 函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构 / 操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。 

从这里我们可以看到,堆和栈相比,由于大量 new/delete 的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址, EBP 和局部变量都采用栈的方式存放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值