面试题(5)
1、介绍STLSTL(标准模板库)由容器算法迭代器组成;vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间;vector使用allocator来进行内存管理,使用3个迭代器来引用这段内存。vector的iterator其实就是T*的别名。在一个连续的内存里(数组),指针可以做算术运算,也支持[]操作,由此vector的iterator也支持算术运算,++、–、+=、-=、
1、介绍STL
- STL(标准模板库)由容器算法迭代器组成;
- vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间;
- vector使用allocator来进行内存管理,使用3个迭代器来引用这段内存。vector的iterator其实就是T*的别名。在一个连续的内存里(数组),指针可以做算术运算,也支持[]操作,由此vector的iterator也支持算术运算,++、–、+=、-=、[],vector的迭代器就是通常的随机访问迭代器
2、分析Visual C++程序出错的原因
- LNK2001:错误的消息主要为unresolved、external symbol
- 主要原因:所引用的函数、变量不存在、拼写错误或者使用错误
- 可能引用了不同版本的链接库
- 编译器错误C2001:常数中有换行符
- 编译器错误C2005:#line应输入行号,却找到“token”。
- 编译器C2011:“identifier”:“type”类型重定义,该标识符已定义为type类型
3、继承和多态的区别
- 多态的基础是继承,没有继承,多态无从谈起
- 子类继承父类的方法和属性,并且继承可以扩展父类已经存在的方法和属性,它的目的是代码复用,避免重复代码的出现
- 多态的作用是:接口重用,类在继承和派生时,保证使用相关子类和父类整个家族任一个类的实例时某一方法的正确调用
- 具体的说: 多态:子类重写父类的方法,使子类具有不同的方法实现;在子类方法中,把父类类型作为参数,在方法实现中调用父类类型的各种方法,当把子类作为参数传递给这个方法时,会根据实际创建的对象类型,来决定方法的调用
指针和引用的区别?传引用比传指针安全,为什么
- 引用在创建同时必须初始化,即引用一个有效地对象,而指针在定义时不必初始化,可以在定义后的任何地方重新赋值
- (安全性)不存在NULL引用,引用必须与合法的存储单元关联;而指针可以使NULL
- 引用一旦初始化为引用一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象
- 引用的创建和销毁不会调用类的拷贝构造函数
参数传递的方式与多态参数传递的实现
- 传值方式适合一般数值发送,并且不会改变原数据(实参),但要消耗内存空间(实参值得拷贝传给形参)
- 传指针方式适合传递数组、指针,由于传递的是地址,所以直接操作会改变原数据
- 引用方式和指针比较类似,一搬情况下,能用传址的就能用引用,而且使用使用更方便,更安全
实现多态主要是采用引用和指针,传值方式是复制数据,其类型编译器就已决定,而多态的类型要等到执行时才能决定,并且多态的参数类型通常是类类型,如果采用传值,会很浪费内存
C++和C定义结构的区别
- C语言的结构体仅仅是数据的集合,结构体内不能声明函数
- C++结构体和类几乎具有一样的功能,结构体内也可以声明函数,只是默认访问属性不一样,结构体默认访问属性是public,而class是private
关于构造函数和析构函数
- 构造函数不能为虚函数:在构造函数执行结束后,虚函数表指针才会被正确的初始化
- 析构函数可以是虚函数,也可以是纯虚函数
拷贝构造函数的深拷贝、浅拷贝和临时对象
- 深拷贝意味着拷贝了资源和指针,而浅拷贝只是拷贝了指针,没有拷贝资源;浅拷贝会使两个指针指向同一份资源,造成一份资源析构两次,程序崩溃,当对象中有成员指向了系统资源,需要深拷贝
- 临时对象的开销比局部变量小
基类有一个虚函数,子类还需要声明为virtual吗
- 不声明为virtual是没关系的,不过显式声明,可以使代码更加清晰,增强可读性
C++类的优点
- 封装,继承和多态
如何实现多态?父类和子类继承关系如何
- 多态的基础是继承,需要虚函数的支持
- 多态有三种实现方式
- 函数重载
- 模板函数
- 虚函数
- 子类继承父类的大部分资源。不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数和友元函数
介绍一下模板和容器,如何实现
- 模板编程实质上就是泛型编程(不考虑具体数据类型的编程方式),它体现了一种通用和泛化的思想
- STL有7中容器:vector,list,deque,map,multimap,set和multiset
什么是MVC?
- MVC(模型/视图/控制器):观察者模式的特例
- 模型:表示业务逻辑(数据处理)
- 视图:数据的界面显示
- 控制器:根据用户数据输入,控制用户界面显示和模型对象状态
进程间通信
- 管道:管道是一种半双工的通信方式,而且只能在具有亲缘(父子进程)关系的进程间使用
- 有名管道:允许无亲缘关系进程间的通信
- 信号量:一个计数器,可以用来控制多个进程对共享资源的访问。通常作为一种锁机制,防止某个进程在访问共享资源时,其他进程也访问该资源
- 消息队列:克服了信号传递信息少,管道只能承载无格式字节流及缓冲区大小受限等缺点
- 信号:用于通知接收进程某个事件已经发生
- 共享内存:映射一段能被其他进程所访问的内存,由一个进程创建,但多个进程都可以访问
- 套接字:它可用于不同机器间的进程通信
线程同步
- 信号量:它允许多个线程同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
- 事件:通过通知操作的方式保持线程的同步,还可以方便实现对多个线程的优先级的比较操作
- 互斥量:只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,可以保证公共资源不会同时被多个线程访问
- 临界区:在任意时刻只允许一个线程对共享资源进行访问
进程死锁的原因
- 死锁是指在两个或多个并发进程中,因相互等待临界资源导致彼此无法继续执行
死锁的4个必要条件
- (互斥条件)一个资源每次只能被一个进程使用
- (请求与保持)一个进程因请求资源而阻塞时,对已获得的资源保持不放
- (不可剥夺)进程已获得的资源,在没使用完之前,不能强行剥夺
- (循环等待)多个进程因互相等待资源而停止继续执行
死锁的处理
- 检测死锁并恢复
- 系统定时运行一个“死锁”检测程序,判断系统是否已发生死锁,若检测到死锁发生则设法加以解除:资源剥夺法;撤销进程法
- 通过破坏死锁产生的四个必要条件之一,来防止死锁发生
- 采用资源动态分配策略,破坏”保持和请求“条件
- 允许进程剥夺使用其他进程占有的资源,从而破坏“不可剥夺”条件
- 采用资源有序分配法,破坏“循环等待”条件
操作系统中进程调度策略有哪几种
- 先来先服务:每次调度从就绪队列中选择一个最先进入该队列的进程,为之分配CPU
- 优先级(最高优先权优先)调度算法
- 时间片轮转调度:系统将所有的就绪进程排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片,执行的时间片用完时,把它送往就绪队列的末尾,依次循环
- 多级反馈:
- 设置多个就绪队列,并为各个队列赋予不同的优先级,第一个队列优先级最高
- 当一个新进程进入内存,首先将它放在第一队列的末尾,按FCFS原则排队等待调度
- 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行
类的静态成员和非静态成员有何区别
- 静态成员使用static修饰符进行声明,静态成员时属于整个类,而不是属于某一个对象
- 静态成员函数只能调用静态成员变量或函数
- 公有静态成员变量可以通过类名来访问
纯虚函数如何定义?使用应注意
- virtual void f() = 0;
- C++通过纯虚函数实现抽象类,抽象类只能用作父类被继承,子类必须实现纯虚函数的具体功能
数组和链表的区别
- 数组:数据顺序存储,在定义时确定大小
- 链表:数据可以随机存储,大小可以动态改变
关于ISO七层模型的问题
- 七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
- TCP/UDP属于传输层
- TCP和UDP优缺点:
- TCP优点:可靠,稳定;可靠性体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认,窗口,重传,拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源
- TCP缺点:慢,效率低,占用系统资源多,易被攻击,TCP在传递数据之前,要先建立连接,会消耗时间,而且在数据传递时,确认机制,重传机制,拥塞控制机制都会消耗大量时间,而且在每台设备上维护所有的传输连接,每个连接都会占用系统的CPU、内存等硬件资源
- UDP优点:块,比TCP稍安全;UDP没有TCP的握手,确认,重传,拥塞控制等机制,所以在传递数据时非常快,UDP较TCP被攻击者利用的漏洞就要少一些,但UDP也是无法避免攻击的
- UDP缺点:不可靠,不稳定;UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包
内存的分配方式及其区别
- 从静态存储区分配,内存在程序编译时已经分配好,例如全局变量和static变量
- 在栈上创建,执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放
- 从堆上分配,也称为动态内存分配,程序在运行时使用malloc或new申请内存,动态内存的生存期由程序员决定,自己负责调用free或delete释放内存
struct和class的区别
- struct的默认访问属性是公有的,class的成员默认访问属性是私有的
- C语言的struct内部不可以声明函数,C++可以
一个类所占内存空间的问题
当一个类A中没有声明任何成员变量与成员函数时,sizeof(A)的值是否为0?
- 空类也可以实例化,为了使这个类的不同实体(对象)在内存中拥有独一无二的地址
在8086汇编中,逻辑地址和物理地址如何转换
- 通用寄存器给出的地址是段内偏移地址,相应段寄存器地址*10+通用寄存器内地址,就得到了真正要访问的物理地址
比较C++的4种类型转换方式
- const_cast:用于去除对象的只读属性;强制转换的目标类型必须是指针或引用
- static_cast:用于基本类型的转换;不能用于基本类型指针间的转换;用于有继承关系对象之间的转换和类指针之间的转换
- reinterpret_cast强制类型转换:用于指针类型间的强制转换;用于整数和指针类型间的强制转换
- dynamic_cast:用于有继承关系的类指针间的转换;具有类型检查功能;需要虚函数支持
面向对象3个基本特征
- 封装(隐藏实现细节):把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
- 继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
- 多态:允许将父对象设置成为和一个或更多它的子对象相等的技术,父对象根据当前赋值给它的子对象的特性以不同的方式运作。允许将子类类型的指针赋值给父类类型的指针
重载和重写的区别
- 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或者两者都不同)
- 重写:子类重新定义父类虚函数的方法。和多态真正相关,当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数。这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出),因此,这样的函数地址是在运行期绑定的。
多态的作用
- 接口重用:为了类在继承和派生时,保证使用家族中任一类的实例的某一方法时的正确调用
- 隐藏实现细节,使得代码能够模块化:扩展代码模块,实现代码重用(继承)
new delete和malloc free有何联系和区别
- 使用malloc函数动态分配内存是按字节数来分配并且不能初始化对象;
- new是基于类型来进行动态内存分配的并且可进行初始化,创建对象只能用new,new会调用对象的构造函数,delete会调用对象的析构函数
哪几种情况只能用初始化列表而不能用赋值
- 当类中含有const和引用等成员变量时,基类的构造函数都需要初始化列表
- 调用一个基类的构造函数,而该函数有一组参数;调用一个数据成员对象的构造函数,,而该函数有一组参数
C++是不是类型安全的
- 不是,两个不同类型的指针之间可以强制转换(reinterpret_cast)
main()函数执行前还会执行什么代码
- 全局对象的构造函数会在main()函数之前执行
static的用途
- 限制全局变量或函数的作用域
- 设置局部变量的存储域
描述实时操作系统的基本特性
- 在特定时间内完成特定的任务,实时性与可靠性
全局变量和局部变量在内存中是否有区别
- 全局变量存储在全局数据区,局部变量存储在栈区
什么是平衡二叉树
- 它是一颗空树或它的左右两个子树的高度差的绝对值不超过1,并且左右子树都是一颗平衡二叉树;最小平衡二叉树的节点的公式:F(n) = F(n-1) + F(n-2) +1;
(噗嗤)定义int * * a[3][4],则变量占用多少内存空间
- int* * p;16位OS:sizeof(p) = 2;32位OS:sizeof(p) = 4;总共3 * 4 * sizeof(p)
堆栈溢出一般是什么原因
- 一般是循环的递归调用导致的;由于过多的函数调用,导致调用堆栈无法容纳这些调用的返回地址
- 使用大数据结构的局部变量
什么函数不能声明为虚函数
- 构造函数不能声明为虚函数
- 从继承的概念来讲,总是要先构造父类对象,然后才能是子类对象;如果构造函数设为虚函数,那么父类构造函数会被覆盖,必须显式的调用构造
- 虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中,在构造函数执行结束后,虚函数表指针才会被正确初始化
写出float x与“零值”比较的if语句
- if(x>0.000001 && x < -0.000001)
* Internet采用哪种网络协议?介绍该协议的主要层次结构*
- TCP/IP协议
- 应用层、传输层、网络层、数据链路层、物理层
Internet物理地址和IP地址转换采用什么协议
- ARP(地址解析协议):就是在主机在发送帧前将目标IP地址转换成目标MAC地址的过程
IP地址的编码分为哪两部分
- IP地址分为两部分:网络号和主机号;不过是要和子网掩码按位与之后才能区分
不能做switch()的参数类型是什么数据类型
- 表达式可以是整形,字符型以及枚举类型;不能是浮点型
static和const关键字作用
- 函数体内static变量存储在静态存储区,该变量的内存只被分配一次,其值下次调用时仍维持上次的值
- 模块内的static全局变量只能被模块内的函数访问,不能被模块外其他函数访问
模块内的static函数只能被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内
在类中的static成员变量属于整个类所有,对类的所有对象共享
在类中的static成员函数属于整个类所拥有,不接收this指针,因而只能访问类的static成员变量
欲阻止一个变量被改变,可以使用const关键字;在定义该const变量时,通常对它进行初始化
- 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const或二者都为const
- 在一个函数声明中,const可以修饰形参,表明在函数体内不能改变它
- 对于类的const成员函数,表明它是一个常函数,函数内部不能修改类的成员变量
什么是预编译
- 预编译就是处理#开头的指令,例如拷贝#include 包含的文件代码,#define宏定义的替换和条件编译;预编译指示了在程序正式编译前由编译器进行的操作
写一个标准宏,使其输入两个参数,返回较小一个
- #define Min(X,Y) (X>Y)?(Y):(X)
嵌入式如何用C编写死循
- while(1)或for(;;)
* 不使用第三个变量,交换两个变量的值*
- 使用算术运算:
- a = a+b; b =a-b; a = a -b;
- 使用异或
- a = a^b; b = a^b; a = a^b;
C和C++中的struct有什么不同
- 主要区别是C中的struct不可以包含成员函数,而C++可以;C++中的struct和class主要区别在于默认的访问权限不同
如何让程序跳到绝对地址0X100000去执行
首先要将0x100000强制转换成函数指针,然后再调用它
*(void (*) ())0x100000)();
已知一个数组tale,用一个宏定义求出数据的元素个数
- #define NTBL sizeof(table)/sizeof(table[0])
**线程与进程有何区别和联系?线程是否具有相同的堆栈?DLL是否有独立的堆栈?
- 一个进程可以有多个线程,但至少有一个线程;线程必须依附于进程来运行
- 进程是操作系统资源分配的基本单位;同一进程的所有线程共享进程所有资源
- 线程是操作系统调度的基本单元
什么是引用?声明和使用引用要注意的问题
- 引用可以说是一个变量的别名,对引用操作与对变量直接操作效果完全一样
- 声明一个引用的时候,一定要对其进行初始化
- 声明完毕以后,不能再把该引用名作为其他变量名的别名
- 不能建立数组的引用
* 将引用作为函数的参数有哪些特点*
- 传递引用给函数与传递指针的效果是相同的。这时,被调函数的形参就成为原来主调函数的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作
- 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储空间,形参变量时实参变量的副本;如果传递的是对象,还将调用拷贝构造函数,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和占有空间都好
- 使用指针作为函数参数虽然也能达到使用引用的效果,但是在被调函数同样给形参分配存储单元
什么时候需要使用常引用
- 如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应该使用常引用
引用作为函数返回值类型的优点和需遵循的规则
- 在内存中不产生被返回值的副本
- 需要遵循的规则:
- 不能返回局部变量的引用:局部变量在函数返回后被销毁,被返回的引用就成为了空引用
- 不能返回函数内部new分配的内存的引用:被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向空间(由new分配)就无法释放,造成内存泄漏
- 可返回类成员的引用,但最好是const
引用与多态的关系
- 引用是除指针外另一个可以产生多态效果的手段,这意味着,一个基类的引用可以指向它的派生类实例
如何判断一段程序时是C编译还是由C++编译
- C++编译时定义了_cplusplus
- C编译时定义了STDC
结构体与联合体有何区别
- 结构体和联合体都是由多个不同的数据成员组成的。但是联合体各个成员公用内存,并同时只有一个成员得到这块内存的使用权;而结构体各个成员各自拥有内存,各自使用互不干涉
- 对于联合体不同成员赋值,将会对其它成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的
类成员函数的重载、覆盖和隐藏的区别
- 重载
- 同一个作用域
- 函数名相同
- 参数不同
- 覆盖(重写)是指派生类函数覆盖基类函数
- 分别位于派生类和基类
- 函数名相同
- 参数相同
- 基类函数必须有virtual关键字
- 隐藏是指派生类的函数屏蔽了与其同名的基类函数
- 如果派生类的函数与基类的函数同名,但是参数不同,无论有无virtual关键字,基类函数都将被隐藏
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时,基类的函数被隐藏
const与#define相比有何优点
- const常量有数据类型,而宏常量没有数据类型;编译阶段会对const常量进行类型安全检查,而宏定义在预处理阶段只进行简单的文本替换,没有类型安全检查
- 可以对const常量进行调试,但是不能对宏常量进行调试
**h头文件中的ifndef/define/endif有何作用
- 防止头文件被重复引用
C++中调用被C编译器编译后的函数为何要加extern “C”
- 该关键字告诉编译器,其声明的函数和变量在其他模块定义
- 被extern “C”修饰的变量和函数是按照C语言编译器的方式编译和链接的
更多推荐
所有评论(0)