烤面筋 Day 03

烤面筋 Day 03

多态与虚函数表:

1. 多态的定义是什么?分类有哪些?

答:多态是允许基类的指针或引用在运行时根据实际指向的子类对象类型,调用相应子类重写方法的能力。主要分为两类。

  • 静态多态(编译时多态):通过函数重载模板实现。编译器在编译期间就确定了调用的函数版本。

  • 动态多态(运行时多态):通过虚函数(Virtual Function)继承来时实现。程序在运行期间根据对象的实际类型来决定调用哪个函数。

2. 虚函数是什么?虚函数表和虚函数指针有什么用?

答:虚函数是允许在派生类中被重写、并通过基类指针或引用实现运行时多态的函数。是实现动态多态的基石

  • 虚函数表: 每个拥有虚函数的都有一个虚表。它本质上是一个存放虚函数地址的数组。
  • 虚函数指针: 每个具体的对象实例中都有一个隐藏指针,指向该类对应的虚函数表。

当通过基类指针调用虚函数时,程序先通过对象的虚指针找到虚函数表,再根据偏移量找到对应的函数地址并执行,从而实现动态绑定。即通过虚指针在运行时查找正确的函数实现。

3. 继承关系中的虚函数表是 “拷贝” 还是 “共享”?

答:派生类会继承基类的虚函数表结构,但会生成一份属于自己的新表。

  • 继承与拷贝: 派生类创建时,会首先拷贝基类的虚表内容到自己的新虚表中。
  • 重写(Override): 如果派生类重写了某个虚函数,编译器会用派生类自己的函数地址,**覆盖(Overwrite)**掉新虚表中原来基类的函数地址。
  • 新增: 如果派生类定义了新的虚函数,这些地址会按顺序添加到新虚表的末尾。
  • 结论: 基类和派生类拥有各自独立的虚函数表,互不干扰。

4. 下面的 B 对象占多大的内存空间?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
char a;
virtual void add1();
}
class C {
char aa;
virtual void add2();
}
class B : public A, public C {
short a2;
int a;
short b;
int c;
char d;

int sum();
virtual void add();
void add1();
void add2();
}

答:首先两个基础,创建一个 B 对象,这个对象的内存分布是怎么样的。构造一个派生类对象,它的内存布局里面肯定存在基类的子对象(以及其虚指针),如下图所示:

![image-20251029202359749](E:\Document\Garbage\Typora Files\image-20251029202359749-1768492539172-1.png)

图中的 vptr分别指向自己类的虚函数表,通常一个类的虚指针放在内存布局的最前面,所以这里 B 的虚指针一般会把 A 的虚指针给覆盖掉。

1
2
构造一个子类对象:首先会先去构建基类的对象内容,比如这里的 A 和 C,然后再构造自己的内容。
析构一个子类对象:和构造相反,先执行自己的析构函数,然后去执行基类的析构函数。

Question: 那这里有个问题,如果 A 的析构函数是虚函数的话,之前覆盖了 A 的虚指针,这里就找不到 A 的虚函数表,那么如何析构 A 的内容呢?

答案就是编译器会在调用 B 的虚析构函数的时候,就把头部的虚指针重置为 A 的了,所以能够正常执行。

所以这里我们就明白了解决上面问题最重要的一个点,那就是 B 的内存空间一定会继承基类的虚指针以及基类的一些成员变量。

那么解决这个问题还需要明白一个知识点,那就是内存对齐。内存对齐的规则是什么呢?

  1. 类的每个基准内存块大小 = 类当中占用内存大小最大的成员变量的内存空间,比如这里的 A,虽然最大的成员变量是 1 字节,但是它存在虚函数,所以内部有虚指针。又由于虚指针是 8 字节,所以基准内存块大小就是 8 字节,所以这里 A 所占用是 8 + 8 = 16 字节

  2. 每个类型的起始地址都是该类型大小的整数倍,比如 int 的成员变量,那么它的起始地址只有可能是 0, 4, 8, ….

明白了上面的两个点以后,现在我们来看这个问题:

首先由于 A 和 C 中存在虚函数(虚指针)的缘故,他们的大小都是 16 字节。然后我们看到 B 中自己的成员变量,首先 short 一定是在 4 字节的,因为 short 本身是占 2 字节的,但是由于对齐规则 2 ,后续跟进的 int 的起始地址只能是 4 的倍数,所以 2 - 4 的空间也是只能给 short ,这样的话就是 16 + 16 + 16,最后还有一个 char 类型,不过因为对齐规则 1,这里只能是也占用 8 个字节,所有总共占用的字节数是 16 + 16 + 16 + 8 = 56 个字节。

Http 协议

1. Http 请求的核心部分有哪些?

一个标准的 HTTP 请求报文由四个部分组成:

  • **请求行:**包含 Method、URL、协议版本
  • **请求头:**键值对,描述客户端环境、压缩格式,持久连接等(Host, User-Agent, Connection)
  • 空行: \r\n,用于分隔 Header 和 Body,这是协议格式的强制要求
  • **请求体:**可选,通常用于 POST/PUT 提交的数据

2. Http1.1 常用 Method 及 GET/POST 区别?

常用 Method:GET, POST,PUT,DELETE,HEAD

GET 和 POST 的区别:

  • **语义:**GET 倾向于获取资源,是幂等的;POST 倾向于处理资源(创建/修改),是非幂等的。
  • **参数位置:**GET 参数放在 URL 后面,POST 参数通常放在 Request Body 中。
  • **数据大小:**GET 受限于 URL 长度(浏览器/服务器限制);POST 理论上无限制。

3. 为什么 GET 请求一般没有请求体?

有下面三点原因:

  • **语义冲突:**GET 的定义是根据 URL 及其参数获取资源,引入 Body 会破坏这种简洁的映射。

  • **缓存兼容性:**CDN、代理服务器和浏览器通常只根据 URL 缓存 GET 请求。如果 Body 影响结果,这些缓存机制将失效。

  • **服务器实现:**许多 Web 服务器和解析器为了性能优化,会直接忽略 GET 请求中的 Body。

4. HTTP/1.1、HTTP/2、HTTP/3 的主要区别是什么?

特性 HTTP/1.1 HTTP/2 HTTP/3
传输格式 文本(明文) 二进制分帧 二进制分帧
多路复用 无(有线头阻塞) 支持(单一连接并发) 支持
底层协议 TCP TCP UDP (QUIC)
头部压缩 HPACK QPACK
连接建立 TCP 握手 + TLS 握手 同 1.1 QUIC 握手(1-RTT/0-RTT)

5. HTTP 有哪些常见的状态码?

首先是分类:Http 状态共 5 大类,首位数字界定类别:1xx(信息)、2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务端错误)

其次列举一些高频的状态码

200(成功)、404(资源不存在)、500(服务器错误)、304(缓存)、401/403(权限)、429(限流)


烤面筋 Day 03
https://dxblacksmith.github.io/2026/01/16/烤面筋_Day03/
作者
DxBlackSmith
发布于
2026年1月16日
许可协议