深信服C/C++面试题目总结


内容收集自网络

C/C++语言

1. 链表和数组的相同点和不同点
特点 优点 缺点
数组 存储区域连续
需要预先分配空间,可能造成空间浪费或者不利于扩展
插入/删除效率低
随机读取效率高
随机访问性强
查找速度快
插入和删除效率低
可能浪费内存
内存空间要求高,必须有足够的连续内存空间
数组大小固定,不能动态拓展
链表 存储区域不要求连续,每个数据都保存了下一个数据的地址(如果有)
数据的增加/删除容易,易于扩展
数据查找效率低,只能顺序查找

插入删除速度快
内存利用率高,不会浪费内存
大小没有固定,拓展很灵活
不能随机查找,必须从第一个开始遍历,查找效率低

性能

- 数组 链表
读取 O(1) O(n)
插入 O(n) O(1)
删除 O(n) O(1)
2. 静态链表及数组的实现

概念
对于没有指针的编程语言,可以用数组替代指针,来描述链表。让数组的每个元素由data和cur两部分组成,其中cur相当于链表的next指针,这种用数组描述的链表叫做静态链表,这种描述方法叫做游标实现法。
特点
这种储存结构仍需要预先分配一个较大的空间,但在作线性表的插入和删除操作是不需移动元素,仅需修改指针,故仍具有链式存储结构的主要有点。
优点
在插入和删除时候,只需要修改游标,不需要移动元素,从而改进了顺序存储结构中插入和删除操作需要移动大量元素的缺点。
缺点
没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性。

3.使用strcpy注意的问题

来自MSDN的定义

char *strcpy(
   char *strDestination,
   const char *strSource
);

参数
strDestination:目标字符串。
strSource:null 终止的源字符串。
返回值
其中每个函数都会返回目标字符串。 没有保留任何返回值以指示错误。
说明
src和dst所指内存区域不可以重叠且dst必须有足够的空间来容纳src的字符串。
注意

因为strcpy不会检查strDestination是否有足够空间 ,它会直接复制strSource,很可能会造成缓冲区溢出。 因此,建议你使用 strcpy_s

strcpy_s的声明

// C定义
errno_t strcpy_s(
   char *dest,
   rsize_t dest_size,
   const char *src
);
C++定义
errno_t strcpy_s(
   char (&dest)[size],
   const char *src
); 

为了避免缓冲区溢出问题,我们也可以使用memcpy来完成的字符串的拷贝工作,memcpy是用来在内存中复制数据的,它会把指定长度的内存块复制到另一块内存中而不管内存的中存放的是什么数据
memcpy的声明

void *memcpy(
	void *str1, 
	const void *str2, 
	size_t n
);
// 作用:从存储区 str2 复制 n 个字符到存储区 str1

返回值
该函数返回一个指向目标存储区 str1 的指针。

strcpy、memcpy、strncpy都会遇到内存重叠的问题
https://blog.csdn.net/magic_world_wow/article/details/79662257
https://blog.csdn.net/weibo1230123/article/details/80382614

4. 链表反转

链表相关问题总是涉及大量指针的操作,链表的反转关键就是调整指针的方向。以下图为例,(a)表示原链表,(b)表示在进行反转的链表。

假设i节点之前都已反转完毕,进行到i时,我们需要知道i的前一个节点h,i节点本身以及(原来)i节点的下一个节点j。相应的这里涉及到3个指针pPre、pNode、pNext,于是写下如下代码

struct ListNode
{
   // 定义链表节点
    int       m_nValue;
    ListNode* m_pNext;
};

ListNode* ReverseList(ListNode* pHead)
{
   
    ListNode* pReversedHead = nullptr;	// 反转后的头指针
    ListNode* pNode = pHead;			// 从源链表的头结点开始
    ListNode* pPrev = nullptr;
    while(pNode != nullptr)
    {
   
        ListNode* pNext = pNode->m_pNext;

        if(pNext == nullptr)
            pReversedHead = pNode;

        pNode->m_pNext = pPrev;

        pPrev = pNode;		// 跟新pPre
        pNode = pNext;		// 反转节点后移
    }

    return pReversedHead;
}
5. 判断含括号的表达式是否合法

这数与简单的符号匹配问题,使用栈来解决,遇到左括号就入栈,右括号出栈。主要分为四种情况:

  1. 左右括号数量相同且正确匹配;
  2. 左右括号数量相同但不能正确匹配;
  3. 左括号多余;
  4. 右括号多余。

每次出栈前将当前的右括号和栈顶元素比较,看是否匹配,这是正确匹配的第一个问题;如果还有右括号而栈已经空了,说明右括号多了,如果最后栈不空,说明左括号多了。C++代码实现如下。

//判断括号是否合法--C++
#include <iostream>
#include <string>
#include <stack>
using namespace std;

int Match(char ch1,char ch2)
{
   // 匹配函数
    int t = 0;
    if(ch1 == '(' && ch2 == ')')    t = 1;
    if(ch1 == '[' && ch2 == ']')    t = 1;
    if(ch1 == '{' && ch2 == '}')    t = 1;
    return t;
}

bool chkLegal(string A) 
{
   // 检查函数
	if(A.size() == 0)   return false;
    stack<char> Stack;		// 定义一个栈容器
    for(int i=0;i<A.size();i++){
   
        if(A[i]=='['||A[i]=='('||A[i]=='{')
            Stack.push(A[i]);	// 左括号入栈
        if(A[i]==']'||A[i]==')'||A[i]=='}')
         {
   
            if(Stack.empty())   return false;               // 如果栈空还有右括号,不匹配
            if(!Match(Stack.top(), A[i]))  return  false;   // 如果没有对应匹配
            Stack.pop();
         }
    }
    if(Stack.empty())   return true;                        // 如果数量正确匹配
    else    return false;
}

int main(int argc, char *argv[])
{
   
    string A = "[a+b*(5-4)]";                   // 测试用例A,期望数出1
    string B = "[a+b*{5-4)]";                   // 测试用例B,期望数出0
    string C = "{[())]}";                       // 测试用例C,期望输出0
    string D = "{[a+b*(5-4)]";                  // 测试用例D,期望输出0
    string E = "";								// 测试用例D,期望输出0	
    cout << "String A is: " << chkLegal(A) << endl;
    cout << "String B is: " << chkLegal(B) << endl;
    cout << "String C is: " << chkLegal(C) << endl;
    cout << "String D is: " << chkLegal(D) << endl;
    cout << "String E is: " << chkLegal(E) << endl;
    return 0;
}

6. Map的底层实现?为什么使用红黑树

C++ STL底层实现

Name Description
vector 底层数据结构为数组 ,支持快速随机访问
list 底层数据结构为双向链表,支持快速增删
deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
priority_queue 底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现(优先队列)
set 底层数据结构为红黑树,有序,不重复
multiset 底层数据结构为红黑树,有序,可重复
map 底层数据结构为红黑树,有序,不重复
multimap 底层数据结构为红黑树,有序,可重复
hash_set 底层数据结构为hash表,无序,不重复
hash_multiset 底层数据结构为hash表,无序,可重复
hash_map 底层数据结构为hash表,无序,不重复
hash_multimap 底层数据结构为hash表,无序,可重复

map是key:value键值对的组合,map类型通常被称为关联数组(associative array)。与之相对,set就是关键字的简单组合。
map的模板函数

template <class Key,
    class Type,
    class Traits = less<Key>,
    class Allocator=allocator<pair <const Key, Type>>>
class map;

参数

  • Key
    要存储在映射中的键数据类型。
  • Type
    要存储在映射中的元素数据类型。
  • Traits
    一种提供函数对象的类型,该函数对象可将两个元素值作为排序键进行比较,以确定其在映射中的相对顺序。 此参数为可选自变量,默认值是二元谓词 less。
    在 C++ 14 中可以通过指定没有类型参数的 std:: less <> 谓词来启用异类查找。
  • Allocator
    一种表示存储的分配器对象的类型,该分配器对象封装有关映射的内存分配和解除分配的详细信息。 此参数为可选参数,默认值为 allocator<pair<const Key, Type> >。

用法参考

https://www.w3cschool.cn/cpp/cpp-fu8l2ppt.html
https://blog.csdn.net/qq_38984851/article/details/81237993
https://www.jianshu.com/p/834cc223bb57

红黑树的特性
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意
a. 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
b. 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
红黑树示意图:
在这里插入图片描述

参考
https://www.cnblogs.com/xuxinstyle/p/9556998.html
https://www.jianshu.com/p/e136ec79235c

平衡二叉树

  • 红黑树是在AVL树的基础上提出来的。
  • 平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。
  • AVL树中所有结点为根的树的左右子树高度之差的绝对值不超过1。
  • 将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。

红黑树较AVL树的优点
AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的rebalance,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。

所以红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。

7. 重载、重写(覆盖)和隐藏的定义与区别

定义

  • 重载: 在同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载
  • 重写/覆盖(override): 派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
  • 隐藏: 指不同作用域中定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。

override与final
使用override关键字来说明派生类中的虚函数。
把某个函数指点为 final ,意味着任何尝试覆盖该函数的操作都将引发错误。
区别
重载和重写的区别
(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。
(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。
(3)virtual的区别:重写的基类必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有。

隐藏和重写,重载的区别
(1)与重载范围不同:隐藏函数和被隐藏函数在不同类中。
(2)参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是被重写。

内容 作用域 virtual 函数名 形参列表 返回值类型
重载 相同 可有可无 相同 不同 可同可不同
隐藏 不同 可有可无 相同 可同可不同 可同可不同
重写/覆盖 不同 相同 不同 相同

参考
https://blog.csdn.net/weixin_39640298/article/details/88725073
https://www.cnblogs.com/zhangjxblog/p/8723291.html

8. virtual关键字是为了实现什么,具体怎么实现?

当一个方法声明包含virtual修饰符,这个方法就是虚方法。如果没有virtual修饰符,那么就不是虚方法。C++中的虚函数(Virtual Function)的作用主要是实现了多态的机制,虚函数是通过一张虚函数表(Virtual Table)来实现的。

9. Hash表及底层实现机制

在数组,线性表、树等数据结构中,记录的查找效率依赖与比较次数。如果能将关键字和储存位置建立一个映射关系,那么就可是实现不经过任何比较,一次便能得到所查记录。即,如果存在映射关系 f f f能确定给定值 K K K的位置,那么可以称这个映射关系 f f f哈希(Hash)函数,由这个思想建立的表为哈希表。

哈希函数构造方法

  • 直接定址法
  • 数字分析法
  • 平方取中法
  • 折叠法
  • 除留取余法
  • 随机法

冲突处理方法

  • 开放定址法再哈希法
  • 链地址法
  • 建立一个公共溢出区

底层实现
数组+链表,用链表处理冲突。如果冲突元素较多,可将链表转换为红黑树来提高查找性能能

参考
https://blog.csdn.net/sinat_35866463/article/details/83316487
https://blog.csdn.net/ACmeinan/article/details/79595960
https://blog.csdn.net/qq_41891803/article/details/82787112
https://www.nowcoder.com/discuss/3098?pos=28&type=0&order=0
https://www.kanzhun.com/gsmsh10802673.html
https://www.nowcoder.com/discuss/116569?type=2

10. BSF(Breadth First Search)之中国象棋跳马问题

题目描述

现在棋盘的大小不一定,由p,q给出,并且在棋盘中将出现障碍物(限制马的行动,与象棋走法相同)

输入

第一行输入n表示有n组测试数据。
每组测试数据第一行输入2个整数p,q,表示棋盘的大小(1<=p,q<=100)。
每组测试数据第二行输入4个整数,表示马的起点位置与终点位置。(位置的取值范围同p,q)
第三行输入m表示图中有多少障碍。
接着跟着m行,表示障碍的坐标。

输出

马从起点走到终点所需的最小步数。
如果马走不到终点,则输入“can not reach!”

C++代码

//中国象棋中的跳马问题
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;

struct position
{
   // 位置结构体
    int x;  // row
    int y;  // col
};
struct node
{
   // 节点属性
    int x;      // 节点位置信息
    int y;
    int sum;    // 到达该节点的距离信息
};
int vis[109][109];  // 访问数组
int barrier[109]<
  • 24
    点赞
  • 141
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值