面试题总结

以下面试题是汇总的牛客网上大家分享的C++方面的面经

计算机系统:

1.一段代码执行的过程(即编译原理)
2.链接有什么用
3.信号量除了PV还有什么
4.多线程安全
5.进程和线程

6.虚拟内存
7.全局变量在什么时候被检查出来,多个文件的变量在什么时候检查出来
全局变量在编译时通过语义分析检查出来,多个文件的全局变量在链接时被检查出来

如果两个全局变量都被初始化了,则直接报错
只有一个被初始化了,那就用这个全局变量替换其他的全局变量
都没被初始化,则根据连接器的喜好,选择一个全区变量替换其他变量。
8.栈溢出:栈中保存函数地址,函数参数和局部变量。溢出的原因:

  • 递归调用太多,每调用一次,就把地址,参数,局部变量压栈,超过栈的大小就溢出了
  • 局部变量体积太大,比如有个很大超过栈容量的地址等。

9.缓冲区溢出:在栈中分配一个数组来保存数据,但数据的大小超过了数组,就会覆盖栈中的其他内容,比如函数地址,局部变量等,你再用时就出错了。

10.汇编语言的优点:

  1. 最大限度的发挥硬件的优势,可以直接访问硬件设备比如I/O设备和存储器
  2. 根据特定的功能对代码进行最佳的优化,提高运行速度
  3. 拜托了编译器的限制,对生成的二进制代码完全控制
  4. 用汇编语言编写的程序要求的存储空间和执行时间将显著减少
  5. 常驻程序以及中断处理程序都是用汇编写的。

11.linux 常用命令

  • cd 命令
  • ls查看目录文件
  • cp 拷贝文件
  • mv 移动文件或改名
  • rm删除文件
  • kill 杀掉进程
  • vim 进入编辑器
  • gcc 编译成可执行文件
  • touch 创建空文件

12.进程间通信的方式
信号,管道(分为有名管道和无名管道),消息队列和共享内存。
消息队列的优点:解耦,异步,削峰
消息队列的缺点:系统可用性降低,复杂性增加(消息可靠性,队列高可用性,消费重复性)。
消息队列如何避免重复消费:不同的场景有具体的解决办法:

  • 如果我们是用消息对数据库进行操作,比如插入数据,我们可以把这个消息作为一个主键,这样重复时就会报错
  • 使用第三方介质做消费记录,用之前去查消费过了没
    13.编译时主要有:词法分析,语法分析,语义分析。词法分析读入字符流,识别出一个个的单词和符号,语法分析,把词法分析得到的词组成语法树,形成语句。语义分析,也叫类型检查和上下文分析。语句和上下文的联系对不对,函数的定义和声明是否一致,数组类型对不对的等。

计算机网络

1.TCP和UDP的区别

  • TCP有连接,UDP无连接
  • TCP提供可靠数据传输,UDP不提供
  • TCP有拥塞控制,UDP没有
  • TCP面向字节流,UDP面向报文
  • TCP点对点,UDP是可以一对多或多对一
  • TCP首部20字节,UDP只有8字节

2. 两个IP地址通信经过的路由器由什么命令查询:tracert
3.网络协议7层模型
4.局域网协议-DHCP
为局域网中的主机,集中的管理,分配IP。三种方式:
自动分配,动态分配,手工分配。
5.路由选择算法:
根据算法是否是集中式的,可以分为

  • 集中式路由选择算法,比如链路状态算法(LS),用完整的链路连接信息和开销信息来计算最小开销,例如Dijkstra算法
  • 分散式路由选择算法,比如距离向量算法(DV),每个节点只有周围邻居的信息,然后迭代的算出最优路径。
    比较:
    1.报文复杂性,LS每次状态改变要向所有节点发送新的状态,DV则不需要
    2.收敛速度,LS快,DV慢,还有可能遇到环路,陷入循环
    3.健壮性,LS仅计算自己的转发表,更健壮,DV要传播,所以会把错误扩大。

版本控制系统Git

git操作
git init 新建一个仓库
git add filename 添加一个文件到暂存区
git commit -m "comments on this change"把文件从暂存区提交到仓库
git status 掌握工作区状态
git diff filename 展示了更改的细节
git reset 回退版本
git remote add 把本地库与远程仓库关联
git push 把内容push到远程库
git clone把远程库克隆到本地库
git branch 创建新分支
git checkout 切换分支
git merge 合并分支
git branch -d删除分支

C++

1.inline和define的区别

2.new和malloc()的区别
1.new是运算符,而malloc()是函数
2.malloc只是申请到了一块空间,对于非内部数据类型,不会自动执行构造函数,而new会
3.malloc申请的可能不是新的空间,申请完后需要用memset清理一下,而new新的。
4.new出来的内存和对象最后需要用delete释放掉,会调用析构函数,malloc对应的操作是free只是简单的归还空间。

3.宏和const的区别
1.宏在预编译时展开,const在编译时处理
2.宏只是简单的字符替换,没有类型检查,而const有
3.const是分配一次空间,宏在每次展开时都分配空间。

4.防止内存泄漏的方法
智能指针:智能指针的本质是一个类对象,在创建智能指针时通过构造函数申请动态资源,使用结束时通过智能指针的析构函数释放动态资源

5.重载,隐藏和覆盖的区别

6.C++程序在运行时内存有几个内存区

  • 静态数据区,里面包括static对象和全局对象,分为全局未初始化区和全局初始化区。
  • 栈内存,函数的参数和局部对象。数据由程序自动创建和销毁
  • 堆内存,里面包括是动态分配的内存,new和malloc出来的内存
  • 字符常量区,字符常量放于此,程序结束后自动释放。
  • 程序代码区,存放函数体的二进制代码

7.拷贝构造函数和=号赋值的区别

8.C11新特性

  1. auto 关键字,用于类型推断,根据初始化的表达式推断类型。
  2. nullptr,之前都是用NULL或0表示空指针,但是这些都可能隐式转换为整型,新版本用nullptr表示空指针,不会被转换为整型。
  3. 范围for,当我们需要遍历一个容器时,用范围for很简洁的就完成了遍历
  4. override,final
  5. 无序容器,通过hash表实现,内部不排序,通常更快。
  6. 智能指针,shared_ptr,unique_ptr,weak_ptr.
  7. 右值引用:C11之前拷贝临时对象,只能复制过来,然后很快临时对象就销毁了,这样十分浪费时间和空间,所以C11定义了右值引用,直接把临时对象的内容偷过来用,避免了我创建,它销毁的额外开销。
  8. lambda表达式:一个类似匿名函数的可调用对象,用于泛型算法的定制操作。算法需要的谓词只有一元和二元,如果我们想用更多的参数,或者我们想用一些局部变量时,就要用lambda表达式。
    lambda表达式的形式:
    [捕获列表](形参列表)->返回类型{函数体}。
    捕获可以值捕获或引用捕获,可以指定捕获某些参数,或者用过=和&来捕获所有局部变量。

9.虚函数
10.模板:分为函数模板和类模板
11.C++如何监测内存泄漏:用mtrace()函数来监测,在需要监测的代码前放置mtrace(),会把监测结果返回到一个log文件里,哪里有泄漏啊,泄漏的数量等。
12.结构体的对齐
规则:
1.结构体首元素偏移为0,每个元素按#pragma pack指定的对齐数值和元素自身大小中较小的那个来对齐
2.结构体整体对齐(sizeof)为,结构体中最大的那个类型和#pragma pack指定的对齐数中最小的那个
3.嵌套的结构体,子结构体要从其中最大的那个类型的整数倍的位置来储存

计算机系统:

1.一段代码执行的过程(即编译原理)
2.链接有什么用
3.信号量除了PV还有什么
4.多线程安全
5.进程和线程

6.虚拟内存

计算机网络

1.TCP和UDP的区别

  • TCP有连接,UDP无连接
  • TCP提供可靠数据传输,UDP不提供
  • TCP有拥塞控制,UDP没有
  • TCP面向字节流,UDP面向报文
  • TCP点对点,UDP是可以一对多或多对一
  • TCP首部20字节,UDP只有8字节

2. 两个IP地址通信经过的路由器由什么命令查询:tracert
3.网络协议7层模型

版本控制系统Git

git操作
git init 新建一个仓库
git add filename 添加一个文件到暂存区
git commit -m "comments on this change"把文件从暂存区提交到仓库
git status 掌握工作区状态
git diff filename 展示了更改的细节
git reset 回退版本
git remote add 把本地库与远程仓库关联
git push 把内容push到远程库
git clone把远程库克隆到本地库
git branch 创建新分支
git checkout 切换分支
git merge 合并分支
git branch -d删除分支

C++

1.inline和define的区别

2.new和malloc()的区别
1.new是运算符,而malloc()是函数
2.malloc只是申请到了一块空间,对于非内部数据类型,不会自动执行构造函数,而new会
3.malloc申请的可能不是新的空间,申请完后需要用memset清理一下,而new新的。
4.new出来的内存和对象最后需要用delete释放掉,会调用析构函数,malloc对应的操作是free只是简单的归还空间。

3.宏和const的区别
1.宏在预编译时展开,const在编译时处理
2.宏只是简单的字符替换,没有类型检查,而const有
3.const是分配一次空间,宏在每次展开时都分配空间。

4.防止内存泄漏的方法
智能指针:智能指针的本质是一个类对象,在创建智能指针时通过构造函数申请动态资源,使用结束时通过智能指针的析构函数释放动态资源

5.重载,隐藏和覆盖的区别

6.C++程序在运行时内存有几个内存区

  • 静态数据区,里面包括static对象和全局对象,分为全局未初始化区和全局初始化区。
  • 栈内存,函数的参数和局部对象。数据由程序自动创建和销毁
  • 堆内存,里面包括是动态分配的内存,new和malloc出来的内存
  • 字符常量区,字符常量放于此,程序结束后自动释放。
  • 程序代码区,存放函数体的二进制代码

7.拷贝构造函数和=号赋值的区别

8.C11新特性

  1. auto 关键字,用于类型推断,根据初始化的表达式推断类型。
  2. nullptr,之前都是用NULL或0表示空指针,但是这些都可能隐式转换为整型,新版本用nullptr表示空指针,不会被转换为整型。
  3. 范围for,当我们需要遍历一个容器时,用范围for很简洁的就完成了遍历
  4. override,final
  5. 无序容器,通过hash表实现,内部不排序,通常更快。
  6. 智能指针,shared_ptr,unique_ptr,weak_ptr.
  7. 右值引用:C11之前拷贝临时对象,只能复制过来,然后很快临时对象就销毁了,这样十分浪费时间和空间,所以C11定义了右值引用,直接把临时对象的内容偷过来用,避免了我创建,它销毁的额外开销。
  8. lambda表达式:一个类似匿名函数的可调用对象,用于泛型算法的定制操作。算法需要的谓词只有一元和二元,如果我们想用更多的参数,或者我们想用一些局部变量时,就要用lambda表达式。
    lambda表达式的形式:
    [捕获列表](形参列表)->返回类型{函数体}。
    捕获可以值捕获或引用捕获,可以指定捕获某些参数,或者用过=和&来捕获所有局部变量。

9.虚函数
10.模板:分为函数模板和类模板
11.C++如何监测内存泄漏:用mtrace()函数来监测,在需要监测的代码前放置mtrace(),会把监测结果返回到一个log文件里,哪里有泄漏啊,泄漏的数量等。
12.vector和内置数组的区别:
1.内置数组,在栈上,vector分配在堆上
2.内置数组长度不能增长,vector可以
3.内置数组不能拷贝,vector可以
4.但内置数组效率比较高,毕竟底层。
13.C++和C语言的区别:
1.C++封装,继承,多态
2.C++范式编程

数据结构与算法:

实际实现时要注意边界条件的判断!
1.实现一个字符串拷贝函数

char* strcpy(char* dst,const char* str){
    assert(dst!=NULL&&str!=NULL);
    char* ret=dst;
    while((*ret++=*str++)!='\0');
    return ret;    
}

2.快排的实现

void QuickSort(int *array,int left,int right){
         assert(array!=NULL);
         if(left>=right) return;
         int index=PartSort(array,left,right);
         QuickSort(array,left,index-1);
         QuickSort(array,index+1,right);
}
int PartSort(int *array,int left,int right){
            int& key=array[right];
            while(left<right){
                  while(left<right&&array[left]<key){
                  ++left;
                }
                 while(left<right&&array[right]>key){
                  --right;
                 }
              swap(array[left],array[right]);   
      }
      swap(array[left],k);
      return left;
}

3.插入排序的实现

void insertionSort(vector<int>array,int n){
            for(int i=1;i<n:++i){
            int temp=array[i];
            int j;
            for( j=i-1;j>=0&&temp<array[j];--j){
                 array[j+1]=array[j];
            }
            array[j+1]=temp;
         }   
}

4.二叉树的前,中,后序遍历的递归,迭代写法,以及层次遍历
递归写法,前中后的差不多,只是语句的顺序需要调换一下。
前序遍历的迭代:
用栈来实现,先把根节点放入栈中,然后每次弹出一个节点,都把他输出,同时把他的左右孩子按先右后左的顺序放入栈中,直到栈为空。
中序遍历的迭代:
用栈来实现,与前序遍历不同的是,不是每步都要弹出节点,而是先一直向左遍历,遍历到头后,再弹出节点,并把当前控制权交给节点的右孩子,循环操作直到栈为空。
后序遍历的迭代:
用两个栈来实现,与前序遍历类似,先把根节点放入一个栈中,然后每弹出一个节点,这次我们不输出,而是把他推入第二个栈中,同时把左右孩子按先左后右的顺序推入第一个栈,循环操作直到栈1为空。然后打印出栈2的内容即可。
层次遍历:用队列实现,把根节点放入队列中,然后每次弹出节点,就把他的左右孩子先左后右的推入队列中,直到队列为空。
5.反转链表
迭代实现:

ListNode* reverseList(ListNode* head) {
        if(head==nullptr)return nullptr;
        ListNode dummy(-1);
        ListNode *cur=head;
        dummy.next=head;
        while(cur->next!=nullptr){
            ListNode*temp=cur->next;
            cur->next=cur->next->next;
            temp->next=dummy.next;
            dummy.next=temp;
        }
        return dummy.next;
    }

递归实现:
6.数组的全排列

void permutation(vector<int> &vec,int begin,int end){
        for(int i=begin;i<=end;++i){
        swap(vec[begin],vec[i]);
        permutation(vec,begin+1,end);
        swap(vec[begin],vec[i]);
       }
 }

动态规划:红蓝球(三个人取,第三个人捣乱)

#include<iostream>
#include<vector>
#include<iomanip>
using namespace std;
int main() {
	int m, n;
	cin >> m >> n;
	vector<vector<double>>dp(m + 1, vector<double>(n + 1));
	for (int i = 0; i < n; ++i)
		dp[0][i] = 0;
	for (int i = 1; i < m; ++i)
		dp[i][0] = 1.0;
	for (int i = 1; i <= m; ++i) {
		for (int j = 1; j <= n; ++j) {
			dp[i][j] += (double)i / (i + j);
			if (j == 1) {
				dp[i][j] += 0;
			}
			else if (j == 2) {
				dp[i][j] += (double)j/ (i + j)*(double)(j - 1) / (i + j - 1)*dp[i - 1][j - 2];
			}
			else if (j > 2) {
				dp[i][j] += (double)j / (i + j)*double(j - 1) / (i + j - 1)*(double)(j - 2) / (i + j - 2)*dp[i][j - 3] +
					(double)j / (i + j)*double(j - 1) / (i + j - 1)*(double)i / (i + j - 2)*dp[i - 1][j - 2];
			}
		}
	}
	cout<<fixed<<setprecision(5)<<dp[m][n];
	system("pause");
	return 0;
}

动态规划:01背包

#include<iostream>
#include<vector>
#include<iomanip>
#include<algorithm>
using namespace std;
int main(){
	vector<int>wei = { 0 , 2 , 3 , 4 , 5 };
	vector<int>val = { 0 , 3 , 4 , 5 , 6 };
	int bagV = 8;
	vector<vector<int>>dp(5,vector<int>(9,0));
	for (int i = 1; i < wei.size(); ++i) {
		for (int j = 1; j <= bagV; ++j) {
			if (j < wei[i])
				dp[i][j] = dp[i - 1][j];
			else
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - wei[i]] + val[i]);
		}
	}
	cout << dp[wei.size() - 1][bagV];
	system("pause");
	return 0;

查找数组中和最接近K的两个数

vector<int>findK(vector<int> vec,int k){
	sort(vec.begin(), vec.end());
	vector<int>res;
	int start = 0, end = vec.size() - 1;
	int absToK = INT_MAX;
	
	while (start < end) {
		if (abs(vec[start] + vec[end] - k) < absToK) {
			if (!res.empty()) {
				res.pop_back();
				res.pop_back();
			}
			res.push_back(vec[start]);
			res.push_back(vec[end]);
		}
		if (vec[start] + vec[end] < k)
			start++;
		else if (vec[start] + vec[end]>k)
			end--;
		else break;
	}
	return res;
}

7.树的种类:
二叉搜索树:任意节点的都大于左子树的所有节点,都小于右子树的所有节点
平衡二叉树:包括AVL树和红黑树,AVL树是严格平衡的树,为了维护平衡需要耗费很大的代价,在插入删除较多时不适用。红黑树是局部平衡,平均性能更优,STL的map就是基于红黑树的
B树和B+树:用于数据库的索引操作。区别,B+树只有叶子节点存信息,并拿链表连了起来

数据库:

drop,truncate,delete table的区别:
drop:直接把表从数据库中删除
truncate:删除表中的所有数据,表还在
delete:可以删除表的部分行,也可以用*删除所有行,删除所有行时,与truncate作用一样,但没有truncate快。

sql的关键字:
选择SELETE,
从哪个表中选择FROM
按某一列排序:ORDER BY
条件选择:WHERE IN NOT
通配符:LIKE
分组: GROUP BY 以及适用于分组的条件选择HAVING
删除表:DELETE, DROP,TRUNCATE
创建表:CREATE
别名:AS
组合查询:UNION
插入,更新,删除数据(行):INSERT,UPDATE,DELETE
创建,更改(列),删除表:CREATE, ALTER TABLE(+ADD DROP),DROP

sql优化的方法
优化主要是避免全表扫描:
1.在WHERE和sort by的列建立索引
2.where子句避免使用OR
3.避免where对null进行分析
4.in 和 not in也要慎用,用exist代替
5.避免创建销毁临时表
6.尽量不用select*,用具体的字段代替
7.尽量避免使用游标,游标数据量过大考虑改写。
sql对象的种类

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值