- 1. 指针和引用的区别
- 2. hash表的实现
- 3. C++控制内存泄漏的方式
- 4. 为什么不要在构造函数或析构函数中调用虚函数
- 5. 动态绑定怎么实现
- 6.浅拷贝和深拷贝
- 7. 在什么情况下系统会调用拷贝构造函数
- 8. 为什么要进行内存对齐
- 9. 调用约定
- 10. RTTI(Run-Time Type Information)
- 11. 定位new
- 12.如何定义一个只能在堆上或者栈上生成对象的类?
1. 指针和引用的区别
-
引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。
-
可以有const指针,但是没有const引用;
-
指针可以有多级,但是引用只能是一级(
int ** p
;合法 而int && a
是不合法的) -
指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
-
指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
-
”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;
-
指针和引用的自增(++)运算意义不一样;
2. hash表的实现
采用了开链法实现哈希表,其中每个哈希节点有数据和next指针
template<class _Val>
struct _Hashtable_node
{
_Hashtable_node* _M_next;
_Val _M_val;
};
可以实现一个简化的哈希表,创建时需要指定大小,插入时用特定的哈希函数计算哈希值,如果位置没有元素,直接放入,有元素使用链表头插入。
对于这个问题,之后有时间需要补充一个哈希表的实现
3. C++控制内存泄漏的方式
- 监控new,重载new,日志
- 智能指针
- 内存泄漏检测工具(Valgrind)
- 定期检查内存结果,查看是否有大量相同内存
4. 为什么不要在构造函数或析构函数中调用虚函数
见effective C++ 第9条,派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。 同样,进入基类析构函数时,对象也是基类类型。在子类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的。
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
Fuction();
}
virtual void Fuction()
{
cout << "Base::Fuction" << endl;
}
};
class A : public Base
{
public:
A()
{
Fuction();
}
virtual void Fuction()
{
cout << "A::Fuction" << endl;
}
};
int main()
{
A a;
}
输出结果:
Base::Fuction
A::Fuction
可以看到子类在构造父类的时候调用的是父类的虚函数。
5. 动态绑定怎么实现
有一个基类,两个派生类,基类有一个virtual函数,两个派生类都覆盖了这个虚函数。现在有一个基类的指针或者引用,当该基类指针或者引用指向不同的派生类对象时,调用该虚函数,那么最终调用的是该被指向对象对应的派生类自己实现的虚函数。
C++中,通过基类的引用或指针调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。
6.浅拷贝和深拷贝
在对象拷贝过程中,如果没有自定义拷贝构造函数,系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。
当对象中含有指针时,需要重新定义拷贝构造函数进行深拷贝,否则两个对象的指针指向同一块空间,容易产生bug。
7. 在什么情况下系统会调用拷贝构造函数
-
用类的一个对象去初始化另一个对象时
-
当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
-
当函数的返回值是类的对象或引用时
8. 为什么要进行内存对齐
-
平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
-
性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐,原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
9. 调用约定
- 调用协议常用场合
__stdcall:Windows API默认的函数调用协议。
__cdecl:C/C++默认的函数调用协议。
__fastcall:适用于对性能要求较高的场合。 - 函数参数入栈方式
__stdcall:函数参数由右向左入栈。
__cdecl:函数参数由右向左入栈。
__fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。
__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。 - 栈内数据清除方式
__stdcall:函数调用结束后由被调用函数清除栈内数据。
__cdecl:函数调用结束后由函数调用者清除栈内数据。
__fastcall:函数调用结束后由被调用函数清除栈内数据。
10. RTTI(Run-Time Type Information)
RTTI提供了以下两个非常有用的操作符:
- typeid操作符,返回指针和引用所指的实际类型。
- dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。
11. 定位new
定位 new(placement new)允许我们向 new 传递额外的参数。
new (palce_address) type
new (palce_address) type (initializers)
new (palce_address) type [size]
new (palce_address) type [size] { braced initializer list }
palce_address
是个指针initializers
提供一个(可能为空的)以逗号分隔的初始值列表 以上代码的作用是,在palce_address所指向的内存区域上新建一个类type的对象,用initializers初始化该对象。在这个过程中,palce_address所指向的内存区域的内容改变了,并且palce_address指向了新参数的对象。12.如何定义一个只能在堆上或者栈上生成对象的类?
- 限制在堆上
方法:将析构函数设置为私有
原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。
-
限制在栈上
方法:将 new 和 delete 重载为私有
原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。