C++常见面试问题
1.struct和union
--union中只存放了一个被选中的成员;而struct的所有成员都存在
--struct变量的总长度等于所有成员长度之和;Union变量的长度等于最长的成员的长度。
2.static和const
--static:
修饰变量:
a.static修饰局部变量,它就改变了局部变量的存储位置(从原来的栈中存放改为静态存储区)及其生命周期(局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能进行访问),但未改变其作用域。
b.static修饰全局变量,并为改变其存储位置及生命周期,而是改变了其作用域,使当前文件外的源文件无法访问该变量,好处如下:(1)不会被其他文件所访问,修改(2)其他文件中可以使用相同名字的变量,不会发生冲突。对全局函数也是有隐藏作用。
修饰类成员:
a.static修饰类的成员变量:实际使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象。因此,static成员必须在类外进行初始化(初始化格式: int base::var=10;),而不能在构造函数内进行初始化。
b.static修饰类的成员函数:(1)使这个类只存在这一份函数,所有对象共享该函数,不含this指针;(2)静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。当static成员函数在类外定义时不需要加static修饰符。
--const
a.修饰变量变量不可以被修改;
b.修饰指针:char * const p【常指针】 ;;;const char *p【指向常量的指针变量】
3.指针与引用
--指针是变量,引用是别名
--指针可以为null,引用不可以为空且必须初始化
--指针可以改变,引用只能一直引用一个
4.重载(overload)、重写(override)、覆盖(overwrite)
--overload
--override: 继承体系中,子类实现父类虚函数
--overwrite:也是在继承体系中,子类覆盖了父类的方法
5.深拷贝与浅拷贝
--浅拷贝:默认的复制构造函数只是完成了对象之间的位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
--深拷贝:自定义复制构造函数需要注意,对象之间发生复制,资源重新分配,即A有5个空间,B也应该有5个空间,而不是指向A的5个空间。
6.虚继承和虚基类
--在多重继承(菱形继承)下,虚继承保证了最终的派生类只有基类的一份拷贝。而在根部的基类称为虚基类。
class istream : public virtual ios { ... }; class ostream : virtual public ios { ... }; // iostream inherits only one copy of its ios base class
class iostream: public istream, public ostream { ... };
7.排序算法
8.容器及特点
9.TCP为什么要三次握手而不是两次:防止失效的请求又到达服务器,产生错误。
三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的
--TCP可靠传输实现:
TCP 连接的每一端都必须设有两个窗口——发送窗口和接收窗口。TCP 的可靠传输机制用字节的序号进行控制。TCP 所有的确认都是基于序号而不是基于报文段。
发送过的数据未收到确认之前必须保留,以便超时重传时使用。发送窗口没收到确认不动,和收到新的确认后前移。
发送缓存用来暂时存放: 发送应用程序传送给发送方 TCP 准备发送的数据;TCP 已发送出但尚未收到确认的数据。
接收缓存用来暂时存放:按序到达的、但尚未被接收应用程序读取的数据; 不按序到达的数据。
--TCP报文格式
(1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
(A)URG:紧急指针(urgent pointer)有效。
(B)ACK:确认序号有效。
(C)PSH:接收方应该尽快将这个报文交给应用层。
(D)RST:重置连接。
(E)SYN:发起一个新连接。
(F)FIN:释放一个连接。
需要注意的是:
(A)不要将确认序号Ack与标志位中的ACK搞混了。
(B)确认方Ack=发起方Req+1,两端配对。
10.线程和进程,线程可以共享进程里的哪些东西。 知道协程是什么吗
--线程共享进程的地址空间,全局变量(数据和堆)。在一个进程中,各个线程共享堆区,而进程中的线程各自维持自己的栈。
--协程:
定义:协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。
11. makefile
--makefile关系到了整个工程的编译规则。makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
--makefile成为了一种在工程方面的编译方法,其本质都是在“文件依赖性”上做文章
--源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.
12. 大端小端模式
--大端:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
--小端:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
http://www.cnblogs.com/LUO77/p/5771237.html
===================================================================
13. new/delete与malloc/free的区别
new/delete | malloc/free |
c++操作符 | c库函数 |
new调用构造函数,delete调用析构函数 | 只对内置类型有效 |
new返回指定类型的指针,并自动计算大小
string *ps=new string(10,’9’) |
malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针 |
在创建对象的时候就可以初始化 |
malloc 只管分配内存,并不能对所得的内存进行初始化 |
new分配失败时,返回什么?
1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[])以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象: class widget { ... };
widget *pw1 = new widget;// 分配失败抛出std::bad_alloc
if (pw1 == 0) ... // 这个检查一定失败
widget *pw2 = new (nothrow) widget; // 若分配失败返回0
if (pw2 == 0) ... // 这个检查可能会成功
14.多态,虚函数,纯虚函数
多态:
--编译时多态:函数重载
--运行时多态:继承和虚函数
虚函数:提供一个接口和一份默认的实现
纯虚函数:提供一份接口(抽象类)
15.引用
-作为参数:
----直接对传入的对象进行修改;(传指针的时候需要在使用前对指针进行检查)
----为了降低复制自定义对象的开销,通常加上const;
-作为返回值(在内存中不产生被返回值的副本):
----返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
----
16.栈内存与文常量区
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;//0 分别指向各自的栈内存
cout << ( str3 == str4 ) << endl;//0 分别指向各自的栈内存
cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同
cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同
结果是:0 0 1 1
解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。
17.复杂声明
http://blog.csdn.net/zjc156m/article/details/16819357
=====================================================================
18.C++对象模型
non-static数据成员被放置到对象内部,static数据成员、static和nonstatic函数成员放到对象之外。
19.智能指针
C++新标准库中定义了两种智能指针shared_ptr和unique_ptr,定义在memory头文件中。
shared_ptr:允许多个指针指向同一个对象;()
shared
--weak_ptr:是一种弱引用,指向shared_ptr所管理的对象
--思想:引用计数为0就自动销毁所管理的对象,释放占用的内存
----拷贝一次,引用计数加1:
用shared_ptr初始化另一个shared_ptr
将其作为参数传递给另一个函数
作为函数返回值
----计数减1的情况:
给shared_ptr赋值一个新值
被销毁(离开局部作用域)
--操作:
p.get(),p.swap(q)(是共有的操作)、p.unique()、p.use_count()
--构造:
----auto p = make_shared_ptr<vector<string>>();
----shared_ptr<int> p(new int(1024));使用直接初始化形式,因为智能指针的构造函数是explicit的
unique_ptr:独占所指向的对象,不支持拷贝与赋值操作,但是可以通过release()和reset()操作将指针的控制权从一个unique转到另一个unique指针
20.虚函数
--静态函数不可以是虚函数:因为静态成员函数没有this,也就没有存放vptr的地方,同时其函数的指针存放也不同于一般的成员函数,其无法成为一个对象的虚函数的指针以实现由此带来的动态机制。静态是编译时期就必须确定的,虚函数是运行时期确定的。
--内联函数不可以是虚函数:inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制。
因此,内联函数是个静态行为,而虚函数是个动态行为,他们之间是有矛盾的。
--设置虚函数须注意:
1:只有类的成员函数才能说明为虚函数;
2:静态成员函数不能是虚函数;
3:内联函数不能为虚函数;
4:构造函数不能是虚函数;
5:析构函数可以是虚函数,而且通常声明为虚函数。
21.一个函数一旦声明为虚函数,那么不管你是否加上virtual 修饰符,它在所有派生类中都成为虚函数
构造函数是不能被继承的,但是可以被调用,如果父类重新定义了构造函数,也就是没有了默认的构造函数,子类创建自己的构造函数的时候必须显式的调用父类的构造函数。而其余的三个在我们平常的使用中都可以被继承使用。 22.静态局部变量只对定义它的函数体始终可见,函数体执行完过后虽然还存在,但是无法被其他的使用了 23.
-- public成员可以被类中的函数、友元函数、类的对象、派生类访问。
25.const
char* const p = &a;//常指针,指向不可以改变,数量可以改变
char const *p = &b;//指向常量的指针,b是常量
const修饰参数、返回值、成员函数 ,non-const 版本可以调用const版本,反之则不行。 26.
volatile (容易改变的)作用是避免编译器优化,说明它是随时会变的,它不是 non-const,和const不矛盾。被const修饰的变量只是在当前作用范围无法修改,但是可能被其它程序修改。优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
27.
引用、const成员变量、基类构造函数一定要通过初始化列表来实现。 static类型不是类对象成员,不需要通过初始化列表来初始化