C++面试

这篇博客详细介绍了C++面试中常见的知识点,包括指针与引用的区别、new/delete与malloc/free的对比、虚析构函数的作用,以及虚函数和纯虚函数的差异。此外,还深入讨论了链表操作中的常见问题,如链表反转、查找公共节点等,强调了双指针在解决链表问题中的应用。
摘要由CSDN通过智能技术生成

一、指针和引用的区别:

1、初始化要求不同:引用在创建的时候必须进行初始化,也就是说必须引用到一个有效的对象;而指针在定义的时候可以不必初始化,也可以在后面的任何地方重新赋值(不是int const*p)

2、可修改性不同:引用一旦被初始化指向某一个对象后,他就不能改变了,也就是说不能指向其他对象的引用;而指针在任何时候都可以指向另一个对象。

3、不存在NULL引用:引用不能指向空的引用,它总是必须指向某一个对象的;而指针则可以指向NULL,任意时刻可以改变指向,指针灵活性较大,但易出错。

4、应用需求:如果你的需求是一旦指向一个对象后就不会改变指向,则用引用。如果存在指向NULL或指向不同的对象时,则应该使用指针。

二、new/delete和malloc/free区别

1、new/delete是C++的运算符,而malloc/free是C++的标准库函数,他们都可以用来申请动态内存和释放内存

2、但是对于非内部对象而言光用malloc/free是无法满足动态对象的要求。因为对象在创建的时候必须执行构造函数,对象在消亡的时候必须执行析构函数。但malloc/free是库函数而不是运算符,不在编译器控制范围之内,不能把构造函数和析构函数强加于malloc/free。因此只能使用new/delete运算符。

三、C++中虚析构函数的作用?

1、大家都知道,析构函数是为了在对象不被使用之后进行释放内存,虚函数为了实现多态。那么虚析构函数(析构函数声明为virtual)有什么作用呢?其实主要的作用就是进行正常的资源释放,为啥这么说呢?如果你父类Base中声明的一个虚函数,父类的子类为Derived。如果我们定义Derived的指针pTest1 ,  Derived* pTest1 = new  Derived()。当我们正常释放delete  pTest1是没问题的。但是如果我们定义Base的指针pTest2 ,  Base* pTest2 = new  Derived()。当我们执行delete  pTest2时,Derived的析构函数没有被调用,这样的话就造成内存泄漏,因为类的析构函数里面都是释放内存资源的。解决这个问题的技术是在Base类中把析构函数声明虚函数。注意只有当一个类被用来作为基类时才会把析构函数声明虚函数。

四、虚函数和纯虚函数的区别

1、类中声明了虚函数,则这个函数是可以实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样编译就是可以使用后期绑定来达到多态。纯虚函数知识一个接口,是个函数的声明,不能在基类中实现必须在子类中实现。

2、虚函数在子类中可以不进行重载,但纯虚函数必须在子类中去实现。通常把很多函数加上virtual,这是一个好习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性。

3、虚函数的类用于“实作继承”,也就是说继承接口的同时也继承了父类的实现,当然我们也可以完成自己的实现。但纯虚函数的类用于“介面继承”,也就说纯虚函数只关注于接口的统一性,实现由子类完成。

4、带纯虚函数的类叫虚基类,这种基类不能直接生成对象,智能被继承,并重写虚函数的实现方法后才能使用。这种类也称为抽象类。

5、定义方式不一样,如虚函数:virtual function()  {}; 纯虚函数virtual function()=0  {}

五、面试和笔试考到链表时不外乎就下面这些问题

1、链表反转

2、从末尾开始的打印链表

3、求链表的倒数第k个节点

4、求链表的中间节点(要求值遍历一次)

5、判断两个单向链表是否存在公共节点

6、求两个单向链表的第一个公共节点

7、判断链表是否存在环

8、求取链表中环的长度

9、求链表中环的入口点

10、合并两个有序的单向链表

程序及思想(碰到链表题,一时半会难以搞定时,想都不用想先用两个指针看一下能不能解决问题,链表题基本都会涉及到双指针解决问题)

1、链表反转。思想:首先我们先定义三个指针 pPrev 指向前一个节点;   pReverseHead 反转链表的头指针; pNode = pHead指向当前节点; 先断开第一个节点,即pHead,把

pNext = pNode->next的下一个节点保存起来防止链表断开,把pNode->next指向当前节点的前一个节点pPrev,在把pPrev这个指针指向当前节点,把当前节点指向下一个节点pNext
,依次进行处理就看实现链表的反转,具体程序如下:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
	ListNode* pPrev = NULL;
        ListNode* pReverseHead = NULL;
        ListNode* pNode = pHead;
        while(pNode != NULL)
        {
            ListNode* pNext = pNode->next;
            
            if(pNode->next == NULL)
            {
                pReverseHead = pNode;
            }
            
            pNode->next = pPrev;
            pPrev = pNode;
            pNode = pNext;           
        }
        return pReverseHead;  
    }
};
2、从末尾开始的打印链表,一看到这道题是不是感觉很简单,其实是的。但是如果面试或笔试的时候不做任何限制的话,你可以先把链表进行反转在进行输出,可以参考1的链表反转。另一种方法是在不修改链表的条件下进行处理时。我们可以考虑借助一个栈来辅助实现,因为栈具有后进先出的特性,因为正好符合本题。首先我们先进行压栈,即第一个节点先压进栈底,然后依次处理。然后就进行出栈。程序如下:

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(struct ListNode* head) 
    {
        vector<int> v;
        while(head != NULL)
        {
            v.insert(v.begin(),head->val);
            head = head->next;
        }
        return v;
    }
};
3、求链表的倒数第k个节点,常规思想需要遍历两次,肯定拿不到Offer。ok那就来个双指针,先用指针p1从头节点走k-1步,然后在用p2指针指向头节点,同时移动p1和p2指针,当p1指针指向尾节点时,则p2指针指向的结点就是倒数第k个节点。在强调一下,链表有难题请认准双指针。程序如下

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    	if(pListHead == NULL || k<=0)
            return NULL;
        ListNode* p1 = pListHead;
        ListNode* p2 = NULL;
        for(int i = 0; i < k-1; i++)
        {
            if(p1->next == NULL)
                return NULL;
             p1 = p1->next;
        }
        p2 = pListHead;
        while(p1->next != NULL)
        {
            p2 = p2->next;
            p1 = p1->next;
        }
        return p2;
    }
};
4、求链表的中间节点(要求值遍历一次),思想:你看你看又来了,这种题秒杀它,定义两个指针,一个指针快(p1=p1->next->next),一个指针慢(p2=p2->next)。同时从头节点开始移动,快的指针指向尾节点时,慢的指针就是中间节点。程序省略。

5、判断两个单向链表是否存在公共节点,如果有求第一个公共节点;思想:碰到这个问题你就去想一下连边的公共点存在什么特征,灵光一现,两个链表存在公共点就是从公共点开始以后的结点都是一样的是吧?也就是说只要我们用两个指针分别指向两个链表的头指,并且每移动一步就进行一次比较,判断他们是否相等(前提时当两个链表的长度一致时才可以)。这道题的难题就是如果两张链表长度不一样时,怎么同时移动指针。解决办法是先用指针分别求出链表的长度,在求它们差的绝对值(m-n),在把指向长链表的指针先移动(m-n)。然后同时移动两个指针,每移动一步做一次判断是否相等,相等时则指针指向的结点为公共节点。程序如下:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode* p1 = pHead1;
        ListNode* p2 = pHead2;
        int m , n ;
        while(p1 != NULL)
        {
            m++;
            p1 = p1->next;
        }
        while(p2 != NULL)
        {
            n++;
            p2 = p2->next;
        }
        int num = 0;
        p1 = pHead1;
        p2 = pHead2;
        if(m > n)
        {
            num = m - n;
            for(int i = 0; (i < num) && (p1 != NULL) ;i++)
            {
                p1 = p1->next;
            }
            while(p1 != p2)
            {
                p1 = p1->next;
                p2 = p2->next;
            }
        }
        else
        {
            num = n - m;
            for(int i = 0; (i < num) && (p2 != NULL);i++)
            {
                p2 = p2->next;
            }
            while(p1 != p2)
            {
                p1 = p1->next;
                p2 = p2->next;
            }
        }   
        return p1;
    }
};


改良版的程序

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode* p1 = pHead1;
        ListNode* p2 = pHead2 ;
        while(p1 != p2)
        {
            p1 = (p1 == NULL)? pHead2 : p1->next;
            p2 = (p2 == NULL)? pHead1 : p2->next;
        }
        return p1;
    }
};
6、判断链表是否存在环,如果存在环,则在求取环的长度和入口点,思想:环的特征是什么?我们假设操场是一个环,你在里面以速度v1奔跑,另一个小伙伴以v2速度奔跑(v2 > v1)。你们肯定在某一个时刻相遇的,这点大家肯定都没问题吧?好了,,,判断链表中是否存在一个环,就是以这种方法。我们假设两个指针分别为pFast1和 pNorn2 ,都指向链表的头节点,然后下一步把pFast1=pFast1->next->next (快指针),pNorn2 = pNorn2->next(慢指针)。如果链表存在环,则他们在某个时刻肯定相等,我们记下相遇点,然后在循环一次,每移动一次m++,再次回到相遇的点时,m得值就是环的长度。求环的入口点则是先用pFast1 和pNorn2指向链表的头节点。然后把pFast1先移动环的长度m,然后同时移动指针pFast1->next和指针pNorn2->next。每移动一步做一次判断,当他们相等时,该节点就是链表的入口点。程序如下:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == NULL || pHead->next == NULL)
            return NULL;
        
		ListNode* pFast1 = pHead->next;
        ListNode* pNorm2 = pHead;
        
        ListNode* pFlag = NULL;
        int Lnum = 1;
        while(pFast1 != pNorm2)//求取环的相遇节点
        {
            pFast1 = pFast1->next->next;
            pNorm2 = pNorm2->next;
        }
        
        pFlag  = pFast1;
        pFast1 = pFast1->next;
        while(pFast1 != pFlag)//求取环的长度
        {
            Lnum++;
            pFast1 = pFast1->next;
        }
        
        pFast1 = pHead;
        pNorm2 = pHead; 
        for(int i = 0; i < Lnum; i++)
        {
            pFast1 = pFast1->next;
        }
        
        while(pFast1 != pNorm2)//求取环的入口点
        {
            pFast1 = pFast1->next;
            pNorm2 = pNorm2->next;
        }
        
        return pNorm2;
    }
};
10、后续将继续更新




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值