C++面试经典问题汇集

最近忙着找工作,终于找到感觉兴趣和专业能对上的了,也不想继续打酱油了,不期望最好的,选择自己适合的。第一个笔的是迅雷,来的很早(九月中旬),并以迅雷不及掩耳盗铃之势就结束了。笔完后就后悔了,后悔自己在简历中写了“精通C++”。也埋怨他们是不是要招人去定制C++标准。话虽如此,毕竟自己技不如人,于是和几个有同感的同学找来“effective C++”、“高质量C++编程指南”、“Bjarne Stroustrup的FAQ”、“C++ 程序设计陷阱”等,大家分工合作,把几个容易搞混的地方汇集于下:

不要被这些东西吓到了,毕竟有些太“边角”了。建议打算找C++工作的同学,还是先把“think in C++”搞透,其实很多知识点这本书都有提到,只是书本太厚,看的时候印象不是太深,容易忘掉,所以特别指出来。另外,学东西还是要多上机实践,即使没实际项目也要多写些实验性的小算法程序,要不然面试时十分钟叫你手写个小程序就会力不从心了,即使写出来也容易出错(比如:漏掉出错检查,访问边界溢出,数据类型使用不恰当。。。)。

PS:面试中写的往往是很简单的程序,但是看你能否细心的进行出错检查,是否有好的编程风格(用a、b、m、n变量命名的同学,建议先把这块整好),另外若果能有性能优化意识就更好(例如将一个浮点数组里的所有值除2.0,你变成乘0.5)

PS:最后找工作前建议看两本书《程序员面试宝典》+《编程之美》 。  当然是要有一定的语言和算法基础上,要不然会看的很郁闷。而且《程序员面试宝典》里有些地方感觉不太正确,都可以再写一本“改错宝典”了,看的同学要注意。

居然又啰嗦了这么多,其实说来说去还是自己问题,要不然再多“经验”也是白费。找工作最让我明白的一句话就是“成功总是属于有准备的人”。

1、类成员指针

class Test

{public:

int fun(int) const;

int fun(int);

static int fun();

int iTemp;

}

1.1 非静态成员函数指针

定义:

int (Test::*pFun)(int) = &Test::fun;

int (Test::*pFunConst)(int)const = &Test::fun;

使用:

Test a;

const Test b;

(a.*pFun)(2) 或 (a.*pFunConst)(2);

(b.*pFunConst)(2);

不能用(b.*pFun)(2);

1.2 非静态成员变量

int Test::*pInt = &Test::iTemp;

(a.*pInt) = 3;

1.3 静态成员函数指针

int (*pFun)() = &Test::fun;

或 int (*pFun)() = Test::fun; 都正确;(注:定义无域操作符)

使用:

(*pFun)() 或 pFun() 都正确;

2、非成员函数指针和静态成员函数一致。

3、非成员函数和静态成员函数上不允许修饰符。例如 void fun() const; void fun() volatile;

但非静态成员函数允许const、volatile等修饰符。

4、变量修饰符

Auto:指定数据存储在栈中。局部变量默认为auto。该修饰符不能用于成员变量和全局变量。

static: 局部变量表示存储在静态空间,全局变量表示不允许外部引用。

volatile:表示该变量可能随时改变,不要做任何假设优化。

mutale:去除成员变量的const属性。

extern:全局变量默认为extern属性,表示可被外部引用,此时与static相对。

        extern int a =2; 表示定义一个可被外部引用的变量。

        extern int a; 表示引用外部变量。

5、数据类型隐式转换

短数据->长数据 (eg: float -> double)

有符号->无符号 (eg: int -> unsigned int )PS: 所以 int(-1)>unsigned int(1);

低精度->高精度 (eg: int -> float)

6、memcpy 有“防重叠”覆盖机制,strcpy 没有。

7、float表示

共计32位,折合4字节

由最高到最低位分别是第31、30、29、……、0位

31位是符号位,1表示该数为负,0反之。

30-23位,一共8位是指数位。

22-0位,一共23位是尾数位。

每8位分为一组,分成4组,分别是A组、B组、C组、D组。

每一组是一个字节,在内存中逆序存储,即:DCBA

8、不能在类的声明中初始化类常量,而只能在构造函数初始化列表来初始化。

9、类中的枚举常量不占用对象的存储空间。

10、有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。

11、赋值函数,应当用“引用传递”的方式返回String 对象。如果用“值传递”的方式,虽然功能仍然正确,但由于return 语句要把 *this 拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率。

12、对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数, 对象在消亡之前要自动执行析构函数。如果用free 释放“new 创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete 释放“malloc 申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。

13、如果用new 创建对象数组,那么只能使用对象的无参数构造函数,delete时如果对象没有析构函数,则delete和delete[]是功能相同的。

14、只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符。并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,因为函数的作用域不同。

15、关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。定义在类声明之中的成员函数将自动地成为内联函数。

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

16、只有静态常量整型数据成员才可以在类中初始化,普通成员只能在初始化列表或函数类初始化,常量成员只能在初始化列表。成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。

17、拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。

18、不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的,则只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。

19、“const T”和“T const”总是都被允许的,而且是等效的。

注意在常量指针(const pointer)中,“const”永远出现在“*”之后。

例如:

int *const p1 = q; //指向int 变量的常量指针

int const* p2 = q; //指向int 常量的指针

const int* p3 = q; //指向int 常量的指针

20、一个常见的微妙问题是,一个函数风格的宏并不遵守函数参数传递的规则。

21、没有引用数组,但可有指向数组的引用,并且保留数组的边界。

22、左值拥有保存值的位置,而右值则没有相关地址,只是简单值。

23、负索引是可以的,如p[-2]或(-2)[p]都是可以的(p必然不能是数组名),但必须保持不越界。

24、sum=p()+q()+r();不能保证p(),q(),r()调用的顺序。

25、逗号运算符","可以保证求值顺序.result= ( p(),q(),r() );是先求p(),q(),然后将r()赋给result

26、在if的条件里声明变量,且可在真假分支里面使用。

27、const int * const * p;p是个指针,指向常指针的,常指针指向一个常量int。

28、不能用空白初始化列表来规定默认的对象初始化.

class a;

a A(); //会警告,看起来像函数声明

a *p=new a(); //ok

a *p=new a;   //ok

29、int i=0;{double i=i;}//会有警告,值不确定

30、可以写一句只有数字的代码,如1234;(void)0;可以编译执行,相当于nop。

31、给函数指针赋值时可以对函数名取地址也可以不取,通过函数指针调用函数时可以用*也可不用。

32、static_cast可以转换基本数据类型(int->char)、void*和有类型指针、基类和派生类指针的转换(多重继承也行,它可重新计算偏移地址),但是不能转换如(int*->char*等)。

33、dynamic_cast主要用于执行"安全的向下转型",reinterpret_cast可执行任何转换,const_cast执行去const转换。

34、将取地址运算符用到完全限定的类成员名(包括变量和函数),就能获得指向成员的地址。使用形式为"X::*"来声明一个指向类X成员的指针。注意声明成员函数指针的时候不能像普通函数指针可以省略&或*的使用,但静态成员函数则除外,它和普通函数一致。成员指针和普通指针不一样,并非指向一个内存区域,而是相当于一个结构的偏移量,当它和具体的对象结合就能指向特定对象的特定成员。

35、当把派生类对象赋给基类对象的时候会产生切割现象,即针对派生类的数据和行为将产生切割。

36、当派生类指针是指向基类的指针时,指向派生类指针的指针并非指向基类的指针的指针。

37、多维数组的第1个元素是数组而非普通类型。

38、在含有单参数构造函数的类中注意隐式转换。如String s="Hello";

39、函数对象是重载函数调用运算符的类对象。

40、引用需要用左值进行初始化,但指向常量的引用除外,编译器将创建一个临时左值。如const int c=12;//ok 一般情况下编译器产生的临时对象的生命期在它所在的最大表达式范围内,但用临时对象初始化常量对象的引用时会让编译器保证临时对象和引用生命周期一样。

41、可以将基类的成员指针(变量或函数)安全的转换为指向派生类成员的指针,但反之则不安全。

42、函数参数的传递是采用拷贝构造函数而非赋值操作。对未初始化的对象赋值可能会出现意外,如类中含有未初始化指针。

43、声明但不定义私有的拷贝构造和赋值运算将会关闭类的复制操作。并且赋值运算、拷贝构造函数和析构函数不会被继承,对派生类重载赋值运算时需要调用基类的赋值运算。

44、在构造函数里对成员变量初始化,比较好的方式是使用初始化列表。在初始化列表中静态成员和数组不能被初始化。

45、类的初始化顺序是虚拟基类的成员->非虚基类成员->类自身成员,和初始化列表的顺序无关。

46、含有虚拟基类和不含的类在成员布局上不一样,含有虚拟基类的类将虚拟基类的数据放在最后面。另外如B:virtual A,C:virtual A,D:B,C;(均是虚继承)则D的构造函数将对A初始化一次(即使在初始化列表没有显式初始化A),B,C将不再对A初始化。

47、所有静态数据(全局变量和静态存储变量)在使用前如未初始化其值都为0.全局变量可以存储在静态初始化区和未初始化区。

48、RVO返回值优化,是指在函数返回中执行拷贝初始化到直接初始化(使用带非对象参数的构造函数)的转换,NRV和RVO类似,但使用命名局部变量来保存返回值。p160

49、重载、覆盖和隐藏的区别:

重载的特征:在同一个类,函数名相同,参数不同,virtual可有可无。

覆盖的特征:在两个类(基类和派生类),函数名和参数都相同,且必须有virtual关键字。

隐藏的特征:基类函数名和派生类函数名相同参数不同,且不管是否有关键字。或函数名、参数均相同,但基类函数没有virtual(有的话就是覆盖)。

不能覆盖而只能隐藏基类非虚函数。

50、基类中虚函数间的重载.p215

51、相同类型的所有对象公用一个虚函数表,在单继承下不管有多少个虚函数都只有一个虚函数表指针。覆盖就是在为派生类构造虚函数表时用派生类的函数地址替换基类成员函数地址的过程。

52、使用常量类成员可能在对类对象赋值的时候产生问题。

53、有时候我们可能会看到 if (NULL == p) 这样古怪的格式。不是程序写错了,是程

序员为了防止将 if (p == NULL) 误写成 if (p = NULL),而有意把p 和NULL 颠倒。

54、约束定义:

赋值约束检查:

template struct Can_copy {

static void constraints(T1 a, T2 b) { T2 c = a; b = a; }

Can_copy() { void(*p)(T1,T2) = constraints; }

};

比较约束检查:

template struct Can_compare {

static void constraints(T1 a, T2 b) { a==b; a!=b; a

Can_compare() { void(*p)(T1,T2) = constraints; }

};

55、对象函数

class Sum {

int val;

public:

Sum(int i) :val(i) { }

operator float() const { return val*0.1f; } // 类型转换重载

operator int() const { return val; } // 类型转换重载

int operator()(int i) { return val+=i; } // 函数调用重载

};

 

int main( void )

{

    Sum s = 0;

    s(2);

    cout<<(int)s;//2

    cout<<(float)s;//0.2

    s(3);

    cout<<(int)s;//5

    cout<<(float)s;//0.5

    return 0;

}

56、 sizeof 操作符,只做类型检查不会做内部表达式运算。

例: int a = 0;

         cout<

         cout<

继续网络相关的:

1、连接设备:

转发器:仅工作在物理层,用于加强信号,中续转发。(hub)

网桥:物理和数据链路层,控制通信量,只向目标网段转发数据。(交换机)

      多端口网桥可以像交换机一样工作,使多个网段使用整个带宽。

路由器:工作在网络层,确定一个分组应当采用的路径。

网关:协议转换器,工作在ISO所有层。

2、交换的三种方法:电路交换、分组交换、报文交换

电路交换:两设备之间具有专有物理连接。

分组交换:使用离散分组方式传输数据、包括数据报(分组独立路径)和虚电路(报文分组同一路径)。

报文交换:存储转发。(已经过时)

3、信元网络:ATM数据分组叫做信元。

    异步传输方式(ATM)是信元中继的协议、用来支持数据、话音和视频信号在高速传输媒体(如光纤)上的传输。

4、各种协议:

ARP : 地址解析协议 IP->Mac

BOOTP:引导程序协议,用于无盘计算机或第一次启动的计算机获取主机IP、掩码、路由IP、名字服务器IP。

DHCP:BOOTP的扩充,用于提供动态配置。

5、IP层协议组成,IP是主机到主机协议。

ARP: 地址解析协议,IP到MAC地址的转换

RARP:逆地址解析协议,MAC到IP的转换

ICMP:Internet控制报文协议,用来将数据包出现的问题以发送通知的方式反馈给发送端。

IGMP:用来将一个报文同时发给一组接收者。

6、运输层协议组成,是运输级协议,负责将报文从一个进程交付到另一个进程,故一般包括端口号。

TCP:传输控制协议

UDP:用户数据报协议

SCTP:串流控制传输协议

物理地址:也叫链路地址,包含在数据链路层使用的帧中。

局域网主要有三种体系结构:以太网,令牌环和光纤分布式数据接口(FDDI).

A类地址:使用1个字节8位作为网络ID,且该字节最高位为0,另有两个网络地址用作它图不计(网络ID全0和127的地址为特殊地址,故有2^7-2个网络),还有一个A类网络最多能连2^24-2个主机(主机ID全0和全1的去掉)。(0-127)

B类地址:使用2个字节16位作为网络ID,且该字节最高2位为10,故有2^14个网络,还有一个B类网络最多能连2^16-2个主机(主机ID全0和全1的去掉)。(128-191)

C类地址:使用3个字节24位作为网络ID,且该字节最高3位为110,故有2^21个网络,还有一个C类网络最多能连2^8-2个主机(主机ID全0和全1的去掉)。(192-223)

D类地址:没有网络ID和主机ID,前4位1110定义该类地址,作为多播使用。(224-239)

E类地址:没有网络ID和主机ID,保留以后使用。(240-255)

特殊地址:

网络ID和主机ID全1的为受限广播地址。

主机ID全1的为直接广播地址,全0则为该网络地址。

网络ID和主机ID全0的为该网络的该主机。

网络ID为0,主机ID为任意的则为该网络特定主机。

网络ID为127,主机ID为任意的则为环回地址。

多播地址是D类地址。

划分子网时全1,全0的网络ID或主机ID也都不分配给任何主机。

只使用UDP的应用层协议:TFTP、SNMP、RIP

只使用TCP的应用层协议:FTP(20\21)、TELNET、Finger、HTTP、SMTP、GOPHER、POP3、BGP

两者皆可使用的:Echo、Discard、Users、Daytime、Quote、Chargen、DNS、RPC、BOOTP、Time

TCP差错控制:

1、受伤 目的端丢弃受伤报文段。

2、重复 目的端丢弃

3、失序 目的端对失序的报文段不确认,直到收到所有以前的报文段位置。

4、确认丢失 无影响

TCP计时器:

1、超时重传:计时器到未收到确认,则重传。TCP使用动态重传时,即对每一个连接,其重传超时是不同的,而在同一个连接上也是会改变的。重传时间一般为RTT(往返时间)的两倍。

2、坚持计时器:当发送方收到0窗口大小时启动,主要防止接收方发的确认丢失,而使双方死等。

3、保活计时器:防止两个TCP长时间空闲。

4、时间等待计时器:在连接终止期间使用,TCP关闭一个连接时并不认为马山就真正关闭了。

TCP三次握手:

1、第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

2、第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

3、三次握手协议第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

TCP关闭的四次挥手:

1、客户TCP发送第一个FIN报文段。

2、服务器发送第二个报文段,即ACK报文段确认从客户发来的FIN段,确认号为FIN段的序列号加1.

3、服务器TCP继续发送数据后发送FIN段。

4、客户端发送ACK段,确认号为服务端FIN序列号加1.

路由选择协议:

RIP:路由信息协议,自治网内路由选择协议,基于距离向量路由选择,在熟知端口520使用UDP服务

OSPF:开放最短路径协议,自治网内,OSPF分组被封装成IP数据报,包括确认机制实现流控制和差错控制,且并不需要运输层协议。

BGP:边界网关协议,用于网间,基于路径向量路由选择,使用TCP服务179端口。

 其他杂项:

1、具有n个节点的不相似二叉树个数为 C(n,2n)/(n+1)

2、编译原理中正则表达式语法:

  *:匹配任意次  

  +:匹配至少一次  

  ?:匹配零或一次  

  |:或(比如a|b,表示a或者是b)  

  []:表示一个范围(比如[0-9],表示从0到9的十个数字)  

  ():   括号  

  除以上这些符号外不使用别的符号。  

3、linux下的正则表达式

.   匹配任何单个字符。

$   匹配行结束符。

^   匹配一行的开始。

*   匹配0或多个正好在它之前的那个字符。

\   这是引用府,用来将这里列出的这些元字符当作普通的字符来进行匹配。

[ ]  

[c1-c2]

[^c1-c2]   匹配括号中的任何一个字符。例如正则表达式r[aou]t匹配rat、rot和rut,但是不匹配ret。可以在括号中使用连字符-来指定字符的区间,例如正则表达式[0-9]可以匹配任何数字字符;还可以制定多个区间,例如正则表达式[A-Za-z]可以匹配任何大小写字母。另一个重要的用法是“排除”,要想匹配除了指定区间之外的字符——也就是所谓的补集——在左边的括号和第一个字符之间使用^字符,例如正则表达式[^269A-Z] 将匹配除了2、6、9和所有大写字母之外的任何字符。

\< \>   匹配词(word)的开始(\<)和结束(\>)。例如正则表达式\

\( \)   将 \( 和 \) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \1 到\9 的符号来引用。

|   将两个匹配条件进行逻辑“或”(Or)运算。例如正则表达式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:这个元字符不是所有的软件都支持的。

+   匹配1或多个正好在它之前的那个字符。例如正则表达式9+匹配9、99、999等。注意:这个元字符不是所有的软件都支持的。

?   匹配0或1个正好在它之前的那个字符。注意:这个元字符不是所有的软件都支持的。

\{i\}

\{i,j\}   匹配指定数目的字符,这些字符是在它之前的表达式定义的。例如正则表达式A[0-9]\{3\} 能够匹配字符"A"后面跟着正好3个数字字符的串,例如A123、A348等,但是不匹配A1234。而正则表达式[0-9]\{4,6\} 匹配连续的任意4个、5个或者6个数字字符。注意:这个元字符不是所有的软件都支持的。

4、进程优先级

linux下的进程调度优先级是从-20到19,一共40个级别,数字越大,表示进程的优先级越低。默认时候,进程的优先级是0

5、基于“比较”的内排序中,最坏情况下的最少比较次数为(log2(n!))向上取整。

6、错排数

概念:全错位排列,n个物质,重新排列顺序,使其均不在原位。

n个相异的元素排成一排a1,a2,...,an,且ai(i=1,2,...,n)不在第i位的排列数为n!(1-1/1!+1/2!-1/3!+...+(-1)^n*1/n!)

7、一个数的约数个数计算方法如下,先做质因数分解,每个质因数次数加一然后相乘就得到约数的个数。

例如:

18 = 2*3*3 所以约数个数为(1+1)*(2+1)= 6。分别是1、2、3、6、9、18

没了,还在奋斗的筒子们加油!!

编辑:孙乐

来源: 清水河畔

你可能感兴趣的:(随笔杂记,面试,c++)