Строковые константы. Константы и переменные в языке си

До этого момента, все переменные, которые мы рассматривали, были не константами. Их значения можно было изменить в любое время. Например:

int x { 4 }; // инициализация переменной x значением 4 x = 5; // изменяем значение x на 5

Тем не менее, иногда полезно использовать переменные, значения которых нельзя изменить – константы . Вот например, сила тяжести на Земле: 9.8м/с^2. Оно вряд ли поменяется в ближайшее время. Использовать константу для этого случая — наилучший вариант, так как таким способом мы предотвратим любое (даже случайное) изменение значения.

Чтобы сделать переменную константой – используйте ключевое слово const перед типом переменной или после. Например:

const double gravity { 9.8 }; // предпочтительнее использовать const перед типом данных int const sidesInSquare { 4 }; // ок, но не предпочтительно

Несмотря на то, C++ примет const как до, так и после типа, мы рекомендуем использовать именно перед типом.

Константы должны быть инициализированы при объявлении . Их значения с помощью операции присваивания изменить не получится.

const double gravity { 9.8 }; gravity = 9.9; // не допускается - ошибка компиляции

Объявление константы без инициализации также вызовет ошибку компиляции:

const double gravity; // ошибка компиляции, константа должна быть инициализирована

Обратите внимание, константы могут быть инициализированы и с помощью неконстантных значений:

std::cout << "Enter your age: "; int age; std::cin >> age; const int usersAge (age); // в дальнейшем переменная usersAge не может быть изменена

const является наиболее полезным (и наиболее часто используемым) с параметрами функций:

void printInteger(const int myValue) { std::cout << myValue; }

void printInteger (const int myValue )

std :: cout << myValue ;

Таким образом, при вызове функции, константа-параметр говорит нам, что функция не будет изменять значение переменной myValue. Во-вторых, она гарантирует, что функция не изменит значение myValue.

Compile time и runtime

Когда вы находитесь в процессе компиляции программы – это compile time (время компиляции ) . Компилятор проверяет вашу программу на синтаксические ошибки, затем конвертирует код в объектные файлы.

Когда вы находитесь в процессе запуска вашей программы или когда программа уже выполняется — это runtime (время выполнения) . Код выполняется строка за строкой.

Constexpr

В C++ есть два вида констант.

Константы r untime . Их значения определяются только во время выполнения (когда программа запущена). Переменные типа usersAge и myValue (в коде выше) — это константы runtime, так как компилятор не может определить их значения во время компиляции. usersAge зависит от пользовательского ввода (который можно получить только во время выполнения программы), а myValue зависит от значения, переданного в функцию (которое станет известным также во время выполнения программы).

Константы c ompile-time . Их значения определяются во время компиляции. Например, сила тяжести Земли – это константа compile-time, мы её определили сами в ходе написания программы.

В большинстве случаев, неважно какой тип константы: compile-time или runtime. Однако, есть все же несколько ситуаций, когда C++ может требовать константу compile-time вместо runtime (например, при определении длины массива фиксированного размера — мы рассмотрим это позже). Так как типов есть два, то компилятору нужно постоянно отслеживать, к какому из них относится какая переменная. Чтобы упростить это задание, в C++ 11 появляется ключевое слово constexpr , которое гарантирует, что тип константы — compile-time:

constexpr double gravity (9.8); // ок, значение определяется во время компиляции constexpr int sum = 4 + 5; // ок, значение 4 + 5 определяется во время компиляции std::cout << "Enter your age: "; int age; std::cin >> age; constexpr int myAge = age; // не ок, переменная age не определяется во время компиляции

Использовать вы его, скорее всего, не будете, но знать о нем не помешает.

Правило: Любая переменная, которая не должна изменять свое значение после инициализации — должна быть объявлена, как const (или как constexpr).

Имена констант

Некоторые программисты пишут имена констант заглавными буквами. Другие используют обычные имена, только с префиксом ‘k’. Мы же не будем их как-то выделять, так как константы – это те же обычные переменные, просто с фиксированными значениями, вот и всё. Особой причины, чтобы их выделять нет. Однако, это дело каждого лично.

Символьные константы

В предыдущем уроке , мы обсуждали «магические числа» – литералы, которые используются в программе как константы. Поскольку их использование является плохой практикой, тогда что использовать вместо них? Ответ: символьные константы. Символьная (или еще символическая ) константа – это тот же литерал (магическое число), только с идентификатором. Есть два способа объявления символических констант в C++. Один из них хороший, а один не очень. Рассмотрим оба.

Плохой способ: Использование макросов-объектов с текстом-заменой в качестве символьных констант

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

В уроке 22 о препроцессорах и директивах , мы говорили, что макросы-объекты имеют две формы – с текстом-заменой и без. Рассмотрим первый случай (с текстом-заменой). Он выглядит так:

#define identifier substitution_text

Как только препроцессор встретит эту директиву, все дальнейшие появления ‘identifier’ будет заменяться на ‘substitution_text’. Идентификатор обычно пишется заглавными буквами с нижним подчеркиванием вместо пробелов.

Например:

int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS;

#define MAX_STUDENTS_PER_CLASS 30

Во время компиляции, препроцессор заменит все идентификаторы MAX_STUDENTS_PER_CLASS на литерал 30.

Согласитесь, это гораздо лучший вариант, чем использование магических чисел, как минимум по нескольким причинам. MAX_STUDENTS_PER_CLASS обеспечивает контекст того, что это за значение и зачем оно надо, даже без комментариев. Во-вторых, если число нужно будет изменить — достаточно будет сделать правки только в директиве #define, все остальные идентификаторы MAX_STUDENTS_PER_CLASS в программе будут автоматически заменены новым значением при повторной компиляции.

Рассмотрим еще один пример:

#define MAX_STUDENTS_PER_CLASS 30 #define MAX_NAME_LENGTH 30 int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS; setMax(MAX_NAME_LENGTH);

#define MAX_STUDENTS_PER_CLASS 30

#define MAX_NAME_LENGTH 30

int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS ;

setMax (MAX_NAME_LENGTH ) ;

Здесь ясно, что MAX_STUDENTS_PER_CLASS и MAX_NAME_LENGTH имеются в виду как разные объекты, даже если у них одни и те же значения (30).

Так почему же этот способ плохой? Есть две причины.

Во-первых, макросы обрабатываются препроцессором, который заменяет идентификаторы на определенные значения. Эти значения не будут отображаться в отладчике (который показывает ваш фактический код). При компиляции int max_students = numClassrooms * 30; в отладчике вы увидите int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS; . А если нужно будет узнать значение MAX_STUDENTS_PER_CLASS? Вам придется самостоятельно найти его в коде. А это может занять некоторое время, в зависимости от размеров программы.

Во-вторых, эти директивы всегда имеют глобальную область видимости (о ней мы поговорим позже). Это означает, что значения #define в одной части кода могут конфликтовать со значениями #define в другой части кода.

Правило: Не используйте #define для создания символьных констант.

Хороший способ: Переменные const

Лучший способ создать символьную константу — использовать const:

const int maxStudentsPerClass { 30 }; const int maxNameLength { 30 };

const int maxStudentsPerClass { 30 } ;

const int maxNameLength { 30 } ;

Такие значения будут отображаться в отладчике, также они следуют всем правилам обычных переменных (в том числе и насчет области видимости).

Правило: Используйте const для создания символьных констант.

Использование символьных констант в программе

Во многих программах символьная константа должна быть использована на протяжении всего кода (а не только в одном месте). Они могут быть физическими или математическими константами, которые не меняются (например, число Пи или число Авогадро) или специфическими значения вашей программы. Чтобы не писать их каждый раз, когда они необходимы — определите их в одном месте и используйте везде, где будет нужно. Таким образом, если вам придется их изменить – достаточно будет зайти в один файл и там внести правки, а не рыскать по всей программе.

Как это осуществить? Очень просто:

  1. Создайте заголовочный файл для хранения констант.
  2. В заголовочном файле объявите пространство имен (

Константы вещественного типа

Целочисленные константы

Константы в программах

Константы - объекты, не подлежащие использованию в левой части оператора присваивания, т.к. константа - является неадресуемой величиной и, хотя она хранится в памяти ЭВМ, обычно нет никакого способа узнать ее адрес. В языке Си константами являются:

Самоопределенные арифметические, символьные и строковые данные;

Идентификаторы массивов и функций;

Элементы перечислений.

Арифметические константы могут быть целого или вещественного типов.

Общий формат: ±n (+ обычно не ставится).

Десятичные константы - последовательность цифр 0...9, первая из которых не должна быть 0. Например, 22 и 273 - обычные целые константы, если нужно ввести длинную целую константу, то указывается признак L (l ) - 273L (273l ). Для такой константы будет отведено – 4 байта. Обычная целая константа, которая слишком длинна для типа int , рассматривается как более длинный тип (long или long long ).

Существует система обозначений для восьмеричных и шестнадца­те­­ри­чных констант.

Восьмеричные константы - последовательность цифр от 0 до 7, первая из которых должна быть 0, например: 020 = 16-десятичное.

Шестнадцатеричные константы - последовательность цифр от 0 до 9 и букв от A до F (a...f), начинающаяся символами 0Х (0х), например: 0X1F (0х1f) = 31-десятичное.

Восьмеричные и шестнадца­те­ричные константы могут также заканчиваться буквой L(l) - long, например, 020L или 0X20L.

Примеры целочисленных констант:

1992 13 1000L - десятичные;

0777 00033 01l - восьмеричные;

0x123 0X00ff 0xb8000l - шестнадцатеричные.

Данные константы размещаются в памяти по формату double, а во внешнем представлении могут иметь две формы:

1) с фиксированной десятичной точкой, формат записи: ±n .m , где n , m - целая и дробная части числа;

2) с плавающей десятичной точкой (экспоненциальная форма): ±n .m p , где n , m - целая и дробная части числа, р - порядок, например, 1,25×10 -8 записывается как 1.25E-8.

Примеры констант с фиксированной и плавающей точками:

1.0 -3.125100е-10 0.12537е+13

Символьная константа - это символ, заключенный в одинарные кавычки: "A", "х" (занимает 1 байт).

В языке Си используются и. специальные (управляющие ) символы, не отображаемые на экране; их назначение - влиять на порядок изображения других символов.. Поскольку они не отображаются на экране, для их обозначения в тексте программы используется пара символов, первый из которых всегда - обратная косая черта (обратный слеш ) ("\"). Основные их них:

С помощью обратного слеша в символьных и строковых (см. ниже) константах представляются и некоторые обычные символы, чье написание там могло бы привести к двусмысленности:

При присваивании символьной переменной эти последователь­ности должны быть заключены в апострофы. Символьная константа "\0" (не путать с символом - цифрой "0" !) часто записывается вместо целой константы 0, чтобы подчеркнуть символьную природу некоторого выражения (см. тему "Строки").

Примеры символьных констант: "А", "9", "$", "\n", "\"".

В языке Си символы заключаются в апострофы. Поэтому, когда мы присваиваем какое-то значение переменной broiled типа char , мы должны писать

broiled = " Т "; /* ПРАВИЛЬНО */,

broiled = Т; /* НЕПРАВИЛЬНО */

Если апострофы опущены, компилятор "считает", что мы используем переменную с именем Т , которую забыли описать.

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

ehar bovine;

bovine = " ox "; /*НЕПРАВИЛЬНО */

Если вы посмотрите на таблицу кода ASCII, то увидите, что некоторые из "символов" в ней не выводятся на печать. Например, при использовании в программе символа номер 7 терминал компьютера издает звуковой сигнал. Но как использовать символ, который невозможно набрать на клавиатуре? В языке Си для этого имеются два способа.

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

beep = " 07 ";

Здесь имеются два важных момента, которые вы должны отчетливо представлять себе. Первый - это то, что последовательность знаков заключается в апострофы точно так же, как это делается с обычным символом. Второе - то, что номер символа должен быть записан в восьмеричном виде. При записи последовательности знаков мы можем случайно пропустить нули в первых позициях; в этом случае для представления кода "сигнал" мы могли бы использовать "7" или даже "7" . Но ни в коем случае не опускайте в записи последние нули! Последовательность символов "20" можно записать в виде "20" , но не "2" .

При использовании кода ASCII необходимо отметить различие между числами и символами, обозначающими числа. Например, символу "4" соответствует код ASCII, равный 52. Это символ "4" а не число 4.

РИС. 3. 4. Формы записи констант целых типов

Во втором способе представления "неудобных" знаков используются специальные последовательности символов. Они называются управляющими последовательностями и выглядят следующим образом:

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

t табуляция

b шаг назад

r возврат каретки

f подача бланка

обратная косая черта ()

" апостроф (")

" кавычки (")

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

nerf = " n ";

а затем вывести на печать переменную nerf; это приведет к продвижению на одну строку вперед на печатающем устройстве или на экране дисплея.

Первые пять управляющих последовательностей являются общепринятыми символами, предназначенными для управления работой печатающего устройства:

символ "новая строка" вызывает переход к новой строке;

символ "табуляция" сдвигает курсор или печатающую головку на некоторое фиксированное число позиций 5 или 8;

символ "шаг назад" производит сдвиг назад на одну позицию;

символ "возврат каретки" осуществляет возврат к началу строки;

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

В последних трех управляющих последовательностях символы , " , " можно считать символьными константами [поскольку они служат для определения символьных констант и непосредственно используются в операторе printf() , применение их самих в качестве символов могло бы привести к ошибке]. Если вы хотите вывести на печать строку.

Запомните, " символ называется обратная косая черта".

оператор будет выглядеть так:

printf(" Запомните, " символ называется обратная косая черта. " n");

Здесь у вас могут возникнуть два вопроса. Во-первых, почему мы не заключили управляющие последовательности в апострофы? Во-вторых, в каких случаях необходимо использовать код ASCII и когда управляющие последовательности, которые мы только что обсуждали? (Мы надеемся, что у вас возникли как раз эти вопросы, ПОТОМУ что мы собираемся отвечать именно на них.)

    Целые Константы

    Константы с Плавающей Точкой

    Символьные Константы

    Строки

    Ноль

    Const

    Перечисления

C++ дает возможность записи значений основных типов: символьных констант, целых констант и констант с плавающей точкой. Кроме того, ноль (0) может использоваться как константа любого указательного типа, и символьные строки являются константами типа char. Можно также задавать символические константы. Символическая константа - это имя, значение которого не может быть изменено в его области видимости. В C++ имеется три вида символических констант:

    любому значению любого типа можно дать имя и использовать его как константу, добавив к его описанию ключевое слово const;

    множество целых констант может быть определено как перечисление;

    любое имя вектора или функции является константой.

Целые Константы

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

их десятичные эквиваленты - это 0, 2, 63, 83. В шестнадцатиричной записи эти константы выглядят так:

0x0 0x2 0x3f 0x53

Б уквы a, b, c, d, e и f, или их эквиваленты в верхнем регистре, используются для представления чисел 10, 11. 12, 13, 14 и 15, соответственно. Восьмеричная и шестнадцатиричная записи наиболее полезны для записи набора битов; применение этих записей для выражения обычных чисел может привести к неожиданностям. Например, на машине, где int представляется как двоичное дополнительное шестнадцатеричное целое, 0xffff является отрицательным десятичным числом -1; если бы для представления целого использовалось большее число битов, то оно было бы числом 65535.

Константы с Плавающей Точкой

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

1.23 .23 0.23 1. 1.0 1.2e10 1.23e-15

З аметьте, что в середине константы с плавающей точкой не может встречаться пробел. Например, 65.43 e-21 является не константой с плавающей точкой, а четырьмя отдельными лексическими символами (лексемами):

65.43 e - 21

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

Const float pi = 3.14159265;

Символьные Константы

Х отя в C++ и нет отдельного символьного типа данных, точнее, символ может храниться в целом типе, в нем для символов имеется специальная и удобная запись. Символьная константа - это символ, заключенный в одинарные кавычки; например, "a" или "0". Такие символьные константы в действительности являются символическими константами для целого значения символов в наборе символов той машины, на которой будет выполняться программа (который не обязательно совпадает с набором символов, применяемом на том компьютере, где программа компилируется). Поэтому, если вы выполняетесь на машине, использующей набор символов ASCII, то значением "0" будет 48, но если ваша машина использует EBCDIC, то оно будет 240. Употребление символьных констант вместо десятичной записи делает программу более переносимой. Несколько символов также имеют стандартные имена, в которых обратная косая \\ используется как escape-символ:

В опреки их внешнему виду каждое является одним символом. Можно также представлять символ одно-, дву- или трехзначным восьмеричным числом (символ \\, за которым идут восьмеричные цифры), или одно-, дву- или трехзначным шестнадцатиричным числом (\\x, за которым идут шестнадцатиричные цифры). Например:

"\\6" "\\x6" 6 ASCII ack
"\\60" "\\x30" 48 ASCII "0"
"\\137" "\\x05f" 95 ASCII "_"

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

Строки

С троковая константа - это последовательность символов, заключенная в двойные кавычки:

"это строка"

К аждая строковая константа содержит на один символ больше, чем кажется; все они заканчиваются пустым символом "\\0" со значением 0.

Например:

Sizeof("asdf")==5;

С трока имеет тип "вектор из соответствующего числа символов", поэтому "asdf" имеет тип char. Пустая строка записывается "" (и имеет тип char). Заметьте, что для каждой строки s strlen(s)==sizeof(s)-1, поскольку strlen() не учитывает завершающий 0.
Соглашение о представлении неграфических символов с обратной косой можно использовать также и внутри строки. Это дает возможность представлять в строке двойные кавычки и escape-символ \\. Самым обычным символом этого рода является, безусловно, символ новой строки "\\n".

Например:

Cout << "гудок в конце сообщения\\007\\n"

Где 7 - значение ASKII символа bel (звонок).

В строке невозможно иметь "настоящую" новую строку:

"это не строка,
а синтаксическая ошибка"

О днако в строке может стоять обратная косая, сразу после которой идет новая строка; и то, и другое будет проигнорировано.

Например:

Cout << "здесь все \\
ok"

Напечатает

Здесь все ok

Н овая строка, перед которой идет escape (обратная косая), не приводит к появлению в строке новой строки, это просто договоренность о записи.

В строке можно иметь пустой символ, но большинство программ не будет предполагать, что есть символы после него. Например, строка "asdf\\000hjkl" будет рассматриваться стандартными функциями, вроде strcpy() и strlen(), как "asdf".

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

Char v1 = "a\\x0fah\\0129"; // "a" "\\xfa" "h" "\\12" "9"
char v2 = "a\\xfah\\129"; // "a" "\\xfa" "h" "\\12" "9"
char v3 = "a\\xfad\\127"; // "a" "\\xfad" "\\127"

И мейте в виду, что двузначной шестнадцатиричной записи на машинах с 9-битовым байтом будет недостаточно.

Ноль

Н оль (0) можно употреблять как константу любого целого, плавающего или указательного типа. Никакой объект не размещается по адресу 0. Тип нуля определяется контекстом. Обычно (но не обязательно) он представляется набором битов все-нули соответствующей длины.

Const

К лючевое слово const может добавляться к описанию объекта, чтобы сделать этот объект константой, а не переменной.

Например:

Const int model = 145;
const int v = { 1, 2, 3, 4 };

П оскольку константе ничего нельзя присвоить, она должна быть инициализирована. Описание чего-нибудь как const гарантирует, что его значение не изменится в области видимости:

Model = 145; // ошибка
model++; // ошибка

З аметьте, что const изменяет тип, то есть ограничивает способ использования объекта, вместо того, чтобы задавать способ размещения константы. Поэтому например вполне разумно, а иногда и полезно, описывать функцию как возвращающую const:

Const char* peek(int i)
{
return private[i];
}

Ф ункцию вроде этой можно было бы использовать для того, чтобы давать кому-нибудь читать строку, которая не может быть затерта или переписана (этим кем-то).

С другой стороны, компилятор может несколькими путями воспользоваться тем, что объект является константой (конечно, в зависимости от того, насколько он сообразителен). Самое очевидное - это то, что для константы не требуется выделять память, поскольку компилятор знает ее значение. Кроме того, инициализатор константы часто (но не всегда) является константным выражением, то есть он может быть вычислен на стадии компиляции. Однако для вектора констант обычно приходится выделять память, поскольку компилятор в общем случае не может вычислить, на какие элементы вектора сделаны ссылки в выражениях. Однако на многих машинах даже в этом случае может достигаться повышение эффективности путем размещения векторов констант в память, доступную только для чтения.

И спользование указателя вовлекает два объекта: сам указатель и указываемый объект. Снабжение описания указателя "префиксом" const делает объект, но не сам указатель, константой.

Например:

Const char* pc = "asdf"; // указатель на константу
pc = "a"; // ошибка
pc = "ghjk"; // ok

Ч тобы описать сам указатель, а не указываемый объект, как константный, используется операция const*.

Например:

Char *const cp = "asdf"; // константный указатель
cp = "a"; // ok
cp = "ghjk"; // ошибка

Ч тобы сделать константами оба объекта, их оба нужно описать const.

Например:

Const char *const cpc = "asdf"; // const указатель на const
cpc = "a"; // ошибка
cpc = "ghjk"; // ошибка

О бъект, являющийся константой при доступе к нему через один указатель, может быть переменной, когда доступ осуществляется другими путями. Это в частности полезно для параметров функции. Посредством описания параметра указателя как const функции запрещается изменять объект, на который он указывает.

Например:

Char* strcpy(char* p, const char* q); // не может изменить q

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

Например:

Int a = 1;
const c = 2;
const* p1 = &c; // ok
const* p2 = &a; // ok
int* p3 = &c; // ошибка
*p3 = 7; // меняет значение c

К ак обычно, если тип в описании опущен, то он предполагается int.

Перечисления

Е сть другой метод определения целых констант, который иногда более удобен, чем применение const.

Например:

Enum { ASM, AUTO, BREAK };

Определяет три целых константы, называемы перечислителями, и присваивает им значения. Поскольку значения перечислителей по умолчанию присваиваются начиная с 0 в порядке возрастания, это эквивалентно записи:

Const ASM = 0;
const AUTO = 1;
const BREAK = 2;

П еречисление может быть именованным.

Например:

Enum keyword { ASM, AUTO, BREAK };

И мя перечисления становится синонимом int, а не новым типом. Описание переменной keyword, а не просто int, может дать как программисту, так и компилятору подсказку о том, что использование преднамеренное.

Например:

Keyword key;
switch (key) {
case ASM:
// что-то делает
break;
case BREAK:
// что-то делает
break;
}

Побуждает компилятор выдать предупреждение, поскольку только два значения keyword из трех используются.

М ожно также задавать значения перечислителей явно.

Например:

Enum int16 {
sign=0100000, // знак
most_significant=040000, // самый значимый
least_significant=1 // наименее значимый
};

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



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: