华汉伟业科技C++面试经验:结构体与类区别、继承、排序及手写String类
6 min
第二篇八股文,整体难度不高
1. 结构体和类的区别
核心区别:默认访问权限不同。
- 默认成员权限:
struct(结构体):默认为public。class(类):默认为private。
- 默认继承权限:
struct继承:默认为public继承。class继承:默认为private继承。
- 使用习惯:
struct通常用于封装数据(POD,纯数据结构),不涉及复杂的逻辑。class通常用于面向对象编程,强调封装性,包含数据和操作数据的方法。
记忆口诀:Struct 像开放的箱子,默认大家都能看;Class 像保险柜,默认锁起来只有自己能用。
2. 继承的兼容性(Struct 与 Class 互继)
答案:完全可以互相继承。
在 C++ 中,struct 和 class 本质上几乎等价(除了默认权限),它们可以互相继承。
- 结构体继承类:可以。
- 类继承结构体:可以。
- 结构体继承结构体:可以。
- 类继承类:可以。
注意点: 继承时的默认访问权限取决于派生类(子类)是用 struct 还是 class 定义的。
- 如果子类是
struct,默认继承方式是public。 - 如果子类是
class,默认继承方式是private。
3. 单继承下的构造与析构顺序
口诀:构造“由内向外”,析构“由外向内”。
假设有父类 Base,子类 Derived,且子类中包含成员变量 Member。
构造函数调用顺序:
- 父类构造函数(先有父亲,再有儿子)。
- 成员变量构造函数(先组装零件,再组装整体)。
- 注意:成员变量的初始化顺序取决于在类中声明的顺序,而与初始化列表中的书写顺序无关。
- 子类自身的构造函数体。
析构函数调用顺序(完全相反):
- 子类自身的析构函数体。
- 成员变量析构函数。
- 父类析构函数。
记忆:穿衣服(构造)先穿内衣再穿外套;脱衣服(析构)先脱外套再脱内衣。
4. 纯虚函数怎么写
语法:在虚函数声明的末尾加 = 0。
class AbstractClass {
public:
// 纯虚函数声明
virtual void function() = 0;
};特点:
- 含有纯虚函数的类称为抽象类。
- 抽象类不能实例化对象。
- 子类必须重写该纯虚函数,否则子类也依然是抽象类。
5. 类的多个实例如何共享数据
答案:使用 static 静态成员变量。
- 定义:在类中使用
static修饰的成员变量,属于类本身,而不属于某个具体的对象。 - 存储:存储在全局/静态数据区,所有对象共享同一份内存地址。
- 初始化:必须在类外进行初始化(不包括常量整型静态成员)。
代码示例:
class MyClass {
public:
static int shared_count; // 声明
};
// 初始化(类外,不加 static)
int MyClass::shared_count = 0;
int main() {
MyClass a, b;
a.shared_count = 10;
// b.shared_count 也是 10,因为它们指向同一块内存
}6. 希尔排序与稳定性
一定间隔的希尔排序,直到为1,不稳定
希尔排序 原理:
- 是插入排序的改进版(也叫缩小增量排序)。
- 将数组按一定间隔分组,对每组使用插入排序;随着间隔逐渐减小,最后间隔为 1 时进行一次直接插入排序。
- 目的是让元素尽早移动到最终位置附近,减少移动次数。
稳定性概念:
- 定义:如果排序前有两个相等的元素 A 和 B,A 在 B 前面。排序后,如果 A 依然在 B 前面,则该排序是稳定的;否则是不稳定的。
希尔排序是否稳定?
- 不稳定。
- 原因:希尔排序涉及跳跃式交换。相同元素的相对位置可能会因为被分到不同的组中进行排序而打乱。
7. 手写 String 类(构造与拷贝构造)
面试中重点考察深拷贝(Deep Copy)和内存管理。
class String {
private:
char* m_data; // 指向字符串数据的指针
public:
// 1. 普通构造函数
String(const char* str = nullptr) {
if (str == nullptr) {
m_data = new char[1];
*m_data = '\0'; // 空字符串
} else {
m_data = new char[strlen(str) + 1]; // +1 是为了存放 '\0'
strcpy(m_data, str);
}
}
// 2. 拷贝构造函数(核心:深拷贝)
String(const String& other) {
// 重新分配内存,防止两个指针指向同一块地址(浅拷贝)
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data, other.m_data);
}
// 3. 析构函数(必须释放内存)
~String() {
delete[] m_data;
}
// 面试补充:通常还需要重载赋值运算符=,这也是深拷贝的一部分
// String& operator=(const String& other) { ... }
};关键点解析:
- 构造函数:要处理传入
nullptr的情况,防止strlen崩溃;记得+1给结束符。 - 拷贝构造:必须是深拷贝。如果只是简单的
m_data = other.m_data(浅拷贝),两个对象会指向同一块内存,析构时会导致同一块内存被delete两次,程序崩溃。 - 析构函数:使用了
new[],析构时必须用delete[]。