C和C++语言基础
参考书籍:《C++ primer》,《effective C++》,《STL源码解析》,《深度搜索C++对象模型》
-
- extern声明变量在在外部定义?
- extern修饰函数?
- extern C的作用?用法?
-
- static修饰局部变量?
- static全局变量?(限定变量在一个编译单元内,一个编译单元就是指一个cpp和它包含的头文件,这个回答可以结合编译需要经历的几个过程来答)
- static修饰普通函数?
- static修饰成员变量?
- static修饰成员函数?
volatile是干啥的
- 访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。
- 一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。
说说const的作用,越多越好
- const修饰全局变量;
- const修饰局部变量;
- const修饰指针,const int *;
- const修饰指针指向的对象, int * const;
- const修饰引用做形参;
- const修饰成员变量,必须在构造函数列表中初始化;
- const修饰成员函数,说明该函数不应该修改非静态成员,但是这并不是十分可靠的,指针所指的非成员对象值可能会被改变
-
- new分配内存按照数据类型进行分配,malloc分配内存按照大小分配;
- new不仅分配一段内存,而且会调用构造函数,但是malloc则不会。new的实现原理?但是还需要注意的是,之前看到过一个题说int* p = new int与int* p = new int()的区别,因为int属于C++内置对象,不会默认初始化,必须显示调用默认构造函数,但是对于自定义对象都会默认调用构造函数初始化。翻阅资料后,在C++11中两者没有区别了,自己测试的结构也都是为0;
- new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化;
- new是一个操作符可以重载,malloc是一个库函数;
- new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会;
- malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作;
- new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。因此对于new,正确的姿势是采用try…catch语法,而malloc则应该判断指针的返回值。为了兼容很多c程序员的习惯,C++也可以采用new nothrow的方法禁止抛出异常而返回NULL;
- new和new[]的区别,new[]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[],同理,delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n;
- 如果不够可以继续谈new和malloc的实现,空闲链表,分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理,free为什么直到销毁多大的空间?
-
- C++多态的实现?
多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。
动态多态实现有几个条件:
(1) 虚函数;
(2) 一个基类的指针或引用指向派生类的对象;
基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。
每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。
虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。 - 虚函数的作用?
- 虚函数用于实现多态,这点大家都能答上来
但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。
动态绑定是如何实现的?
第一个问题中基本回答了,主要都是结合虚函数表来答就行。静态多态和动态多态。静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。
- 虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?
- 编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。
- C++多态的实现?
纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数
为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。
//纯虚函数定义
virtual ~myClass() = 0;
- 析构函数能抛出异常吗
答案肯定是不能。
C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。
(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
(2) 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
构造函数和析构函数中调用虚函数吗?
指针和引用的区别
- 指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问;
- 指针可以改变地址,从而改变所指的对象,而引用必须从一而终;
- 引用在定义的时候必须初始化,而指针则不需要;
- 指针有指向常量的指针和指针常量,而引用没有常量引用;
- 指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。
指针与数组千丝万缕的联系
- 一个一维int数组的数组名实际上是一个int* const 类型;
- 一个二维int数组的数组名实际上是一个int (*const p)[n];
- 数组名做参数会退化为指针,除了sizeof
-
- 构造函数中计数初始化为1;
- 拷贝构造函数中计数值加1;
- 赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
- 析构函数中引用计数减一;
- 在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。
- share_prt与weak_ptr的区别?
//share_ptr可能出现循环引用,从而导致内存泄露
class A
{
public:
share_ptr<B> p;
};
class B
{
public:
share_ptr<A> p;
}
int main()
{
while(true)
{
share_prt<A> pa(new A()); //pa的引用计数初始化为1
share_prt<B> pb(new B()); //pb的引用计数初始化为1
pa->p = pb; //pb的引用计数变为2
pb->p = pa; //pa的引用计数变为2
}
//假设pa先离开,引用计数减一变为1,不为0因此不会调用class A的析构函数,因此其成员p也不会被析构,pb的引用计数仍然为2;
//同理pb离开的时候,引用计数也