一、编程语言
1.根据熟悉的语言,谈谈两种语言的区别?
主要浅谈下C/C++和PHP语言的区别:
1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互联网开发公司的主流web后台开发语言,主要框架为mvc模型,如smarty,yaf,升级的PHP7速度较快,对服务器的压力要小很多,在新浪微博已经有应用,对比很明显。
2)C/C++开发语言,C语言更偏向硬件底层开发,C++语言是目前为止我认为语法内容最多的一种语言。C/C++在执行速度上要快很多,毕竟其他类型的语言大都是C开发的,更多应用于网络编程和嵌入式编程。
2.volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻),使用实例有哪些?(重点)
1)访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会读脏数据。声明变量为volatile,编译器不再对访问该变量的代码优化,仍然从内存读取,使访问稳定。
总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不再编译优化,以免出错。
2)使用实例如下(区分C程序员和嵌入式系统程序员的最基本的问题。):
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
3)一个参数既可以是const还可以是volatile吗?解释为什么。
可以。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
4)一个指针可以是volatile 吗?解释为什么。
可以。尽管这并不很常见。一个例子当中断服务子程序修该一个指向一个buffer的指针时。
下面的函数有什么错误:
int square(volatile int *ptr) {
return *ptr * *ptr;
}
下面是答案:
这段代码有点变态。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr){
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}
3.static const等等的用法,(能说出越多越好)(重点)
² 首先说说const的用法(绝对不能说是常数)
1)在定义的时候必须进行初始化
2)指针可以是const 指针,也可以是指向const对象的指针
3)定义为const的形参,即在函数内部是不能被修改的
4)类的成员函数可以被声明为常成员函数,不能修改类的成员变量
5)类的成员函数可以返回的是常对象,即被const声明的对象
6)类的成员变量是常成员变量不能在声明时初始化,必须在构造函数的列表里进行初始化
(注:千万不要说const是个常数,会被认为是外行人的!!!!哪怕说个只读也行)
下面的声明都是什么意思?
const int a; a是一个常整型数
int const a; a是一个常整型数
const int *a; a是一个指向常整型数的指针,整型数是不可修改的,但指针可以
int * const a; a为指向整型数的常指针,指针指向的整型数可以修改,但指针是不可修改的
int const * a const; a是一个指向常整型数的常指针,指针指向的整型数是不可修改的,同时指针也是不可修改的
通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Const如何做到只读?
这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定。
² 再说说static的用法(三个明显的作用一定要答出来)
1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用
4)类内的static成员变量属于整个类所拥有,不能在类内进行定义,只能在类的作用域内进行定义
5)类内的static成员函数属于整个类所拥有,不能包含this指针,只能调用static成员函数
static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
4.extern c 作用
告诉编译器该段代码以C语言进行编译。
5.指针和引用的区别
1)引用是直接访问,指针是间接访问。
2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间
3)引用绑定内存空间(必须赋初值),是一个变量别名不能更改绑定,可以改变对象的值。
总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性
6. 关于静态内存分配和动态内存分配的区别及过程
静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源;
2)静态内存分配是在栈上分配的,动态内存是堆上分配的;
3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;
4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。
5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员;
6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。
7. 头文件中的 ifndef/define/endif 干什么用?
预处理,防止头文件被重复使用,包括pragma once都是这样的
8. 宏定义求两个元素的最小值
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
9. 分别设置和清除一个整数的第三位?
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void){
a |= BIT3;
}
void clear_bit3(void){
a &= ~BIT3;
}
10. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
关于面试,关于技术,需要沟通交流点这里。
面试,技术,岗位信息全网覆盖中~
一切只为渴望更优秀的自己!
11. 预处理器标识#error的目的是什么?
抛出错误提示,标识外部宏是否被定义!
12. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
记住这是第一方案!!!!
while(1)
{
}
一些程序员更喜欢如下方案:
for(; {
}
汇编语言的无线循环是:
Loop:
…
goto Loop;
13. 用变量a给出下面的定义
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 int (*a[10])(int);
14. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt
16. memcpy函数的实现
void *memcpy(void *dest, const void *src, size_t count) {
char *tmp = dest;
const char *s = src;
while (count--)
*tmp++ = *s++;
return dest;
}
17. Strcpy函数实现
char *strcpy(char *dst,const char *src) {
assert(dst != NULL && src != NULL);
char *ret = dst;
while((* dst++ = * src++) != '\0') ;
return ret;
}
18. strcat函数的实现
char *strcat(char *strDes, const char *strSrc){
assert((strDes != NULL) && (strSrc != NULL));
char *address = strDes;
while (*strDes != ‘\0′)
++ strDes;
while ((*strDes ++ = *strSrc ++) != ‘\0′)
return address;
}
19.strncat实现
char *strncat(char *strDes, const char *strSrc, int count){
assert((strDes != NULL) && (strSrc != NULL));
char *address = strDes;
while (*strDes != ‘\0′)
++ strDes;
while (count — && *strSrc != ‘\0′ )
*strDes ++ = *strSrc ++;
*strDes = ‘\0′;
return address;
}
20. strcmp函数实现
int strcmp(const char *str1,const char *str2){
/*不可用while(*str1++==*str2++)来比较,当不相等时仍会执行一次++,
return返回的比较值实际上是下一个字符。应将++放到循环体中进行。*/
while(*str1 == *str2){
if(*str1 == '\0')
return0;
++str1;
++str2;
}
return *str1 - *str2;
}
21. strncmp实现
int strncmp(const char *s, const char *t, int count){
assert((s != NULL) && (t != NULL));
while (*s && *t && *s == *t && count –) {
++ s;
++ t;
}
return (*s – *t);
}
22.strlen函数实现
int strlen(const char *str){
assert(str != NULL);
int len = 0;
while (*str ++ != ‘\0′)
++ len;
return len;
}
23. strpbrk函数实现
char * strpbrk(const char * cs,const char * ct){
const char *sc1,*sc2;
for( sc1 = cs; *sc1 != '\0'; ++sc1){
for( sc2 = ct; *sc2 != '\0'; ++sc2){
if (*sc1 == *sc2){
return (char *) sc1;
}
}
}
return NULL;
}
24. strstr函数实现
char *strstr(const char *s1,const char *s2){
int len2;
if(!(len2=strlen(s2)))//此种情况下s2不能指向空,否则strlen无法测出长度,这条语句错误
return(char*)s1;
for(;*s1;++s1)
{
if(*s1==*s2 && strncmp(s1,s2,len2)==0)
return(char*)s1;
}
return NULL;
}
25. string实现(注意:赋值构造,operator=是关键)
class String{
public:
//普通构造函数
String(const char *str = NULL);
//拷贝构造函数
String(const String &other);
//赋值函数
String & operator=(String &other) ;
//析构函数
~String(void);
private:
char* m_str;
};
分别实现以上四个函数
//普通构造函数
String::String(const char* str){
if(str==NULL) //如果str为NULL,存空字符串{
m_str = new char[1]; //分配一个字节
*m_str = ‘\0′; //赋一个’\0′
}else{
str = new char[strlen(str) + 1];//分配空间容纳str内容
strcpy(m_str, str); //复制str到私有成员m_str中
}
}
//析构函数
String::~String(){
if(m_str!=NULL) //如果m_str不为NULL,释放堆内存{
delete [] m_str;
m_str = NULL;
}
}
//拷贝构造函数
String::String(const String &other){
m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中
}
//赋值函数
String & String::operator=(String &other){
if(this == &other) //若对象与other是同一个对象,直接返回本{
return *this
}
delete [] m_str; //否则,先释放当前对象堆内存
m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中
return *this;
}
26. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b; 等同于 c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。
27. 用struct关键字与class关键定义类以及继承的区别
(1)定义类差别
struct关键字也可以实现类,用class和struct关键字定义类的唯一差别在于默认访问级别:默认情况下,struct成员的访问级别为public,而class成员的为private。语法使用也相同,直接将class改为struct即可。
(2)继承差别
使用class保留字的派生类默认具有private继承,而用struct保留字定义的类某人具有public继承。其它则没有任何区别。
主要点就两个:默认的访问级别和默认的继承级别 class都是private
28.派生类与虚函数概述
(1) 派生类继承的函数不能定义为虚函数。虚函数是希望派生类重新定义。如果派生类没有重新定义某个虚函数,则在调用的时候会使用基类中定义的版本。
(2)派生类中函数的声明必须与基类中定义的方式完全匹配。
(3) 基类中声明为虚函数,则派生类也为虚函数。
29. 虚函数与纯虚函数区别
1)虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现
2)带纯虚函数的类叫虚基类也叫抽象类,这种基类不能直接生成对象,只能被继承,重写虚函数后才能使用,运行时动态动态绑定!
30.深拷贝与浅拷贝
浅拷贝:
char ori[]=“hello”;char *copy=ori;
深拷贝:
char ori[]=“hello”; char *copy=new char[]; copy=ori;
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
浅拷贝可能出现的问题:
1) 浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃。
2) 浅拷贝使得两个指针都指向同一块内存,任何一方的变动都会影响到另一方。
3) 同一个空间,第二次释放失败,导致无法操作该空间,造成内存泄漏。
31. stl各容器的实现原理(必考)
Vector顺序容器,是一个动态数组,支持随机插入、删除、查找等操作,在内存中是一块连续的空间。在原有空间不够情况下自动分配空间,增加为原来的两倍。vector随机存取效率高,但是在vector插入元素,需要移动的数目多,效率低下。
注:vector动态增加大小时是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector空间重新配置,指向原vector的所有迭代器就都失效了。
Map关联容器,以键值对的形式进行存储,方便进行查找。关键词起到索引的作用,值则表示与索引相关联的数据。红黑树的结构实现,插入删除等操作都在O(logn)时间内完成。
Set是关联容器,set每个元素只包含一个关键字。set支持高效的关键字检查是否在set中。set也是以红黑树的结构实现,支持高效插入、删除等操作。
32.哪些库函数属于高危函数,为什么?
strcpy 赋值到目标区间可能会造成缓冲区溢出!
33.STL有7种主要容器:vector,list,deque,map,multimap,set,multiset
34.你如何理解MVC。简单举例来说明其应用。
MVC模式是observer 模式的一个特例,现在很多都是java的一些框架,MFC的,PHP的。
35.C++特点是什么,多态实现机制?(面试问过)多态作用?两个必要条件?
C++中多态机制主要体现在两个方面,一个是函数的重载,一个是接口的重写。接口多态指的是“一个接口多种形态”。每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。
多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数,友元函数等等
作用:
隐藏实现细节,代码能够模块化;2. 接口重用:为了类在继承和派生的时候正确调用。
必要条件:
一个基类的指针或者引用指向派生类的对象;2.虚函数
36. 多重继承有什么问题? 怎样消除多重继承中的二义性?
1)增加程序的复杂度,使程序的编写和维护比较困难,容易出错;
2)继承类和基类的同名函数产生了二义性,同名函数不知道调用基类还是继承类,C++中使用虚函数解决这个问题
3)继承过程中可能会继承一些不必要的数据,对于多级继承,可能会产生数据很长
可以使用成员限定符和虚函数解决多重继承中函数的二义性问题。
37.求两个数的乘积和商数,该作用由宏定义来实现
#define product(a,b) ((a)*(b))
#define divide(a,b) ((a)/(b))
38.什么叫静态关联,什么叫动态关联
多态中,静态关联是程序在编译阶段就能确定实际执行动作,程序运行才能确定叫动态关联
39.什么叫智能指针?常用的智能指针有哪些?智能指针的实现?
智能指针是一个存储指向动态分配(堆)对象指针的类,构造函数传入普通指针,析构函数释放指针。栈上分配,函数或程序结束自动释放,防止内存泄露。使用引用计数器,类与指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建,增加引用计数;对一个对象进行赋值时,减少引用计数,并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数,当引用计数减至0,则删除基础对象。
std::auto_ptr,不支持复制(拷贝构造函数)和赋值(operator =),编译不会提示出错。
C++11引入的unique_ptr, 也不支持复制和赋值,但比auto_ptr好,直接赋值会编译出错。
C++11或boost的shared_ptr,基于引用计数的智能指针。可随意赋值,直到内存的引用计数为0的时候这个内存会被释放。还有Weak_ptr
40.枚举与#define 宏的区别
1)#define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
2)可以调试枚举常量,但是不能调试宏常量。
3)枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。
41.介绍一下函数的重载
重载是在不同类型上作不同运算而又用同样的名字的函数。重载函数至少在参数个数,参数类型, 或参数顺序上有所不同。
42.派生新类的过程要经历三个步骤
1.吸收基类成员 2.改造基类成员 3.添加新成员
43.面向对象的三个基本特征,并简单叙述之?
1)封装:将客观事物抽象成类,每个类对自身的数据和方法实行2)继承3)多态:允许一个基类的指针或引用指向一个派生类对象
44.多态性体现都有哪些?动态绑定怎么实现?
多态性是一个接口,多种实现,是面向对象的核心。 编译时多态性:通过重载函数实现。运行时多态性:通过虚函数实现,结合动态绑定。
45.虚函数,虚函数表里面内存如何分配?
编译时若基类中有虚函数,编译器为该的类创建一个一维数组的虚表,存放是每个虚函数的地址。基类和派生类都包含虚函数时,这两个类都建立一个虚表。构造函数中进行虚表的创建和虚表指针的初始化。在构造子类对象时,要先调用父类的构造函数,初始化父类对象的虚表指针,该虚表指针指向父类的虚表。执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。每一个类都有虚表。虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。当用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。当涉及到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。
46. 纯虚函数如何定义?含有纯虚函数的类称为什么?为什么析构函数要定义成虚函数?
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。纯虚函数是虚函数再加上= 0。virtual void fun ()=0。含有纯虚函数的类称为抽象类在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。如果析构函数不是虚函数,那么释放内存时候,编译器会使用静态联编,认为p就是一个基类指针,调用基类析构函数,这样子类对象的内存没有释放,造成内存泄漏。定义成虚函数以后,就会动态联编,先调用子类析构函数,再基类。
47. C++中哪些不能是虚函数?
1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有。
48. 类型转换有哪些?各适用什么环境?dynamic_cast转换失败时,会出现什么情况(对指针,返回NULL.对引用,抛出bad_cast异常)?
静态类型转换,static_cast,基本类型之间和具有继承关系的类型。
例子A,double类型转换成int。B,将子类对象转换成基类对象。
常量类型转换,const_cast, 去除指针变量的常量属性。
无法将非指针的常量转换为普通变量。
动态类型转换,dynamic_cast,运行时进行转换分析的,并非在编译时进行。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast用于类层次间的向上转换和向下转换,还可以用于类间的交叉转换。在类层次间进行向上转换,即子类转换为父类,此时完成的功能和static_cast是相同的,因为编译器默认向上转换总是安全的。向下转换时,dynamic_cast具有类型检查的功能,更加安全。类间的交叉转换指的是子类的多个父类之间指针或引用的转换。该函数只能在继承类对象的指针之间或引用之间进行类型转换,或者有虚函数的类。
49. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的?
#ifdef __cplusplus
cout<<“C++”;
#else
cout<<“c”;
#endif
50. 为什么要用static_cast转换而不用c语言中的转换?
Static_cast转换,它会检查类型看是否能转换,有类型安全检查。
比如,这个在C++中合法,但是确实错误的。
A* a= new A;
B* b = (B*)a;
51. 操作符重载(+操作符),具体如何去定义?
除了类属关系运算符”.”、成员指针运算符”.*”、作用域运算符”::”、sizeof运算符和三目运算符”?:”以外,C++中的所有运算符都可以重载。
<返回类型说明符> operator <运算符符号>(<参数表>){}
重载为类的成员函数和重载为类的非成员函数。参数个数会不同,应为this指针。
52. 内存对齐的原则?
A.结构体的大小为最大成员的整数倍。
B.成员首地址的偏移量为其类型大小整数倍。
53. 内联函数与宏定义的区别?
内联函数是用来消除函数调用时的时间开销。频繁被调用的短小函数非常受益。
A. 宏定义不检查函数参数,返回值什么的,只是展开,相对来说,内联函数会检查参数类型,所以更安全。
B. 宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的
54. 动态分配对象和静态分配对象的区别?
动态分配就是用运算符new来创建一个类的对象,在堆上分配内存。
静态分配就是A a;这样来由编译器来创建一个对象,在栈上分配内存。
55. explicit是干什么用的 ?
构造器 ,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。explicit是用来防止外部非正规的拷贝构造的,要想不存在传值的隐式转换问题。
56. 内存溢出有那些因素?
(1) 使用非类型安全(non-type-safe)的语言如 C/C++ 等。
(2) 以不可靠的方式存取或者复制内存缓冲区。
(3) 编译器设置的内存缓冲区太靠近关键数据结构。
57. new与malloc的区别,delete和free的区别?
1.malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符
2.new能够自动分配空间大小,malloc传入参数。
3. new/delete能进行对对象进行构造和析构函数的调用进而对内存进行更加详细的工作,而malloc/free不能。
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
58. 必须使用初始化列表初始化数据成员的情况
1.是对象的情况;
2.const修饰的类成员;
3.引用成员数据;
类成员变量的初始化不是按照初始化表顺序被初始化,是按照在类中声明的顺序被初始化的。
59.深入谈谈堆和栈
1).分配和管理方式不同 :
堆是动态分配的,其空间的分配和释放都由程序员控制。
栈由编译器自动管理。栈有两种分配方式:静态分配和动态分配。静态分配由编译器完成,比如局部变量的分配。动态分配由alloca()函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无须手工控制。
2).产生碎片不同
对堆来说,频繁的new/delete或者malloc/free势必会造成内存空间的不连续,造成大量的碎片,使程序效率降低。
对栈而言,则不存在碎片问题,因为栈是先进后出的队列,永远不可能有一个内存块从栈中间弹出。
3).生长方向不同
堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长。
栈是向着内存地址减小的方向增长,由内存的高地址向低地址方向增长。
60.内存的静态分配和动态分配的区别?
时间不同。静态分配发生在程序编译和连接时。动态分配则发生在程序调入和执行时。
空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。alloca,可以从栈里动态分配内存,不用担心内存泄露问题,当函数返回时,通过alloca申请的内存就会被自动释放掉。
61. 模版怎么实现?模版作用?
实现:template void swap(T& a, T& b){}
作用:将算法与具体对象分离,与类型无关,通用,节省精力
62. 多重类构造和析构的顺序
记住析构函数的调用顺序与构造函数是相反的。
63. 迭代器删除元素的会发生什么?
迭代器失效
64. 静态成员函数和数据成员有什么意义?
1)非静态数据成员,每个对象都有自己的拷贝。而静态数据成员被当作是类的成员,是该类的所有对象所共有的,在程序中只分配一次内存只有一份拷贝,所以对象都共享,值对每个对象都是一样的,它的值可以更新。
2)静态数据成员存储在全局数据区,所以不能在类声明中定义,应该在类外定义。由于它不属于特定的类对象,在没有产生类对象时作用域就可见,即在没有产生类的实例时,我们就可以操作它。
3)静态成员函数与静态数据成员一样,都是在类的内部实现,属于类定义的一部分。因为普通成员函数总是具体的属于具体对象的,每个有this指针。静态成员函数没有this指针,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数。静态成员之间可以互相访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
4)非静态成员函数可以任意地访问静态成员函数和静态数据成员;
5)没有this指针的额外开销,静态成员函数与类的全局函数相比,速度上会有少许的增长;
6)调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指调用静态成员函数。
66请用C/C++实现字符串反转(不调用库函数)”abc”类型的
char *reverse_str(char *str) {
if(NULL == str) { //字符串为空直接返回
return str;
}
char *begin;
char *end;
begin = end = str;
while(*end != '\0') { //end指向字符串的末尾
end++;
}
--end;
char temp;
while(begin < end) { //交换两个字符
temp = *begin;
*begin = *end;
*end = temp;
begin++;
end--;
}
return str; //返回结果
}
67.写一个函数,将字符串翻转,翻转方式如下:“I am a student”反转成“student a am I”,不借助任何库函数
1 #include "stdio.h"
2 #include <iostream>
3 using namespace std;
4
5 void revesal(char * start, char* end){
6 char *temp_s = start;
7 char *temp_e = end;
8 while(temp_s < temp_e){
9 char temp= *temp_s;
10 *temp_s= *temp_e;
11 *temp_e = temp;
12 ++temp_s;
13 --temp_e;
14 }
15 return;
16 }
17
18 void revesal_str(char *str){
19 if(str == NULL){
20 return;
21 }
22
23 char *start = str;
24 char *end = str;
25
26 while(*++end !='\0');
27 revesal(start, end-1);
28 cout << str << endl;
29 char *sub_start = str;
30 while(start < end + 1 ){
31 if(*start == ' ' || *start == '\0'){
32 char *temp = start - 1;
33 revesal(sub_start,temp);
34 while(*++start ==' ');
35 sub_start = start;
36 continue;
37 }
38 ++start;
39 }
40 }
68.析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?
C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。
1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
69. 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?
一般如果构造函数中存在动态内存分配,则必须定义拷贝构造函数。否则,可能会导致两个对象成员指向同一地址,出现“指针悬挂问题”。
70. 100万个32位整数,如何最快找到中位数。能保证每个数是唯一的,如何实现O(N)算法?
1).内存足够时:快排
2).内存不足时:分桶法:化大为小,把所有数划分到各个小区间,把每个数映射到对应的区间里,对每个区间中数的个数进行计数,数一遍各个区间,看看中位数落在哪个区间,若够小,使用基于内存的算法,否则 继续划分
71. OFFSETOF(s, m)的宏定义,s是结构类型,m是s的成员,求m在s中的偏移量。
#define OFFSETOF(s, m) size_t(&((s*)0)->m)
72. C++虚函数是如何实现的?
使用虚函数表。 C++对象使用虚表, 如果是基类的实例,对应位置存放的是基类的函数指针;如果是继承类,对应位置存放的是继承类的函数指针(如果在继承类有实现)。所以 ,当使用基类指针调用对象方法时,也会根据具体的实例,调用到继承类的方法。
73. C++的虚函数有什么作用?
虚函数作用是实现多态,虚函数其实是实现封装,使得使用者不需要关心实现的细节。在很多设计模式中都是这样用法,例如Factory、Bridge、Strategy模式。
74.MFC中CString是类型安全类吗,为什么?
不是,其他数据类型转换到CString可以使用CString的成员函数Format来转换
74.动态链接库的两种使用方法及特点?
1).载入时动态链接,模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2)运行时动态链接。