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)。