面试题有难有易,不能因为容易,我们就轻视,更不能因为难,我们就放弃。我们面对高薪就业的态度永远不变,那就是坚持、坚持、再坚持。出现问题,找原因;遇到困难,想办法。我们一直坚信只有在坚持中才能看到希望,而不是看到希望才去坚持。
人生没有如果,只有结果和后果。既然选择了,就不后悔。年轻就是资本,年轻就要吃苦,就要历练。就要学会在坚持中成长。如此感慨,至深的心得体会,绝对的经验之谈。
1、 Static有什么用途?
(1)函数体内static变量的作用范围是该函数体,该变量的内存只被分配一次,因此它的值在下次调用时不变;如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
(2)模块内的static全局变量同样只能在该模块内的函数访问和调用,不能被模块外的其他函数访问;
(3)在类中的static成员变量属于整个类所有,对类的所有对象只有一份拷贝;静态成员函数内部不能调用非静态成员函数,原因是,非静态成员函数需要传入一个this指针,这让静态成员函数很为难,它并不知道与之相关的信息,也就无法提供this指针。
普通成员变量每个对象都有各自的一份,但是静态成员变量一共只有一份,被所有的本类对象共享。如果使用sizeof运算符计算对象的大小,得到的结果是不包含静态成员变量在内的。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
2、 const
(1)不管在函数声明修饰形参、还是修饰类的成员变量,表示该成员变量不能被改变,而且通常需要进行初始化,因为之后不能再改变;
(2)对于指针来说,可以修饰指针所指向的变量(在*左边,即指针指向内容为常量),也可以指定指针本身为const(在*右边,指针本身是常量),或者两者同时指定为const(都是常量)。
3、 this指针
(1)this指针本质是一个函数参数,只是编译期隐藏起形式的,语法层面上的参数,且this指针只能在成员函数中使用,全局函数、静态函数都不能使用;
(2)this在成员函数开始前构造,在成员结束后清除;
(3)this指针不占用对象的空间。
4、 ifndef/define/endif的作用
防止头文件被重复引用和定义;
5、 C和C++的区别
(1)C主要面向过程,C++面向对象;
(2)C是一种结构化语言,重点在于算法和数据结构。C主要考虑通过一个过程将输入进行各种运算后得到输出,C++主要考虑的是如何构造一个对象模型,契合与之相对应的问题域,这样就可以通过获得对象的状态信息得到输出。
(3)什么是面向对象:面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。
(4)请用简单的语言告诉我C++ 是什么?
C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛。C++支持多种编程范式-------面向对象编程、泛型编程和过程化编程。 其编程领域众广,常用于系统开发,引擎开发等应用领域,是最受广大程序员受用的最强大编程语言之一,支持类、封装、重载等特性!
6、 C++函数值传递的方式
值传递、指针传递和引用传递
7、 extern “C”的作用
实现C和C++的混合编程;因为函数被C++编译后的名字会变长,与C生成的不一致,造成C++不能直接调用C函数。
extern关键字的作用
extern置于变量或函数前,用于标示变量或函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。它只要有两个作用:
当它与“C”一起连用的时候,如:extern "C" void fun(int a,int b);则告诉编译器在编译fun这个函数时候按着C的规矩去翻译,而不是C++的(这与C++的重载有关,C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同)
当extern不与“C”在一起修饰变量或函数时,如:extern int g_Int;它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或其他模块中使用。记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。
如何引用一个已经定义过的全局变量?
引用头文件和extern关键字。 如果采用引用头文件, 若变量写错了,则在编译期间便会出错。 如果用extern则在链接阶段报错。
8、 struct 和class 的区别
(1)struct的成员默认是公有的,而类的成员默认是私有的;
(2)C中的struct不能包含成员函数,C++中的class可以包含成员函数。
结构与联合有和区别?
(1)结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
(2)对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
9、 new和malloc
(1)都可用来申请动态内存和释放内存,都是在堆(heap)上进行动态的内存操作。
(2)malloc和free是C语言的标准库函数,new和delete是C++的运算符。
(3)new会自动调用对象的构造函数,delete 会调用对象的析构函数, 而malloc返回的都是void指针。
10、 heap与stack(堆与栈)的差别
(1)heap是堆,stack是栈;
(2)stack的空间由操作系统自动分配和释放,存放函数的参数值、 局部变量的值等。heap上的空间一般由程序员分配和释放,并要指明大小;
(3)栈空间有限而且是一块连续的内存区域,堆是很大的自由存储区;
(4)C中的malloc函数分配的内存空间就是在堆上,C++是new;
(5)程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时的参数传递也在栈上进行
堆栈溢出原因:数组越界, 没有回收内存, 深层次递归调用
11、 Vector、List和Deque的区别
Vector:表示一段连续的内存区域,每个元素被顺序存储在这段内存中,对vector的随机访问效率很高,但对非末尾元素的插入和删除则效率非常低。
List:表示非连续的内存区域并通过一对指向首尾元素的指针双向链接起来,插入删除效率高,随机访问效率低。
Deque:也表示一段连续的内存区域,但与vector不同的是它支持高效地在其首部插入和删除元素,它通过两级数组结构来实现,一级表示实际的容器,第二级指向容器的首和尾。
vector拥有一段连续的内存空间,能很好的支持随机存取,因此vector::iterator支持“+”,“+=”,“<”等操作符。
list的内存空间可以是不连续,它不支持随机访问,因此list::iterator则不支持“+”、“+=”、“<”等
vector::iterator和list::iterator都重载了“++”运算符。
总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;如果需要大量的插入和删除,而不关心随机存取,则应使用list。
13、 内联函数和宏的差别
内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
内联函数要做参数类型检查,这是内联函数的优势;
inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。
对于短小的代码来说inline增加空间消耗换来的是效率提高,这方面和宏是一模一样的,但是inline在和宏相比没有付出任何额外代价的情况下更安全。 至于是否需要inline函数,就需要根据实际情况来取舍了。
inline一般只用于如下情况:
(1)一个函数不断被重复调用。
(2)函数只有简单的几行,且函数内不包含for、 while、 switch语句。
宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。
关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。
inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。 内联能提高函数的执行效率,至于为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。 如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。 (2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。 类的构造函数和析构函数容易让人误解成使用内联更有效。 要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。 所以不要随便地将构造函数和析构函数的定义体放在类声明中。 一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。
请说出const与#define 相比,有何优点?
const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
14、 引用和指针的区别
(1)指针是一个变量,用于存放地址的变量,指向内存的一个存储单元,引用仅是别名;
(2)引用必须初始化,指针不必;
(3)不存在指向空值的引用,但是存在指向空值的指针;
(4)sizeof() 引用对象得到的是所指对象变量的大小,sizeof() 指针得到是指针本身的大小;
(5)内存分配上,程序为指针分配内存,不用为引用分配内存
15、 数组和链表的区别
C++语言中可以用数组处理一组数据类型相同的数据,但在使用数组之前必须确定数组的大小。而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费。
链表是一种常见的数据组织形式,它采用动态分配内存的形式实现。需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。
(1)从逻辑结构来看:
数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况,即数组的大小一旦定义就不能改变。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;
链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)。
(2)从内存存储来看:
静态数组从栈中分配空间(用NEW创建的在堆中),对于程序员方便快速,但是自由度小;
链表从堆中分配空间, 自由度大,但是申请管理比较麻烦。
(3)从访问方式来看:
数组在内存中是连续存储的,因此,可以利用下标索引进行随机访问;
链表是链式存储结构,在访问元素的时候只能通过线性的方式由前到后顺序访问,所以访问效率比数组要低。
16、 链表
(1)链表是否有环
算法的思想是使用追赶的方法设定两个指针p, q,其中p每次向前移动一步,q每次向前移动两步。那么如果单链表存在环,则p和q相遇;否则q将首先遇到null退出。
(2)如何知道环的长度
记录下上个问题的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。
(3)如何找出环的连接点
有定理:碰撞点p到连接点的距离 = 头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。
(4)带环链表的长度
根据已经求出连接点距离头指针的长度,加上求出的环的长度,二者之和就是带环单链表的长度
(5)单链表的逆置
输入一个链表,反转链表后,输出链表的所有元素。
方法一:
思路:从原链表的头部一个一个取节点并插入到新链表的头部
1)
1 struct ListNode{
2 int val;
3 struct ListNode *next;
4 ListNode(int x):val(x),next(NULL){}
5 };
6
7 class Solution{
8 public:
9 ListNode* ReverseList(ListNode *pHead){
10 ListNode* newh = NULL;
11 for(ListNode *p = pHead; p; )
12 {
13 ListNode *tmp = p->next;
14 p->next=newh;
15 newh=p;
16 p=tmp;
17 }
18 return newh;
19 }
20 };
2)
1 class Solution {
2 public:
3 ListNode* ReverseList(ListNode* pHead) {
4 if(pHead == NULL)
5 return pHead;
6 ListNode *res,*cur,*next;
7 res = new ListNode(-1);
8 cur = pHead;
9 next = cur->next;
10 while(cur != NULL)
11 {
12 ListNode *first = res->next;
13 cur->next = first;
14 res->next = cur;
15
16 cur = next;
17 next = next->next;
18 }
19 return res->next;
20 }
21 };
方法二:
思路:每次都将原第一个结点之后的那个结点放在新的表头后面。
比如1,2,3,4,5
第一次:把第一个结点1后边的结点2放到新表头后面,变成2,1,3,4,5
第二次:把第一个结点1后边的结点3放到新表头后面,变成3,2,1,4,5
……
直到: 第一个结点1,后边没有结点为止。
代码如下:
1 class Solution {
2 public:
3 ListNode* ReverseList(ListNode* pHead) {
4 if(pHead == NULL)
5 return pHead;
6
7 ListNode *res,*first,*temp;
8 res = new ListNode(-1);
9 res->next = pHead;
10
11 first = res->next; //first 始终为第一个结点,不断后移
12 while(first->next!=NULL) //temp为待前插的
13 {
14 temp = first->next;
15 first->next = temp->next;
16 temp->next = res->next;
17 res->next = temp;
18 }
19
20 return res->next;
21 }
22 };
方法三
第三种方法跟第二种方法差不多,第二种方法是将后面的结点向前移动到头结点的后面,第三种方法是将前面的结点移动到原来的最后一个结点的后面,思路跟第二种方法差不多,就不贴代码了。
17、 重载和重写(覆盖)的区别
(1)从定义来说:
重载:是指存在多个重名函数,而这些函数的参数表不同(参数个数,类型不同);
重写:是指子类重新定义父类虚函数的方法。
(2)从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。而函数的调用,在编译时就已经确定是静态的,也就是说它们的地址在编译期就绑定(早绑定),因此重载与多态无关。
重写:和多态有关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,它们的地址是在运行期绑定的(晚绑定)。
18、 封装、继承、多态、虚函数
封装
封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元(类)中。其意义在于保护或者防止代码被无意中破坏
继承
继承主要实现重用代码,扩展已存在的代码,节省开发时间。子类可以继承父类的一些东西。
多态
定义:“一个接口,多种方法”,程序运行时才决定调用的函数
实现:C++多态主要是通过虚函数实现。虚函数允许子类重写。
目的:封装可以使代码模块化,继承可以扩展已存在的代码,它们的目的都是为了代码重用。而多态的目的是为了接口重用,将接口与实现分离。
虚函数
定义:被virtual关键字修饰的成员函数,就是虚函数。其作用就是实现多态性。
为什么虚函数效率低?
因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址,虚函数(动态类型调用)要根据某个指针定位到函数的地址。多增加了一个过程,效率虽然会低一些,但带来了运行时的多态。
纯虚函数
为什么要用纯虚函数?
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数。则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。
使用纯虚数的情况:
(1)当想在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化;
(2)这个方法必须在派生类(derived class)中被实现;
虚函数与纯虚函数的区别:
虚函数是为了重载和多态。在基类中是有定义的,即便定义为空。在子类中可以重写。
纯虚函数在基类中没有定义,必须在子类中加以实现。
多态的基础是继承,需要虚函数的支持。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数, operator=函数,友元函数等等。
19、 内存
内存类别
栈 ——由编译器自动分配释放, 局部遍历存放位置
堆 ——由程序员分配和释放.
全局区(静态区) ——全局变量和静态变量的存储是放在一起的, 初始化的全局变量和static静态变量在一块区域.
程序代码区 ——存放二进制代码.
内存分配方式
静态存储区,程序编译时便分好, 整个运行期间都存在,比如全局变量,常量;
栈上分配;
堆上分配。
内存泄漏
原因:动态分配的内存没有手动释放完全。
避免:使用的时候应记得指针的长度; 分配多少内存应记得释放多少, 保证一一对应的关系; 动态分配内存的指针最好不要再次赋值。
内存溢出
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用。原因可能如下:
内存中加载的数据量过于庞大,如一次从数据库取出过多数据
代码中存在死循环或循环产生过多重复的对象实体
递归调用太深,导致堆栈溢出等
内存泄漏最终导致内存溢出
缓冲区溢出(栈溢出)
20、 进程和线程的差别
进程是最小的分配资源单位,线程是最小的执行单位。
进程是程序的一次执行。线程可以理解为进程中执行的一段程序片段。 在一个多任务环境中下面的概念可以帮助我们理解两者间的差别。
进程间是独立的,这表现在内存空间、 上下文环境上;线程运行在进程空间内。 一般来讲(不使用特殊技术),进程无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。
同一进程中的两段代码不能够同时执行,除非引入线程。
线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。 线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。
21、 TCP和UDP
TCP是传输控制协议,提供的是面向连接、 可靠的字节流服务。 当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。 TCP提供超时重发、 丢弃重复数据、 检验数据、 流量控制等功能,保证数据能从一端传到另一端。
UDP是用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不保证它们能到达目的地。 由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
TCP和UDP通信的差别?
(1)TCP面向连接, UDP面向无连接的
(2)TCP有保障的,UDP传输无保障的
(3)TCP是效率低的,UDP效率高的
(4)TCP是基于流的,UDP基于数据报文
(5)TCP传输重要数据,UDP传输不重要的数据
22、 编写Socket套接字
Socket相当于进行网络通信两端的插座,只要对方的Socket和自己的Socket有通信联接,双方就可以发送和接收数据了。 如果你要编写的是一个服务程序,那么先调用socket()创建一个套接字,调用bind()绑定IP地址和端口,然后启动一个死循环,循环中调用accept()接受连接。对于每个接受的连接,可以启动多线程方式进行处理,在线程中调用send()、 recv()发送和接收数据。
如果你要编写的是一个客户端程序,那么就简单多了。 先调用socket()创建一个套接字,然后调用connect()连接服务器,之后就是调用send()、 recv()发送和接收数据了。
服务器端程序编写:
(1)调用ServerSocket(int port)创建一个服务器端套接字,并绑定到指定端口上。
(2)调用accept(),监听连接请求,则接收连接,返回通信套接字。
(3)调用Socket类的getOutStream()和getInputStream获取输出流和输入流,开始网络数据的发送和接收。
(4)关闭通信套接字.Socket.close()。
客户端程序编写:
(1)调用Socket()创建一个流套接字,并连接到服务器端。
(2)调用Socket类的getOutputStream()和fetInputStream获取输出流和输入流,开始网络数据的发送和接收。
(3)关闭通信套接字.Socket.close()。
23、 三次握手和四次挥手
1、建立连接协议(三次握手)
(1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
(2)服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
(3)客户必须再次回应服务段一个ACK报文,这是报文段3。
2、连接终止协议(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段1)。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段2)。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段3)。
(4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段4)。
24、 排序
http://www.cnblogs.com/eniac12/p/5329396.html#s3
http://www.cnblogs.com/eniac12/p/5332117.html
25、 linux基本命令
http://www.cnblogs.com/laov/p/3541414.html#grep
26、 gdb调试
http://www.jb51.net/article/36393.htm
参考:http://www.cnblogs.com/bozhicheng/p/6259784.html 程序员面试宝典
27、设计模式懂嘛,简单举个例子?
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
(1)比如单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用于:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时;当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
(2)比如工厂模式,定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
适用于:当一个类不知道它所必须创建的对象的类的时候;当一个类希望由它的子类来指定它所创建的对象的时候;当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
C++中常用的设计模式有哪些?
共有23种设计模式,但真正在开发中常用的模式有:
(1)Factory Method(工厂模式);
(2)Strategy(策略模式);
(3)Singleton(单例模式);
(4)Iterator(迭代器模式);
(5)Abstract Factory(抽象工厂模式);
(6)Builder(建造者模式);
(7)Adapter(适配器模式);
(8)Bridge(桥接模式);
(9)Composite(组合模式);
(10)Interpreter(解释器模式);
(11)Command(命令模式);
(12)Mediator(中介者模式);
(13)Observer(观察者模式);
(14)State(状态模式);
(15)Proxy(代理模式)。
编写一个单例模式的例子:
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。该实例被所有程序模块共享。
1 #include<iostream>
2 using namespacestd;
3
4 classCSingleton
5 {
6 private:
7 CSingleton(){}//构造函数是私有的
8 static CSingleton *m_pInstance; // static
9
10 public:
11 static CSingleton *GetInstance() // static
12 {
13 if(m_pInstance==NULL) // 判断是否第一次调用
14 m_pInstance=newCSingleton();
15
16 return m_pInstance;
17 }
18 };
19
20 CSingleton * CSingleton::m_pInstance=NULL; // static属性类外初始化
21
22 void main()
23 {
24 CSingleton *p1= CSingleton::GetInstance();
25 CSingleton *p2= CSingleton::GetInstance();
26 cout<<(p1==p2)<<endl;//结果为true表示单例
27 }
28、STL库用过吗?常见的STL容器有哪些?算法用过哪几个?
STL包括两部分内容:容器和算法。(重要的还有融合这二者的迭代器)
(1)容器,即存放数据的地方。比如array等。
在STL中,容器分为两类:序列式容器和关联式容器。
序列式容器,其中的元素不一定有序,但都可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue;
关联式容器,内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、ash_set、hash_map、hash_multiset、hash_multimap。
下面各选取一个作为说明。
vector:它是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。
set:其内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值。
(2)算法,如排序,复制……以及个容器特定的算法。这点不用过多介绍,主要看下面迭代器的内容。
(3)迭代器是STL的精髓,我们这样描述它:迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构。它将容器和算法分开,好让这二者独立设计。
STL中unordered_map和map的区别?
map是STL中的一个关联容器,提供键值对的数据管理。底层通过红黑树来实现,实际上是二叉排序树和非严格意义上的二叉平衡树。所以在map内部所有的数据都是有序的,且map的查询、插入、删除操作的时间复杂度都是O(logN)。
unordered_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是unordered_map不会根据key进行排序。unordered_map底层是一个防冗余的哈希表,存储时根据key的hash值判断元素是否相同,即unoredered_map内部是无序的。
28、请编写能直接实现strlen()函数功能的代码
1 int strlen(char*str)
2 {
3 int i= 0;
4 for(;str[i]!=’0’;i++);
5 return i;
6 }
请编写能直接实现strstr()函数功能的代码
1 char *strstr(char *str, const char *sub)
2 {
3 4 for(int i=0;i<strlen(str);i++)
5 {
6 char *p = &str[i];
7 char *q=sub;
8 while(*p == *q)
9 {
10 p++;
11 q++;
12 if(*q==’0’)
13 return &str[i];
14 }
15
16 }
17 return null;
18 }
请编写能直接实现strcmp()函数功能的代码
1 int strcmp(const char *str1, const char *str2)
2 {
3 assert(str1 != NULL && str2 != NULL);
4 while (*str1 && *str1 == *str2)
5 {
6 str1++;
7 str2++;
8 }
9 if (*(unsigned char*)str1 < *(unsigned char*)str2)
10 {
11 return -1;
12 }
13 else if (*(unsigned char*)str1 > *(unsigned char*)str2)
14 {
15 return 1;
16 }
17 else
18 {
19 return 0;
20 }
21 }
注意:
1.参数是 const
2.异常输入处理 assert(str1 != NULL&&str2 != NULL);
3.字符之间大小比较时一定要先将 char* 型指针先转换为 unsigned char*
因为有符号字符值的范围是-128~127,无符号字符值的范围是0~255,而字符串的ASCII没有负值。
例如 *str1的值为1,*str2的值为255。
本来 *str1 < *str2,但是有符号数中255是-1.
请编写能直接实现strcat()函数功能的代码
1 char *strcat(char *strDest, const char *strSrc)
2 {
3 assert(strDest != NULL && strSrc != NULL);
4 char* address = strDest;
5 while (*strDest != '