同学的4道面试题,应聘的职位是搜索引擎工程师,后两道超级难,(希望大家多给一些算发)
1.给两个数组和他们的大小,还有一动态开辟的内存,求交集,把交集放到动态内存dongtai,并且返回交集个数
long jiaoji(long* a[],long b[],long* alength,long blength,long* dongtai[])
2.单连表的建立,把'a'--'z'26个字母插入到连表中,并且倒叙,还要打印!
方法1:
typedef struct val
{ int date_1;
struct val *next;
}*p;
void main(void)
{ char c;
for(c=122;c>=97;c--)
{ p.date=c;
p=p->next;
}
p.next=NULL;
}
}
方法2:
node *p = NULL;
node *q = NULL;
node *head = (node*)malloc(sizeof(node));
head->data = ' ';head->next=NULL;
node *first = (node*)malloc(sizeof(node));
first->data = 'a';first->next=NULL;head->next = first;
p = first;
int longth = 'z' - 'b';
int i=0;
while ( i<=longth )
{
node *temp = (node*)malloc(sizeof(node));
temp->data = 'b'+i;temp->next=NULL;q=temp;
head->next = temp; temp->next=p;p=q;
i++;
}
print(head);
104.可怕的题目终于来了
象搜索的输入信息是一个字符串,统计300万输入信息中的最热门的前十条,我们每次输入的一个字符串为不超过255byte,内存使用只有1G,
请描述思想,写出算发(c语言),空间和时间复杂度,
7.国内的一些帖吧,如baidu,有几十万个主题,假设每一个主题都有上亿的跟帖子,怎么样设计这个系统速度最好,请描述思想,写出算发(c语言),空间和时间复杂度,
#include string.h
main(void)
{ char *src="hello,world";
char *dest=NULL;
dest=(char *)malloc(strlen(src));
int len=strlen(str);
char *d=dest;
char *s=src[len];
while(len--!=0)
d++=s--;
printf("%s",dest);
}
找出错误!!
#include "string.h"
#include "stdio.h"
#include "malloc.h"
main(void)
{
char *src="hello,world";
char *dest=NULL;
dest=(char *)malloc(sizeof(char)*(strlen(src)+1));
int len=strlen(src);
char *d=dest;
char *s=src+len-1;
while(len--!=0)
*d++=*s--;
*d='\0';
printf("%s",dest);
}
105.判断字符串是否为回文
bool IsSymmetry(const char* p)
{
assert(p!=NULL);
const char* q=p;
int len=0;
while(*q++!='\0')
{
len++;
}
bool bSign=true;
q=p+len-1;
if (0<len)
{
for (int i=0;i<len/2;i++)
{
if(*p++!=*q--){ bSign=false;break;};
}
}
if(bSign==true)
{
printf("Yes!\n");
}
else
{
printf("No!\n");
}
return bSign;
}
107.ASDL使用的是什么协议?并进行简单描述?
108.Static 作用是什么
首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
109.什么是预编译,何时需要预编译?
预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:1)宏定义 2)文件包含 3)条件编译
1、总是使用不经常改动的大型代码体。
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。
110.进程和线程的区别
什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:
进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。 一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.
111.插入排序和选择排序
插入排序基本思想:(假定从大到小排序)依次从后面拿一个数和前面已经排好序的数进行比较,比较的过程是从已经排好序的数中最后一个数开始比较,如果比这个数,继续往前面比较,直到找到比它大的数,然后就放在它的后面,如果一直没有找到,肯定这个数已经比较到了第一个数,那就放到第一个数的前面。那么一般情况下,对于采用插入排序法去排序的一组数,可以先选 取第一个数做为已经排好序的一组数。然后把第二个放到正确位置。
选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。
112.运算符优先级问题
能正确表示a和b同时为正或同时为负的逻辑表达式是(D )。
sssA、(a>=0||b>=0)&&(a<0||b<0)
B、(a>=0&&b>=0)&&(a<0&&b<0)
C、(a+b>0)&&(a+b<=0)
D、a*b>0
以下关于运算符优先顺序的描述中正确的是(C)。
A、关系运算符<算术运算符<赋值运算符<逻辑与运算符
B、逻辑与运算符<关系运算符<算术运算符<赋值运算符
C、赋值运算符<逻辑与运算符<关系运算符<算术运算符
D、算术运算符<关系运算符<赋值运算符<逻辑与运算符
113.字符串倒序
写一个函数将"tom is cat" 倒序打印出来,即 "cat is tom"
//a.ch
#define SPACE ' '
#define ENDL '\0'
char* str = "Tom is cat"; // 字符串
char* p1 = str+strlen(str)-1;
char* p2 = p1; // 开始时,p1,p2都指向字符串结尾处
char t=0; // 临时变量,用来保存被临时替换为ENDL的字符
while(str!=p1--)
{
if(SPACE!=*p1){
for(p2=p1+1;SPACE!=*p1; p1--, t=*p2, *p2=ENDL);
// p1+1指向单词的第一个字母,p2指向单词的结尾,此时输出这个单词
printf("%s ",p1+1);
*p2=t;
p2=p1;
}
}
Output:
cat is Tom
----------------------------------------------------------------------
1)写一个递归函数将内存中的字符串翻转"abc"->"cba"
2)写一个函数将"tom is cat" 将内存中的字符串翻转,即 "cat is tomm"
#include <stdio.h>
#define SPACE ' '
#define ENDL '\0'
char* s = "The quick brown fox jumps over the lazy dog";
void str_reverse(char* p1,char* p2){
if(p1==p2)return;
*p1 = (*p1)+(*p2);
*p2 = (*p1)-(*p2);
*p1 = (*p1)-(*p2);
if(p1==p2-1)return;
else str_reverse(++p1,--p2);
}
void str_word_reverse(char* str){
char *q1=str, *q2=str, *t;
while(*q1==SPACE)q1++;
if(*q1==ENDL)return; //!
else q2=q1+1;
while( (*q2!=SPACE) && (*q2!=ENDL) )q2++;
t=q2--;
str_reverse(q1,q2);
if(*t==ENDL)return;
else str_word_reverse(t);
}
int
main(int a ,char** b)
{
printf("%s\n",s);
str_reverse(s,s+strlen(s)-1);
printf("%s\n",s);
str_word_reverse(s);
printf("%s\n",s);
return 0;
}
Output:
The quick brown fox jumps over the lazy dog
god yzal eht revo spmuj xof nworb kciuq ehT
dog lazy the over jumps fox brown quick The
----------------------------------------------------------------------
今天同学又问一道题,和上面有些类似,但是要求更严格了一些:
写一个递归函数将内存中的字符串翻转"abc"->"cba",并且函数原型已确定:void reverse(char* p)
其实,要求越多,思路越确定,我的解如下:
#include <stdio.h>
#include <string.h>
char* s = "0123456789";
#define ENDL '\0'
void reverse(char* p){
//这是这种方法的关键,使用static为的是能用str_reverse的思路,但是不好
static char* x=0;
if(x==0)x=p;
char* q = x+strlen(p)-1;
if(p==q)return;
*q=(*p)^(*q);
*p=(*p)^(*q);
*q =(*p)^(*q);
if(q==p+1)return;
reverse(++p);
}
//这种方法就直观多了,但是当字符串很长的时候就很低效
void reverse2(char* p){
if(*(p+1)==ENDL)return;
for(char* o=p+strlen(p)-1,char t=*o;o!=p;o--)
*o=*(o-1);
*p=t;
reverse2(p+1);
}
int main(int c,char** argv){
reverse2(s);
printf("%s\n",s);
return 0;
}
114.交换两个数的宏定义
交换两个参数值的宏定义为:. #define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);
115.Itearator各指针的区别
游标和指针
我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“--”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。
116. C++中的class和struct的区别
从语法上,在C++中(只讨论C++中)。class和struct做类型定义时只有两点区别:
(一)默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;
(二)成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。
除了这两点,class和struct基本就是一个东西。语法上没有任何其它区别。
不能因为学过C就总觉得连C++中struct和class都区别很大,下面列举的说明可能比较无聊,因为struct和class本来就是基本一样的东西,无需多说。但这些说明可能有助于澄清一些常见的关于struct和class的错误认识:
(1)都可以有成员函数;包括各类构造函数,析构函数,重载的运算符,友元类,友元结构,友元函数,虚函数,纯虚函数,静态函数;
(2)都可以有一大堆public/private/protected修饰符在里边;
(3)虽然这种风格不再被提倡,但语法上二者都可以使用大括号的方式初始化:
A a = {1, 2, 3};不管A是个struct还是个class,前提是这个类/结构足够简单,比如所有的成员都是public的,所有的成员都是简单类型,没有显式声明的构造函数。
(4)都可以进行复杂的继承甚至多重继承,一个struct可以继承自一个class,反之亦可;一个struct可以同时继承5个class和5个struct,虽然这样做不太好。
(5)如果说class的设计需要注意OO的原则和风格,那么没任何理由说设计struct就不需要注意。
(6)再次说明,以上所有说法都是指在C++语言中,至于在C里的情况,C里是根本没有“class”,而C的struct从根本上也只是个包装数据的语法机制。
---------------------------------------------------------------
最后,作为语言的两个关键字,除去定义类型时有上述区别之外,另外还有一点点:“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。
关于使用大括号初始化
class和struct如果定义了构造函数的话,都不能用大括号进行初始化
如果没有定义构造函数,struct可以用大括号初始化。
如果没有定义构造函数,且所有成员变量全是public的话,可以用大括号初始化。
关于默认访问权限
class中默认的成员访问权限是private的,而struct中则是public的。
关于继承方式
class继承默认是private继承,而struct继承默认是public继承。
关于模版
在模版中,类型参数前面可以使用class或typename,如果使用struct,则含义不同,struct后面跟的是“non-type template parameter”,而class或typename后面跟的是类型参数。
class中有个默认的this指针,struct没有
不同点:构造函数,析构函数 this 指针
117.有关重载函数
返回值类型不同构不成重载
参数参数顺序不同能构成重载
c++函数同名不同返回值不算重载!函数重载是忽略返回值类型的。
---------------------------------------------
成员函数被重载的特征有:
1) 相同的范围(在同一个类中);
2) 函数名字相同;
3) 参数不同;
4) virtual关键字可有可无。
5) 成员函数中 有无const (函数后面) 也可判断是否重载
118.数据库与T-SQL语言
关系数据库是表的集合,它是由一个或多个关系模式定义。SQL语言中的数据定义功能包括对数据库、基本表、视图、索引的定义。
119.关系模型的基本概念
关系数据库以关系模型为基础,它有以下三部分组成:
●数据结构——模型所操作的对象、类型的集合
●完整性规则——保证数据有效、正确的约束条件
●数据操作——对模型对象所允许执行的操作方式
关系(Relation)是一个由行和列组成的二维表格,表中的每一行是一条记录(Record),每一列是记录的一个字段(Field)。表中的每一条记录必须是互斥的,字段的值必须具有原子性。
120.SQL语言概述
SQL(结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。通过编写SQL,我们可以实现对关系数据库的全部操作。
●数据定义语言(DDL)——建立和管理数据库对象
●数据操纵语言(DML)——用来查询与更新数据
●数据控制语言(DCL)——控制数据的安全性
起来是一个很简单的问题,每一个使用过RDBMS的人都会有一个概念。
事务处理系统的典型特点是具备ACID特征。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔离的)以及Durable(持续的),它们代表着事务处理应该具备的四个特征:
原子性:组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分
一致性:在事务处理执行之前和之后,数据是一致的。
隔离性:一个事务处理对另一个事务处理没有影响。
持续性:当事务处理成功执行到结束的时候,其效果在数据库中被永久纪录下来。
121.C语言中结构化程序设计的三种基本控制结构
顺序结构
选择结构
循环结构
122.CVS是什么
cvs(Concurrent Version System) 是一个版本控制系统。使用它,可以记录下你的源文件的历史。
例如,修改软件时可能会不知不觉混进一些 bug,而且可能过了很久你才会察觉到它们的存在。有了 cvs,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个 bug。有时这是很有用的。
CVS服务器端对每个文件维护着一个修订号,每次对文件的更新,都会使得文件的修订号加1。在客户端中也对每个文件维护着一个修订号,CVS通过这两个修订号的关系,来进行Update,Commit和发现冲突等操作操作
123.三种基本的数据模型
按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。
124.设计模式:工厂模式 和 单例模式 介绍一下?
工程模式即将对象创建过程封装即为工厂模式。
单例模式即整个类只有一个对象,并且不允许显示创建。
125.vector 和 list的区别?
vector内部使用数组,访问速度快,但是删除数据比较耗性能
list内部使用链表,访问速度慢,但是删除数据比较快
126.纯虚函数是怎样实现的?在编译原理上讲一下?
在类内部添加一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包含了所有的虚拟函数的入口地址,每个类的虚拟函数表都不
一样,在运行阶段可以循此脉络找到自己的函数入口。
纯虚函数相当于占位符,先在虚函数表中占一个位置由派生类实现后再把真正的函数指针填进去。除此之外和普通的虚函数没什么区别。
127.抽象类为什么不能实例化?
抽象类中的纯虚函数没有具体的实现,所以没办法实例化。
128.在函数后面加个const是怎么理解的?
在函数后面加个const一般在类的成员函数中使用,表示这个函数不修改数据成员的值。
另:void set_prt_val(int val)const {*ptr= val}理解:指针ptr指向的内容不是类的数据成员,所以这可这么写:*ptr = val;但这个指针在这个函数中不能修改。如果写成这样:ptr = &i(假设i是另外一个整形变量)就不对了,因为改变了指针的内容。
129.进程间通信类型:
(1)环境变量、文件描述符 一般Unix环境下的父进程执行fork(),生成的子进程拥有了父进程当前设置的环境变量以及文件描述符;由于通信是一个单向的、一次性的通信,随后的父进程以及子进程后续的内容不能再能共享;
(2)命令行参数 大多数用户都使用过ShellExec相关的命令,此API可以打开新的进程,并可以通过接口里的输入参数进行信息共享;同样,他也是一个单项、一次性的通信;
(3)管道 使用文件和写方式访问公用的数据结构;管道分为匿名管道和命名管道,前者是用作关联进程间用,后者为无关联的进程使用;前者通过文件描述符或文件句柄提供对命名管道的访问,后者需要知道管道名称才能读写管道;一般来讲,读写的内容是字节流,需要转换为有意义的结构才有意义;
(4)共享内存 进程需要可以被其他进程访问浏览的进程块;进程间共享内存的关系与函数间共享全局变量的关系类似
(5)DDE 动态数据交互
线程间通信类型:
(1)全局数据;
(2)全局变量;
(3)全局数据结构;
(4)线程间通信的参数:pThread_create这类API接口中的参数
130.关于内存对齐的问题以及sizof()的输出
答:编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
131.winsock建立连接的主要实现步骤?
答:
TCP:服务器端:1.socket()建立套接字,2将套接字绑定到本地地址和端口上,绑定(bind)3.将套接字设为监听模式,准备接收客户端,监听(listen);4.等待客户端请求到来,请求到来后,连接请求,并返回一个新的对应此连接的套接字,accept()5.用返回的套接字和客户端进行通讯(send/recv);6.返回并等待另一客户请求。7.关闭套接字。
客户端:1.socket()建立套接字2.向服务器发出连接请求,(connect)2。和服务器进行通信,send()和recv(),在套接字上写读数据,直至数据交换完毕;4closesocket()关闭套接字。
UDP:1服务器端:1.创建套接字(socekt)2.将套接字绑定到本地地址和端口上(bind);3.等待接收数据(recvfrom);4.closesocket()关闭套接字。
客户端:1.创建套接字(socekt)2,向服务器端发送数据(sendto)3.closesocket()关闭套接字。
132.C++中为什么用模板类。
答:(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
133.动态连接库的两种方式?
答:调用一个DLL中的函数有两种方法:
1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2.运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。
134..CSingleLock是干什么的。
答:同步多个线程对一个数据类的同时访问
135.编写strcat函数(6分)
已知strcat函数的原型是char *strcat (char *strDest, const char *strSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数 strcat
答:
VC源码:
char * __cdecl strcat (char * dst, const char * src)
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to end of dst */
return( dst ); /* return dst */
}
(2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值?
答:方便赋值给其他变量
已知类String 的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写String 的上述4 个函数。
标准答案:
// String 的析构函数
String::~String(void) // 3 分
{
delete [] m_data;
// 由于m_data 是内部数据类型,也可以写成 delete m_data;
}
// String 的普通构造函数
String::String(const char *str) // 6 分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加 NULL 判断则更好
*m_data = ‘\0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(const String &other) // 3 分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other) // 13 分
{
// (1) 检查自赋值 // 4 分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 // 3 分
delete [] m_data;
// (3)分配新的内存资源,并复制内容 // 3 分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
// (4)返回本对象的引用 // 3 分
return *this;
}