Читать книгу Мыслим на Си - Группа авторов - Страница 3
Глава 2. Переменные и типы данных – Как я вышла на Reddit
ОглавлениеПролог: Первый пост на английском
Мне было почти одиннадцать лет. Пять лет я изучала Си, пять лет писала код, компилировала, ломала, чинила. Я понимала указатели лучше, чем английскую грамматику. Я знала, как работает память, но не знала времён глаголов.
Но у меня была идея. Мысль, которая не давала мне спать: можно ли написать программу, которая думает?
Я хотела обсудить это с кем-то. Папа был занят. Тётя Люда не понимала программирования. Соседская Настя давно уехала учиться в другой город. Мне нужны были люди, которые пишут код. Программисты. Инженеры.
Я зашла на Reddit. Раздел r/programming. Тысячи людей, которые обсуждают алгоритмы, языки, архитектуру. Но все говорили по-английски.
На Reddit не принято хвастаться возрастом или учёными званиями. Здесь важен только код. Только идеи. Это меня устраивало – никто не узнает, что мне нет и одиннадцати.
Я села за клавиатуру. Открыла текстовый редактор. Мне нужно было написать пост. Объявление. Манифест. Но как?
Я знала несколько фраз из учебника английского: Hello. My name is… I am twelve years old. The cat is on the table . Грамматику я не понимала. Артикли казались бессмысленными. Времена глаголов – хаосом.
Но я знала Си. Я знала его синтаксис, его логику, его ритм.
И тогда я подумала: "А что если написать пост как код?"
Я набрала:
c// PhoeNIX: Hello, world.
// Goal: write program that think.
// Status: learning, experimenting.
// Question: who have ideas? Let's discuss.
// Return: knowledge++;
Грамматически это было ужасно. Write program that think вместо правильного write a program that thinks . Who have ideas вместо who has ideas . Но структура была правильной. Комментарии. Цель. Статус. Вопрос. Возврат.
Я нажала Submit .
Сердце билось так громко, что я слышала его в ушах.
Часть 1: Как я чиркнула спичкой
Через пять минут пришёл первый ответ:
Nice try, kid. But "thinking" is not a data type. You need to define what "think" means in computational terms.
Потом второй:
Are you talking about neural networks? Expert systems? Symbolic AI?
Третий:
This is stupid. Computers don't think. They execute instructions.
Четвёртый возразил третьему:
@user3 That's reductive. Turing test exists for a reason. If it behaves like thinking, maybe it IS thinking.
И началось.
Как я потом говорила: "Я чиркнула спичкой, а чему гореть – уже было". Мои оппоненты спорили друг с другом. Высказывали идеи одна безумнее другой. Дискутировали о философии сознания, о тесте Тьюринга, о китайской комнате Сёрла, о нейронных сетях и символьных системах.
А я сидела с английским словарём и переводила слова. Одно за другим. Neural – нейронный. Network – сеть. Symbolic – символьный. Execute – выполнять. Instruction – инструкция.
Часть 2: Программа для изучения языка
Но переводить каждое слово вручную было медленно. И тогда я подумала: "Я же программист. Почему бы не написать программу?"
Я скопировала все комментарии из треда в текстовый файл comments.txt. Открыла редактор. Написала простую программу на Си:
c#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX_WORDS 10000
#define MAX_WORD_LEN 50
typedef struct {
char word[MAX_WORD_LEN];
int count;
} WordFreq;
WordFreq words[MAX_WORDS];
int word_count = 0;
void to_lowercase(char *str) {
for (int i = 0; str[i]; i++) {
str[i] = tolower(str[i]);
}
}
int find_word(char *word) {
for (int i = 0; i < word_count; i++) {
if (strcmp(words[i].word, word) == 0) {
return i;
}
}
return -1;
}
void add_word(char *word) {
int index = find_word(word);
if (index != -1) {
words[index].count++;
} else {
strcpy(words[word_count].word, word);
words[word_count].count = 1;
word_count++;
}
}
int main() {
FILE *file = fopen("comments.txt", "r");
char word[MAX_WORD_LEN];
while (fscanf(file, "%s", word) != EOF) {
to_lowercase(word);
// Убираем знаки препинания
int len = strlen(word);
while (len > 0 && !isalpha(word[len-1])) {
word[–len] = '\0';
}
if (len > 0) {
add_word(word);
}
}
fclose(file);
// Сортируем по частоте (простой пузырёк)
for (int i = 0; i < word_count – 1; i++) {
for (int j = 0; j < word_count – i – 1; j++) {
if (words[j].count < words[j+1].count) {
WordFreq temp = words[j];
words[j] = words[j+1];
words[j+1] = temp;
}
}
}
// Выводим топ-50
printf("Top 50 most frequent words:\n");
for (int i = 0; i < 50 && i < word_count; i++) {
printf("%3d. %-20s (%d times)\n", i+1, words[i].word, words[i].count);
}
return 0;
}
Эта программа:
Читает файл comments.txt
Разбивает текст на слова
Подсчитывает, сколько раз каждое слово встречается
Сортирует по частоте
Выводит топ-50 самых частых слов
Результат выглядел так:
textTop 50 most frequent words:
1. the (347 times)
2. is (201 times)
3. a (156 times)
4. to (134 times)
5. think (89 times)
6. program (76 times)
7. computer (71 times)
8. neural (54 times)
9. network (52 times)
10. data (48 times)
Я взяла этот список. Убрала служебные слова (the , is , a , to – они бесполезны для понимания смысла). Осталось около 200 ключевых слов.
Я начала переводить и запоминать самые часто используемые. Think – думать. Program – программа. Computer – компьютер. Neural network – нейронная сеть. Data – данные.
Часть 3: Переменные – это слова
Я заметила закономерность. В английском, как и в Си, есть типы слов.
Существительные – это переменные типа int или char*. Они хранят объекты: program , computer , network .
Глаголы – это функции. Они выполняют действия: think , execute , process .
Прилагательные – это константы или модификаторы. Они описывают свойства: neural , symbolic , computational .
Я начала строить предложения как код:
c// Английское предложение:
// "The program thinks using neural networks"
// Как я это понимала:
const char* subject = "program"; // Подлежащее
const char* verb = "thinks"; // Глагол
const char* method = "neural networks"; // Метод
printf("The %s %s using %s\n", subject, verb, method);
Это работало! Через месяц я бойко дискутировала короткими фразами:
Consciousness ≠ computation?Neural networks = pattern matching?
Thinking = information processing?
Люди отвечали. Спорили. Объясняли. И с каждым днём мой словарный запас рос.
Часть 4: Переменные в Си – коробки для данных
Вернёмся к Си. Что такое переменная?
Переменная – это имя для куска памяти. Компьютер выделяет место в оперативной памяти и говорит: "Вот адрес 0x7ffd1234. Теперь этот адрес называется age".proza+1
Простейшая программа с переменной:
c#include <stdio.h>
int main() {
int age; // Объявляем переменную
age = 11; // Присваиваем значение
printf("Мне почти %d лет\n", age);
return 0;
}
Что происходит в памяти?
Компилятор видит int age; и резервирует 4 байта памяти
Эти 4 байта получают имя age
Команда age = 11; записывает число 11 в эти байты: 00 00 00 0B (в шестнадцатеричном)
printf читает эти байты и выводит их как число
Часть 5: Типы данных – грамматика Си
В Си нельзя просто сказать "создай переменную". Нужно указать тип – какие данные она будет хранить.
Это как части речи в языке. Нельзя сказать "слово". Нужно: "существительное", "глагол", "прилагательное".
int – целые числа
cint age = 11;
int temperature = -5;
int population = 1000000;
int хранит целые числа (обычно от -2,147,483,648 до 2,147,483,647). Занимает 4 байта.
char – символы
cchar letter = 'P'; // Первая буква PhoeNIX
char digit = '7';
char newline = '\n';
char хранит один символ. Занимает 1 байт.proza+1
Но секрет: char – это на самом деле маленькое целое число. Символ 'P' – это число 80 (код ASCII). 'h' – 104. 'o' – 111.
cchar letter = 'P';
printf("%c\n", letter); // Выведет: P
printf("%d\n", letter); // Выведет: 80
float – дробные числа
cfloat pi = 3.14159;
float temperature = -5.5;
float хранит числа с плавающей точкой. Занимает 4 байта. Точность – около 6-7 десятичных знаков.
double – дробные числа двойной точности
cdouble precise_pi = 3.141592653589793;
double – это float с двойной точностью. Занимает 8 байт. Точность – около 15-16 знаков.
Таблица типов
ТипРазмерДиапазонСпецификаторchar1 байт-128 до 127%c или %dint4 байта±2 млрд%dfloat4 байта±3.4×10³⁸%fdouble8 байт±1.7×10³⁰⁸%lf
Часть 6: Структуры – группы переменных
В моей программе для подсчёта слов я использовал структуру:
ctypedef struct {
char word[MAX_WORD_LEN];
int count;
} WordFreq;
Структура – это способ объединить несколько переменных в одну. Как коробка с отделениями: в одном отделении – слово (char word), в другом – счётчик (int count).
Создаём переменную типа структуры:
cWordFreq w;
strcpy(w.word, "think");
w.count = 89;
printf("Слово '%s' встречается %d раз\n", w.word, w.count);
// Слово 'think' встречается 89 раз
Точка . означает "обратиться к полю структуры".
Массив структур – это таблица:
cWordFreq words[10000]; // Массив из 10000 структур
words[0].word = "think";
words[0].count = 89;
words[1].word = "program";
words[1].count = 76;
Так я хранил словарь частотности.
Часть 7: Инициализация – создание и заполнение
Вы можете объявить переменную и присвоить ей значение в одной строке:
cint age = 11;
char initial = 'Z';
float pi = 3.14159;
Это инициализация. Безопаснее, чем в два шага.
Почему? Потому что неинициализированная переменная содержит мусор:proza+1
cint x; // Не инициализировали!
printf("%d\n", x); // Может вывести 0, может 184759392, может -5
В Unix-философии: "Будь явным, не неявным". Если создаёшь переменную – сразу скажи, что в ней должно быть.
Часть 8: Константы – переменные, которые не меняются
cconst int MAX_WORDS = 10000;
const float PI = 3.14159;
MAX_WORDS = 5000; // ОШИБКА! Компилятор не позволит
Или через препроцессор:
c#define MAX_WORDS 10000
#define MAX_WORD_LEN 50
#define – текстовая замена до компиляции. Препроцессор просто заменяет MAX_WORDS на 10000 везде в коде.
Часть 9: Метафора – память это файл
В Unix всё – файл. Клавиатура – файл (/dev/input). Экран – файл (/dev/fb0). Память – тоже файл (/dev/mem).
Переменная – это имя файла в памяти. Тип – это формат файла. Значение – это содержимое.
cint age = 11;
Это как создать файл /memory/age размером 4 байта с содержимым 00 00 00 0B.
cage = 12;
Это как открыть файл, стереть содержимое и записать новое: 00 00 00 0C.
В Linux вы можете увидеть память процесса:
bashcat /proc/self/maps
Всё прозрачно. Всё доступно. Это Unix.
Часть 10: Философия обучения через код
Когда я учила английский через частотный анализ, я не учила грамматику. Я учила паттерны.
Neural networks process data – паттерн: прилагательное + существительное + глагол + существительное.The program thinks – паттерн: артикль + существительное + глагол.
Компьютер помог мне увидеть структуру. Как компилятор видит структуру кода.
Через месяц я писала:
Neural network = weighted graph. Thinking = pattern recognition. Consciousness = emergent property?
Короткие фразы. Без артиклей. Без сложных времён. Но понятные. Логичные. Правильные.
Один пользователь написал:
@PhoeNIX Your English is improving fast. How old are you, by the way?
Я ответила:
Age = irrelevant. Ideas = relevant.
Он засмеялся (написал lol ) и больше не спрашивал.
На Reddit важен только код. Только идеи. Возраст не имеет значения.
Заключение главы
Теперь вы знаете:
Что такое переменные и типы данных
Как объявлять и инициализировать переменные
Что такое структуры и массивы
Как память работает как файловая система
Что код может помочь учить человеческий языкotvet.mail
В следующей главе мы изучим операторы – сложение, вычитание, сравнение, логику. Мы научимся принимать решения и повторять действия.
Практическое задание:
Напишите программу, которая подсчитывает частоту слов в тексте. Используйте структуры и массивы. Не обязательно делать сортировку – просто посчитайте и выведите.
Потом возьмите любой текст на английском (статью, форум, книгу), пропустите через программу и начните учить самые частые слова.otvet.mail
Компьютер – лучший учитель языка. Он не осуждает ошибки. Он просто показывает паттерны
P.S. от PhoeNIX:
Кто-то сказал мне на Reddit: You think in code, not in language.
Я ответила: Code IS language. Universal language.
И это правда.
Переменные – это слова. Типы – грамматика. Программы – предложения.
Если вы понимаете это, вы можете говорить с кем угодно. На любом языке.
Потому что логика универсальна.
Зара
Часть 11: Код говорит громче слов
Через две недели после первого поста я заметила изменение. Люди начали скидывать свои исходники на Си – прямо в тред. Не абстрактные рассуждения о философии сознания, а реальный код.
@PhoeNIX Here's my attempt at a simple pattern matcher. Thoughts?
cint match_pattern(char *text, char *pattern) {
int i, j;
for (i = 0; text[i] != '\0'; i++) {
for (j = 0; pattern[j] != '\0' && text[i+j] == pattern[j]; j++);