额,误打误撞打出来的(下有思路和解释)
2026-08-13 16:18:47
发布于:广东
#include <bits/stdc++.h> // 万能头文件,包含C++标准库几乎所有常用内容
using namespace std; // 使用标准命名空间,省得每次写std::
// ---------------- 类型信息相关 ----------------
struct Member {// 表示结构体中的一个成员
string type; // 成员的类型名,例如 "int"、"short" 或自定义结构体名
string name; // 成员的名字,例如 "a"、"b"
long long offset; // 该成员相对于结构体起始地址的偏移(字节)
};
struct TypeInfo {// 表示一种类型(基本类型或结构体类型)的完整信息
long long size; // 该类型占用的总字节数
long long align; // 该类型的对齐要求(字节)
vector<Member> members; // 如果是结构体,存所有成员;基本类型为空
};
// 用map保存类型名到类型信息的映射
// 例如 typeMap["int"] 就能得到 int 的大小、对齐和成员列表
map<string, TypeInfo> typeMap;
// ---------------- 辅助函数 ----------------
// 把 x 向上取整到 align 的倍数
// 例如 align_to(2, 4) = 4,align_to(5, 4) = 8
long long align_to(long long x, long long align) {
// 整数除法会向下取整,所以先加 align-1 再除
// 这样就能实现向上取整
return (x + align - 1) / align * align;
}
// 判断一个类型名是否是基本类型
bool isBasic(const string& t) {
return t == "byte" || t == "short" || t == "int" || t == "long";
}
// ---------------- 元素信息相关 ----------------
// 表示一个已经定义的元素(变量)的信息
struct ElementInfo {
string name; // 元素名,例如 "e"
string type; // 元素的类型名,例如 "d"
long long addr; // 元素在内存中的起始地址
};
vector<ElementInfo> elements; // 按定义顺序存储所有元素
unordered_map<string, int> nameToIndex; // 通过元素名快速找到在 elements 中的下标
long long currentAddr = 0; // 当前空闲内存的起始地址,定义新元素时从这个地址向后分配
// ---------------- 主函数 ----------------
int main() {
freopen("struct.in","r",stdin);
freopen("struct.out","w",stdout);
// 初始化四种基本类型
// 基本类型的大小 = 对齐要求,且没有成员
typeMap["byte"] = {1, 1, {}}; // byte:1字节,对齐1
typeMap["short"] = {2, 2, {}}; // short:2字节,对齐2
typeMap["int"] = {4, 4, {}}; // int:4字节,对齐4
typeMap["long"] = {8, 8, {}}; // long:8字节,对齐8
int n; // 操作次数
cin >> n;
while (n--) {
int op; // 操作类型
cin >> op;
// ========== 操作1:定义结构体类型 ==========
if (op == 1) {
string s; // 新结构体类型名
int k; // 成员数量
cin >> s >> k;
vector<Member> members; // 保存这个结构体的所有成员
long long cur = 0; // 当前已排布到的偏移(相对结构体开头)
long long maxAlign = 1; // 所有成员中最大的对齐要求(初始为1)
// 读取 k 个成员的信息
for (int i = 0; i < k; ++i) {
string t, name; // 成员类型和名字
cin >> t >> name;
TypeInfo ti = typeMap[t]; // 查这个成员类型的信息
cur = align_to(cur, ti.align); // 先对齐到成员类型的对齐要求
// 记录这个成员,它的偏移就是对齐后的 cur
members.push_back({t, name, cur});
cur += ti.size; // 偏移向后移动成员大小
maxAlign = max(maxAlign, ti.align); // 更新最大对齐要求
}
// 结构体总大小需要对齐到最大对齐要求
long long size = align_to(cur, maxAlign);
// 把新类型存入 typeMap
typeMap[s] = {size, maxAlign, members};
// 输出大小和对齐要求
cout << size << ' ' << maxAlign << '\n';
}
// ========== 操作2:定义元素 ==========
else if (op == 2) {
string t, name;
cin >> t >> name; // 类型和元素名
TypeInfo ti = typeMap[t]; // 查类型信息
currentAddr = align_to(currentAddr, ti.align); // 全局内存对齐
long long startAddr = currentAddr; // 元素的起始地址
currentAddr += ti.size; // 内存游标向后移动该类型大小
// 保存元素信息
elements.push_back({name, t, startAddr});
// 记录名字到下标,便于之后快速访问
nameToIndex[name] = (int)elements.size() - 1;
// 输出起始地址
cout << startAddr << '\n';
}
// ========== 操作3:访问元素(路径) ==========
else if (op == 3) {
string s;
cin >> s;
// 将字符串按 '.' 分割成路径段
// 例如 "a.b.c" 分割成 ["a", "b", "c"]
vector<string> parts;
string cur;
for (char c : s) {
if (c == '.') {
parts.push_back(cur);
cur.clear();
} else {
cur += c;
}
}
parts.push_back(cur); // 最后一段
// 通过根元素名找到下标
int idx = nameToIndex[parts[0]];
string curType = elements[idx].type; // 当前类型
long long curAddr = elements[idx].addr; // 当前地址
// 从第1段开始,一层层往里找
for (int i = 1; i < (int)parts.size(); ++i) {
TypeInfo ti = typeMap[curType]; // 当前类型的信息
// 在当前类型的成员里找名字为 parts[i] 的成员
for (const Member& m : ti.members) {
if (m.name == parts[i]) {
curAddr += m.offset; // 加上成员偏移
curType = m.type; // 更新当前类型
break; // 找到就跳出循环
}
}
}
cout << curAddr << '\n'; // 输出最内层元素的地址
}
// ========== 操作4:查询内存地址 ==========
else if (op == 4) {
long long addr;
cin >> addr;
string ans = "ERR"; // 默认没有找到
// 如果没有任何元素,直接输出 ERR
if (!elements.empty()) {
// 二分查找:找到最后一个起始地址 <= addr 的元素
// 因为元素是按定义顺序连续存放的,所以可以用二分
int l = 0, r = (int)elements.size() - 1;
int pos = -1; // 记录找到的下标
while (l <= r) {
int mid = (l + r) / 2;
if (elements[mid].addr <= addr) {
pos = mid; // 当前 mid 可能是候选
l = mid + 1; // 继续向右找更大的起始地址
} else {
r = mid - 1; // 向左找
}
}
// 如果找到了可能的元素
if (pos != -1) {
ElementInfo& e = elements[pos];
TypeInfo& ti = typeMap[e.type];
// 检查 addr 是否在这个元素占用的整体范围内
if (addr < e.addr + ti.size) {
string curType = e.type; // 当前类型
long long curAddr = e.addr; // 当前地址
string path = e.name; // 当前路径,从元素名开始
bool found = true; // 标记是否成功找到基本类型
// 如果当前类型不是基本类型,就要一层层往下找
while (!isBasic(curType)) {
TypeInfo& info = typeMap[curType];
long long relative = addr - curAddr; // 相对当前结构的偏移
// 在当前结构体的成员里二分查找:
// 找到最后一个 offset <= relative 的成员
int ll = 0, rr = (int)info.members.size();
while (ll < rr) {
int mid = (ll + rr) / 2;
if (info.members[mid].offset <= relative) {
ll = mid + 1; // 这个成员可能是,继续向右
} else {
rr = mid; // 这个成员偏移太大,向左
}
}
// 如果没有成员的偏移 <= relative,说明 addr 在结构体头部之前或空洞里
if (ll == 0) {
found = false;
break;
}
// ll-1 就是最后一个偏移 <= relative 的成员
Member& m = info.members[ll - 1];
long long memberAddr = curAddr + m.offset; // 该成员实际地址
long long memberSize = typeMap[m.type].size; // 该成员大小
// 检查 addr 是否真的落在这个成员占用的范围内
// (因为结构体成员之间可能有填充字节)
if (addr < memberAddr || addr >= memberAddr + memberSize) {
found = false;
break;
}
// 进入该成员
curAddr = memberAddr;
curType = m.type;
path += "." + m.name; // 路径后面加上 ".成员名"
}
// 如果最终找到基本类型,再检查一次地址是否真的在这个基本类型内
if (found && isBasic(curType)) {
TypeInfo& basicInfo = typeMap[curType];
if (addr >= curAddr && addr < curAddr + basicInfo.size) {
ans = path; // 成功找到,更新答案
}
}
}
}
}
cout << ans << '\n'; // 输出路径或 ERR
}
}
fclose(stdin);
fclose(stdout);
return 0;
}
一、题目想让我们做什么?
题目在模拟 C++ 里的结构体内存排布。你需要处理 4 种操作:
定义结构体类型
输入类型名和成员列表,输出这个结构体类型的大小和对齐要求。
定义一个元素/变量
所有变量从地址 0 开始往后排,同时要对齐。输出这个变量的起始地址。
访问元素
输入 a.b.c 这样的路径,输出最里层那个元素的起始地址。
访问内存地址
输入一个地址,如果这个地址恰好被某个“基本类型元素”占据,输出它的完整路径,比如 e.a;否则输出 ERR。
二、关键概念:对齐
对齐可以理解成:某些类型必须从某些特殊地址开始放。
基本类型:
类型 大小 对齐要求
byte 1 1
short 2 2
int 4 4
long 8 8
比如 int 大小是 4 字节,它的起始地址必须是 4 的倍数。
short 大小是 2 字节,起始地址必须是 2 的倍数。
结构体类型的对齐要求:
它所有成员里最大的那个对齐要求。
结构体类型的大小:
成员依次排布完之后,把总大小向上取整到结构体对齐要求的倍数。
三、核心思路
我们需要一个“类型信息表”,记录每个类型的大小、对齐要求、成员列表。
还要一个“元素信息表”,记录每个元素的名字、类型、起始地址。
3.1 定义结构体类型时怎么计算?
用一个变量 cur 表示当前已经排布到了第几字节。
每放一个成员:
先看这个成员类型的对齐要求 align
把 cur 向上取整到 align 的倍数
这个成员的偏移就是 cur
cur 加上这个成员的大小
所有成员放完后:
结构体对齐要求 = 所有成员对齐要求的最大值
结构体大小 = 把 cur 向上取整到结构体对齐要求
3.2 定义元素时怎么计算?
全局有一个内存游标 currentAddr,表示下一个变量应该从哪个地址开始考虑。
定义一个新元素时:
看这个元素类型的对齐要求
把 currentAddr 向上取整到对齐要求
这个元素的起始地址就是对齐后的 currentAddr
currentAddr 再加上这个类型的大小
3.3 访问元素路径怎么计算?
比如访问 a.b.c:
先找到根元素 a,得到它的类型和起始地址
在当前类型里找成员 b,得到它的偏移
当前地址加上 b 的偏移
更新当前类型为 b 的类型
再找成员 c,继续加偏移
最后输出地址
3.4 地址查询怎么做?
因为操作 4 只问“基本类型元素”占据了哪个地址,所以我们可以在定义元素时,把这个元素内部所有基本类型成员都拆出来,记录它们的地址区间。
如·
struct d {
short a;
int b;
short c;
};
d e;
定义 e 时,我们记录:
e.a 占据地址 [0, 1]
e.b 占据地址 [4, 7]
e.c 占据地址 [8, 9]
查询地址 4 时,发现它落在 e.b 的区间里,就输出 e.b。
四、代码逐段讲解
- 头文件和基本类型信息
#include <bits/stdc++.h>
using namespace std;
bits/stdc++.h 是万能头文件,包含了 C++ 常用的标准库,初学可以直接用。
struct Member {
string type; // 成员的类型名
string name; // 成员的名字
long long offset; // 相对结构体开头的偏移
};
Member 表示结构体里的一个成员。
比如 int b 就是一个成员,类型是 int,名字是 b,偏移是这个成员在结构体里的起始地址。
struct TypeInfo {
long long size; // 类型大小
long long align; // 类型对齐要求
vector<Member> members; // 成员列表,基本类型为空
};
TypeInfo 表示一个类型的信息。
基本类型没有成员,所以 members 为空。
map<string, TypeInfo> typeMap;
typeMap 是一个字典,可以通过类型名查到类型信息。
比如 typeMap["int"] 就能查到 int 的大小和对齐。
2. 对齐函数
long long align_to(long long x, long long align) {
return (x + align - 1) / align * align;
}
这个函数把 x 向上取整到 align 的倍数。
比如:
align_to(2, 4)
(2 + 3) / 4 * 4 = 5 / 4 * 4 = 1 * 4 = 4
align_to(10, 4)
(10 + 3) / 4 * 4 = 13 / 4 * 4 = 3 * 4 = 12
注意:C++ 里整数除法会向下取整。
- 判断是否是基本类型
bool isBasic(const string& t) {
return t == "byte" || t == "short" || t == "int" || t == "long";
}
基本类型就是这四种。
- 元素信息和基本类型区间
struct ElementInfo {
string type; // 元素的类型
long long addr; // 元素的起始地址
};
map<string, ElementInfo> elementMap;
elementMap 可以通过元素名查到它的类型和起始地址。
cpp
struct BasicVar {
string name; // 完整路径,比如 e.a
long long l, r; // 占据的地址区间 [l, r]
};
vector<BasicVar> basicVars;
basicVars 用来记录所有基本类型元素的地址区间,给操作 4 查询用。
5. 递归拆解结构体,记录基本类型区间
cpp
void addBasicVars(const string& typeName, const string& path, long long baseAddr) {
TypeInfo info = typeMap[typeName];
if (isBasic(typeName)) {
basicVars.push_back({path, baseAddr, baseAddr + info.size - 1});
return;
}
for (const Member& m : info.members) {
addBasicVars(m.type, path + "." + m.name, baseAddr + m.offset);
}
}
这个函数的作用是:定义一个元素后,把它里面所有基本类型成员都记录下来。
如果当前类型是基本类型,直接记录区间。
如果当前类型是结构体,就遍历它的成员,递归地继续拆。
比如定义 d e 时调用:
cpp
addBasicVars("d", "e", 0);
它会拆成:
e.a,地址 [0, 1]
e.b,地址 [4, 7]
e.c,地址 [8, 9]
-
主函数开始
cpp
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
这两行是加快 C++ 输入输出速度的固定写法。 -
初始化基本类型
typeMap["byte"] = {1, 1, {}};
typeMap["short"] = {2, 2, {}};
typeMap["int"] = {4, 4, {}};
typeMap["long"] = {8, 8, {}};
基本类型的大小和对齐要求相同,成员列表为空。
例如 typeMap["int"] = {4, 4, {}} 表示:
大小 size = 4
对齐 align = 4
成员列表为空
- 处理操作 1:定义结构体类型
int n;
cin >> n;
while (n--) {
int op;
cin >> op;
if (op == 1) {
string s;
int k;
cin >> s >> k;
vector<Member> members;
long long cur = 0;
long long maxAlign = 1;
for (int i = 0; i < k; ++i) {
string t, name;
cin >> t >> name;
TypeInfo ti = typeMap[t];
cur = align_to(cur, ti.align);
members.push_back({t, name, cur});
cur += ti.size;
maxAlign = max(maxAlign, ti.align);
}
long long size = align_to(cur, maxAlign);
typeMap[s] = {size, maxAlign, members};
cout << size << ' ' << maxAlign << '\n';
}
解释:
cur 是当前结构体已经排布到的地址偏移。
每读入一个成员 t name:
用 typeMap[t] 查到类型信息。
把 cur 对齐到 ti.align。
记录成员偏移。
cur 加上成员大小。
maxAlign 是所有成员里最大的对齐要求。
最后结构体大小要把 cur 对齐到 maxAlign。
存进 typeMap,输出大小和对齐。
- 处理操作 2:定义元素
else if (op == 2) {
string t, name;
cin >> t >> name;
TypeInfo ti = typeMap[t];
currentAddr = align_to(currentAddr, ti.align);
long long startAddr = currentAddr;
currentAddr += ti.size;
elementMap[name] = {t, startAddr};
addBasicVars(t, name, startAddr);
cout << startAddr << '\n';
}
解释:
查出元素类型 t 的信息。
把全局地址 currentAddr 对齐到 ti.align。
起始地址就是对齐后的地址。
currentAddr 加上类型大小,留给下一个元素。
记录这个元素到 elementMap。
调用 addBasicVars,把这个元素内部的基本类型成员记录下来。
输出起始地址。
- 处理操作 3:访问元素
else if (op == 3) {
string s;
cin >> s;
vector<string> parts;
string cur;
for (char c : s) {
if (c == '.') {
parts.push_back(cur);
cur.clear();
} else {
cur += c;
}
}
parts.push_back(cur);
先把 a.b.c 按 . 拆开,得到 ["a", "b", "c"]。
ElementInfo ei = elementMap[parts[0]];
string curType = ei.type;
long long curAddr = ei.addr;
先找到根元素的信息。
for (int i = 1; i < (int)parts.size(); ++i) {
TypeInfo ti = typeMap[curType];
for (const Member& m : ti.members) {
if (m.name == parts[i]) {
curAddr += m.offset;
curType = m.type;
break;
}
}
}
cout << curAddr << '\n';
}
从根元素开始,一层一层往里找:
在当前类型 curType 中找成员 parts[i]
找到后,当前地址加上该成员的偏移
当前类型更新为该成员的类型
继续找下一层
最后输出最内层元素的地址。
11. 处理操作 4:地址查询
cpp
else if (op == 4) {
long long addr;
cin >> addr;
string ans = "ERR";
for (const BasicVar& bv : basicVars) {
if (bv.l <= addr && addr <= bv.r) {
ans = bv.name;
break;
}
}
cout << ans << '\n';
}
遍历所有基本类型区间,看 addr 是否落在某个区间里。
如果找到,就输出它的完整路径;否则输出 ERR。
这里空空如也




有帮助,赞一个