0Pricing
C Academy · Lekcja

Dodawanie zmiennych

Zapisze Pan/Pani wartości i odczyta je później.

Dodawanie zmiennych to bezpłatna lekcja C Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C Academy zawiera 4 lekcji w sumie.

Poza arytmetykę

Aby obsługiwać x = 5, a później x + 1, interpreter potrzebuje identyfikatorów oraz miejsca do przechowywania ich wartości. Tym miejscem jest środowisko.

Rozszerzymy lekser, parser i ewaluator tak, aby obsługiwały nazwy i przypisania.

Lekserowanie identyfikatorów

Identyfikator zaczyna się od litery lub znaku podkreślenia, a następnie może zawierać litery, cyfry lub znaki podkreślenia. Kopiujemy nazwę do tokenu.

Bufor o stałym rozmiarze upraszcza przykład; rzeczywiste leksery internują nazwy.

#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;
}

Węzeł zmiennej

AST zyskuje dwa rodzaje węzłów: odwołanie do zmiennej i przypisanie. Każdy przechowuje nazwę zmiennej.

Przypisanie przechowuje również wyrażenie, którego wartość zapisuje.

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;

Środowisko

Środowisko mapuje nazwy na wartości. W przypadku małego interpretera wystarczy prosta tablica par nazwa/wartość.

Większe języki używają tablic haszujących do wyszukiwania w czasie O(1) oraz zagnieżdżonych zakresów.

#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 */

Ustawianie zmiennej

Przypisanie aktualizuje istniejący slot albo dopisuje nowy. Wyszukiwanie liniowe pozwala zachować krótki kod.

Zwracanie zapisanej wartości pozwala używać przypisań jako wyrażeń, na przykład 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;
}

Odczytywanie zmiennej

Wyszukiwanie przegląda nazwy i zwraca wartość znalezionej zmiennej. Odwołanie do niezdefiniowanej zmiennej jest błędem wykonania.

Wykrycie tego błędu w tym miejscu pozwala wyświetlić jasny komunikat zamiast zwracać przypadkową wartość.

#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);
}

Parsowanie przypisania

Przypisanie ma najniższy priorytet i jest prawostronnie łączne. Parsujemy czynnik; jeśli jest nim samodzielny identyfikator, po którym następuje =, tworzymy węzeł assign.

W przeciwnym razie identyfikator jest po prostu odczytem zmiennej.

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();
}

Obliczanie zmiennych

Ewaluator zyskuje dwa przypadki. Węzeł zmiennej wyszukuje jej nazwę w środowisku, a węzeł assign oblicza wyrażenie wartości i zapisuje je.

Oba przypadki ponownie wykorzystują funkcje pomocnicze env_get i 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;
}

Działający krok REPL

Ten program zapisuje zmienną, a następnie odczytuje ją z powrotem w wyrażeniu — wszystko za pośrednictwem środowiska. Odwzorowuje jeden wiersz rzeczywistego interpretera.

Uruchom program, aby zobaczyć współpracę przypisywania i wyszukiwania.

#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;
}

Zakresy i przesłanianie

Pojedyncze płaskie środowisko jest globalne. Rzeczywiste języki dodają zagnieżdżone zakresy, aby zmienne lokalne funkcji nie nadpisywały nazw z zewnętrznych zakresów.

Implementuje się to przez łańcuchowe łączenie środowisk: wyszukiwanie przechodzi od najbardziej wewnętrznego zakresu na zewnątrz.

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

Co dalej

Mając zmienne, można dodawać instrukcje, instrukcje warunkowe i funkcje. Każdy z tych elementów to nowy rodzaj węzła oraz odpowiadający mu przypadek w eval.

Od tego miejsca ten sam potok lekser–parser–ewaluator można rozbudować do rzeczywistego małego języka.

Szybkie sprawdzenie

Zastanów się, za co odpowiada środowisko.

Podsumowanie

Dodali Państwo identyfikatory do leksera, węzły var/assign do AST, środowisko z operacjami set i get oraz przypadki eval dla odczytów i przypisań.

Interpreter pamięta teraz stan — to podstawa obsługi instrukcji i funkcji.

Często zadawane pytania

Czy lekcja „Dodawanie zmiennych” jest bezpłatna?

Tak — pełny tekst „Dodawanie zmiennych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C Academy, przejdź na CoddyKit PRO. Kurs C Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Dodawanie zmiennych”?

Zapisze Pan/Pani wartości i odczyta je później. Ćwiczysz C Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C Academy?

Nie wymagamy żadnego doświadczenia. C Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Dodawanie zmiennych”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C Academy?

Tak. Każda lekcja C Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Tokenizacja danych wejściowych
  2. Parsowanie wyrażeń
  3. Ewaluacja drzewa
  4. Dodawanie zmiennych
← Powrót do C Academy