把用一种小语言写的程序按两种变量作用域规则分别运行,并把结果并排输出。 程序有 n 行,每行一条命令。 从 def f 行到 end 行是函数 f 的定义,中间的行是 f 的函数体。 函数定义只出现在最外层,不在函数里定义函数。 函数定义之外的行是最外层命令,从上到下依次执行。 函数定义本身不执行,函数的定义无论在程序的哪里都可以调用。 变量放在“格”里。 最外层命令使用全局格。 每次调用函数都新建一个空格,在这个格里执行该函数的函数体。 函数结束后这个格就消失。 命令有五种。 - var x v:在当前格里创建变量 x 并放入值 v。 当前格里已经有 x 时,只把它的值改为 v。 - set x v:查找 x,把它的值改为 v。 - add x v:查找 x,给它的值加上 v。 - print x:查找 x 并输出它的值。 找不到时输出 ?。 - call f:调用函数 f。 f 结束后继续下一条命令。 set 和 add 找不到 x 时什么也不做,不创建新变量。 “查找”是按规定的顺序查看各个格,选出第一个含有 x 的格。 这个顺序就是作用域规则。 静态作用域下,先看当前格,再看全局格。 动态作用域下,依次看当前格、调用当前函数的函数的格、调用那个函数的函数的格……最后看全局格。 在最外层,当前格就是全局格。 变量名和函数名分开算,同一个名字可以同时用作变量和函数。 把程序按静态作用域运行一次,按动态作用域运行一次,各自从初始状态开始。 两次运行中 print 执行的次数相同。 每执行一次 print,就在一行中输出静态作用域下的输出和动态作用域下的输出,用一个空格隔开。
13
var x 1
def show
print x
end
def change
var x 2
call show
set x 3
call show
end
call change
print x
call show
stdout
1 2
1 3
1 1
1 1
样例 2 · stdin
21
def inc
add n 1
print n
end
def run
var n 10
call inc
call inc
end
var n 0
call run
print n
def peek
print m
end
def outer
var m 5
call peek
end
call outer
call peek
stdout
1 11
2 12
2 0
? 5
? ?
main.py
错误列表00F8 ↓ · ⇧F8 ↑
输入(stdin)
运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。
$ 就绪
请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。
其他语言怎么写?查看各语言题解 · 8 种语言题解
8 种语言都是静态作用域。 函数里能看到的名字取决于“写在哪里”,而不是“从哪里调用”。 动态作用域可以在老式 Lisp、Emacs Lisp(默认)、bash 函数的 local、Perl 的 local 中看到。 第一个例子中的 show 在静态作用域下总是看到全局的 x(1),在动态作用域下看到调用它的 change 的 x(2、3)。 解法是每个格放一个字典,把格堆在列表(栈)里:Python dict 列表,JS・TS Map[],Java List<Map<String, Integer>>,C# List<Dictionary<string, int>>,C++ std::vector<std::unordered_map<…>>,Craykon 字典() 列表。 C 没有字典,先把名字换成编号,把(编号, 值)对排成一列堆起来,并记住每个格的起始位置。 call 压入一个格,递归执行函数体后再弹出这个格。 静态查找只看最上面的格和 0 号格,动态查找从最上面依次看到 0 号格。 和各语言的实际规则比较:Python 把函数里被赋值的名字当作整个函数的局部变量,要修改外层的需要 global・nonlocal。 本题的 set 则相反,修改找到的那个格。 Craykon 中,在函数里给最上层(函数外)创建的变量赋值会改变那个变量,函数里第一次创建的名字属于该函数,在外面使用会被检查器拦下。 JS 的 var 是函数作用域,let・const 是块作用域。 C・C++・Java・C# 是块作用域,Java・C# 禁止在内层块中重新声明与外层局部变量同名的变量。 陷阱 ① 动态作用域只查当前格和全局格,或者静态作用域却查到调用者的格。 ② 动态作用域从全局格开始查(最近的格优先)。 ③ 函数结束后仍保留格,下次调用看到旧值(类似 C 的 static 局部变量)。 ④ set 找不到时在当前格新建变量(Python 式赋值)。 ⑤ 让 var 修改已经可见的变量(var 总是用当前格)。
Pythonmain.py · 53 行
import syslines = sys.stdin.read().split("\n")n = int(lines[0])funcs = {} # 함수 이름 → 몸체 명령들main = [] # 맨 바깥 명령들body = Nonefor line in lines[1:1 + n]: t = line.split() if t[0] == "def": body = funcs[t[1]] = [] elif t[0] == "end": body = None else: (main if body is None else body).append(t)def run(dynamic): frames = [{}] # frames[0]은 맨 바깥(전역), 끝이 지금 실행 중인 함수의 칸 printed = [] def find(name): """이름을 가진 칸(사전)을 찾는다. 없으면 None.""" # 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역 order = reversed(frames) if dynamic else (frames[-1], frames[0]) for frame in order: if name in frame: return frame return None def execute(cmds): for cmd in cmds: op = cmd[0] if op == "var": frames[-1][cmd[1]] = int(cmd[2]) elif op in ("set", "add"): frame = find(cmd[1]) if frame is not None: frame[cmd[1]] = int(cmd[2]) if op == "set" else frame[cmd[1]] + int(cmd[2]) elif op == "print": frame = find(cmd[1]) printed.append("?" if frame is None else str(frame[cmd[1]])) else: # call frames.append({}) # 부를 때마다 빈 칸에서 시작한다 execute(funcs[cmd[1]]) frames.pop() # 끝나면 그 칸은 사라진다 execute(main) return printedfor a, b in zip(run(False), run(True)): print(a, b)
JavaScriptmain.js · 43 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const funcs = new Map(); // 함수 이름 → 몸체 명령들const main = []; // 맨 바깥 명령들let body = null;for (const line of lines.slice(1, 1 + n)) { const t = line.trim().split(" "); if (t[0] === "def") funcs.set(t[1], (body = [])); else if (t[0] === "end") body = null; else (body ?? main).push(t);}function run(dynamic) { const frames = [new Map()]; // frames[0]은 전역, 끝이 지금 실행 중인 함수의 칸 const printed = []; // 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역 const find = (name) => { const order = dynamic ? [...frames].reverse() : [frames[frames.length - 1], frames[0]]; return order.find((f) => f.has(name)); }; const execute = (cmds) => { for (const [op, name, arg] of cmds) { if (op === "var") { frames[frames.length - 1].set(name, Number(arg)); } else if (op === "set" || op === "add") { const f = find(name); if (f) f.set(name, op === "set" ? Number(arg) : f.get(name) + Number(arg)); } else if (op === "print") { const f = find(name); printed.push(f ? String(f.get(name)) : "?"); } else { frames.push(new Map()); // 부를 때마다 빈 칸에서 시작한다 execute(funcs.get(name)); frames.pop(); // 끝나면 그 칸은 사라진다 } } }; execute(main); return printed;}const lex = run(false), dyn = run(true);console.log(lex.map((v, i) => `${v} ${dyn[i]}`).join("\n"));
TypeScriptmain.ts · 48 行
import { readFileSync } from "node:fs";type Frame = Map<string, number>;const lines: string[] = readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const funcs = new Map<string, string[][]>(); // 함수 이름 → 몸체 명령들const main: string[][] = []; // 맨 바깥 명령들let body: string[][] | null = null;for (const line of lines.slice(1, 1 + n)) { const t = line.trim().split(" "); if (t[0] === "def") { body = []; funcs.set(t[1], body); } else if (t[0] === "end") body = null; else (body ?? main).push(t);}function run(dynamic: boolean): string[] { const frames: Frame[] = [new Map()]; // frames[0]은 전역, 끝이 지금 실행 중인 함수의 칸 const printed: string[] = []; // 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역 const find = (name: string): Frame | undefined => { const order = dynamic ? [...frames].reverse() : [frames[frames.length - 1], frames[0]]; return order.find((f) => f.has(name)); }; const execute = (cmds: string[][]): void => { for (const [op, name, arg] of cmds) { if (op === "var") { frames[frames.length - 1].set(name, Number(arg)); } else if (op === "set" || op === "add") { const f = find(name); if (f) f.set(name, op === "set" ? Number(arg) : (f.get(name) ?? 0) + Number(arg)); } else if (op === "print") { const f = find(name); printed.push(f ? String(f.get(name)) : "?"); } else { frames.push(new Map()); // 부를 때마다 빈 칸에서 시작한다 execute(funcs.get(name) ?? []); frames.pop(); // 끝나면 그 칸은 사라진다 } } }; execute(main); return printed;}const lex = run(false), dyn = run(true);console.log(lex.map((v, i) => `${v} ${dyn[i]}`).join("\n"));
JavaMain.java · 78 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class Main { static Map<String, List<String[]>> funcs = new HashMap<>(); // 함수 이름 → 몸체 명령들 static List<Map<String, Integer>> frames; // 0번이 전역, 끝이 지금 함수의 칸 static boolean dynamic; static List<String> printed; // 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역 static Map<String, Integer> find(String name) { int top = frames.size() - 1; if (dynamic) { for (int i = top; i >= 0; i--) if (frames.get(i).containsKey(name)) return frames.get(i); return null; } if (frames.get(top).containsKey(name)) return frames.get(top); if (frames.get(0).containsKey(name)) return frames.get(0); return null; } static void execute(List<String[]> cmds) { for (String[] c : cmds) { switch (c[0]) { case "var" -> frames.get(frames.size() - 1).put(c[1], Integer.parseInt(c[2])); case "set", "add" -> { Map<String, Integer> f = find(c[1]); if (f != null) f.put(c[1], c[0].equals("set") ? Integer.parseInt(c[2]) : f.get(c[1]) + Integer.parseInt(c[2])); } case "print" -> { Map<String, Integer> f = find(c[1]); printed.add(f == null ? "?" : String.valueOf(f.get(c[1]))); } default -> { frames.add(new HashMap<>()); // 부를 때마다 빈 칸에서 시작한다 execute(funcs.get(c[1])); frames.remove(frames.size() - 1); // 끝나면 그 칸은 사라진다 } } } } static List<String> run(List<String[]> main, boolean dyn) { dynamic = dyn; frames = new ArrayList<>(); frames.add(new HashMap<>()); printed = new ArrayList<>(); execute(main); return printed; } public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(in.readLine().trim()); List<String[]> main = new ArrayList<>(), body = null; for (int i = 0; i < n; i++) { String[] t = in.readLine().trim().split(" "); if (t[0].equals("def")) { body = new ArrayList<>(); funcs.put(t[1], body); } else if (t[0].equals("end")) { body = null; } else { (body == null ? main : body).add(t); } } List<String> lex = run(main, false), dyn = run(main, true); StringBuilder out = new StringBuilder(); for (int i = 0; i < lex.size(); i++) out.append(lex.get(i)).append(' ').append(dyn.get(i)).append('\n'); System.out.print(out); }}
C#Main.cs · 81 行
using System;using System.Collections.Generic;using System.Text;class Program{ static Dictionary<string, List<string[]>> funcs = new(); // 함수 이름 → 몸체 명령들 static List<Dictionary<string, int>> frames = new(); // 0번이 전역, 끝이 지금 함수의 칸 static bool dynamic; static List<string> printed = new(); // 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역 static Dictionary<string, int> Find(string name) { int top = frames.Count - 1; if (dynamic) { for (int i = top; i >= 0; i--) if (frames[i].ContainsKey(name)) return frames[i]; return null; } if (frames[top].ContainsKey(name)) return frames[top]; if (frames[0].ContainsKey(name)) return frames[0]; return null; } static void Execute(List<string[]> cmds) { foreach (string[] c in cmds) { switch (c[0]) { case "var": frames[^1][c[1]] = int.Parse(c[2]); break; case "set": case "add": var f = Find(c[1]); if (f != null) f[c[1]] = c[0] == "set" ? int.Parse(c[2]) : f[c[1]] + int.Parse(c[2]); break; case "print": var g = Find(c[1]); printed.Add(g == null ? "?" : g[c[1]].ToString()); break; default: frames.Add(new Dictionary<string, int>()); // 부를 때마다 빈 칸에서 시작한다 Execute(funcs[c[1]]); frames.RemoveAt(frames.Count - 1); // 끝나면 그 칸은 사라진다 break; } } } static List<string> Run(List<string[]> main, bool dyn) { dynamic = dyn; frames = new List<Dictionary<string, int>> { new() }; printed = new List<string>(); Execute(main); return printed; } static void Main() { int n = int.Parse(Console.ReadLine().Trim()); var main = new List<string[]>(); List<string[]> body = null; for (int i = 0; i < n; i++) { string[] t = Console.ReadLine().Trim().Split(' '); if (t[0] == "def") funcs[t[1]] = body = new List<string[]>(); else if (t[0] == "end") body = null; else (body ?? main).Add(t); } var lex = Run(main, false); var dyn = Run(main, true); var sb = new StringBuilder(); for (int i = 0; i < lex.Count; i++) sb.Append(lex[i]).Append(' ').Append(dyn[i]).Append('\n'); Console.Write(sb); }}
C++main.cpp · 76 行
#include <iostream>#include <sstream>#include <string>#include <unordered_map>#include <vector>using Frame = std::unordered_map<std::string, int>;struct Cmd { std::string op, name; int arg;};std::unordered_map<std::string, std::vector<Cmd>> funcs; // 함수 이름 → 몸체 명령들std::vector<Frame> frames; // 0번이 전역, 끝이 지금 함수의 칸bool dynamic;std::vector<std::string> printed;// 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역Frame *find(const std::string &name) { if (dynamic) { for (auto it = frames.rbegin(); it != frames.rend(); ++it) if (it->count(name)) return &*it; return nullptr; } if (frames.back().count(name)) return &frames.back(); if (frames.front().count(name)) return &frames.front(); return nullptr;}void execute(const std::vector<Cmd> &cmds) { for (const Cmd &c : cmds) { if (c.op == "var") { frames.back()[c.name] = c.arg; } else if (c.op == "set" || c.op == "add") { if (Frame *f = find(c.name)) (*f)[c.name] = c.op == "set" ? c.arg : (*f)[c.name] + c.arg; } else if (c.op == "print") { Frame *f = find(c.name); printed.push_back(f ? std::to_string(f->at(c.name)) : "?"); } else { // 부를 때마다 빈 칸에서 시작한다. push_back이 vector를 옮길 수 있어 칸을 가리키는 포인터는 오래 들지 않는다 frames.emplace_back(); execute(funcs[c.name]); frames.pop_back(); // 끝나면 그 칸은 사라진다 } }}std::vector<std::string> run(const std::vector<Cmd> &main, bool dyn) { dynamic = dyn; frames.assign(1, Frame{}); printed.clear(); execute(main); return printed;}int main() { int n; std::string line; std::getline(std::cin, line); n = std::stoi(line); std::vector<Cmd> main; std::vector<Cmd> *body = nullptr; for (int i = 0; i < n; i++) { std::getline(std::cin, line); std::istringstream ss(line); Cmd c{"", "", 0}; ss >> c.op >> c.name >> c.arg; if (c.op == "def") body = &funcs[c.name]; else if (c.op == "end") body = nullptr; else (body ? *body : main).push_back(c); } auto lex = run(main, false), dyn = run(main, true); std::string out; for (size_t i = 0; i < lex.size(); i++) out += lex[i] + " " + dyn[i] + "\n"; std::cout << out;}
Cmain.c · 121 行
#include <stdio.h>#include <string.h>enum { VAR, SET, ADD, PRINT, CALL };typedef struct { int op, id, arg; } Cmd; /* id: 변수 번호, call이면 함수 번호 */static char vname[1000][11], fname[1000][11];static int nv, nf;static Cmd cmds[1000];static int ncmd;static int fstart[1000], fend[1000]; /* 함수 몸체는 cmds[fstart, fend) */static int mainlist[1000], nmain; /* 맨 바깥 명령의 번호들 *//* 칸 하나는 (변수 번호, 값) 묶음들. 모든 칸을 한 줄로 쌓고, 칸마다 시작 자리를 둔다 */static int ent_id[1000], ent_val[1000], nent;static int frame_at[64], depth; /* frame_at[d]: d번 칸의 시작. 0번이 전역 */static int dynamic;static char printed[2][50000][12];static int nprinted;static int intern(char names[][11], int *count, const char *s) { for (int i = 0; i < *count; i++) if (strcmp(names[i], s) == 0) return i; strcpy(names[*count], s); return (*count)++;}/* d번 칸에서 변수 id의 자리(없으면 -1) */static int in_frame(int d, int id) { int end = d == depth ? nent : frame_at[d + 1]; for (int e = frame_at[d]; e < end; e++) if (ent_id[e] == id) return e; return -1;}/* 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역 */static int find(int id) { int e; if (dynamic) { for (int d = depth; d >= 0; d--) if ((e = in_frame(d, id)) >= 0) return e; return -1; } if ((e = in_frame(depth, id)) >= 0) return e; return depth > 0 ? in_frame(0, id) : -1;}static void exec_one(const Cmd *c);static void call(int f) { depth++; frame_at[depth] = nent; /* 부를 때마다 빈 칸에서 시작한다 */ for (int i = fstart[f]; i < fend[f]; i++) exec_one(&cmds[i]); nent = frame_at[depth]; /* 끝나면 그 칸은 사라진다 */ depth--;}static void exec_one(const Cmd *c) { int e; switch (c->op) { case VAR: if ((e = in_frame(depth, c->id)) < 0) { e = nent++; ent_id[e] = c->id; } ent_val[e] = c->arg; break; case SET: case ADD: if ((e = find(c->id)) >= 0) ent_val[e] = c->op == SET ? c->arg : ent_val[e] + c->arg; break; case PRINT: e = find(c->id); if (e < 0) strcpy(printed[dynamic][nprinted++], "?"); else snprintf(printed[dynamic][nprinted++], 12, "%d", ent_val[e]); break; case CALL: call(c->id); break; }}int main(void) { int n, cur = -1; scanf("%d", &n); for (int i = 0; i < n; i++) { char op[8], name[11]; scanf("%7s", op); if (strcmp(op, "end") == 0) { fend[cur] = ncmd; cur = -1; continue; } scanf("%10s", name); if (strcmp(op, "def") == 0) { cur = intern(fname, &nf, name); fstart[cur] = ncmd; continue; } Cmd c = {0, 0, 0}; if (strcmp(op, "call") == 0) { c.op = CALL; c.id = intern(fname, &nf, name); /* 뒤에서 정의되는 함수도 번호를 먼저 받는다 */ } else { c.op = strcmp(op, "var") == 0 ? VAR : strcmp(op, "set") == 0 ? SET : strcmp(op, "add") == 0 ? ADD : PRINT; c.id = intern(vname, &nv, name); if (c.op != PRINT) scanf("%d", &c.arg); } if (cur < 0) mainlist[nmain++] = ncmd; cmds[ncmd++] = c; } int count = 0; for (dynamic = 0; dynamic <= 1; dynamic++) { nent = depth = nprinted = 0; frame_at[0] = 0; for (int i = 0; i < nmain; i++) exec_one(&cmds[mainlist[i]]); count = nprinted; } for (int i = 0; i < count; i++) printf("%s %s\n", printed[0][i], printed[1][i]); return 0;}
Craykonmain.cray · 116 行
n = 控制台.读行().转数字();funcs = 字典(); // 함수 이름 → 몸체 명령들main = []; // 맨 바깥 명령들body = 空;循环 (i = 0; i < n; i++){ t = 控制台.读行().去空白().分割(" "); 如果 (t[0] == "def") { body = []; funcs[t[1]] = body; } 否则 如果 (t[0] == "end") { body = 空; } 否则 如果 (body == 空) { main.添加(t); } 否则 { body.添加(t); }}frames = []; // 0번이 전역, 끝이 지금 실행 중인 함수의 칸dynamic = 假;printed = [];// 이름을 가진 칸의 번호(없으면 -1)// 정적: 지금 칸 → 전역. 동적: 지금 칸 → 부른 함수의 칸 → … → 전역찾기(name){ top = frames.数量 - 1; 如果 (dynamic) { 循环 (d = top; d >= 0; d--) { 如果 (frames[d].包含(name)) { 返回 d; } } 返回 -1; } 如果 (frames[top].包含(name)) { 返回 top; } 如果 (frames[0].包含(name)) { 返回 0; } 返回 -1;}실행(cmds){ 遍历 (c 在 cmds) { 选择 (c[0]) { 情况 "var": frames[frames.数量 - 1][c[1]] = c[2].转数字(); 跳出; 情况 "set": 情况 "add": d = 찾기(c[1]); 如果 (d >= 0) { 如果 (c[0] == "set") { frames[d][c[1]] = c[2].转数字(); } 否则 { frames[d][c[1]] = frames[d][c[1]] + c[2].转数字(); } } 跳出; 情况 "print": d = 찾기(c[1]); 如果 (d >= 0) { printed.添加("" + frames[d][c[1]]); } 否则 { printed.添加("?"); } 跳出; 默认: frames.添加(字典()); // 부를 때마다 빈 칸에서 시작한다 실행(funcs[c[1]]); frames.按位置移除(frames.数量 - 1); // 끝나면 그 칸은 사라진다 跳出; } }}돌리기(동적인가){ dynamic = 동적인가; frames = [字典()]; printed = []; 실행(main); 返回 printed;}lex = 돌리기(假);dyn = 돌리기(真);循环 (i = 0; i < lex.数量; i++){ 控制台.输出(lex[i] + " " + dyn[i]);}