0Pricing
C Academy · 课时

添加变量

存储并取回值。

添加变量 是 CoddyKit 上的免费 C Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C Academy 课程共包含 4 节课。

超越算术

为了支持x = 5以及之后的x + 1,解释器需要标识符以及存储它们值的位置。这个存储区域就是环境。

我们将扩展词法分析器、解析器和求值器,以处理名称和赋值。

词法分析标识符

标识符以字母或下划线开头,后面可以继续出现字母、数字或下划线。我们会将名称复制到令牌中。

固定大小的缓冲区可以让示例保持简单;实际的词法分析器会对名称进行驻留。

#include <ctype.h>
#include <string.h>

typedef struct { TokKind kind; int value; char name[32]; } Token;

static Token lex_ident(void) {
  Token t; t.kind = TOK_IDENT; int i = 0;
  while (isalnum((unsigned char)peek()) || peek() == '_')
    if (i < 31) t.name[i++] = advance(); else advance();
  t.name[i] = '\0';
  return t;
}

变量节点

AST 增加两种节点类型:变量引用和赋值。每种节点都会存储变量名称。

赋值节点还会保存它所存储值对应的表达式。

typedef struct Node {
  enum { N_NUM, N_BINOP, N_VAR, N_ASSIGN } kind;
  int value; char op; char name[32];
  struct Node *left, *right;  /* assign uses right as value expr */
} Node;

环境

环境将名称映射到值。对于小型解释器,简单的名称/值对线性数组就足够了。

更大型的语言会使用哈希表,以实现 O(1) 查找和嵌套作用域。

#define MAX_VARS 64

typedef struct {
  char names[MAX_VARS][32];
  int  values[MAX_VARS];
  int  count;
} Env;

static Env env;  /* global for this tiny example */

设置变量

赋值操作要么更新已有的槽位,要么追加一个新槽位。线性搜索可以让代码保持简短。

返回存储的值后,赋值就可以作为表达式使用,例如y = (x = 3)。

#include <string.h>

static int env_set(const char *name, int v) {
  for (int i = 0; i < env.count; i++)
    if (strcmp(env.names[i], name) == 0) {
      env.values[i] = v; return v;
    }
  strcpy(env.names[env.count], name);
  env.values[env.count] = v;
  env.count++;
  return v;
}

读取变量

查找会扫描名称并返回其值。引用未定义变量会产生运行时错误。

在这里捕获该错误,可以给出清晰的消息,而不是返回一个垃圾值。

#include <stdio.h>
#include <stdlib.h>

static int env_get(const char *name) {
  for (int i = 0; i < env.count; i++)
    if (strcmp(env.names[i], name) == 0)
      return env.values[i];
  fprintf(stderr, "undefined variable: %s\n", name);
  exit(1);
}

解析赋值

赋值的优先级最低,并且具有右结合性。我们先解析一个因子;如果它是一个裸标识符,后面紧跟=,就构建一个赋值节点。

否则,该标识符就只是一次变量读取。

static Node *parse_assign(void) {
  if (cur().kind == TOK_IDENT) {
    char saved[32]; strcpy(saved, cur().name);
    bump();
    if (cur().kind == TOK_ASSIGN) {
      bump();
      Node *val = parse_assign();
      return assign_node(saved, val);
    }
    return var_node(saved);  /* not an assignment */
  }
  return parse_expr();
}

计算变量

求值器增加了两种情况。变量节点会在环境中查找其名称;赋值节点会计算它的值表达式并将结果存储起来。

两者都会复用env_get和env_set辅助函数。

int eval(Node *n) {
  switch (n->kind) {
    case N_NUM:    return n->value;
    case N_VAR:    return env_get(n->name);
    case N_ASSIGN: return env_set(n->name, eval(n->right));
    case N_BINOP: {
      int l = eval(n->left), r = eval(n->right);
      switch (n->op) {
        case '+': return l + r; case '-': return l - r;
        case '*': return l * r; case '/': return l / r;
      }
    }
  }
  return 0;
}

一个可运行的 REPL 步骤

这个程序先存储一个变量,然后通过环境在表达式中读回它。它模拟了真实解释器的一行操作。

运行它,观察赋值和查找如何协同工作。

#include <stdio.h>
#include <string.h>

#define MAX_VARS 64
static char names[MAX_VARS][32];
static int  values[MAX_VARS];
static int  count;

static int env_set(const char *n,int v){
  for(int i=0;i<count;i++) if(!strcmp(names[i],n)){values[i]=v;return v;}
  strcpy(names[count],n); values[count]=v; count++; return v;
}
static int env_get(const char *n){
  for(int i=0;i<count;i++) if(!strcmp(names[i],n)) return values[i];
  return 0;
}

int main(void){
  env_set("x", 5);            /* x = 5 */
  int r = env_get("x") + 1;   /* x + 1 */
  printf("x = %d\n", env_get("x"));
  printf("x + 1 = %d\n", r);
  return 0;
}

作用域与遮蔽

单一的扁平环境是全局环境。实际语言会添加嵌套作用域,使函数的局部名称不会覆盖外层名称。

您可以通过链接多个环境来实现这一点:查找会从最内层作用域向外层逐级进行。

typedef struct Env {
  char names[MAX_VARS][32];
  int  values[MAX_VARS];
  int  count;
  struct Env *parent;  /* enclosing scope */
} Env;

下一步方向

有了变量之后,您就可以添加语句、条件和函数。每一种功能都对应一种新的节点类型以及一个求值分支。

从这里开始,同一套词法分析器—解析器—求值器流水线可以扩展为真正的小型语言。

快速检查

思考一下环境负责什么。

回顾

您为词法分析器添加了标识符,为 AST 添加了变量/赋值节点,添加了支持设置和获取的环境,并为读取和赋值添加了求值分支。

现在,解释器可以记住状态,这为语句和函数奠定了基础。

常见问题解答

「添加变量」课时是免费的吗?

是的 — 「添加变量」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C Academy 课程的其余内容,请升级到 CoddyKit PRO。 C Academy 课程共包含 4 节课。

「添加变量」这节课中我会学到什么?

存储并取回值。 你通过在浏览器中直接运行的动手代码来练习 C Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「添加变量」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C Academy 课中编写并运行代码吗?

能。每节 C Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 标记化输入
  2. 解析表达式
  3. 计算语法树
  4. 添加变量
← 返回 C Academy