【C++基础】结构体与类的差异:C 到 C++ 的演进 - 面试高频考点与真题解析

在 C/C++ 编程领域,结构体(struct)和类(class)是两种核心的数据组织方式。从 C 到 C++ 的演进过程中,结构体从单纯的数据容器演变为支持面向对象特性的类,这一转变深刻反映了编程语言范式的升级。

一、从 C 到 C++:结构体的进化史

1.1 C 语言中的结构体:数据聚合的基石

在 C 语言中,结构体是一种用户自定义的数据类型,用于将不同类型的数据聚合在一起:

struct Point {
    int x;
    int y;
};

核心特性

  1. 仅包含数据成员(成员变量)
  2. 没有成员函数
  3. 默认所有成员为public访问权限
  4. 无法实现封装、继承、多态等面向对象特性

1.2 C++ 中的结构体:类的 "孪生兄弟"

C++ 对结构体进行了革命性扩展,使其具备与类几乎相同的功能: 

struct Point {
    int x;
    int y;
    void move(int dx, int dy) { x += dx; y += dy; } // 成员函数
};

关键升级

  1. 支持成员函数(包括构造函数、析构函数)
  2. 可以使用访问修饰符(public/private/protected
  3. 支持继承和多态
  4. 与类的唯一区别:默认访问权限为public

字节跳动2024真题:C++结构体能实现多态吗?请给出示例
答案:可以!通过虚函数实现: 

struct Base { virtual void foo() = 0; };
struct Derived : Base { void foo() override {} }; // 多态实现

1.3 演进的本质:从数据到对象的跨越

C 到 C++ 的演进过程中,结构体从单纯的数据存储进化为完整的对象模型。我们可以用一个 "房屋" 比喻来理解:

维度C 结构体C++ 结构体 / 类
功能定位毛坯房(仅存储数据)精装房(数据 + 行为封装)
访问控制开放式(默认 public)门禁系统(public/private)
扩展性固定结构(无法继承)模块化设计(支持继承)
交互方式直接操作数据通过接口访问(封装)

二、核心差异深度解析

2.1 访问控制:默认权限的分水岭

特性结构体(struct)类(class)
默认访问权限publicprivate
继承默认方式public 继承private 继承
成员初始化直接赋值通过构造函数
外部可见性成员直接暴露需通过接口访问

示例代码

struct StructExample {
    int data; // 默认public
};

class ClassExample {
    int data; // 默认private
};

// 继承差异
struct DerivedStruct : BaseStruct {}; // public继承
class DerivedClass : BaseClass {};    // private继承

2.2 成员函数与构造析构

  • 结构体

    • 可以定义成员函数,但通常用于简单数据操作
    • 没有默认构造函数,需显式定义
    • 析构函数可选,但通常不需要
    • 成员函数是核心组成部分,实现行为封装
    • 编译器自动生成默认构造函数(无参)
    • 必须显式定义析构函数以管理资源

示例代码: 

struct MyStruct {
    int value;
    MyStruct(int v) : value(v) {} // 显式定义构造函数
    ~MyStruct() {} // 可选析构函数
};

class MyClass {
    int value;
public:
    MyClass() = default; // 显式使用默认构造函数
    MyClass(int v) : value(v) {}
    ~MyClass() {}
};

2.3 继承与多态

  • 结构体

    • 支持继承,默认 public 继承
    • 可以定义虚函数实现多态
    • 通常用于数据继承,而非行为继承
    • 支持多种继承方式(public/private/protected)
    • 虚函数是多态的核心机制
    • 广泛用于面向对象设计

示例代码: 

// 结构体多态示例
struct Base {
    virtual void print() { cout << "Base" << endl; }
};

struct Derived : Base {
    void print() override { cout << "Derived" << endl; }
};

// 类多态示例
class BaseClass {
public:
    virtual void print() { cout << "BaseClass" << endl; }
};

class DerivedClass : public BaseClass {
public:
    void print() override { cout << "DerivedClass" << endl; }
};

2.4 内存布局与性能

  • 结构体

    • 数据成员紧密排列,无额外开销(除非包含虚函数)
    • 内存对齐规则与类相同
    • 适合作为 POD(Plain Old Data)类型
    • 包含虚函数时会增加虚表指针(vptr)
    • 成员函数存储在代码段,不占用对象内存
    • 多态调用存在间接寻址开销

腾讯2024真题sizeof(空struct)sizeof(空class)结果相同吗?

struct EmptyStruct {};
class EmptyClass {};

cout << sizeof(EmptyStruct); // 输出1
cout << sizeof(EmptyClass);  // 输出1

原理:空类型大小均为1字节,确保不同实例有唯一地址

2.5 终极对比表:struct与class核心差异全景

特性C结构体C++结构体C++类
成员函数
访问控制仅public支持三类权限支持三类权限
默认权限publicpublicprivate
继承支持
多态支持✅(虚函数)✅(虚函数)
构造/析构
空类型大小禁止空结构体1字节1字节
初始化列表仅C风格C++11统一语法C++11统一语法

字节跳动2023真题:计算以下结构体大小(64位系统)

struct S {
    char c;     // 1字节
    double d;   // 8字节
    int i;      // 4字节
};

答案

  • c 偏移0,占1字节

  • d 对齐数=min(8,8)=8 → 偏移8

  • i 对齐数=4 → 偏移16

  • 总大小=8的整数倍 → 24字节

三、面试高频考点深度解析

3.1 基础概念类问题

考点 1:简述结构体与类的主要区别

核心要点

  1. 默认访问权限:结构体public,类private
  2. 默认继承方式:结构体public,类private
  3. 成员函数:结构体可定义但较少使用
  4. 构造函数:结构体无默认构造函数
  5. 多态支持:结构体可通过虚函数实现多态

考点 2:为什么 C++ 中结构体可以定义成员函数?

  • 考察点:C++ 对结构体的扩展
  • 关键回答
    1. C++ 结构体与类功能几乎等价,唯一区别是默认访问权限
    2. 结构体可以定义成员函数、构造函数、虚函数等
    3. 这一设计保持了对 C 语言的兼容性,同时支持面向对象编程

3.2 实践应用类问题

考点 3:分析以下代码的输出结果

struct Base {
    int x;
    Base() : x(10) {}
};

struct Derived : Base {
    int y;
    Derived() : y(20) {}
};

int main() {
    Derived d;
    cout << d.x << " " << d.y << endl;
    return 0;
}
  • 输出结果

  • 原因分析
    1. 结构体Derived继承自Base,默认 public 继承
    2. 构造函数调用顺序:先基类Base,再派生类Derived
    3. 成员变量xy分别被正确初始化 

考点 4:解释以下代码的错误原因 

struct MyStruct {
    int data;
    MyStruct(int d) : data(d) {}
};

int main() {
    MyStruct s; // 错误:没有合适的默认构造函数
    return 0;
}
  • 错误原因
    1. 默认构造函数的缺失MyStruct 类中定义了一个带参数的构造函数 MyStruct(int d),该构造函数用于初始化 data 成员。一旦类中定义了任何构造函数,编译器就不会再自动生成默认构造函数(即无参数的构造函数)。因此,MyStruct 类此时没有默认构造函数。

    2. 错误行分析:在 main 函数中,语句 MyStruct s; 尝试调用 MyStruct 的默认构造函数来创建对象 s,但由于类中没有定义默认构造函数,编译器报错:
      error: no matching function for call to 'MyStruct::MyStruct()'

  • 解决方法:
    1. 显式定义默认构造函数:在类中添加无参数的构造函数,例如:
      MyStruct() : data(0) {} // 默认构造函数,初始化 data 为 0
    2. 使用带参数的构造函数:在创建对象时提供参数,例如:

      MyStruct s(42); // 使用已定义的构造函数
    3. 使用 = default 语法(C++11+):

      MyStruct() = default; // 显式要求编译器生成默认构造函数
  • 修正后的代码示例:
struct MyStruct {
    int data;
    // 显式定义默认构造函数
    MyStruct() : data(0) {}
    // 已有的带参构造函数
    MyStruct(int d) : data(d) {}
};

int main() {
    MyStruct s; // 正确:使用显式定义的默认构造函数
    return 0;
}

3.3 底层原理类问题

考点 5:结构体可以有虚函数吗?为什么?

  • 考察点:结构体的多态支持
  • 关键回答
    1. 可以,结构体和类在 C++ 中功能等价
    2. 虚函数通过虚表指针(vptr)实现
    3. 结构体包含虚函数时,内存布局会增加 vptr
    4. 通常不推荐,结构体更适合数据聚合

考点 6:结构体和类的内存对齐规则是否相同?

  • 考察点:内存布局差异
  • 关键回答
    1. 内存对齐规则完全相同
    2. 成员变量按照声明顺序排列
    3. 编译器自动插入填充字节以满足对齐要求
    4. 可通过#pragma packalignas关键字调整

3.4 设计思想类问题

考点 7:何时应该使用结构体而非类?

  • 考察点:设计原则
  • 最佳实践
    1. 存储简单数据集合(如坐标、时间)
    2. 需要与 C 语言代码兼容
    3. 作为 POD 类型进行高效内存操作
    4. 避免面向对象特性带来的开销

考点 8:如何让结构体具备类的封装性?

  • 考察点:访问控制
  • 实现方法
    1. 将成员变量声明为private
    2. 提供public接口函数
    3. 示例代码: 
struct EncapsulatedStruct {
private:
    int data;
public:
    void setData(int d) { data = d; }
    int getData() const { return data; }
};

考点 9:单例模式在结构体中的实

答案要点: 

  1. 结构体可实现单例,但语义上推荐用类
  2. 实现方式与类相同

示例代码

struct Logger {
    static Logger& getInstance() {
        static Logger instance;
        return instance;
    }
private:
    Logger() {} // 私有构造函数
    Logger(const Logger&) = delete;
};

四、历年面试真题详解

4.1 字节跳动 2023 秋招 C++ 开发真题

题目:分析以下代码的输出结果,并解释原因

#include <iostream>
using namespace std;

struct Base {
    virtual void func() { cout << "Base::func" << endl; }
};

struct Derived : Base {
    void func() override { cout << "Derived::func" << endl; }
};

int main() {
    Base* ptr = new Derived();
    ptr->func();
    delete ptr;
    return 0;
}

解析

  • 输出结果

  • 原因分析
    1. 结构体Base定义了虚函数func
    2. 结构体Derived重写了func
    3. 通过基类指针调用虚函数时,发生动态绑定
    4. 实际执行的是派生类Derivedfunc

考点:结构体的多态支持、虚函数动态绑定 

4.2 腾讯 2022 社招 C++ 高级工程师真题

题目:实现一个结构体,要求包含以下功能:

  1. 存储学生姓名和成绩
  2. 提供设置成绩的接口
  3. 确保成绩在 0-100 之间

解析

  • 设计思路
    1. 使用结构体封装数据和行为
    2. 将成绩声明为private
    3. 提供public接口函数setScore进行验证
    4. 示例代码: 
struct Student {
private:
    string name;
    int score;
public:
    void setScore(int s) {
        if (s >= 0 && s <= 100) {
            score = s;
        } else {
            throw invalid_argument("Score must be between 0 and 100");
        }
    }
};

考点:结构体的封装性、数据验证

4.3 微软 2021 校招真题

题目:解释以下代码的内存布局差异 

struct A {
    int a;
    double b;
};

class B {
    int a;
    double b;
};

 解析

  • 内存布局差异
    1. 结构体A和类B的数据成员完全相同
    2. 内存对齐方式相同,假设在 64 位系统上:
      • int占 4 字节,double占 8 字节
      • 总大小为4 + 4(填充) + 8 = 16字节
    3. B若包含虚函数,会增加 8 字节虚表指针(vptr)

考点:内存对齐、虚函数对内存的影响

4.4 Google 2020 面试题

题目:结构体和类在继承时的默认访问权限有何不同?如何修改?

解析

  • 默认访问权限
    1. 结构体默认public继承
    2. 类默认private继承
  • 修改方法
    1. 结构体使用private关键字:struct Derived : private Base {};
    2. 类使用public关键字:class Derived : public Base {};

考点:继承的默认访问权限、显式指定继承方式

4.5 腾讯2023:结构体继承陷阱

struct Base { int x; };
class Derived : Base {}; // 默认private继承!

Derived d;
d.x = 10; // 错误!x不可访问:cite[3]

考点:class默认private继承切断外部访问

4.6 字节跳动2024:内存对齐实战 

#pragma pack(4) // 设置对齐数4
struct S {
    char c;
    double d; 
};
// sizeof(S)=? 答案:12(1+3填充+8):cite[5]

4.7 阿里2024:空类大小问题

class Empty {
    void func() {}
};
sizeof(Empty) = ? // 答案:1字节:cite[5]:cite[9]

 解析:成员函数不占实例内存,空类仍需1字节保证地址唯一性

五、最佳实践与避坑指南

5.1 现代 C++ 设计原则

①优先使用类

  • 需要封装行为和数据时
  • 需要继承和多态时
  • 需要严格控制访问权限时

②结构体的适用场景

  • 简单数据存储(如配置参数、网络协议)
  • 与 C 语言代码交互
  • POD 类型的高效内存操作

③避免过度设计

  • 结构体应保持数据聚合的初衷
  • 复杂逻辑应封装在类中

5.2 常见错误与解决方案

①默认构造函数缺失

  • 错误:结构体定义了带参构造函数后,无法默认初始化
  • 解决:显式定义默认构造函数或使用带参构造函数

②内存对齐问题

  • 错误:结构体成员顺序导致内存浪费
  • 解决:将小成员放在一起,大成员放在后面

③多态误用

  • 错误:结构体包含虚函数但未正确重写
  • 解决:确保派生类使用override关键字

5.3 工具与调试技巧

①内存布局查看

  • 使用sizeof运算符查看类型大小
  • 通过编译器指令(如-fdump-class-hierarchy)生成类结构信息

②静态分析工具

  • Clang-Tidy:检测结构体与类的误用
  • Coverity:分析内存布局和访问控制问题

③调试技巧

  • 在构造函数中添加日志输出,验证初始化顺序
  • 使用调试器查看对象内存布局

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byte轻骑兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值