Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Внутри CPython гид по интерпретатору Python.pdf
Скачиваний:
6
Добавлен:
07.04.2024
Размер:
8.59 Mб
Скачать

Базовый синтаксис C    341

1. #ifdef <макрос> включает следующий блок текста, если указанный макрос определен. Также эта конструкция иногда записывается в виде

#if defined(<макрос>).

2. #ifndef <макрос> включает следующий блок текста, если указанный макрос не определен.

3.#if <макрос> включает следующий блок текста, если макрос определен и его вычисление дает результат True.

Обратите внимание: для описания того, что включается или исключается из файла, используется термин «текст», а не «код». Препроцессор ничего не знает о синтаксисе C, и его не интересует, что собой представляет заданный текст.

#pragma

Директивы #pragma содержат инструкции или рекомендации для компилятора. Как правило, при чтении кода на них можно не обращать внимания, так как обычно они влияют на компиляцию кода, а не на его выполнение.

#error

Наконец, директива #error выводит сообщение и заставляет препроцессор прервать обработку. При чтении исходного кода CPython эти директивы также можно смело игнорировать.

БАЗОВЫЙ СИНТАКСИС C

Этот раздел не охватывает все аспекты языка C, и я не ожидаю, что вы научитесь писать на нем код. Основное внимание здесь уделяется тем частям C, которые могут показаться незнакомыми или непонятными для Pythonразработчиков.

Общие сведения

В отличие от Python, пробельные символы (whitespace) не важны для компилятора C. Компилятор не обратит внимания на то, что одна команда разбита

Книги для программистов: https://t.me/booksforits

342    Приложение. Введение в C для Python-программистов

на несколько строк или же вся программа состоит из одной очень длинной строки. Это объясняется тем, что во всех командах и блоках используются разделители.

Конечно, существуют очень конкретные правила для парсера, но в общем случае для понимания исходного кода CPython достаточно знать, что каждая команда завершается точкой с запятой (;), а все блоки кода заключаются в фигурные скобки ({}).

У этого правила есть исключение: если блок состоит только из одной команды, то фигурные скобки можно опустить.

Все переменные в C должны быть объявлены, то есть в программе должна присутствовать одна команда, в которой указывается тип этой переменной. Обратите внимание: в отличие от Python, тип данных, хранящихся в одной переменной, меняться не может.

Рассмотрим несколько примеров:

/* Комментарии размещаются между символами "косая черта-звездочка" */ /* и "звездочка-косая черта" */ /* Такие комментарии могут занимать несколько строк -

а значит, эта часть все еще является комментарием */

//Комментарии также могут следовать за двумя косыми чертами

//Такие комментарии могут идти только до конца строки, поэтому новые

//строки также должны начинаться с двойной косой черты (//)

int x = 0; // Объявляет x типа 'int' и инициализирует его со значением 0

if (x == 0) {

// Это блок кода

int y = 1; // Имя переменной y действительно только до закрывающей скобки } // Другие команды

printf("x is %d y is %d\n", x, y);

}

// В однострочных блоках фигурные скобки не нужны if (x == 13)

printf("x is 13!\n"); printf("past the if block\n");

Как правило, код CPython очень четко отформатирован, а в пределах модуля обычно применяется единый стиль.

Книги для программистов: https://t.me/booksforits

Базовый синтаксис C    343

Операторы if

ВC оператор if работает в целом так же, как в Python: если условие истинно, то выполняется следующий за ним блок. Синтаксис else и elseif должен быть знаком большинству Python-программистов. Следует помнить, что операторам if в C endif не нужен, потому что блоки ограничиваются фигурными скобками {}.

ВC существует сокращенная запись для коротких команд if … else — так называемый тернарный оператор:

условие ? true_результат : false_результат

Например, такой оператор встречается в файле semaphore.c, где он используется для определения макроса SEM_CLOSE() на Windows:

#define SEM_CLOSE(sem) (CloseHandle(sem) ? 0 : -1)

Возвращаемое значение этого макроса будет равно 0, если функция CloseHandle() возвращает true, или -1 в противном случае.

ПРИМЕЧАНИЕ

Переменные логического типа поддерживаются и используются в раз­ ных частях исходного кода CPython, но они не являются частью языка. C интерпретирует бинарные условия по простому правилу: 0 или NULL интерпретируется как false, а все остальное — как true.

Операторы switch

В отличие от Python, C также поддерживает команду switch. Эту команду можно рассматривать как сокращенную запись для расширенных цепочек if … elseif. Следующий пример взят из файла semaphore.c:

switch (WaitForSingleObjectEx(handle, 0, FALSE)) { case WAIT_OBJECT_0:

if (!ReleaseSemaphore(handle, 1, &previous)) return MP_STANDARD_ERROR;

*value = previous + 1;

Книги для программистов: https://t.me/booksforits

344    Приложение. Введение в C для Python-программистов

return 0; case WAIT_TIMEOUT:

*value = 0; return 0;

default:

return MP_STANDARD_ERROR;

}

Эта команда осуществляет выбор в зависимости от возвращаемого значения

WaitForSingleObjectEx(). Если значение равно WAIT_OBJECT_0, то выполняется первый блок. При значении WAIT_TIMEOUT выполняется второй блок, а все остальные значения перехватываются блоком default.

Обратите внимание: проверяемое значение (в данном случае значение, возвращаемое WaitForSingleObjectEx()) должно быть целочисленного или перечисляемого типа, а в каждой секции case должна быть указана константа.

Циклы

ВC существует три разновидности циклических конструкций:

1.Циклы for.

2.Циклы while.

3.Циклы do while.

Синтаксис циклов for заметно отличается от Python:

for ( <инициализация>; <условие>; <приращение>) { <многократно выполняемый код>

}

Кроме многократно выполняемого кода цикл for содержит три блока:

1.Блок <инициализация> выполняется ровно один раз при запуске цикла. Обычно он используется для присваивания начального значения счетчику цикла (и, возможно, объявления счетчика цикла).

2.Блок <приращение> выполняется после каждого прохождения основного блока цикла. Традиционно в нем увеличивается счетчик цикла.

3.Наконец, блок <условие>, который выполняется после блока <приращение>. Вычисляется возвращаемое значение, и цикл прерывается, если условие возвращает false.

Книги для программистов: https://t.me/booksforits

Базовый синтаксис C    345

Пример из файла Modules/sha512module.c:

for (i = 0; i < 8; ++i) {

S[i] = sha_info->digest[i];

}

Цикл выполняется 8 раз, с увеличением i от 0 до 7, и завершается, когда при проверке условия окажется, что значение i достигло 8.

Циклы while практически идентичны своим аналогам в Python, а синтаксис do … while несколько отличается. Условие цикла do ... while проверяется только после того, как цикл будет выполнен в первый раз.

Примеры использования циклов for и while в кодовой базе CPython встречаются достаточно часто, но цикл do ... whilе не используется.

Функции

По синтаксису функций язык C похож на Python, не считая того, что в C типы возвращаемого значения и параметров должны быть заданы явно.

Функция в C выглядит так:

<возвращаемый_тип> имя_функции(<параметры>) { <тело_функции>

}

Возвращаемым типом может быть любой действительный тип C, включая встроенные типы (такие, как int и double), а также специальные типы вроде PyObject, как в следующем примере из semaphore.c:

static PyObject *

semlock_release(SemLockObject *self, PyObject *args)

{

<команды тела функции>

}

Здесь вы видите несколько особенностей, характерных для C. Прежде всего напомню, что пробельные символы игнорируются. В исходном коде CPython возвращаемый функцией тип очень часто указывается в строке над остальной частью объявления функции (фрагмент PyObject *). Смысл символа * будет описан позднее, а пока достаточно сказать, что к функциям и переменным могут применяться различные модификаторы. static — один из таких модификаторов.

Книги для программистов: https://t.me/booksforits

346    Приложение. Введение в C для Python-программистов

Работой модификаторов управляют довольно сложные правила. Например, смысл модификатора static в данном случае полностью отличается от того, что он означал бы при размещении перед объявлением переменной.

К счастью, когда вы пытаетесь прочитать и понять исходный код CPython, модификаторы обычно можно игнорировать.

Список параметров для функций представляет собой переменные, разделенные запятыми, как и в Python. И снова C требует указания типов всех параметров, так что SemLockObject *self сообщает, что первый параметр содержит указатель на SemLockObject и называется self. Все параметры в C являются позиционными.

А теперь разберемся, что же означает «указатель» в этой команде.

Добавим немного контекста: параметры передаются функциям C по значению. Это означает, что функция работает с копией значения, а не с исходным значением в вызывающей функции. Чтобы обойти это ограничение, функциям часто передается адрес данных, чтобы эти данные могли изменяться функцией.

Эти адреса называются указателями и обладают типом. Так, int * — указатель на целое число, и этот тип отличается от double * — указателя на число с плавающей точкой с двойной точностью.

Указатели

Как упоминалось ранее, указатели представляют собой переменные, которые хранят адрес значения. Они часто используются в C, как показано в следующем примере:

static PyObject *

semlock_release(SemLockObject *self, PyObject *args)

{

<команды тела функции>

}

Здесь параметр self содержит адрес значения SemLockObject (то есть указатель на него). Также обратите внимание на то, что функция возвращает указатель на значение PyObject.

В C существует специальное значение NULL; оно сообщает, что указатель ни на что не указывает. В исходном коде CPython присваивание NULL и проверка указателей на NULL встречаются достаточно часто. Такие проверки важны,

Книги для программистов: https://t.me/booksforits

Базовый синтаксис C    347

потому что ограничения на возможные значения указателей минимальны, а обращение к адресу памяти, не принадлежащему вашей программе, может привести к ее странному поведению.

С другой стороны, если вы попытаетесь обратиться к памяти по адресу NULL, программа немедленно завершится. Может показаться, что это слишком радикальное решение, но обычно проще искать ошибку памяти, проверяя указатель на NULL, чем проверяя, что некоторый адрес в памяти был изменен.

Строки

В языке C нет строкового типа. Существует условное соглашение, на базе которого написаны многие функции стандартной библиотеки, но реального типа не существует. Строки в C хранятся в виде массивов char (для ASCII) или wchar (для Юникода); каждый из этих типов содержит один символ. Строки помечаются нуль-терминатором, то есть значением 0, которое обычно обозначается в коде как \0.

Основные строковые операции, такие как strlen(), полагаются на расположение нуль-терминатора, отмечающего конец строки.

Так как строки представляют собой массивы значений, их нельзя сравнивать или копировать напрямую. Для выполнения этих операций в стандартной библиотеке имеются функции strcpy() и strcmp() (и их аналоги для wchar).

Структуры

В последнем разделе нашего мини-обзора C рассмотрим возможность создания новых типов в C. Ключевое слово struct позволяет сгруппировать набор разных типов данных в новый нестандартный тип данных — структуру:

struct <имя_структуры> { <тип> <имя_поля>; <тип> <имя_поля>;

...

};

Следующий фрагмент из файла Modules/arraymodule.c демонстрирует объявление структуры:

struct arraydescr { char typecode;

Книги для программистов: https://t.me/booksforits