小结小结


1.简单自我介绍:


2.项目:

1. 说一下你认为印象最深刻的项目详细说一下?

2. 项目的改进,应用,如何做,流程图

3. 说一个最熟悉的项目?


4. 项目中遇到的最难解决的问题?怎么解决?举实际例子说明。


3.C++基础:

(1) 多态如何实现

--1.1 用virtual关键字声明的函数叫做虚函数,虚函数肯定是类的成员函数。

--1.2 存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

--1.3 多态性是一个接口多种实现,是面向对象的核心。分为类的多态性和函数的多态性。

--1.4. 多态用虚函数来实现,结合动态绑定。

--1.5 纯虚函数是虚函数再加上= 0。

--1.6 抽象类是指包括至少一个纯虚函数的类

----编译器在编译的时候,发现某个类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即vtable),该表是一个一维数组,在这个数组中存放每个虚函数的地址。

----那么如何定位虚表呢?编译器另外还为每个类的对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。

----正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数。那么虚表指针在什么时候,或者说在什么地方初始化呢? 答案是在构造函数中进行虚表的创建和虚表指针的初始化。

要注意:对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。

----总结(基类有虚函数):

1. 每一个类都有虚表。
2. 虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。
3. 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。


(5)  静态全局变量与动态全局变量的区别?
1.变量被放在程序的全局存储区中,这样在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2.变量用static告知编译器,自己仅在变量的作用范围内可见。这一点是它与全局变量的区别
A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
E.函数中必须要使用static变量的情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数与普通函数作用域不同,只在定义该变量的源文件内有效;
struct能包含成员函数吗? 能!
struct能继承吗? 能!!
struct能实现多态吗? 能!!! 


这里说的静态数据区,是相对于堆、栈等动态数据区而言的。
静态数据区存放的是全局变量和静态变量,从这一点上来说,字符串常量又可以称之为一个无名的静态变量,
因为"Hello world!"这个字符串在函数 s1和s2 中都引用了,但在内存中却只有一份拷贝,这与静态变量性质相当神似。
封装机制将数据和代码捆绑到一起,避免了外界的干扰和不确定性。它同样允许创建对象。简单的说,一个对象就是一个封装了数据和操作这些数据的代码的逻辑实体。
继承是可以让某个类型的对象获得另一个类型的对象的属性的方法。
多态是OOP的另一个重要概念。多态的意思是事物具有不同形式的能力。
举个例子,对于不同的实例,某个操作可能会有不同的行为。这个行为依赖于所要操作数据的类型。
---1 修改内容
#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char a[] = "hello";
a[0] = 'X';
cout << a << endl;
char *p = "world"; // 注意p指向常量字符串
p[0] = 'X'; // 编译器不能发现该错误,但会在运行时错误
cout << p << endl;
return 0;
}
-----从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。
2、动态链表的搜索效率比较低,属于线性搜索,数组是索引,速度快
利用数组来组织数据结构
优点是:存储效率高,存取速度快。
但是,对于数据元素个数动态增长的情况,由于数组个数不能自由扩充(动态数组除外),一旦空间用完就不能再向里加入新元素,否则,就会导致系统停工。
利用链表则适用于插入或删除频繁、存储空间需求不定的情况
从逻辑结构来看
1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取。
2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素
从内存存储来看
1. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小
2. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦
从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。
参考:http://blog.csdn.net/haibarahu/article/details/8213875
1.接口是包含一组虚方法的抽象类型,其中每一种方法都有其名称、参数和返回值。接口方法不能包含任何实现
如果创建的功能将在大范围的全异对象间使用,则使用接口。
2.抽象类提供多个派生类共享基类的公共定义,它既可以提供抽象方法,也可以提供非抽象方法。抽象类不能实例化,必须通过继承由派生类实现其抽象方法。
相同点:都不能被直接实例化,都可以通过继承实现其抽象方法。 都是面向抽象编程的技术基础,实现了诸多的设计模式。
2. 如果一个类B在语法上实现了(implement)接口I, 那么类B遵从接口I制定的协议.
而interface的本质是一套协议. 在程序设计的发展中, 人们又发现接口可以用来表示对行为的抽象, 不过, 这只是interface的一种用法不是其本质.
比如,一家生产门的公司,需要先定义好门的模板,以便能快速生产出各种规格的门。
这里的模板通常会有两类模板:抽象类模板和接口模板
抽象类模板:这个模板里面应该包含所有门都应该具有的共同属性(如,门的形状和颜色等)和共同行为(如,开门和关门)。
接口模板:有些门可能需要具有报警和指纹识别等功能,但这些功能又不是所有门必须具有的,所以像这样的行为应该放在单独的接口中。
有了上面的两类模板,以后生产门就很方便了:利用抽象类模板和包含了报警功能的接口模板就能生产具有报警功能的门了。同理,利用抽象类模板和包含了指纹识别功能的接口模板就能生产具有指纹识别功能的门了。

可以参考:http://blog.csdn.net/tujiaw/article/details/6753498

(2) 多态的内存分布


(3) 构造函数可以是虚函数吗? 画内存分布图说明。

----不能。因为,虚拟表指针需要在

(4) 构造函数可以调用虚函数吗,编译能通过吗?可以运行吗?----可以的

---可以的。

#include "stdafx.h"
#include <iostream>
using namespace std;
class Base {
public:
Base() { f(); }
virtual void f() { cout << "base" << endl; }
};
class Derived: public Base {
public:
Derived() { f(); };
void f() { cout << "Derived" << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Base *p = new Derived();
p->f(); 
return 0;
}

----static 声明的变量在C语言中有两方面的特征:

Tips:

---可重入函数:是指能够被多个线程“同时”调用的函数,并且能保证结果正确性的函数。在C语言中编写可重入函数时,尽量不要使用全局变量

或静态变量,如果使用了全局变量或静态变量,就需要特别注意对这类变量访问的互斥。



---函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

---扩展分析:术语static有着不寻常的历史。起初,在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。随后,static在C中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。最后,C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数(与Java中此关键字的含义相同)。

---全局变量、静态全局变量、静态局部变量和局部变量的区别

变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。

按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区


---从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。

---

 static函数与普通函数作用域不同,只在定义该变量的源文件内有效。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;

全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

---全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知

参考:http://blog.sina.com.cn/s/blog_63278e550100lcb9.html


(6) Class 与 struct 的区别?

C++中的struct对C中的struct进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。

既然这些它都能实现,那它和class还能有什么区别?

最本质的一个区别就是默认的访问控制: 

默认的继承访问权限

struct是public的,class是private的。


“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数

参考:http://blog.csdn.net/yuliu0552/article/details/6717915

(7) 堆栈,如何检测内存泄露问题,并且如何避免?

 Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具

内存泄漏产生的原因一般是三种情况: 

1. 分配完内存之后忘了回收;   Temp = new BYTE[100];, 没delete

2. 程序Code有问题,造成没有办法回收; 

Temp1 = new BYTE[100];

Temp2 = new BYTE[100];

Temp2 = Temp1;

这样,Temp2的内存地址就丢掉了

3. 某些API函数操作不正确,造成内存泄漏。


(8) 静态变量存在哪里堆还是栈中还是其他?

---全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。


--全局区(静态去)static : 全局变量和静态变量的存储是放在一块的。

                    初始化的全局变量和静态变量在一块区域,

                    未初始化的全局变量和静态变量又放在相邻的另一块区域中。

                    程序结束后由系统释放


---在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了

在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。


---字符串常量,之所以称之为常量,因为它可一看作是一个没有命名的字符串且为常量,存放在静态数据区。

参考:http://bbs.csdn.net/topics/230051041

(9) 面向对象的理解?

----首先面向对象的三大特性和6大原则

---封装:

---本质是对客观事物的抽象!!

---继承:


---多态:


--面向对象的六大原则:

一、单一职责原则:就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因

二、里氏代换原则:子类型必须能够替换它们的基类型。

三、依赖倒置原则:要依赖于抽象,不要依赖于具体。客户端依赖于抽象耦合。要针对接口编程,不针对实现编程。

四、接口隔离原则:

使用多个专一功能的接口比使用一个的总接口总要好。从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小接口上的。过于臃肿的接口是对接口的污染,不应该强迫客户依赖于它们不用的方法

五、迪米特原则:对象与对象之间应该使用尽可能少的方法来关联,避免千丝万缕的关系。

六、开放-封闭原则:对扩展开放,对修改关闭。

参考:http://blog.csdn.net/coolingcoding/article/details/8043265


(10)指针和数组的区别;

1.1 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

1.2 指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。


----2内容复制与比较

不能对数组名进行直接复制与比较

--3计算内存容量

用运算符sizeof可以计算出数组的容量(字节数)。上面示例中,sizeof(a)的值是6(注意别忘了\0)。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

参考:http://blog.chinaunix.net/uid-23544029-id-311366.html


(11) 链表和数组的区别以及分别适用于什么情况;

数组又叫做顺序表,主要区别在于,顺序表是在内存中开辟一段连续的空间来存储数据,而链表是靠指针来连接多块不连续的的空间,在逻辑上形成一片连续的空间来存储数据。两种各有各的好处,链表方便删除和插入,顺表表方便排序等。

1、动态链表的长度不是固定的,可以弹性伸缩,数组元素数量是固定的,开始由编译器分配好内存空间,动不了。


(12) static关键字在C,JAVA,C#中的作用

1.1 java中的static用来修饰类的成员函数,或者是成员变量,这样这些变量就成为了静态方法(类方法)和静态变量(类变量)

此外静态方法有一些限制:1.只能调用静态方法,使用静态变量  2.不能调用this或者super 

static变量存放在常量和静态变量区,在编译时分配内存。


1.2 c中的static

c中的static也可以用来修饰变量和函数,称之为静态变量(内部变量)和静态函数(内部函数)

他们是与java不同的,c中的静态变量中静态标示的是变量的作用域

这里静态变量根据声明的位置不同,可以分为全局静态变量和局部静态变量

其中全局静态变量的作用域为定义开始到文件结束。

局部静态变量的作用域为定义开始到函数或者语句块结束处。

但是全局静态变量和局部静态变量都是存储在全局数据区的,只有当程序结束时才会释放。

静态函数中的静态也是指作用域,他的作用域是声明开始到文件结束。


1.3 c++中的static

c++在static的使用分为一般使用和面向对象使用,前者就是类似c的static使用,而后者则类似java中static中的使用


(13) 抽象类和接口的区别;


1. 如果一个类B在语法上继承(extend)了类A, 那么在语义上类B是一个类A.

使用abstract class的根本原因在于, 人们希望通过这样的方式, 表现不同层次的抽象.


总之:抽象类用来抽象自然界一些具有相似性质和行为的对象。而接口用来抽象行为的标准和规范,用来告诉接口的实现者必要按照某种规范去完成某个功能。


4. 算法:

在地图上给定两个点,怎么搜索从一个点另一个点的最优路径。

1. A*(A-Star) 启发搜索,是一种基于广度搜索的算法;

---A*算法,A*(A-Star)算法是一种静态路网中求解最短路最有效的方法。估价值与实际值越接近,估价函数取得就越好。

A*算法,作为启发式算法中很重要的一种,被广泛应用在最优路径求解和一些策略设计的问题中。而A*算法最为核心的部分,就在于它的一个估值函数的设计上:

                 f(n)=g(n)+h(n)
1)搜索树上存在着从起始点到终了点的最优路径。
2)问题域是有限的。
3)所有结点的子结点的搜索代价值>0。
4)h(n)=<h*(n) (h*(n)为实际问题的代价值)
       当此四个条件都满足时,一个具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法,并一定能找到最优解。([1]P89给出了相关的证明)

    其中f(n)是每个可能试探点的估值,它有两部分组成:

一部分为g(n),它表示从起始搜索点到当前点的代价(通常用某结点在搜索树中的深度来表示)。

另一部分,即h(n),它表示启发式搜索中最为重要的一部分,即当前结点到目标结点的估值,

h(n)设计的好坏,直接影响着具有此种启发式函数的启发式算法的是否能称为A*算法。

一种具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法的充分条件是:

1)搜索树上存在着从起始点到终了点的最优路径。

2)问题域是有限的。
    3)所有结点的子结点的搜索代价值>0。
    4)h(n)=<h*(n) (h*(n)为实际问题的代价值)。
       当此四个条件都满足时,一个具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法,并一定能找到最优解。([1]P89给出了相关的证明)
条件4): h(n)<=h*(n)是需要精心设计的,由于h*(n)显然是无法知道的,

 

对于一个搜索问题,显然,条件1,2,3都是很容易满足的,而

所以,一个满足条件4)的启发策略h(n)就来的难能可贵了。

不过,对于图的最优路径搜索和八数码问题,有些相关策略h(n)不仅很好理解,而且已经在理论上证明是满足条件4)的,从而为这个算法的推广起到了决定性的作用。不过h(n)距离h*(n)的呈度不能过大,否则h(n)就没有过强的区分能力,算法效率并不会很高。对一个好的h(n)的评价是:h(n)在h*(n)的下界之下,并且尽量接近h*(n).


A*搜寻算法,俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。

该算法像Dijkstra算法一样,可以找到一条最短路径;也像BFS一样,进行启发式的搜索。
如果h(n)为0,
只需求出g(n),即求出起点到任意顶点n的最短路径,则转化为单源最短路径问题,即Dijkstra算法
如果h(n)<=n到目标的实际距离,则一定可以求出最优解。而且h(n)越小,需要计算的节点越多,算法效率越低。

 

在此算法中,g(n)表示从起点到任意顶点n的实际距离,h(n)表示任意顶点n到目标顶点的估算距离。因此,A*算法的公式为:f(n)=g(n)+h(n)。这个公式遵循以下特性:



2. Dij, DFS, BFS的实现关键点

----DFSBFS展开子结点时均属于盲目型搜索,也就是说,它不会选择哪个结点在下一次搜索中更优而去跳转到该结点进行下一步的搜索。在运气不好的情形中,均需要试探完整个解集空间, 显然,只能适用于问题规模不大的搜索问题中。


3. 知道哪些排序算法:

(1)堆排序、归并排序、快排的时间复杂度各是多少,分析平均时间和最坏时间复杂度?

堆排序:最好O(nlogn),平均:O(nlogn),最坏:O(nlogn)  ---不稳定

归并排序:最好O(nlogn),平均:O(nlogn),最坏:O(nlogn) ---稳定

快速排序:最好O(nlogn),平均:O(nlogn),最坏:O(nlogn) ---不稳定


(2)快排如何实现?,快速排序,直接写出来?

#include <iostream>

using namespace std;

int partion (int a[], int start, int end)
{
	int pivot = a[start];
	while (start < end)
	{
		while (start < end && pivot <= a[end])
			end--;
		a[start] = a[end];
		while (start < end && pivot >= a[start])
			start++;
		a[end] = a[start];
	}
	a[start] = pivot;

	return start;
}

void quickSort(int a[], int start, int end)
{
	if (a == NULL || start > end)
		return;

	int index = partion(a, start, end);

	if( start < index )
		quickSort(a, start, index-1);
	if( index < end )		
		quickSort(a, index + 1, end);
	
}
int _tmain(int argc, _TCHAR* argv[])
{
	int a[] = {1,2,3,4,5,6,7};
	quickSort(a,0,sizeof(a)/sizeof(int)-1);
	
	for(int i = 0; i < sizeof(a)/sizeof(int); i++)
		cout << a[i] << " ";
	cout << endl;
	return 0;
}


4. 编程:给定一个已经排序的数组,然后统计其中出现次数最多的那个数?

方法1:暴力,遍历,统计

int getMoreNum(int a[], int len)
{
	if (a == NULL || len <= 0)
		exit(-1);
	int max = INT_MIN;
	int k = 0, index = 0;

	for (int i = 1; i < len; i++) {
		
		if (a[i-1] == a[i]) {			
			if (max <= index) {
				max = index;
				k = i;
			}
			index++;
			continue;
		}
		index = 0;
	}
	return a[k];
}

方法2:以空间换换取时间(感觉,不太好,这里负数也可以,可能会溢出)

int getMaxMinNum(int a[], int len, int& max, int& min)
{
	if (a== NULL || len <= 0)
		return 0;
	int _max = a[0];
	int _min = a[0];
	for (int i = 0; i < len; i++)	
		if (_min >= a[i])
			_min = a[i];		
	
	for (int i = 0; i < len; i++)
		if (_max <= a[i])
			_max = a[i];
	
	max = _max;
	min = _min;

	return 1;
}

int getMoreNum2(int a[], int len)
{
	int min, max;
	int _max = INT_MIN;
	getMaxMinNum(a, len, max, min);

	if (min < 0) {
		for (int i = 0; i < len; i++)
			a[i] = a[i] - min;
		max = max -min;
	}
	
	int *arr = new int[max];
	for (int i = 0; i < max; i++)
		 arr[i]= 0;

	int k = 0;
	for(int i = 0; i < len; i++)
		arr[a[i]]++;

	for(int j = 0; j < max; j++) {		
		if (arr[j] >= _max) {
			_max = arr[j];
			k = j;
		}
	}

	return  min > 0 ? k : k + min;
}

方法3.哈希表(STL)

int getMoreNum3(int a[], int len)
{
	if (a== NULL || len <= 0)
		return 0;
	map<int,int> hash;
	int val = 0;

	for (int i = 0; i < len; i++)
	{
		hash[a[i]]++;
		if (hash[a[i]] >= hash[val])
			val = a[i];
	}
	return val;

}

5. 用两个栈实现一个队列?



6. Char ** StrToK(const char* S1,const char* S2)实现该函数,功能:S2将S1字符串截断后,分别输出截断的字符串。举例例如S1=abcdefg, S2=be,将a,cd,fg三个字符串用指向指针的指针返回。

char ** strToK(const char *s1, const char *s2)
{
	int len_s1 = strlen(s1);
	int len_s2 = strlen(s2);
	char* ptr = new char[len_s1+1];
	strcpy(ptr, s1);

	for (int i = 0; i < len_s2; i++ ) {
		for (int j = 0; j < len_s1; j++)
			if (s2[i] == ptr[j])
				ptr[j] = '\0';
	}
	int num = 0;
	for (int i = 0; i < len_s1; i++)
		if (ptr[i] == '\0')
			num++;
	char ** str = new char * [num +2];
	int k = 0;
	str[k] = ptr;
	str[num+1] = NULL;
	for (int i = 0; i < len_s1; i++)
		if (ptr[i] == '\0')
			str[++k] = &ptr[i+1];
	return str;
}



7. 树的子结构问题?


8. 写段代码,1到1000,统计出1的个数?

方法1:暴力法(枚举,判断每个数中1的个数)

int hasOneNum(int num)
{
	int index = 0;
	while (num)
	{
		if (num % 10 == 1)
			index ++;
		num = num / 10;
	}
	return index ++;
}

int getOneToK(int k)
{
	int sum = 0;
	for (int i = 1; i <= k; i++)
		sum = sum + hasOneNum(i);
	return sum;
}

另外针对在一数中统计1的个数,可以把她转换为字符串,然后计算1的个数,如:

int hasOneNum1(int num)
{
	int index = 0;
	char tmp[100];
	itoa(num,tmp,10);
	for(int i = 0; i < strlen(tmp); i++)
		if (tmp[i] == '1')
			index ++;
	return index;
}

9.写代码: 一个二分查找,一个堆排序?

二分查找:

int binarySearch(int a[], int len, int key)
{
	int low = 0;
	int high = len -1;

	while (low <= high)
	{
		int mid = low+ (high-low)/2;
		if (a[mid] == key)
			return mid;
		else if (a[mid] < key)
			low = mid +1;
		else 
			high = mid -1;
	}
	//查找失败返回-1
	return -1;
}

//递归实现
int binarySearch1(int a[], int start, int end, int key)
{	
	if (start <= end) {
		int mid = start + (end - start)/2;
		if (a[mid] == key)
			return mid;
		else if (a[mid] < key)
			return binarySearch1(a, mid + 1, end, key);
		else 
			return binarySearch1(a, start, mid -1, key);
	}
	return -1;
}

堆排序:(感觉堆排序是最难写的一个了)

void adjustMinHeap(int a[], int pos, int len)
{
	int temp;
	int child;

	while(2*pos+1 <= len)
	{
		temp = a[pos];
		child = 2*pos + 1;
		//child < len 说明pos只有右子结点,没有左子结点
		if (child < len && a[child] > a[child+1])
			child ++;
		if (a[child] < temp)
		{
			a[pos] = a[child];
			a[child] = temp;
		}
		pos = child;
	}
}

void swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void myHeapSort(int a[], int len)
{
	//1.建堆
	for (int i = len/2 -1; i>= 0; i--)
		adjustMinHeap(a, i, len-1);
	for(int i = 0; i < len; i++)
		cout << a[i] << " ";
	cout << endl;
	//2.调整堆
	for (int i = len-1; i>= 0; i--)
	{
		swap(a[0],a[i]);
		//把最小的结点放在数组最后,同时数组长度减少1
		adjustMinHeap(a,0,i-1);
	}

	for(int i = len-1; i >= 0; i--)
		cout << a[i] << " ";
	cout << endl;
}


10. 说一说排序算法 比较快的有啥? 详细说一说堆排序?

----希尔排序:最好O(n), 平均:O(nlogn), 最坏O(n^s) 1<s<2

----快速排序:最好O(nlogn), 平均:O(nlogn),最坏O(n^2) ,当序列有序的时候为:O(n^2),退化为冒泡排序

----堆排序:最好O(nlogn), 平均:O(nlogn), 最坏O(nlogn)

----归并排序:最好O(nlogn),平均:O(nlogn),最坏O(nlogn)


11. 写了一个字符串转数字的程序?


12. 快速排序在什么情况下其时间复杂度是O(n2),并举例说明;

----当快速排序的序列有序的时候为:O(n^2),退化为冒泡排序


5. STL:

1. 知道vector吗,一个自然数数组,要删除其中的奇数,写出代码

2. 需要注意的是erase删除结点,则该结点的iterator会失效,同时erase会返回下一个有效迭代器,所以iter++只有在偶数的时候才运行。

3. iter++和++iter的区别? ----前者会产生一个临时变量,后者的效率更高,如果前面有=的话,得到的值不一样

4. vector是怎么存储的,如果让你实现vector,你怎么做? ---用数组实现vector

5. 注意vector满时要重新分配两倍的内存空间,将原数组拷贝进去。

6. vector是线程安全的吗?

线程安全就是说多线程访问同一代码,不会产生不确定的结果。编写线程安全的代码是低依靠线程同步。

STL当有两个线程对同一个容器进行写操作时就会以抛出异常的方式去阻止这种破坏安全性的操作行为

7. 写个stack的实现代码,最好不要用其他容器? 用数组实现?用deque实现?


8. 写vector实现?



6. 计算机网络:

1. 三次握手,四次挥手,握手阶段什么时候可以传数据,四次挥手为什么客户端要再等待一会?画图说明就可以了。


2. 网络协议 TCP/IP? 网络层协议以及传输层协议上?


3. selectpollepoll的区别

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作

select的几大缺点:

(1)每次调用select,都需要把fd集合用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024


select函数,该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生

经历一段指定的时间后才唤醒。

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1

函数参数介绍如下:

(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。(描述符是从0开始的)

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

          void FD_ZERO(fd_set *fdset);           //清空集合

          void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中

          void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除

          int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

         struct timeval{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds

       };

这个参数有三种可能:

(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。

(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。

(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。


poll函数, poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

pollfd结构体定义如下:

struct pollfd {

int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 实际发生了的事件 */
} ; 

每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

  POLLIN         有数据可读。

  POLLRDNORM       有普通数据可读。

  POLLRDBAND      有优先数据可读。

  POLLPRI         有紧迫数据可读。

  POLLOUT            写数据不会导致阻塞。

  POLLWRNORM       写普通数据不会导致阻塞。

  POLLWRBAND        写优先数据不会导致阻塞。

  POLLMSGSIGPOLL     消息可用。

  此外,revents域中还可能返回下列事件:

       POLLER     指定的文件描述符发生错误。

  POLLHUP   指定的文件描述符挂起事件。

  POLLNVAL  指定的文件描述符非法。

这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。

使用poll()和select()不一样,你不需要显式地请求异常情况报告。
  POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

  timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

epoll接口, epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

epoll操作过程需要三个接口,分别如下

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);


(1) int epoll_create(int size);
  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  epoll的事件注册函数它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。


epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

        LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

  ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。


总结:

select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

2 poll实现

  poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。

epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

  对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

  对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

  对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

总结:

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。


 

select、poll、epoll之间的区别总结[整理]http://www.cnblogs.com/Anker/p/3265058.html

网络IO之阻塞、非阻塞、同步、异步总结

IO多路复用之epoll总结

IO多路复用之select总结




7. 操作系统:

1. 进程和线程是什么,区别和联系?

(1)进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。它时系统进行资源分配和调度的一个独立单位。

(2)线程是进程的一个实体。它是CPU调度和分配的基本单位。

(3)一个进程至少拥有一个线程。属于一个进程的所有线程共享该进程的所有资源。

(4)线程又被称为轻量级进程,线程间切换比进程间切换代价小。


2. 多线程知道多少,互斥量(Mutex)、事件(Event)、信号量(Semaphore)怎样用?--还有一个临界区(Critical Section)

---同步

互斥量: 采用互斥对象机制。只有拥有互斥对象的线程才有访问公共资源的权限。互斥不仅能实现同一个应用程序的公共资源安全共享,还能

实现不同应用程序的公共资源安全共享。

事件: 通过通知操作的方式来保存线程的同步,还可以方便实现对多个线程优先级比较的操作。

信号量: 允许多个线程在同一时刻范围同一资源,但是需要限制在同一时刻访问此资源的最大线程数。PV操作

临界区: 通过对多线程的串行化,来访问公共资源或一段代码,速度快,适合控制数据访问。

---通信

管道 :无名管道,有名管道(FIFO)

消息队列

共享存储

信号量

套接字


3. 写一个生产者消费者多线程程序?如何优化? ----用信号量可以实现并发,多个生产者和多个消费者,既有同步也有互斥

(1) 一个生产者,一个消费者,一个公共缓冲区

定义两个同步信号量:

empty---表示缓冲区是否为空,初值为1

full   -----表示缓冲区是否为满,初值为0

生产者进程:

while(true) {

生产一个产品;

P(empty);

产品送往Buffer;

V(full);

}

消费者进程:

while(true) {

P(full);

从Buffer取出一个产品;

V(empty);

消费该产品;

}

(2) 一个生产者,一个消费这,n个公共缓冲区

定义两个同步信号量:

empty---表示缓冲区是否为空,初值为 n

full   -----表示缓冲区是否为满,初值为0

设缓冲区的编号为:1~n,定义两个指针in和out,分别是生产者进程和消费者进程使用的指针

生产者进程:

while(true) {

生产一个产品;

P(empty);

产品送往buffer(in);

in = (in + 1) % n;

V(full);

}

消费者进程

while(true) {

P(full);

从buffer(out)中取出产品;

out = (out + 1) %n;

V(empty);

消费该产品;

}

(3)多个生产者,多个消费者,n个公共缓冲区

定义四个信号量:

//前面两个是资源信号量

empty ----- 表示缓冲区是否为空,初值为n;

full      ------ 表示缓冲区是否为满,初值为0;

//后面两个是,互斥信号量

mutex1 -----生产者之间的互斥信号量,初值为1.

mutex2 -----消费者之间的互斥信号量,初值为1.

设缓冲区的编号为1-n,定义两个指针in和out,分别是生产者进程和消费者进程使用的指针,指向下一个可用缓冲区。

生产者进程:

while(true) {

生产一个产品;

P(empty);

P(mutex1);

产品送往buffer(in);

in = (in + 1) % n;

V(mutex1);

V(full);

}

消费者进程:

while(true) {

P(full);

P(mutex2);

从buffer(out)中取出产品;

out = (out + 1) % n;

V(mutex2);

V(empy);

}

4.假设m个面试者,n个面试官,写个多线程并发的程序来描述这个过程,可以写伪代码。


----这个其实还好,创建资源为n的信号量,即可实现。


5. 如何写多线程,项目中怎么用的?


6. 虚拟内存管理了解不?

虚拟内存,简称虚存,是计算机系统内存管理的一种技术。它是相对物理内存而言的。

它使得应用程序认为它拥有连续可用的内存,允许程序员编写并运行比实际系统拥有的内存大得多

的程序,这使得许多大型软件项目能够在具有有限内存资源的系统上实现。而实际上,它通常被分割成

多个物理内存碎片,还有一部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

好处:

(1)扩大地址空间;段式虚存;页式虚存;段页式;

(2)内存保护

(3)公平分配内存

(4)当进程需要通信时,可以采用虚存共享实现。


程序局部性原理,使得在程序在整个运行过程中程序引用的不同页面的总数可能超出物理存储器总的大小,但是局部性原则保证了

在任意时刻,程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集或者常驻集。

只要我们的程序有好的时间局部性,虚拟存储器系统就能工作得相当好。


事实上,操作系统为每个进程提供了一个独立的页表(常驻内存),因而也就是一个独立的虚拟地址空间。


Linux 采用了 Intel CPU 的内存分页管理机制,使用虚拟线性地址与实际物理内存地址映射的方法让所有同时执行
的程序共同使用有限的内存。内存分页管理的基本原理是将整个主内存区域划分成 4096 字节为一页的内
存页面。程序申请使用内存时,就以内存页为单位进行分配。


进程的虚拟地址需要首先通过其局部段描述符变换为 CPU 整个线性地址空间中的地址,然后再使用

页目录表 PDT (一级页表)和页表 PT (二级页表)映射到实际物理地址页上。


7. 进程调度?

进程调度模块用来负责控制进程对 CPU 资源的使用。

内存管理模块用于确保所有进程能够安全地共享机器主内
存区,同时,内存管理模块还支持虚拟内存管理方式,使得 Linux 支持进程使用比实际内存空间更多大的
内存容量。并可以利用文件系统把暂时不用的内存数据块会被交换到外部存储设备上去,当需要时再交换
回来。


文件系统模块用于支持对外部设备的驱动和存储。虚拟文件系统模块通过向所有的外部存储设备提
供一个通用的文件接口,隐藏了各种硬件设备的不同细节。从而提供并支持与其它操作系统兼容的多种文
件系统格式。

进程间通信模块子系统用于支持多种进程间的信息交换方式。网络接口模块提供对多种网络
通信标准的访问并支持许多网络硬件

8. linux:

1. top命令用过没有?

2. fork()函数用过没有?

3. gdb调试

4. 如果用gdb调试像fork()这样的多线程怎么调试?

5. 多线程里面的断点调试怎么实现的?


6.写几个常用的linux命令? ps –ef 中e、f是什么意思?


7. Linux——如何查看内存,CPU,负载

Linux是如何减少内存碎片的问题的

9. Nginx 进程模型,优点


9. 海量数据:

TOP K问题---:

1. 给定1G文本,文本每行是一个单词,现在可用内存很少,假设1M,怎么统计文本中出现频率最多的10个单词

----类似map-reduce,分而治之,将大文本分为多个小文本,即使用hash函数将相同hash值的单词分到同一子文本,直到内存能装下,再hashmap统计次数,最后建立元素为10的最小堆,即可完成。

2. 这个算法的时间复杂度,解释了下统计和堆的时间?


3. 作为哈希函数的条件 à 同一个key值每次映射必须是一样的哈希值


10. 设计模式:

1.熟悉哪些设计模式?

2. 写一下单例模式?(写了内部类实现的方式,然后接着问还有没有别的方式,问二者区别,哪个更好。)

3. 设计模式中分为几大类?

4. 适配器和桥接模式的应用场景和区别?


11. 数据库:

1. 数据库 三范式

2. 数据库如何进行事务处理?


12. 服务器:

1. 服务器架构问题



13. 开放性问题:

1.开放性问题1:你是怎么学习C++的?

2.开放性问题2:除了学C++外,你还学过或者想学哪些其他的东西?

3. 如何实现车牌识别?

----车牌字符比较大,而且边缘明显,所以用滤波加二值化和膨胀等形态学处理应该就可以得到,可能效果不好。复杂一点的就是提典型特征+神经网络SVM等分类器了。

4. 以后要做什么方向,系统还是业务,客户端还是服务器端?


5. 图像上的文本识别问题?


6. 图像上水印文本的识别?


14. 自己提问:

1. 阿里的共享业务事业部是干啥的,为何它和聚划算啊天猫啊淘宝啊等等业务都有关系?


15. HR姐姐:

1. 说一下你的成长经历。

2. 说一下你的活动经历。

3. 大学期间成绩如何,哪一门学的最好。

4. 读书期间谁对你影响最大,举例说明。

5.  说一下你在做项目期间,是怎么和其他人合作的,是负责人还是参与者,你和其他人的职责各是什么,怎么分工。还有要是你需要的代码别人没完成,你是怎么和他处理的。能不能举例说明。

6. 说一下你学什么东西的心得最多,其中的亮点在哪里。

7. 为什么要写博客?

8. 你以后要做什么方向?

9. 你的规划是怎样的?

-------可以说自己的优势是做技术,三五年内肯定是做技术比较合适,最好是进公司,提升自己的技术水平,和公司一起进步,至于再往后,我觉得要根据这几年的积累来决定


10. 对什么新技术比较了解,还了解哪些数据库新技术等,大数据处理,并行,分布式等。面试官会说一个应用场景让你利用你所了解的这些技术去解决。

11. 垃圾邮件的判断问题?


------总的来说,写在简历上的东西都会问到,只有你不敢写的,没有不敢问的,写在上面的一定要搞清楚。C++基础,数据结构,STL,多线程这几点被频繁问到,基础一定要扎实才能不被问倒。

自己研究的东西一定要研究透,问的都是比较有深度的。自己搞不明白的就别写简历了


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值