您的位置:首页 > 职场人生

《程序员面试笔试宝典》学习笔记(一)

2016-05-19 10:24 489 查看

1、extern的作用

区分extern在C语言中和C++语言中的作用:

(1)C语言中extern声明的函数和变量可以被该文件外部模块引用。

(2)C++语言中除了该作用还可以声明extern “C”声明一段代码编译连接的方法为C语言的方法。

(a)extern是C/C++语言中声明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量在本模块或其他模块中使用。

(b) 被extern “C”修饰的变量和函数是按照C语言的方式编译和链接的。(C语言不支持函数重载,所以函数的C++和C的编译方式不同)

extern的使用问题

(1)extern 变量

在一个源文件里定义了一个数组:char a[6];在另外一个文件里用下列语句进行声明:extern char *a; 问:这样可以吗?

不可以,程序运行时会告诉你非法访问。原因在于,指向类型 char的指针并不等价于类型char的数组。extern char *a声明的是一个指针变量而不是一个字符数组,因此,与实际的定义不同,从而造成运行时非法访问。应将声明改为 extern char a[];

(2)单方面修改extern函数原型

当函数提供方面单方面修改函数原型时,如果使用者不知情继续沿用原来的extern声明,这样编译时编译器不会报错。但是在运行过程中,因为少输入或者是多输入参数,往往会造成系统错误,这种情况如何解决?

目前业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h 中提供对外部接口的声明,然后调用方include该头文件,从而省去extern这一步。避免错误。

(3)extern “C”

C++环境下使用C函数的时候,常常会出现编译器无法找到obj模块中的C函数定义,从而导致链接失败的情况,应该如何解决这种情况呢?

答案与分析:C++在编译的时候,为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern”C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名

(4)extern函数声明

C语言的函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其他作用。

(5)extern和static

A、extern表明该变量在别的地方已经定义过了,在这里要使用那个变量

B、static表示静态的变量,分配内存的时候,存储在静态区,不存储在栈上面

static作用范围是内部连接的关系。extern可以被其他的对象用extern引用,而static不可以,只允许对象本身用它。

具体的差别,首先static和extern不能同时修饰一个变量;其次,static修饰全局变量声明与定义同时进行;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到它。一般定义static全局变量时,都把它放在源文件中而不是头文件。

(6)extern和const

C++中const修饰的全局常量具有跟static相同的特性,它们只能作用于本编译模块中,但是const可以与extern连用来声明该常量可以作用于其他编译模块中。

2、strstr()函数的作用

strstr()函数的原型一般为
extern char * strstr(const char *src , const char *dest)
, 其作用就是判断字符串dest是否是src的子串,如果是,则返回目标字符串在源字符串中第一次出现的位置(地址,返回的是个指针)。否则,返回NULL。

3、windows线程优先级问题( 进程和线程的区别和联系 )

(a)进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念。每个进程都有个自己的地址空间,即进程空间或(虚空间)。进程至少有5种基本状态,它们是:初始态、执行态、等待状态,就绪状态,终止状态。

(b)线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。

通常一个进程可以包含若干个线程,它们可以利用进程所拥有的资源。进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),线程可与同属于一个进程的其他线程共享进程所拥有的全部资源。

线程和进程区别归纳:

(1)地址空间和其他资源:进程间互相独立,同一个进程的各线程共享。

(2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信-需要进行同步和互斥的辅助。

(3)调度和切换:线程上下文切换比进程上下文切换快速,高效。多线程的OS中,进程不是一个可执行的实体。

4、多方法交换x与y的值

(1)不借助第三方变量交换x和y的值 (x=x+y,y=x-y;x=x-y)

(2)用函数实现,参数是引用 void swap( int &x,int &y )

(3)用函数实现,参数是指针 void swap( int *x,int *y)

5、指针的自加与引用

假设指针指向的是一个整型数组(int a[]={1,2,3,4,5}; int *p = a;),那么,指针p的地址是数组a的首地址,而指针p++,自加之后,p所指的是a[1]的地址。

指针的引用相当于传递的是:指针的指针,这样,指针的数值可以进行改变。

6、前置++与后置++

前置自增 (++i) 通常要比后置自增 (i++) 效率更高。理由是:后置++会生成临时对象。

++a表示取a的地址,增加它的内容,然后把值放在寄存器中;

a++表示取a的地址,把它的值装入寄存器,然后增加内存中的a的值;

7、inline的作用

inline函数不像正常函数在调用时存在压栈和call的操作,它会把程序代码直接嵌入到调用代码段中,也就是说使用inline函数会增大二进制程序的体积,但是会使执行速度加快。

同时,编译期间可以对参数进行强类型的检查,这是inline优于宏的一个方面。

9、ifndef的作用

条件编译的语法,一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

11、函数调用方式

(1)函数调用介绍

调用函数的时候,计算机常用栈来存储传递给函数的参数。

栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作称为入栈(push),压栈后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。

函数调用的时候,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者是调用者、或者函数本身修改堆栈,使堆栈恢复原状。

12、重载函数

函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。

优点:

(1)重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

(2)构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!

(3)操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

编译器在对重载函数调用进行处理时,由语法分析、C++文法、符号表、抽象语法树交互处理

13、构造函数和析构函数

构造函数的调用顺序总是如下:

1)基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。

2)成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。

3)派生类构造函数

析构函数:

析构函数的调用顺序与构造函数的调用顺序正好相反:首先调用派生类的析构函数;其次再调用成员类对象的析构函数;最后调用基类的析构函数。

析构函数在下边3种情况时被调用:

1)对象生命周期结束,被销毁时。

2)delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类析构函数是虚函数时;

3)对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

20、TCP的流量控制和拥塞控制机制

TCP的流量控制就是让发送方的发送速率不要太快,让接收方来得及接收。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接收方给出的接收窗口的数值。

所谓的拥塞控制防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能承受现有的网络负荷。流量控制往往指点对点通信量的控制,是一个端到端的问题。因特网建议标准RFC2581定义了进行拥塞控制的四种算法,即慢开始(Slow-start),拥塞避免(Congestion Avoidance)快重传(Fast Restrangsmit)和快回复(Fast Recovery)。

21、写一个函数,返回一个字符串中只出现一次的第一个字符

哈希表。跟字符出现的次数有关,我们考虑如何统计字符出现的次数,然后找出第一个次数为1的那个字符。

第一次扫描字符串,每扫描到一个字符就在哈希表的对应项中把次数加1。第二次扫描的时候,找出哈希表中第一个Value为1的那个key就是我们需要找到那个字符。

23、面向对象继承,多态问题,如多态的实现机制

面向对象编程有三个特性:封装,继承,多态。这三个特性从低级到高级描述了面向对象的特征。一种语言只有同时具备这三种特性才能被称为面向对象的语言。

(1)封装

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

(2)继承

继承也称为派生,继承关系中,被继承的称为基类,从基类继承而得的被称为派生类或者子类。继承是保持对象差异性的同时共享对象相似性的复用。能够被继承的类总是含有并只含有它所抽象的那一类事物的共同特点。

继承概念的实现方式有三类:实现继承、接口继承和可视继承。

实现继承是指使用基类的属性和方法而无需额外编码的能力;

接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

(3)多态

简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

实现多态,有二种方式,覆盖,重载。

覆盖,是指子类重新定义父类的虚函数的做法。

重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。

重载只是一种语言特性,与多态无关,与面向对象也无关!

那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

25、成员函数初始化列表有什么作用?什么必须在成员初始化列表中进行初始化?

因为初始化列表的作用是初始化而不是赋值,免去了构造对象(调用拷贝构造函数)然后调用operator=(赋值操作符)的过程,所以效率更高一点(对于内置类型或者简单类型这个并不明显)

必须用到初始化成员列表的四种情况:

1) 初始化一个reference成员(引用必须在定义的时候初始化,并且不能重新赋值,所以要写在初始化列表里面;)
2) 初始化一个const成员(因为常量只能初始化不能赋值,所以必须放在初始化列表里面;)
3) 调用一个基类的构造函数,而该函数有一组参数
4) 调用一个数据成员对象的构造函数,而该函数有一组参数(没有默认构造函数的类类型。因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。)


成员是按照他们在类中声明的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的。

摘自《C++对象模型》

27、指针与引用的区别

相同点:

都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

不同点:

1)指针是一个实体,而引用仅是个别名

2)引用只能并且必须在定义时被初始化一次,之后不可变(类似常量指针,引用自带常量指针属性);指针可变;

3)引用没有const,指针有const,const的指针不能够改变;(int & const refer 不存在,因为引用本身就初始化一次不可变,但是const int &refer是存在的,指引用所指向的值不可改变)

4)引用不能为空,指针可以为空

5)sizeof针对指针得到的是指针的大小,针对引用得到的是指向对象的大小;

6)指针的++操作和引用的++操作完全不同,指针为移动指针地址,引用++操作作用于指向的对象;

7)引用是类型安全的,而指针不是类型安全的。

28.创建空类时,哪些成员函数是系统默认的?

构造函数,拷贝构造,赋值函数,析构函数,取址运算符,const取址运算符

class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};


29、有10W个IP段,这些IP段之间都不重合,随便给定一个IP,求出属于哪个IP段

我的想法:

比如1~5,6~10,11~15….等等有这么多数据段,随便给定一个数求出属于哪个数据段?

按照数据段的起始值进行排序,如1,6,11。。。然后利用二分查找。如要找4,则利用二分查找给出下界1,从而属于1~5这个范围。

30、网络编程(网络编程范式,非阻塞connect)

常见的IO模型有阻塞、非阻塞、IO多路复用、异步。

36、内存溢出和内存泄露有什么区别?

1)内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

2)内存泄露 memory leak,是指程序在申请内存后,没有释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

51.struct与class有什么区别和联系

(a)默认的访问控制,struct默认访问控制public,class默认访问控制private,写代码时最好标明确访问控制。

(b)class这个关键字还用于定义模板参数,like “typename”。关键字struct不能用于定义模板参数。

52.函数指针和指针函数

函数指针:char (*p)();p为指向函数的指针

指针函数:char *p();返回指针的函数

53.指针数组和数组指针

指针数组:char *cp_array[];

数组指针:char (*p_array)[];

54.大端小端

大端模式,是指数据的高位,保存在内存的低地址中,而数据的低位,保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

小端模式,是指数据的高位保存在内存的高地址中,而数据的低位保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

56.如何判断单链表是否有环

快慢指针,相遇则存在环。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: