感觉自己还是很菜,有的答的不够全,有的只是基本的方法,深深的鄙视自己。。。。不过失败是成功之母,总结下面试出错的地方吧。
1、TCP/IP协议栈
TCP/IP协议栈又称TCP/IP协议,分为以下四层:
(1)应用层(融合了OSI中的表示层,会话层):FTP(文件传输协议使得计算机间共享文件)、SMTP(邮件传输协议、是一组由源地址到目的地址传送邮件的规则)、HTTP(超文本传输协议,用于浏览器和万维网服务器之间的通信与传输)。。。
(会话层:对话管理,数据流同步)
(表示层:通用的数据结构)
(应用层:主要是应用程序接口,提供常见的网络应用服务)
(2)传输层:提供端到端的数据交换,协议有:TCP、UDP
(3)网络层:选择合适的传输路径,通过网络连接交换传输层发出的实体数据。协议有:IP(分配IP地址/IP分片-由于数据链路层的最大传输单元MTU限制)、ICMP(用于主机、路由之间传递控制消息,比如网络通不通、是否可达等)、IGMP(主机向直接相邻的路由器报告他们的组成员情况)
(4)网络接口层:即OSI中的物理层和数据链路层,是TCP/IP与各种LAN、WLAN的接口,主要有ARP(地址解析协议、通过IP协议获得MAC地址)、RARP(和ARP相反)。
(物理层:面向实际承担传输的物理媒体|透明比特流传输)
(数据链路层:实现数据的可靠传输|主要功能:将数据组合为帧并控制帧的传输(差错控制、速率),在两个网络实体之间提供数据链路的建立、维持和释放。)
2、C++的类型转换
const_cast::去掉类型的const和volatile属性。
static_cast:类似于C风格的强制转换,无条件转换。
dynamic_cast:动态类型转换,基类和子类之间的转换。
reinterpret_cast:用于指针级别的转换,在比特位级别上进行。
3、虚函数实现的机制
即通过父类指针指向子类对象实现多态,具体机制是怎么实现的呢?
分情况讨论:
如果子类并没有重写父类方法,那么在子类的虚函数表中,存在着父类的虚函数和子类的虚函数,其中父类的在前面。
如果子类重写父类方法,那么在子类的虚函数表中,重写的虚函数代替了原来父类的虚函数位置,即在实现多态时,父类指针指向的虚函数表中,原来函数的位置已发生变化,会调用子类的方法。
如果是多重继承没有重写方法,那么子类有多个虚函数表,其中子类的虚函数会放到第一个虚函数表中,这样不同的父类指向子类对象,都能调用实际函数。
如果多重继承有重写方法,那么每个函数表中对应的父类函数都被子类的函数代替,这样不同的父类可以实现多态。
当然上面的描述有点不严格,因为虚函数表中存放的是函数指针,以调用对应的函数,而不是被函数直接代替。。。等等。
http://blog.youkuaiyun.com/neiloid/article/details/6934135具体的描述可以参考这个链接。
关于类中有一个还是多个虚表指针看这里:http://bbs.youkuaiyun.com/topics/250037854
4、多进程比多线程的好处
多进程的优势在于独立性。如果任务做为单独的进程,那么崩溃只会影响自己的服务,而其它任务不会影响。如果是进程内用多线程执行多任务的话,一个线程crash,可能会使整个进程崩溃,从而可能会导致其它任务失败。资源分配也更加灵活自由。
进程和线程的区别如下:
概念:进程是具有一定功能的程序在某个数据集合的运行,它的资源调度和分配时独立的。而线程是进程的实体,一个进程包含多个线程,它是CPU调度和分配的基本单位,与其它在一个进程的线程共享资源。
区别:
执行过程:每个线程都有程序运行的入口、执行序列和出口,但不能独立运行,必须靠进程来控制。
逻辑角度:线程的划分尺度小于进程,这样多线程程序会有很好的并发性。多线程在一个应用程序中有多个部分可以同时执行,但操作系统并没有把它们当做独立的应用。
资源管理:进程有独立的地址空间,在保护模式下一个崩溃后不会影响其它进程,虽然线程有自己的堆栈和局部变量,但并没有单独的地址空间,一个线程死掉会影响进程中的其它线程,所以多进程要比多线程更健壮。但在切换时,进程消耗资源多导致效率慢。但对于某些需要同时进行且共享某些变量的操作还是需要多线程进行。
5、一亿个64位的数排序
当时只想到的是位图法,让我们估算下需要多少内存来存储。(考虑2的64次方最大情况)
2的64个数,占的字节数为2的61次方,则为2的41次方MB,即2的31次方GB,也就解决于2GB的GB的位图来存储,能存下么。。
借鉴某大神的思路:针对存不下这个问题,可以用哈希来存储到不同的机器上,然后分别进行排序,最后用归并排序(外部排序)来对所有数进行排序。但这只是针对分布式的,那一台机器上如果加快排序速度呢?哈希到不同的文件中,分别进行排序(可以使用快排),然后用归并。。
6、100万以内的质数
当时只想到的是基本算法,即判定一个数是不是质数,对它求根号,然后除以所以小于它根号值,根据结果是否能整除来判断。
其实两个优化步骤也比较简单,面的时候没想到:1、偶数肯定不是质数;2、合数能分解成质数的乘积,即合数也是有更小的因子组成,那么如果此数能整除合数肯定能整除其因子,但不一定保证能整除其因子时可以整除合数。所以直接判断小于根号值的质数是否能整除它即可。代码如下:
int FindSushu(){
int *prime = (int *)malloc(sizeof(int)*MAX_SIZE);
int count =0;
prime[count++] = 2;
int i,j,stop;
stop = count;
for(j=3;j<=1000000;j += 2){
int key = sqrt((double)j);
while(prime[stop] < key && stop <count)
stop++;
for(i =0;i<stop;i++){
if(j%prime[i] == 0)
break;
}
if(i == stop){
prime[count++] = j;
}
}
free(prime);
return j;
}
7、建立堆
见后面
8、几种常见排序的常考知识
(1)稳定性
没必要死记硬背,其实稳定性指的就是元素相同的时候在排序之后位置是否改变。可以逐一分析来得到结果。
稳定的:
冒泡:在排序过程中如果两个数时相等的肯定不会交换,所以稳定。
插入:在插入过程中很明显顺序相等的元素顺序是不会变的。
归并:算法是将其递归分成短序列,如果是两个元素的短序列,则没不会变换位置。同时在归并的过程中,稳定性也没有遭到破坏。
基数:是先按低位再按高位进行排序,如果是相等它们的位置肯定不会变化。
不稳定的:
快速:在以某点为轴进行运算时,相等不会交换;但如果排序后将轴交换到相应的位置则可能发生变化。
选择:选择最小的进行交换,如果有相等的在交换中会改变次序。
堆排序:不是稳定的。。。。
希尔排序(不同步长的插入):一次插入是稳定的,但多次插入可能导致相等元素在各自序列中交换位置。
(2)一些排序算法的实现
堆排序算法的实现:
有两个关键点:1、如果建立最小(大堆):一种方法是通过插入的方法,另外一种是已知数组的情况下对其进行调整,得到最小(大堆)。
2、排序:将堆顶元素输出,然后将最后一个元素放到堆顶,用算法再次将其调整为最小(大堆),直到剩下最后一个元素。
为了排序方便,建立堆的算法一般是知道数组的情况下,对堆进行调整,代码如下。
#include<iostream>
using namespace std;
//最小堆算法
void BuildHeap(int heap[],int i, int size){
int child = 2*i+1;
if(child < size){
int rchild = 2*i+2;
if(rchild < size){
if(heap[child] > heap[rchild])
child = rchild;
}
if(heap[i] > heap[child]){
int temp = heap[i];
heap[i] = heap[child];
heap[child] = temp;
BuildHeap(heap,child,size);
}
}
}
//排序算法
void HeapSort(int heap[],int size){
//建立最小堆
for(int i = (size-1)/2;i>=0;i--){
BuildHeap(heap,i,size);
}
//排序
int last = size -1;
while(last > 0){
int temp = heap[0];
heap[0] = heap[last];
heap[last] = temp;
BuildHeap(heap,0,--last);
}
}
//堆排序
int main(){
int a[] = {7,5,3,10,1,5,2,16,30,24};
HeapSort(a,10);
//倒着存储原来的排序结果
for(int i =9;i>=0;i--){
cout<<" "<<a[i];
}
cout<<endl;
system("PAUSE");
return 0;
}
测试结果正确。
二路归并算法的实现:
归并排序指的是将两个或两个以上有序表合并为新的有序表,二路的意思是对两个有序表合并,代码如下。
#include<iostream>
using namespace std;
//合并子序列
void Merge(int array[],int beg,int mid,int end){
int i = beg,j= mid+1,cnt =0;
int *temp = (int *)malloc(sizeof(int)*(end-beg+1));
while(i <= mid && j<= end){
if(array[i] < array[j]){
temp[cnt++] = array[i];
++i;
}
else{
temp[cnt++] = array[j];
++j;
}
}
while(i <= mid){
temp[cnt++] = array[i++];
}
while(j <= end){
temp[cnt++] = array[j++];
}
for(cnt = beg;cnt <= end;++cnt){
array[cnt] = temp[cnt-beg];
}
free(temp);
}
void TwoMergeSort(int array[],int beg,int end){
if(beg <end){
int mid = (beg+end)/2;
TwoMergeSort(array,beg,mid);
TwoMergeSort(array,mid+1,end);
//合并左右序列
Merge(array,beg,mid,end);
}
}
int main(){
int test[] = {7,5,3,10,1,5,2,16,30,24};
TwoMergeSort(test,0,9);
for(int i =0;i<=9;i++){
cout<<" "<<test[i];
}
cout<<endl;
system("PAUSE");
return 0;
}
又是分治法的典型应用,复杂度为O(n*logn)
9、IP地址的分类。
按照首位来分:A、首位为0 B、前两位10 C、前三位110 D、前四位1110 E、前五位 11111