C++面试问题整理


1. 为什么基类的析构函数要声明成虚函数?

原因:

在公有继承中,基类的虚构函数如果不声明成为虚函数,那么销毁派生类时有可能造成资源泄漏

  1. class Base{ public:  
  2.   
  3.         Base( ) {   };  
  4.   
  5.       ~Base( ) {   };    
  6.   
  7. };  
  8.   
  9. class Derived :public Base{  
  10.   
  11.    public:  
  12.   
  13.        Derived( ) {   };  
  14.   
  15.      ~Derived( ){   };  
  16.   
  17. };  
  18.   
  19. void main( )  
  20.   
  21. {  
  22.   
  23.    Base *p;  
  24.   
  25.    p = new Derived;  
  26.   
  27.    delete p;  
  28.   
  29. }  

这种情况会发生销毁不完全的情况,因为delete p调用的是声明类型(即基类)的析构函数,所以只能销毁基类对象而无法销毁派生类对象。

修改:

  1. class Base{ public:  
  2.   
  3.         Base( ) {   };  
  4.   
  5.       virtual ~Base( ) {   };   
  6.   
  7. };  
  8.   
  9. class Derived :public Base{  
  10.   
  11.    public:  
  12.   
  13.        Derived( ) {   };  
  14.   
  15.       ~Derived( ){   };   
  16.   
  17. };  
  18.   
  19. void main( )  
  20.   
  21. {  
  22.   
  23.    Base *p;  
  24.   
  25.    p = new Derived;  
  26.   
  27.    delete p;  
  28.   
  29. }  

      当基类的析构函数声明为虚函数,那么派生类的析构函数也是虚函数,此时调用delete p时发生动态绑定,运行时会根据实际类型调用该对象的虚函数。

      当然,并不是要把所有类的析构函数都写成虚函数。只有当一个类是基类(即希望被继承)的时候才需要声明成虚函数,因为虚函数的作用是实现多态,而多态是建立在继承的基础上。单一类不能把析构函数写成虚函数,因为会产生额外的开销,比如虚表的创建和虚指针的定义。

2  二分查找算法

转自:https://blog.csdn.net/silence723/article/details/52043670

 

  二分查找算法是在有序数组中用到的较为频繁的一种算法,在未接触二分查找算法时,最通用的一种做法是,对数组进行遍历,跟每个元素进行比较,其时间为O(n).但二分查找算法则更优,因为其查找时间为O(lgn),譬如数组{1, 2, 3, 4, 5, 6, 7, 8, 9},查找元素6,用二分查找的算法执行的话,其顺序为:
1.第一步查找中间元素,即5,由于5<6,则6必然在5之后的数组元素中,那么就在{6, 7, 8, 9}中查找,
2.寻找{6, 7, 8, 9}的中位数,为7,7>6,则6应该在7左边的数组元素中,那么只剩下6,即找到了。

    二分查找算法就是不断将数组进行对半分割,每次拿中间元素和goal进行比较。


(1)  二分查找可以解决的问题:二分查找可以解决预排序数组的查找问题。只要数组中包含T(即要查找的值),那么通过不断的缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组,将数组的中间项与T进行比较,可以排除一般的元素,范围缩小一半。就这样反复比较反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素的表,整个查找过程大约要经过Log(2)N次比较。
       注意:二分查找是针对有序的数组而言的。

(2)  二分查找的相关代码如下:
  1. #include<iostream>  
  2. using namespace std;  
  3. int binary_search(int arr[], int n, int key)  
  4. {  
  5.            int left = 0;   //数组的首位置,即arr[0]处  
  6.            int right = n - 1;//数组的最后一个位置,即arr[n-1],数组大小为n  
  7.   
  8.            //循环条件一定要注意  
  9.            while (left <= right)  
  10.           {  
  11.                     int mid = left + ((right - left) >> 1);//此处的mid的计算一定要放在while循环内部,否则mid无法正确更新;并且此处用移位代替除以2可以提高效率,而且可以防止溢出。  
  12.                     if (arr[mid] > key)//数组中间的位置得数大于要查找的数,那么我们就在中间数的左区间找  
  13.                    {  
  14.                              right = mid - 1;  
  15.                    }  
  16.                     else if (arr[mid] < key)//数组中间的位置得数小于要查找的数,那么我们就在中间数的右区间找  
  17.                    {  
  18.                              left = mid + 1;  
  19.                    }  
  20.                     else  
  21.                    {  
  22.                               return mid;//中间数刚好是要查找的数字。  
  23.                    }  
  24.           }  
  25.   
  26.            //执行流如果走到此处说明没有找到要查找的数字。  
  27.            return -1;  
  28. }  
  29. 测试用例如下所示:  
  30. void Test()  
  31. {  
  32.            int arr[5] = { 1, 3, 5, 9, 10 };  
  33.            int ret1 = 0,ret2 = 0,ret3 = 0;  
  34.           ret1 = binary_search(arr, 5, 9);  
  35.           cout << ret1 << endl;  
  36.           ret2 = binary_search(arr, 5, 5);  
  37.           cout << ret2 << endl;  
  38.           ret3 = binary_search(arr, 5, 2);  
  39.           cout << ret3 << endl;  
  40. }  

(3) 二分查找代码需要注意的地方:
right=n-1-------->while(left <= right)-------->right=mid-1;
right=n-------->while(left < right)-------->right=mid;

此外:大部分人喜欢在最开始就判断arr[mid]与key相等,但是这样是不明智的,因为相等情况在大多状况下都是少数,写在开始的话,每次循环都需要判断一次相等,浪费时间,效率太低。


3.链表相关问题

转自:https://blog.csdn.net/hyqsong/article/details/49429859

反转链表

输入一个链表,反转链表后,输出链表的所有元素。

据找工作的师兄说,反转单链表基本各个公司面试都会有,整理出一些写的比较好的code,供我等小白们学习。


方法一

  • 常规思路,简洁,清晰,我觉得写得蛮好的。 
思路就是: 从原链表的头部一个一个取节点并插入到新链表的头部
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* newh = NULL;
        for(ListNode* p = pHead; p; )//p为工作指针
        {
            ListNode* tmp = p -> next;//temp保存下一个结点
            p -> next = newh;
            newh = p;
            p = tmp;
        }
        return newh;
    }
};
  • 这是我一开始写得,留着对比用,衬托,哈哈哈 

思路一样,但是感觉没第一个写得简洁,但是我的绝对最好理解哈

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == NULL)
            return pHead;      
        ListNode *res,*cur,*next;
        res = new ListNode(-1);
        cur = pHead;
        next = cur->next;
        while(cur != NULL)
            {       
            ListNode *first = res->next;
            cur->next = first;
            res->next = cur;

            cur = next;
            next = next->next;       
        }   
        return res->next;  
    }
};

方法二

思路:每次都将原第一个结点之后的那个结点放在新的表头后面。
比如1,2,3,4,5
第一次:把第一个结点1后边的结点2放到新表头后面,变成2,1,3,4,5
第二次:把第一个结点1后边的结点3放到新表头后面,变成3,2,1,4,5
……
直到: 第一个结点1,后边没有结点为止。
代码如下:

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == NULL)
            return pHead;

        ListNode *res,*first,*temp;
        res = new ListNode(-1);
        res->next = pHead;

        first = res->next;       //first 始终为第一个结点,不断后移
        while(first->next!=NULL) //temp为待前差的
            {
            temp = first->next;
            first->next = temp->next;
            temp->next = res->next;
            res->next = temp;          
        }

        return res->next;
    }
};

方法三

第三种方法跟第二种方法差不多,第二种方法是将后面的结点向前移动到头结点的后面,第三种方法是将前面的结点移动到原来的最后一个结点的后面,思路跟第二种方法差不多,就不贴代码了。


3.2 删除单向链表中的某一个节点

原文链接:http://blog.csdn.net/huahuahailang/article/details/8762785

已知一个单向链表的表头head,写出一个删除某一个节点的算法,要求先找到此节点,然后删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
 
using  namespace  std;
 
typedef  struct  node
{
     int  number;
     struct  node *next;
}Node;
 
Node *Delete(Node *head,  int  key){
     Node *node1 = head;
     Node *node2 = NULL;
     if (head == NULL){
         return  NULL;
     }
     else {
         if (node1->number == key){
             head = head->next;
             free (node1);
             return  head;
         }
         else {
             while (node1 != NULL){
                 node2 = node1;
                 node2 = node1 -> next;
                 if (node2 -> number == key){
                     node1 -> next = node2 -> next;
                     free (node2);
                     break ;
                 }
                 node1 = node1 -> next;
             }
             return  head;
         }
     }
}
 
int  main(){
 
     Node *head = (Node*) malloc ( sizeof (Node));
     Node *p, *q, *q1;
     int  key;
     p = (Node*) malloc ( sizeof (Node));
     q1 = q = head;
     int  i;
     for ( int  i = 1; i < 10; i++){
         p -> number = i;
         head -> next = p;
         head = p;
         p = (Node*) malloc ( sizeof (Node));   
     }
     head -> next = NULL;
     cout <<  "原链表数据:"  << endl;
     q1 = q1 -> next;
     while (q1 != NULL){
         cout << q1 -> number <<  " " ;
         q1 = q1 -> next;
     }
     cout << endl;
     cout <<  "输入要删除的数据:" ;
     cin >> key;
     p = Delete(q -> next, key);
     cout <<  "删除一个"  << key <<  "之后的链表数据:"    << endl;
     while (p != NULL){
         cout << p -> number <<  " " ;
         p = p ->next;
     }
     cout << endl;
     free (p);
     free (head);
     return  0;
}

  程序运行结果:

原链表数据:
1 2 3 4 5 6 7 8 9
输入要删除的数据:5
删除一个5之后的链表数据:
1 2 3 4 6 7 8 9


猜你喜欢

转载自blog.csdn.net/alisa_xf/article/details/80805022