面试时候被问到的问题,统一整理
10/20 更新
-
空类和空结构体的大小为什么为1?
C++中空类和空结构体可以实例化。C++标准中规定,任何不同的对象不能拥有相同的内存地址。所以,C++在编译器在空类和空结构体中插入一个字节,用于存储空类或空结构体实例化后的地址。 -
空类默认有哪些成员函数?
构造函数、析构函数、复制/拷贝函数、赋值函数。 -
构造函数的调用顺序?
(1)基类构造函数
(2)按声明顺序初始化数据成员。
(3)调用自身构造函数。 -
构造函数和析构函数是否可以是虚函数?为什么?
构造函数不能是虚函数。
(1)虚函数的调用是通过虚函数vtable表来查找。而虚函数表由vptr指针指向,该指针存放在对象的内部空间中,需要调用构造函数完成初始化。如果构造函数是虚函数,那么调用构造函数就需要去找vptr,但此时vptr还没有初始化。
(2)虚函数主要是实现多态,在运行时才可以明确调用对象,根据传入的对象类型来调用函数,例如通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用。那使用虚函数也没有实际意义。在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,没有必要成为虚函数。
析构函数可以是虚函数
(1)析构函数被调用时, vtable 已经初始化了,完全可以把析构函数放在虚函数表里面来调用。
(2)C++类有继承时,析构函数必须为虚函数。如果不是虚函数,则使用时可能存在内存泄漏的问题。基类的析构函数不是虚函数的话,删除指针时,只有基类的内存被释放,派生类的没有。这样就内存泄漏了。 -
指针和引用的区别
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;
引用:跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化。
(3)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(4)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(5)指针和引用的自增(++)运算意义不一样。
(6)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的) -
strlen 和sizeof的区别?
(1)strlen是用于计算字符串的长度,遇到第一个’\0’为止;sizeof用于计算字符分配的字节大小。
char str[20] = "hello";
printf("strlen: %d\n", strlen(str));
printf("sizeof: %d\n", sizeof(str));
//结果:
strlen: 5
sizeof: 20
(2)strlen是函数,sizeof是运算符。
(3)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以’’\0’'结尾的。
-
解决hash冲突的3个方法
(1)开放地址法
当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。
线性探测再散列
dii=1,2,3,…,m-1
这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。
二次探测再散列
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
伪随机探测再散列
di=伪随机数序列。
(2)再哈希法
这种方法是同时构造多个不同的哈希函数:
Hi=RH1(key) i=1,2,…,k
当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。
(3)链地址法
这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。 -
静态成员变量与静态成员函数
静态成员变量被所有的同类对象共享,不具体作用在某个对象上。不由类的构造函数初始化,一般在类外定义且初始化,常量静态成员可以在类内初始化。
静态成员变量的访问:
(1)使用类作用域直接访问
类名::静态成员变量
(2)使用类的对象访问
类的对象.静态成员变量
(3)成员函数内访问
静态成员函数:
(1)静态成员函数被所有对象共享。
(2)静态成员函数仅可以调用类的静态成员变量,不可以调用普通成员变量。
(3)不具有this指针,不能被声明为const。 -
const用法
(1)const修饰普通变量、全局变量、静态变量。增加了只读属性。
const int a=1;
int const b=2;
(2)const修饰指针
<1>常量指针 int* const
常量指针必须初始化。
常量指针一旦初始化完成,存放在指针内的地址就不能改变。
<2>指向常量的指针 const int* 和 int const*
指向常量的指针可以先不初始化。
指针指向的内容是一个常量(即地址),可以通过改变常量(地址)中的值来改变指针解引用的值。但是不能直接解引用指针来改变值,因为这里的指针是一个常量。
也可以将另外一个常量赋值给指针,来改变指针所指向内容的值。
<3>指向常量对象的常量指针 const int * const
-
C 和C++的区别
C是面向过程编程,C++是面向对象编程。
(1)面向对象的基本概念:类、对象、继承。
(2)面向对象的基本特征:封装、继承、多态。
封装:将低层次的元素组合起来形成新的、更高实体的技术;
继承:广义的继承有三种实现形式:实现继承、可视继承、接口继承。
多态:允许将子类类型的指针赋值给父类类型的指针 -
共有继承、保护继承、私有继承的区别?
(1)public:派生类对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有和受保护成员;
(2)private:基类的成员只能被直接派生类的成员访问,无法再往下继承;
(3)protected:基类的成员也只被直接派生类的成员访问,无法再往下继承。
(4)公有继承时基类受保护的成员,可以通过派生类对象访问但不能修改。 -
多态?
多态是将基类类型的指针或引用指向派生类的对象。通过虚函数机制实现。
多态作用:
(1)隐藏细节,使代码模块化;扩展代码模块,实现代码重用。
(2)实现接口重用,为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。接口重用时,基类和派生类的转换:
a. 派生类对象可以向基类对象赋值。
b.派生类对象可以代替基类对象向基类对象的引用进行赋值或者初始化。
c. 如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。
d. 派生类对象的地址可以赋给基类对象的指针变量。
**静态特性:**编译时确定程序功能
**动态特性:**运行时确定程序功能
运行时多态:继承、虚函数
编译时多态:函数和运算符重载