1.C++中什么数据分配在栈或堆,静态存储区以及常量存储区中?
堆区是动态分配的数据 通过malloc及new来申请动态分配存储
栈区是存储系统自动分配空间的变量, 如局部变量
静态存储区存放全局变量和静态变量, 未被初始化或初始化为0的存放在bss段, 非0全局变量的存在数据段的数据区 . 程序结束后有系统释放
常量存储区存放常量字符串就是放在这里的。 程序结束后由系统释放
程序代码区—存放函数体的二进制代码
2.C++编译器自动为类产生的四个缺省函数是什么?
默认构造 , 拷贝构造 ,析构 , =运算符函数, &运算符函数
3.我们可以用static修饰一个类的成员函数,也可以用const修饰类的而成员函数请问:能不能同时用const和static修饰类的成员函数? 并进行说明.
不可以 const修饰的成员函数, 为了保证函数不能改变成员属性, 添加隐藏参数 const this * , 但static修饰的成员函数, 没有this指针, 此时const 和static 的用法是冲突的.
4.在C++中,构造函数是没有返回值的,那么该如何处理构造函数中可能发生的错误(例如资源分配失败)?
异常或者assert断言
5.请简述C/C++语言中栈空间和堆空间的主要区别.
栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区(heap)一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
区别主要体现在下面几个方面
1、内存分配方面:
堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式是类似于链表。可能用到的关键字如下:new、malloc、delete、free等等。
栈:由编译器(Compiler)自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、申请方式方面:
堆:需要程序员自己申请,并指明大小。在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符,但是注意p1、p2本身是在栈中的。因为他们还是可以认为是局部变量。
栈:由系统自动分配。 例如,声明在函数中一个局部变量 int b;系统自动在栈中为b开辟空间。
3、系统响应方面:
堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
4、大小限制方面:
堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
栈:在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
5、效率方面:
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
栈:由系统自动分配,速度较快。但程序员是无法控制的。
6、存放内容方面:
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
栈:在函数调用时第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈,然后是函数中的局部变量。 注意: 静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
7、存取效率方面:
堆:char *s1 = “Hellow Word”;是在编译时就确定的;
栈:char s1[] = “Hellow Word”; 是在运行时赋值的;用数组比用指针速度要快一些,因为指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上直接读取。
6.C++处理异常可以有两种方式,一种是throw异常,一种是在函数中return 错误码.你怎么理解这两种方法的优劣.
throw 异常要优于return错误码
throw异常就像中断机制, 对异常能够单独操作 , 确保程序的稳定性
return 错误码, 通过错误码进行判断 , 根据错误码处理错误.
throw是专门处理异常的, 代码写起来, 结构比较容易. return 错误码, 异常处理部分需要自己写, 结构更复杂.
7.为什么析构函数建议加virtual属性
delete一个指向基类的指针时,该基类指针指向派生类实例,如果基类的析构函数不加virtual属性,会导致无法调用派生类的析构函数,会造成内存泄漏
8.拷贝构造函数的形参形式是什么样子的?为什么采用这种形式?什么情况下会被调用?
1)函数名为类名,参数为类对象的引用.
2)拷贝构造的实参是对象,那么形参可能是对象或对象的引用,形参如果是对象,那么在调用函数时要先对形参进行初始化又是一次拷贝构造,从而陷入死循环;那么形参应该是对象的引用。拷贝构造会通过类对象的引用将对象的属性值赋值给新对象, 因此使用引用
3)当一个对象通过另一个对象初始化时, 调用函数形参是该类对象时 , 返回类对象产生临时变量时。
9.在C++的语法中有友元函数friend(一个类的成员函数可以作为另一个类的友元,前者可以访问后者对象的私有成员)。但后期的高级语言java和C#
却没有这方面的语法支持,为什么要去掉友元函数,谈谈你的理解。
虽然友元函数能够提高效率,表达简单、清晰,但是友元函数破环了封装机制,除非不得已的情况下才使用友元函数
10.简单说明面向对象的特性及重要性。
面向对象的特性:封装,继承,多态
继承: 派生类可以从它的基类那里继承方法和属性 , 提高代码复用性
封装: 把过程和数据包围起来, 保证了模块具有较好的独立性,使得程序维护修改较为容易.
多态: 多态性是指允许不同类的对象对同一消息作出响应, 多态性语言具有灵活、抽象、行为共享、代码共享的优势
11.(1)指针和引用的区别是什么
(2)malloc/free & new/delete的区别是什么
(3)struct 与 class 的区别是什么
1)1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(3)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化。
(4)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(5)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;
/*引用:必须初始化, 不占空间, 唯一 不可改变指向 , 无空引用
指针:32位系统指针占用4个字节, 初始化不是必须, 可以改变指向, 有空指针
*/
2)malloc free 是标准库函数 new delete 是关键字 new delete 会调用构造和析构 malloc加空间大小 , new 加类型
3)C++中 struct 与class 的区别只在于成员默认属性
12.虚函数的理解
(1)C++中虚函数的实现机制
(2)对一个包含虚函数成员的对象bzero()会有什么问题
1)每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针,包括新声明的虚函数和重写的虚函数。含有虚函数的类的对象,系统都会为其分配一个虚指针指向这张虚函数表。通过父类声明虚函数,然后子类对虚函数进行重写,当父类指针指向子类实例时,父类指针调用虚函数,查找虚指针指向的虚函数表中的虚函数地址,并对虚函数进行调用,从而实现多态。
2)bzero() 将字符串的前n个字节置0 , 那么会将对象的虚指针清空, 无法找到虚表, 进而影响虚函数使用.
13.请简单描述数组(vector)与链表(list)的区别以及应用场景。
数组的特点是相同类型数据在连续空间中存储, 便于访问查询,不利于插入删除操作
链表的特点是在非连续的空间存储数据, 数据之间通过指针连接, 可以任意添加长度,便于插入删除操作, 不利于访问查询
14.C++语言中的函数参数传递方式有哪几种?从功能,效率,安全,灵活性等方面比较一下。
值传递, 地址传递 引用传递 值传递是实参表达式的值传递给形参, 形参的变化并不会影响到实参,地址传递方式虽然可以使得形参的改变对相应的实参有效,但如果在函数中反复利用指针进行间接访问,会使程序容易产生错误且难以阅读。如果以引用为参数,则既可以使得对形参的任何操作都能改变相应的数据,又使得函数调用显得方便、自然。
15.描述观察者模式及其实现过程,并举例说明你是怎么使用观察者模式的
观察者模式又称订阅-发布模式, 定义了一种一对多的依赖关系, 让多个观察者对象同时监听某一个主体对象, 在这个主体对象在发生变化的时候, 会通知所有观察者对象, 使他们能自动更新自己
实现过程: 首先声明一个抽象主体类, 一个抽象观察者类 , 抽象主体类提供接口可以添加和删除观察者, 并提供通知的接口方法, 抽象观察者有更新的接口方法. 然后声明具体主体和具体观察者, 具体主体实现添加观察者和删除观察者以及给所有登记的观察者发出通知, 具体观察者在接受通知后更新自己.
当一个对象的改变需要同时改变其他对象的时候, 而且还不知道有多少对象有待改变的时候,应该考虑使用观察者。
16、static 局部变量和普通局部变量有什么区别?
static 局部变量的生命周期是程序结束, 因为他在静态数据区 , 普通局部变量的生命周期是到下面第一个}结束 , 普通局部变量存放在栈区;static局部变量只会执行一次初始化,会保存上一次执行之后的结果;普通局部变量是在每一次生命周期结束后重新初始化,并不能保存上一次执行后的值
17、友元函数与成员函数的区别是什么?
成员函数是类定义的一部分,可以隐式访问调用类的成员。友元函数不是类的组成部分,友元函数不能直接访问类成员, 要通过对象来调用。友元函数不能被继承。
18、在C++程序中调用被C编译器编译后的函数,为什么要加extern c ?
extern c的含义是 用c规则编译C++代码
19.面向对象的三个基本特征,请简单描述。
封装 继承 多态
继承: 派生类可以从它的基类那里继承方法和属性 , 提高代码复用性
封装: 把过程和数据包围起来, 保证了模块具有较好的独立性,使得程序维护修改较为容易.
多态: 多态性是指允许不同类的对象对同一消息作出响应, 多态性语言具有灵活、抽象、行为共享、代码共享的优势
20.简述面向对象及面向过程的定义并回答为何要在软件工程中采用面向对象的思想。
面向对象是把构成问题的事务分解成各个对象所发生的行为。
面向过程是分析解决问题的步骤,然后用函数把这些步骤一步一步的实现。
面向对象保证了功能的统一型,从而为扩展打下基础。同时面向对象能够提高代码的复用性,安全性,和可维护性。
21.请简述深拷贝和浅拷贝的定义。
浅拷贝: 在对象复制时,只是对对象中的数据成员进行简单的赋值,如果对象中存在动态成员,即指针,浅拷贝并不会为其申请新的空间, 而是共用同一个空间
深拷贝:对于深拷贝,针对成员变量存在指针的情况,不仅仅是简单的指针赋值,而是重新分配内存空间。
22.请简述static, const, inline 的定义及用法。
Static用于声明静态变量和静态函数,类中只有一份共用,const用来声明常变量和常函数,用于设置成员属性为不可改变,inline是内联函数的关键字,用于声明内联函数,编译时在函数调用处展开,用于代码少,调用频繁的函数。
23.简述多态作用及应用方式?
多态的作用是试图使用不变的代码来完成可变的算法
多态分为运行时多态和编译时多态。
其中编译时多态采取重载来实现;运行时多态通过虚函数和继承来实现,具体过程是父类指针指向子类实例,子类重写父类虚函数,通过父类指针调用虚函数,完成调用子类中实现的功能。
24.为什么类的静态成员函数不能直接访问类得非静态成员变量?
静态成员函数先于对象存在, 存在不依赖于对象, 而非静态成员变量后于对象存在, 存在依赖对象, 在没有对象的情况下, 存在静态成员函数, 而没有非静态成员变量, 因此不能直接访问
25.c++中,什么函数不能声明为虚函数?为什么?
构造函数, 静态成员函数 , 内联函数 , 非类的成员函数, 友元函数
构造函数: 构造函数无法被继承,同时无法在子类重写父类构造函数
静态成员函数: 静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码, 只属于该类, 并且没有声明为虚函数的必要 ,相当于访问受限的全局函数,不能声明为虚函数.
内联函数: inline函数在编译时被展开,虚函数在运行时才能动态的绑定函数,内联函数是编译时机制,虚函数时运行时机制。
友元函数: C++不支持友元函数的继承
非类的成员函数: 只能被重载, 不能被重写
26.C++中虚函数如何定义,使用时应该注意什么?
在成员函数前添加virtual关键字, 使用时 注意构造函数, 静态成员函数 , 内联函数 , 非类的成员函数, 友元函数不可以 申请为虚函数.
27.C++拷贝构造函数和赋值运算符有那些不同和相同点?
形参都是类对象的引用, 都是利用已存在对象对另一个对象赋值, 不同在于 一个使用在初始化的情况下, 另一个在赋值的情况下
28.评价多继承的优点和缺点。
派生类同时得到了多个已有类的特征, 体现在代码上可以调用多个基类中的接口
容易出现继承向上的二义性,会造成冗余和浪费
29.什么是虚指针?
用virtual关键字声明的函数叫做虚函数。
存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。
30.虚函数的入口地址和普通函数有什么不同?
每个虚函数都在虚表中占了一个表项,保存虚函数的入口地址。当一个包含虚函数的对象被创建的时候,它在头部附加了一个指针,指向虚表中相应的位置。
调用虚函数的时候,会根据虚指针找到虚表,再根据虚表找到入口地址再执行,从而实现了“动态联编”。
普通函数只是简单的跳到一个固定的地址。
31.1、C++中如何阻止一个类被实例化?
构造私有并且不提供创建对象的公有方法 , 或声明纯虚函数
2、一般在什么时候构造函数被声明成private呢?
阻止一个类被实例化,如单例模式
3、什么时候编译器会生成默认的 copy constructor呢?
在未定义拷贝构造函数, 而使用类的已有对象初始化对象时
4、如果你已经写了一个拷贝构造函数,编译器还会生成copy constructor?不会
32.struct是否可以拥有constructor/destructor以及成员函数?
如果可以那么struct和class有什么区别.
在C中不可以 C++中可以 区别在于默认成员访问属性
33.析构函数可以是内联函数吗?
可以
析构函数定义为内联函数,和普通函数定义为内联函数好处一样,都是建议编译器做内联,提高代码执行速度. 如果是做基类的话,析构必须定义为虚函数. 此时定义为内联会报错, 因为虚函数不可以是内联函数.
34.编写string的构造函数,析构函数,和赋值函数。
class CMyString
{
private:
char* m_pStr;
public:
CMyString()
{
m_pStr = new char[2];
}
CMyString(char* pStr)
{
this->m_pStr = new char[strlen(pStr)+1];
strcpy_s(this->m_pStr,strlen(pStr)+1,pStr);
}
CMyString(const CMyString& str) // 拷贝构造
{
this->m_pStr = new char[strlen(str.m_pStr)+1];
strcpy_s(this->m_pStr,strlen(str.m_pStr)+1,str.m_pStr);
}
~CMyString()
{
if(this->m_pStr){
delete[] this->m_pStr;
this->m_pStr = NULL;
}
}
public:
CMyString& operator=(const char* pStr)
{
delete[] this->m_pStr; // 删除原有的空间
this->m_pStr = new char[strlen(pStr)+1]; // 重新分配空间
strcpy_s(this->m_pStr,strlen(pStr)+1,pStr);
return *this;
}
CMyString& operator=(const CMyString& str)
{
if(&str == this) // 是否是同一个对象
return *this;
delete[] this->m_pStr; // 删除原有的空间
this->m_pStr = new char[strlen(str.m_pStr)+1];
strcpy_s(this->m_pStr,strlen(str.m_pStr)+1,str.m_pStr);
return *this;
}
}
35.什么是多态?
解:多态性是面向对象方法的重要特征。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。
36.重载和覆盖有什么不同?
重载可以发生在普通函数和成员函数 , 是函数名相同形参列表不同,可以是多个函数之间的关系
覆盖是虚函数重写, 只能是成员函数,是基类与派生类同名函数之间的关系
37.what is v-table and v-ptr in C++? what is the relationship between them?
参考前面内容
38.C和C++中如何让动态分配合释放内存?他们的区别是什么?
malloc free new delete 参考前面内容
39.为什么类的静态成员函数不能直接访问类的非静态成员变量?
参考前面内容
40.C和C++的struct有什么不同
1)C中struct不可以有函数 2)C中没有构造析构 this指针
3)c中内部成员的访问权限只有public c++中有三种
4)c不可以继承 C++可以
41.为了实现C++中的多态性,编译器做了哪些事情?也就是说C++语言内部怎么实现多态?
上面提到
42.函数前的static和volatile变量中关键字作用
static变量,生命周期是整个程序结束,只在声明变量的位置可见,函数前的static是静态函数,调用时不依赖于对象,一个类只有一份共用。
volatile是一个类型修饰符关键字,作用:1.确保本条指令不会因编译器的优化而被省略,且要求每次直接读值(访问内存地址的值而非寄存器的值).
43.使用C++赋值运算符应注意什么地方?
(考察重写赋值运算符的注意事项)
返回类型必须为该类型的引用
传入参数声明为引用
释放实例自身已有的类型,防止内存泄露;
考虑当因内存不足在new char时候抛出异常
44.请用你熟悉的开发语言实现一个单例模型Singleton(单例模型:即只有一个对象的类)
class Singleton{
public:
static Singleton* getInstance();
private:
Singleton(){};
//把构造 拷贝构造函数设为私有,防止被复制
Singleton(const Singleton&){};
static Singleton* instance;
~ Singleton(){}
};
Singleton* Singleton::instance = new Singleton;
Singleton* getInstance()
{
return instance;
}
45.用C++语言实现单例模式(Singleton)时,如何确保调用者无法通过正常手段创建出第二个对象?
把构造 拷贝构造函数设为私有,防止被复制
同时获取实例的函数判断没有时生成对象并返回, 存在时, 返回对象
46.简述C++语言中的dynamic_cast相对于C语言中强制类型转换的必要性.
1)用在多态的子类情况下,父类不能提供处理接口,这时可以针对子类做特殊的处理。使用dynamic_cast进行转化会根据类型进行检验, 如果是父类的指针指向子类对象, 并转化为子类指针, 那么返回一个子类对象的地址, 否则返回空
2)dynamic_cast比另外3个cast优势就是会对转换进行检查,如果出错,会报错。