Pascal vs. C++

Кувшинов Д.Р.

2016


Общее оглавление


Данный раздел предназначен для облегчения начала изучения C++ теми, кто знает Pascal.

Таблица-сравнение

Сравнение Pascal (Free Pascal) и C++ (упрощённо, структурное программирование)
Pascal C++ Замечание
 program MyProg;
 unit MyUnit;
 library MyLib;
 interface
 implementation

C++ не предоставляет средств для объявления модулей. Модулем считается файл с исходным кодом. Название модуля — название файла.

Процесс трансляции в C и C++.

 uses MyUnit;
 #include "my_unit.h"
Неточное соответствие: строка #include просто заменяется на содержимое указанного файла.
 (* комментарий *)
 { комментарий }
 /* комментарий */

Составной оператор

 begin
 end

Блок

 {
 }
 program Empty;
 begin
 end.
 int main()
 {
 }

При запуске программы выполняется функция main.

Блоки могут быть только телами функций или располагаться внутри тел функций. “Свободных” блоков нет.

Строковая константа

 'I don''t know what''s this: "'#199'".'

Строковый литерал

 "I don't know what's this: \"\xC7\"."

Строковые константы заключаются в двойные кавычки. Специальные символы доступны через экранирование обратной косой чертой \ (“escape-последовательность”).

См. также здесь

 { Hello, world! }
 program Hello;
 begin
   writeln('Hello, world!')
 end.
 // Hello, world!
 #include <iostream>
 int main()
 {
   std::cout << "Hello, world!\n";
 }

Файл iostream — часть Стандартной библиотеки C++, используется для организации текстового ввода-вывода.

stdпространство имён, в которое “спрятаны” все определения Стандартной библиотеки C++.

\n — обозначение символа новой строки.

; нужна даже перед завершением блока.

Скобки после имени функции нужны даже в том случае, когда функция не принимает параметров.

 function sqr(x: Integer): Integer;
 begin
   sqr := x * x
 end;
 int sqr(int x)
 {
   return x * x;
 }
Тип указывается перед именем переменной, параметра или функции. В C++ нельзя указать результат вычисления функции через присваивание. Вместо этого результат указывается как “аргумент” инструкции return, выполнение которой приводит к выходу из тела функции.
 procedure hello(const name: String);
 begin
   write('Hello, ');
   writeln(name)
 end;
 void hello(const char *name)
 {
   std::cout << "Hello, "
     << name << std::endl;
 }

В C++ нет разделения на “процедуры” и “функции”. Процедуры — это функции, не возвращающие значения. Для указания этого факта используется специальный тип void.

Строки в C++ — отдельная тема.

return; (без аргумента) можно использовать, чтобы закончить выполнение такой функции.

 var
   i, j: Integer;
   a, b: Single;
   x, y: Double;
   enabled: Boolean;
 int i, j;
 float a, b;
 double x, y;
 bool enabled;

Секции var нет. Переменные определяются в произвольном нужном месте функции, их область видимости ограничена блоком. Глобальные переменные определяются вне функций и доступны фунциям, определённым ниже по тексту.

Значения переменных по умолчанию не определены.

 var
   bin: Integer = %1001;
   hex: Integer = $ABCD;
   ch: Char = chr(65);
   codA: Integer = ord('A');
 int bin = 0b1001;
 int hex = 0xABCD;
 int oct = 07337;
 char ch = 65;
 int codA = 'A';

Для записи целочисленных констант в двоичной форме используется префикс 0b или 0B.

Для записи целочисленных констант в шестнадцатеричной форме используется префикс 0x или 0X.

Для записи целочисленных констант в восьмиричной форме используется префикс 0.

Символьный тип является целочисленным (“байт”). Символьные константы записываются в одинарных кавычек и не считаются строками.

 const
   meaning = 42;
 const int meaning = 42;
Секции const нет. Константность является частью типа и включается в описание типа.
 function length(x, y: Double): Double;
 begin
   length := sqrt(
    x * x + y * y)
 end;
 #include <cmath>
 double length(double x, double y)
 {
   return std::sqrt(
      x * x + y * y);
 }

Функция sqrt, вычисляющая квадратный корень, объявлена в cmath. Подробнее о вычислениях в плавающей точке см. здесь

При определении функции в C++ необходимо указывать тип для каждого параметра функции.

Оператор ветвления

 if a < b then
   write('a is too low!');

Инструкция if

 if (a < b)
   std::cout << "a is too low!";

Условие обязательно помещается в скобки. Ключевого слова then нет.

Оператор ветвления — полный

 if a < b then
   write('a is too low!')
 else
   write('a is ok');

Инструкция if-else

 if (a < b)
   std::cout << "a is too low!";
 else
   std::cout << "a is ok";
 if a < b then
   write('a is too low!')
 else if a > c then
   write('a is too high!')
 else
   write('a is ok');
 if (a < b)
   std::cout << "a is too low!";
 else if (a > c)
   std::cout << "a is too high!";
 else
   std::cout << "a is ok";
Можно вкладывать произвольное количество веток else-if.
 procedure DontRunMe;
 label forever;
 begin
   forever: write('*');
   goto forever
 end;
 void DontRunMe()
 {
 forever:
   std::cout << '*';
   goto forever;
 }

Секции label нет, метки для goto не требуется объявлять заранее.

Вызов функции, не принимающей параметров также требует наличия скобок:

 DontRunMe();

Выбор варианта

 case season of
   0: write('winter');
   1: write('spring');
   2: write('summer');
   3: write('autumn');
   else write('error');
 end;

Инструкция switch-case

 switch (season)
 {
 case 0:
   std::cout << "winter";
   break;
 case 1:
   std::cout << "spring";
   break;
 case 2:
   std::cout << "summer";
   break;
 case 3:
   std::cout << "autumn";
   break;
 default:
   std::cout << "error";
 }

Проверяемое значение должно быть заключено в скобки: switch (season), а не switch season. Далее должен следовать блок, содержащий метки case.

Принцип действия можно условно описать как goto case season: выполнение передаётся на соответствующую метку case, поэтому требуется явный выход из блока (break;) для того, чтобы избежать выполнения кода, расположенного под нижележащими метками case.

В качестве “целей” case могут быть указаны только целочисленные константы. Строки, списки и диапазоны значений не допускаются.

Переход на метку default осуществляется в том случае, если среди меток case нет подходящей.

 if x <> 0 then
   y := 0
 else if y = 0 then
   x := 1;
 if (x != 0)
   y = 0;
 else if (y == 0)
   x = 1;

Присваивание записывается через одно “равно”: =.

Сравнение на равенство записывается через два “равно”: ==.

Сравнение на неравенство записывается !=.

Решение квадратного уравнения

 function SolveQuadratic(
   a, b, c: Double;
   var x1, x2: Double): Boolean;
 var d: Double;
 begin
   result := false;
   if a <> 0 then
   begin
     d := b * b - 4.0 * a * c;
     if d >= 0 then
     begin
       x1 := (-b - d) / (2.0 * a);
       x2 := (-b + d) / (2.0 * a);
       result := true;
     end
   end
 end;
 bool SolveQuadratic(
   double a, double b, double c,
   double &x1, double &x2)
 {
   if (a == 0)
     return false;

   double d = b * b - 4.0 * a * c;
   if (d < 0)
     return false;

   d = std::sqrt(d);
   x1 = (-b - d) / (2.0 * a);
   x2 = (-b + d) / (2.0 * a);
   return true;
 }

Передача переменных по ссылке осуществляется с помощью “ссылочного типа” — параметры x1 и x2 имеют тип “ссылка на переменную типа double”.

 inc(i);
 dec(j);
 ++i;
 --j;

Инкремент переменной: ++.

Декремент переменной: --.

 x ** y
 // #include <cmath>
 std::pow(x, y)
Возведение в степень выполняется с помощью стандартной функции pow, объявленной, как и sqrt, в cmath.

Возведение в степень

 n div p
 n mod p

Функция возведения в степень

 n / p
 n % p

Деление нацело также записывается как / и применяется компилятором в том случае, когда оба операнда — целые.

Операция взятия остатка от деления записывается через %.

 ord(a), succ(a), pred(a)
Строго говоря, прямых аналогов данным функциям в C++ нет. Вместо ord обычно можно использовать явное приведение к целочисленному типу (которое можно оформить как int(a)).
 low(a), high(a)

Прямых аналогов в C++ нет. Нумерация элементов массивов начинается с нуля, поэтому, если a — массив, то вместо low(a) в C++ следует писать 0.

Последний элемент массива имеет индекс на единицу меньший количества элементов в массиве.

 (a = 1) or (a = 2) and not waiting
 a == 1 || a == 2 && !waiting
Логическое “или” — ||, логическое “и” — &&, логическое “не” — !. “Или” и “и” вычисляются по короткой схеме.

Битовые операции

 not, and, or, xor, shl, shr

Битовые операции

 ~, &, |, ^, <<, >>

Операнды — целые числа, интерпретируются этими операциями как последовательности бит.

См. также здесь

Цикл с предусловием

 while an < b do
 begin
   an := an + a;
   inc(n)
 end;

Инструкция while

 while (an < b)
 {
   an += a;
   ++n;
 }

Простейший способ организации цикла. Условие после while должно быть в скобках.

Запись an += a эквивалентна an = an + a.

Аналогичные комбинации определены для ряда других бинарных операций: -=, *=, /=, %= и т. д.

Цикл с постусловием

 repeat
   PerformOperation;
   write('Repeat? Y/N ');
   read(ch);
 until (ch = 'n') or (ch = 'N');

Инструкция do-while

 do
 {
   PerformOperation();
   std::cout << "Repeat? Y/N ";
   ch = std::cin.get();
 } while (ch != 'n' && ch != 'N');

Инструкция while ожидает условие продолжения цикла, а не выхода из него, как until в Pascal, поэтому в примере на C++ выполнено логическое отрицание условия, записанного в примере на Pascal.

Функция cin.get извлекает следующий введённый пользователем символ.

Цикл for

 for i := 1 to 9 do
   writeln(i * i);
 for i := 9 downto 1 do
   writeln(i * i * i);

Инструкция for

 for (i = 1; i < 10; ++i)
   std::cout << i * i << '\n';
 for (i = 9; i > 0; --i)
   std::cout << i * i * i << '\n';

В C++ for представляет собой довольно общую конструкцию, см. здесь.

Секции внутри for помещаются в скобки и разделяются точками с запятыми ;.

Бесконечный цикл:

 for (;;)
   std::cout << "It never ends!\n";

Массив фиксированного размера

 a: array[0..10] of Integer;

Статический массив

 int a[11];

В C++ указывается количество элементов. Нумерация элементов начинается с нуля.

Работа с массивами в C++ имеет свою специфику.

 a2: array[0..10, 0..20] of Integer;
 (* какой-то код *)
 a2[5, 6] := 56;
 int a2[11][21];
 // какой-то код
 a2[5][6] = 56;
При обращении к элементу многомерного массива каждый индекс заключается в собственную пару квадратных скобок.

Объявление синонима типа

 type
   MyInt = Integer;
   MyArr = array[0..9] of Double;

Объявление синонима типа

 using MyInt = int;
 using MyArr = double[10];
 typedef int MyInt;
 typedef double MyArr[10];

Секции type нет, типы можно объявлять в произвольном месте.

Конструкция using напоминает присваивание и поддерживается современными компиляторами C++. Конструкция typedef синтаксически строится как определение переменной, к которому добавили слово typedef. И то, и другое вводит синоним типа.

Тип-перечисление

 type
   Season =
     (Winter, Spring, Summer, Autumn);

Тип-перечисление

 enum Season
 { Winter, Spring, Summer, Autumn };

Тип-диапазон и тип-множество

 type
   MyRange = -100..100;
   MySet = set of Season;

Прямых аналогов в C++ нет.

Запись

 type
   Point = record
     x, y: Single;
   end;

Структура

 struct Point
 {
   float x, y;
 };

После определения структуры обязательно ставится ;. Поля структуры можно инициализировать “разом” с помощью следующей конструкции:

 Point origin = {};    // все -- нули
 Point oX = { 1 };     // y -- нуль
 Point p = { -1, 12 }; // задали явно и x и y

Вариантная запись

 type
   RGBA = record case Boolean of
     false: data: Longword;
     true: r, g, b, a: Byte;
   end;

   PointKind = (Cartesian, Polar);
   Point = record case
     kind: PointKind of
     Cartesian: x, y: Double;
     Polar: rho, phi: Double;
   end;

Объединение

 union RGBA
 {
   uint32_t data;
   struct
   {
     uint8_t r, g, b, a;
   };
 };

 enum PointKind {Cartesian, Polar};
 struct Point
 {
   union
   {
     struct { double x, y; };
     struct { double rho, phi; };
   };

   PointKind kind;
 };

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

Вариантная запись с тегом может быть реализована через комбинацию структуры и объединения.

На практике не рекомендуется использовать структуры вроде приведённой слева Point: данная конструкция требует для хранения одной точки 24 байта, в то время как для пары координат типа double достаточно 16 байт. По техническим причинам поле kind, для представления которого формально должно хватить одного бита, занимает 8 байт (64 бита).

Файловый тип

 raw: file;
 samples: file of Double;

Прямых аналогов в C++ нет, однако Стандартная библиотека содержит средства для работы с файлами как с потоками байт.

Указатели

 var
   n: Integer = 0;
   p: ^Integer = nil;
 begin
   p := @n; { пусть p указывает на n }
   p^ := 1; { теперь n = 1 }

Указатели

 int n = 0;
 int *p = nullptr;
 p = &n; // пусть p указывает на n
 *p = 1; // теперь n == 1

“Нулевой” указатель — nullptr.

Разыменование (обращение по указателю) — унарная операция “звёздочка” *. Взятие адреса (получение указателя на переменную) — унарная операция “амперсанд” &.

Указатель на массив (строку)

 var
   a: String[100];
   p: ^String[100];
 begin
   p := @a; { пусть p указывает на a }
   p^[50] := 'A'; { теперь a[50] = 'A' }

Указатель на массив

 char a[100];
 char *p = a; // p указывает на a
 p[49] = 'A'; // теперь a[49] == 'A'

Статические массивы неявно приводятся к указателям на первый элемент. К самому указателю можно обращаться как к массиву, используя операцию [].

Так как в C++ a — просто массив символов, отсчёт начинается с нуля, а не с единицы как в примере на Pascal (поэтому 49, а не 50).

Нетипизированный указатель

 type
   IntPointer = ^Integer;
 var
   pn: IntPointer;
   pp: Pointer;
 begin
   { ... }
   pp := pn;
   pn := IntPointer(pp);

Нетипизированный указатель

 using IntPointer = int*;
 IntPointer pn;
 void *pp;
 // ...
 pp = pn;
 pn = IntPointer(pp);

Указатель на некие данные в памяти, тип которых может быть любым, оформляется как “указатель на void”.

Любой указатель неявно приводится к void* или const void* (если это был указатель на константу). Обратное приведение требует явного приведения типа.

Динамическая память

 var
   p: ^Integer;
 begin
   new(p);
   { ... }
   dispose(p);

Динамическая память

 int *p;
 p = new int;
 // ...
 delete p;

С помощью new можно сразу проинициализировать новосозданную переменную, указав начальное значение в скобках:

 p = new int(42);  // теперь *p == 42

Оператор delete не изменяет значение указателя. Это можно сделать явно:

 delete p;
 p = nullptr;

Динамический массив

 var
   n: Integer;
   a: ^array[0..10000] of Integer;
 begin
   n := ...;
   GetMem(a, n * 4);
   { ... }
   FreeMem(a, n * 4);

Динамический массив

 size_t n;
 int *a;
 n = ...;
 a = new int[n];
 // ...
 delete[] a;

size_t — целочисленный тип, специально предназначенный для представления размеров массивов. О целочисленных типах, доступных в C++ см. здесь.

Форма new[] не требует указания размера элемента массива. delete[] не требует указания размера массива.

Процедурный/функциональный тип

 type
   StrProc = procedure(N: String);
 var
   hello: StrProc;
   name: String;
   procedure HelloEn(N: String);
   begin write('Dear ', N, ',') end;
   procedure HelloRu(N: String);
   begin
     write('Здравствуйте, ', N, '!')
   end;
 begin
   { ... }
   hello := @HelloEn;
   hello(name);
   hello := @HelloRu;
   hello(name);

Указатель на функцию

 using StrProc =
   void (*)(const char *n);

 StrProc hello;
 char name[100];

 void HelloEn(const char *n)
 {
   cout << "Dear " << n << ',';
 }

 void HelloRu(const char *n)
 {
   cout << "Здравствуйте, " << n << '!';
 }

 int main()
 {
   // ...
   hello = HelloEn;
   hello(name);
   hello = HelloRu;
   hello(name);

Указатель на функцию позволяет изменять вызываемую функцию во время исполнения программы, передавать функцию как параметр другой функции.

Предварительное объявление типа

 type
   PLink = ^TLink;
   TLink = record
     data: Pointer;
     next: PLink;
   end;

Объявление типа

 struct TLink;
 using PLink = TLink*;

 struct TLink
 {
   void *data;
   PLink next;
 };

В данном примере на C++ нет необходимости в объявлении типа PLink, так как на структуру допускаются ссылаться изнутри неё (структура считается объявленной сразу после {):

 struct Link
 {
   void *data;
   Link *next;
 };

Доступ к полю записи по указателю

 var
   list: PLink;
   data: PLink;
 begin
   { ... }
   list^.data := data;

Доступ к полю структуры по указателю

 TLink *list, *data;
 // ...
 list->data = data;

Естественно, данная запись эквивалентна

 (*list).data = data;

Но операция -> не только лучше выглядит, но и даёт возможность для развития абстракции “косвенный доступ”, широко используемой библиотеками на C++.


Прочие особенности C++

Операция Обычная для C++ запись Альтернативная запись

Логическое “и”

&&

and

Логическое “или”

||

or

Логическое “не”

!

not

Побитовое “и”

&

bitand

Побитовое “или”

|

bitor

Побитовое “исключающее или”

^

xor

Побитовое “не”

~

compl

Побитовое “и” с присваиванием

&=

and_eq

Побитовое “или” с присваиванием

|=

or_eq

Побитовое “исключающее или” с присваиванием

^=

xor_eq

Не равно

!=

not_eq


Общее оглавление

Кувшинов Д.Р. © 2016