面试问题疑点总结

1、hash函数

就是把不同长度的输入,通过散列算法,变成整形输出,返回类型为size_t.。其应用如加密领域的MD5(可是是16位或者32位的字符串等)、SHA1都是HASH加密。

对于一些字符串hash函数,一般都是左移多少位然后不断异或之类的,得到一个整形值。

使用方式为:hash<string> a;    int value=a("100");  cout<<value;   结果为1960084178; 不需要头文件,很方便。

2、hash_map

map和hash_map的区别是map内部是红黑树实现的,是标准的一部分,且元素按照键值升序排列,而hashmap是用哈希表,有点在于各项操作的平均时间复杂度接近常数。

这里说到hash表,其实现一般为拉链法,由数组和链表组成,对于每一个元素存储链表的头结点,对于hash(key)%len相同的元素,存在一个下标中,然后按照链表连接起来。所以插入查找删除都接近常数。

对于hash_map,包括一个vector,一个list和一个pair,其中vector用于保存桶(顺序结构),list用于进行冲突处理(链表),pair用于保存key->value结构(节点)。

#include <hash_map> ,访问方式为

hash_map<int, int> IntHash;
IntHash[1] = 123;
IntHash[2] = 456;

同样,对于无序容器 unordered_map<string,size_t> word_count;内部同样是用hash函数实现的。

3、单例模式

其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。

《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

class CSingleton
{
private:
	CSingleton()   //构造函数是私有的
	{
	}
	static CSingleton *m_pInstance;
public:
	static CSingleton * GetInstance()
	{
		if(m_pInstance == NULL)  //判断是否第一次调用
			m_pInstance = new CSingleton();
		return m_pInstance;
	}
};
可以用CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();

这种方法来调用,但是无法将创建的实例析构,因此,在类内部添加一个静态嵌入类,其作用是在析构的时候将CSingleton释放掉,这样就解决问题了。

还有其它方法,就是用静态指针返回 static CSingleton instance; return instance  //局部静态变量

详细信息,参考:http://blog.csdn.net/hackbuteer1/article/details/7460019

4、智能指针

STL提供有auto_ptr、unique_ptr、shared_ptr、weak_ptr

共同点,都有一个explict的构造函数,不允许隐式调用。

现在auto_ptr已经不用了,其原因是,不允许两个智能指针指向同一个对象,不方便,而且又没有限制。

(1)这样unique_ptr就做了限制,当另外一个unique_ptr是作为右值,也就是相当于是临时变量时,是可以的,以及接收以值返回的临时变量。而对于会存在一段时间的左值,是不允许的。

unique_ptr<string> p; //
p=unique_ptr<string>(new string("you"));//右值,允许
unique_ptr<string> p1(new string("hello"));//只允许显示调用
unique_ptr<string> p2;
p2=p1;//这种方式是不允许的

(2)对于shared_ptr则是可以统计指向对象的指针计数。赋值时,计数加一,指针过期,计数减一,当为0时,才调用delete.

而对于weak_ptr是专门为shared_ptr准备的,可以对shared_ptr但是不改变其引用计数,当被观察的shared_ptr实效时,weak_ptr也失效。

5、右值引用

只能绑定到一个将要销毁的对象,变量是左值,是持久的。而右值是短暂的,是临时变量。

如:

int i=4;
int &r=i;
int &&rr=i*4;
//int &&rr=i;是错误的

右值引用是专门为std::move(变量)提出来的,move函数可以显示的将一个左值转换为右值引用类型。避免了拷贝带来的消耗,特别是对于临时变量,本来是要销毁的,但是又要执行一次拷贝。这里直接将对象移动过来。

如 int &&rr=std::move(rr1);

书上说除了对rr1赋值或销毁它之外,将不能再使用它。

但实际上是可以用的。

6、构造函数的执行顺序

先基类的构造函数、成员的构造函数、自己的构造函数。如果父类是虚继承的话,需要先调用共同的祖先类的构造函数,需要在初始化列表中指明。而对于成员的构造顺序与声明的顺序使一样的。

printf("%s,%s,%s",s1,s2);最后一个%s输出不定。

7、lambda表达式

向一个算法可以传递可调用的对象如函数和函数指针,现在新加了一个lambda表达式,其特点是:

[捕获列表](参数列表){retrun以及一些操作}

在for_each算法中用法如下:

vector<int> v(10,1);
for_each(v.begin(),v.end(),[](const int &a){cout<<a<<" ";});
可以值捕获以及可以引用捕获如:[v1]/[&v1]如果想改变以值捕获的内容则在后面加上mutable修饰。

要想用两个return需要定义返回类型如:

int a;
for_each(v.begin(),v.end(),[a](const int &a)-> int{cout<<a<<" "; if(a>0)return 1;else return 0;});
特点是定义了返回类型->

8、auto与decltype

其中auto在生命对象的时候不用制定类型,而decltype可以俘获表达式的类型并返回,用法如下:

const vector<int> v;

typedef decltype (v.begin()) ct;

ct iter;//直接声明一个int的vector迭代器。

9、deleted函数和defaulted函数

10、nullptr

非空指针

11、委托构造函数

可以在类的一个构造函数中,利用初始化列表来调用另一个构造函数。可以这样写:

A():A(6){};

12、forward_list和array类型

list(双向链表)和forward_list(单项链表)可以使得在任何位置插入删除都很快,但是不支持随机访问。

list<int> c(ar.begin(),ar.end());
c.insert(c.begin(),3); //这样3就插在了最前面,在迭代器前面插入,同样支持push_back push_front

fc.insert_after(c.begin(),3);//单项链表只支持在迭代器后插入,且支持push_front,前插法

array是一种更安全更容易使用的数组类型,长度是固定的。用法为:array<int,10> ar={1,2};  但是其他的都不能这样初始化;

13、try语句和异常处理

cerr<<"error"//只是打印

而throw 异常类("")这种新式抛出异常,并把控制权转移给能处理该异常的代码,一般会弹出对话框。

同样,try{}catch(异常类 err){cout<<err.what()<<endl;}//用这样的语句块是比较常用的

异常类有:exception(最常见的问题)、runtime_error、range_error、overflow_error等

14、数据对齐

◆struct中对齐方式为最大的格式。

◆class CA{

double a;

float b;

int c;

char d;

}

sizeof(CA)为24因为要照顾最大对其的double a

◆int b=0;
cout<<sizeof(b=6)<<" "<<b<<endl;//输出4 0

也就是说sizeof 里面是不编译的。

◆sizeof(string),不同的编译器实现不一样,在vs2010上,是32,因此

string a[]={"dfsf","afsd"};总共两个string 大小为64

◆注意指针永远是4个字节

◆利用sizeof可以计算元素的个数。

15、指针

int *p;*p=5;这样是错的。p没有指向空间

16、const 常量必须要初始化,但是类内声明除外(包括引用,可以放在初始化列表中)

16、char * 和char s[]以及static char s[]

char *test()
{

static char c[]="fosdfi";//ok存在静态存储区

char *c="fosdfi";//ok,字符常量存在静态存储区,且不允许修改

char c[]="fosdfi"//error栈区,允许修改 c[1]='a';ok

return c;

}

17、偏移问题

◆int a[2]={1,2,3};

int *p=a;

int *q=&a[2];

cout<<*(p+1)<<a[q-p]<<endl;

输出23;因为计算的时候会自动从sizeof(int)做整数计算,q-p最后还要除掉sizeof(int),所以为a[2]=3;

◆类对象偏移

class A
{
public:
	A(){m_a=1;m_b=2;}
	~A(){}
	void fun(){cout<<m_a<<endl;}
	int m_a;
	int m_b;
}a;
class B
{
public:
	B(){m_c=3;}
	~B(){}
	void fun(){cout<<m_c<<endl;}
	int m_c;
};
B *b=(B*)(&a);
	b->fun();

输出1

对于B而言是强制转化,在调用fun函数的时候,会输出m_c,此时会默认是原来对象的第一个偏移值。


18、注意不要使用常量地址

int *p;

p=(int *)0x8000;

*p=0x9000;

这样是很危险的,也许0x8000里面有很重要的数据。

19、子类调用父类的函数(打印成员,这个成员子类覆写了),会打印父类的成员。

20、第91、93页还存在问题。

21、指针数组

int a[2][10] = { {1,2,3,3,3,4,5,6,8,7},{ 11,12,13,13,13,14,15,16,18,17 } };
int(*b)[10] = a;//*b=a,b=&a;因此b[0]=&a[0],b[1]=&a[1];
cout << **b <<" "<<*(*b+1)<<" "<<**(b+1)<<" "<<*(b[0]+1) << " " <<*(b[1])<< endl;
输出:1 2 11 2 11
请按任意键继续. . .

注意:b[0]指向a[0],b[1]指向a[1]    b+1  和b[0]/*b+1是不一样的。

数组名本来就是指针,&就变成了双指针,就变成了二维指针
22、野指针和空指针

23、找空字符前面字符的个数

这里我的方法更加简单,但是要注意++s,不要写成s++传递

int mystrlen(char *s, int n)
{
	assert(s != nullptr);
	if (*s == '\0' || n == 0)
		return 0;
	return mystrlen(++s, n - 1) + 1;
}

int main()
{
	char a[20] = { 'a','b','f','d','e','\0','e','r','w','r','q' };
	cout << mystrlen(a, 6) << " " << mystrlen(a, 3) << " " << mystrlen(a, 7) << endl;	
	system("pause");
}
输出 5 3 5

24、书写规范

if(NULL==m)//m为指针,这里一定要注意,否则很容易出问题,我已经出了几次问题了,判断的时候一定要注意。

25、全局变量与局部变量

int i=1;

int main()

{

int i=i;

}

则会显示i未定义。

26、cout和printf输出的顺序。都是从后面向前面处理的。

int a[]={1,2,3};
int *p=a;
cout<<*p<<*(++p)<<endl;
输出 22

27、++前与++后,以及和一些其它符号混合使用

如:*(++p)与*(p++)的区别。!p++为先判断后加

cout<<c++<<endl;//则是输出c的值之后再加;cout,printf同样是先从后面算起。
28、类型转换

	unsigned int a=0x8002;
	unsigned char b=(unsigned char)a;
	char *p=(char *)&a;
	printf("%08x,%08x",b,*p);
输出:00000002,00000002请按任意键继续. . .

29、运算符问题

unsigned char a=0xA5;
unsigned char b=~a>>4+1;
printf("%d",b);
先后顺序为:先取反,再右移,但是+的优先级比>>高,所以先加,也就是要右移5位。

还有一个问题是对于unsigned char 虽然只有1个字节,但是是存在16位寄存器中,因此高位也会右移,最终再取一个字节。

结果为1111 1010  位250

int f(int x,int y)
{
	return (x&y)+((x^y)>>1);
}
上面这个函数的功能是求平均(x+y)/2

思想:若有相同的位,求平均,直接&,只保留一次。

对于不同的位,则先结合,整个再右移1位,也是求平均。

◆注意

也就是相同的位和不同的位分别求平均。

30、不用+号求和,这里采用递归,更加清晰。

int Add(int a,int b)
{
	if(0==b)
		return a;
	int sum=a^b;
	int carry=a&b<<1;
	return Add(sum,carry);
}
31、三数取中,先求出两个最大的,再在这两个里面求出小的,就是中间的。

int Mid(int a,int b,int c)
{
	int max1=max(a,b);
	int max2=max(a,c);
	int max3=max(b,c);
	return min(max1,min(max2,max3));
}
而对于快速排序的三数取中,则是比较第一个和第二个,将大的放前面。

比较中间和后面的,把大的放中间。

最后比较出前面和中间的就可以了。

32、注意switch的break;

defalut放在前面与后面的效果都是一样的,放在前面的话要看是否有其它合适的值否则是不会进入的。

33、对于define 一定要注意()否则会出问题。

如:#define  sub(x,y) x-y

调用 *sub(&x,y)则是*&x-y

标准的:

#define Min(x,y) ((x)<=(y)?(x):(y))

34、const的初始化

◆在类外定义必须要赋初始值 const int a=1;

◆类内定义必须要通过列表初始化。A():a(1){}

◆通过静态常量成员可以直接初始化,如static const int a=1;

◆static非常量成员必须在类外全局空间初始化。只能用类名来初始化如 int A::a=1;  但是可以通过对象来访问。

35、类中成员内存偏移问题

注意类中成员会默认初始化,这里i为0.  其存储方式为顺序存储。所以pa[1]位i下面的一个存储单元,也就是*p的存储单元。

注意从内存的角度出发,如用a.p[1]将对象a中的p成员改变之后,其不再是一个指针,a.p[0]就失去了意义。

class A
{
public:
	int i;
	int *p;
};
int main()
{ 
	A a;
	int *pa=&a.i;
	pa[0]=1;
	pa[1]=2;
	cout<<*(&a.i+1)<<endl;//输出为2,a.p的值为常量2,不是地址
	a.p=pa;	//该为pa的地址
	a.p[1]=1;//a.p变为1,是常量
	a.p[0]=2;//相当于是*(1)=2;
	system("pause");	
	return 0;
}
36、函数指针

int (*p)(int,int);

p=Max;//或者p=&Max都是可以的。

37、this指针

this指针是作为成员函数的参数被隐藏起来。

不占用空间

通过寄存器进行传递。

存放位置不一定,可能是堆中也可能是栈中等,随编译器而改变。

38、递归问题

F(1025)mod 5

通过F(5n)=..进行拆开,最后可以得到是5的整数倍。

39、x(x(8))递归计算次数,为9+9,计算的时候一定要仔细。

40、面向对象设计的3个原则:继承、多态、封装

另外有里氏代换(是继承的基石)

开闭原则(扩展是开的,修改是闭的)

防御式编程(对于任何输入参数,函数都不能奔溃,不是面向对象的特性)

41、测试用例?

黑盒测试,点点点。

如要写对手机功能的测试用例:

对于打电话:

是否有打电话功能

是否能正常打电话

是否拨错误号码会有提示。

短信功能测试:

。。。

对于白盒测试,需要根据程序来写测试用例。常用方法有代码检查法、静态结构分析法、基本路径测试法等,其中基本路径测试法

应用最广。

测试用例如:

输入xx

输出xx.

42、互斥mutex和临界区的区别

互斥用于进程间互斥,临界区用于线程之见的互斥

43、死锁产生的4个必要条件

互斥条件

保持和请求

不被剥夺

循环等待。

通过破坏后面三个条件,可以预防死锁的发生(互斥条件是不能被破坏的)。而通过银行家算法则可以避免死锁,也就是防止进入不安全状态。

预防死锁的办法如:

资源静态分配策略,也就是先把资源分配好,破坏条件2

允许剥夺其它进程的资源

采用资源有序分配法,破坏条件3.

44、fork函数

fpid=fork(),可以产生一个子进程,其中子进程复制了父进程的绝大部分。

为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;

经典用法如下:是linux中的c程序。

#include <unistd.h>  
#include <stdio.h>   
int main ()   
{   
    pid_t fpid; //fpid表示fork函数返回的值  
    int count=0;  
    fpid=fork();   
    if (fpid < 0)   
        printf("error in fork!");   
    else if (fpid == 0) {  
        printf("i am the child process, my process id is %d/n",getpid());   
        printf("我是爹的儿子/n");//对某些人来说中文看着更直白。  
        count++;  
    }  
    else {  
        printf("i am the parent process, my process id is %d/n",getpid());   
        printf("我是孩子他爹/n");  
        count++;  
    }  
    printf("统计结果是: %d/n",count);  
    return 0;  
}  
输出:

运行结果是:
    i am the child process, my process id is 5574
    我是爹的儿子
    统计结果是: 1
    i am the parent process, my process id is 5573
    我是孩子他爹
    统计结果是: 1

也就是说,如对于if(!fork())i++;的用法,只有子进程才会执行i++;

函数:getpid()获取进程PID,getppid()获取父进程pid。

45、windows将按照什么路径来搜索dll?

内存(当前进程的工作目录)、exe目录、进程的工作目录、系统目录、环境变量中制定的目录

46、动态链接库相对静态链接库的优点是?

以lib为后缀的库有两种,一种是静态链接库(static Libary),另一种是动态链接库(DLL)的导入库(Import Libary)。虽然静态链接库和动态链接库的导入库

都是lib文件,但是区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、地址符号表等。而对于导入而言,其实际执行的代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。同时动态链接库中还可以包含其他动态或静态链接库,而静态的则不可以。

静态链接库是一个或多个目标文件obj的打包,当工程里面用到静态链接库时,会参与编译,因此在生成文件之exe后,lib文件就可以不要了。

动态链接库提供了一种方法,使程序可以调用不属于其可执行代码的函数,同时多个程序可以访问一个dll,其优点是,节省内存,节省磁盘空间,更易升级(不需要重链接和编译),支持多语言程序。


47、创建两个线程买票

48、内存管理的3种方式

49、假定有3个程序,每个程序花费80%的时间进行I/O,20%的时间使用CPU。每个程序的启动时间和其需要使用CPU进行计算机的分钟数如表

所示,在多线程环境下,系统总的响应时间是多少?

程序编号 启动时间 需要CPU时间(分钟)

1 00:00分 3.5

2 00:10分 2

3 00:15分 1.5

当只有一个进程运行时,cpu的利用率为0.2,当两个进程运行时为1-0.8*0.8=0.36也就是说去掉同时I/O,其它都是利用CPU的情况。

同样,当三个进程同时运行CPU利用率为1-0.8*0.8*0.8

分析过程:

第一个进程到十分钟,CPU运行时间为10*0.2是2分钟,因此在第10分钟1还有1.5分钟,2进程还有2分钟。

到第15分钟,CPU利用率为0.36,CPU时间为5*0.36=1.8,两个进程各分0.9,进程1还剩0.6分钟,进程2还剩1.1分钟

从第15分钟开始,先按照跑完进程1计算,其它的类推下去就可以了。最终答案是近似23.5


50、端口号

◆TCP端口

21-FTP, Telnet-2325-SMTP,80-HTTP

◆UDP端口

53-DNS 161-SNMP8000-QQ4000-QQ

51、向函数传递参数

f(i++);传进去的是i的值,f(++i)传进去的才是++i,f(i>>1)传进的是i/2,。


52、常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数。

1、为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。2、为什么C++不支持构造函数为虚函数?这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来是为了明确初始化对象成员才产生的,然而virtual function主要是为了在不完全了解细节的情况下也能正确处理对象。另外,虚函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用虚函数来完成你想完成的动作。3、为什么C++不支持静态成员函数为虚函数? 静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他不归某个对象所有,所以他也没有动态绑定的必要性。4、为什么C++不支持内联成员函数为虚函数? 其实很简单,内联函数就是为了在代码中直接展开,减少函数调用话费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。再说,inline函数在编译时被展开,虚函数在运行时才能动态的绑定函数。5、为什么C++不支持友元函数为虚函数?友元函数是没有继承机制的。

因此只支持类的析构函数、类的非静态成员函数可以。


53、卡特兰数应用汇总

其前几项为 : 1, 2, 5, 14, 42, 132, 429.  此处从h(1)开始。

做题的时候,可以看前几项来决定。
◆n对括号有多少种匹配方式?答案f(2n)=h(n)
◆矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?f(n)等于h(n-1)。
◆n个节点构成的二叉树,共有多少种情形?f(n)等于h(n)。
◆在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数?f(n)等于h(n)。
◆求一个凸多边形区域划分成三角形区域的方法数?f(n)等于h(n-2)。
◆2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?
可以将持5元买票视为进栈,那么持10元买票视为5元的出栈。这个问题就转化成了栈的出栈次序数。由应用三的分析直接得到结果,f(2n) 等于h(n)。




















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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值