Как сделать число отрицательным c
В прошлом уроке мы прошли целые числа: как они хранятся, считываются, выписываются и обрабатываются в языке программирования С/С++. Но в математике целые числа занимают лишь малую часть среди всех чисел. Потому что есть ещё и дробные числа, и они, конечно же, тоже есть и в языке программирования С/С++.
Хранение дробных чисел
Дробные числа хранятся в переменных типа double (от англ. double — двойной). Данный тип так называется, потому что для хранения дробного числа необходимо использовать две ячейки в памяти: для целой и для дробной части.
Целая часть числа
[3.273] = [3.989] = 3 \\ [-3.273] = [-3.989] = -4
Для того, чтобы вычислить целую часть положительного числа в языке программирования С/С++, необходимо применить конвертацию переменной из типа double в тип int . Чтобы сконвертировать переменную типа double в тип int , необходимо просто поставить перед ней int и взять её в круглые скобки:
Для того, чтобы вычислить целую часть отрицательного числа в языке программирования С/С++, необходимо отнять единицу от числа, сконвертированного в тип int .
Дробная часть числа
\ = 3.273 - [3.273] = 3.273 - 3 = 0.273 \\ \ = -3.273 - [-3.273] = -3.273 - (-4) = -3.273 + 4 = 0.727
Проще говоря, целая часть - то, что слева от точки, а дробная часть - то, что справа.
Для того, чтобы вычислить дробную часть числа в языке программирования С/С++, необходимо вычесть из этого числа его целую часть.
Но бывают дробные числа, дробная часть которых бесконечна. Такие числа хранятся в компьютере с некоторой точностью в силу невозможности хранить бесконечное число чисел.
Дробные числа в языке программирования могут также храниться в типе float , но его чаще всего не используют, так как он храните меньшее количество цифр после точки.
Создание переменной типа double
Чтобы создать переменную типа double :
Как и в случае с целыми переменными, рекомендуется сразу же очищать переменную от мусора, присваиванием ей нулевое значение:
В случае типа double необходимо присваивать значение именно 0.0 , так как первый нуль очищает целую часть, а второй - дробную. Это правило хорошего тона.
Считывание дробных чисел с клавиатуры
Считывание дробных чисел с клавиатуры в языке программирования С/С++ происходит аналогично считыванию целых чисел с помощью функции scanf() за исключением другого типа данных (6 строка):
Для типа double короткое имя при считывании это lf .
lf - образовано от словосочетания long float (длинное плавающее число - англ.) и подразумевает, что будет считано длинное дробное число.
Вывод дробных чисел на экран
Вывод дробных чисел на экран в языке программирования С/С++ происходит аналогично выводу целых чисел с помощью функции printf() (7-8 строки):
Формат вывода дробных чисел на экран
В языке программирования С/С++ можно регулировать, сколько цифр после точки будет выписано на экран. По умолчанию выписываются 6 цифр после точки.
Чтобы указать, сколько цифр после точки необходимо отобразить, можно написать следующий код (7-8 строки):
Пример вывода данного кода:
II Операция деления с остатком
В языке программирования С/С++ есть операция деления с остатком. Более того, данная операция является очень популярной и широко используемой.
Определение оператора %
Для того, чтобы узнать остаток при делении одного числа на другое, существует специальный оператор, обозначающийся символом процента % :
Остаток при делении отрицательных чисел
В математике принято, что остатки могут быть только положительными. Например, у числа -12 остаток 3 при делении на 5.
В языке программирования С/С++ остатки могут быть отрицательными. У числа -12 при делении на 5 остаток будет не 3, а -2. Необходимо иметь это в виду при решении зада
Процедура универсализации взятия остатка написана в данном уроке чуть ниже.
III Цикл if
Мы начинаем знакомство с таким объектом языка программирования С/С++, как цикл. В языке программирования С/С++ есть несколько основных базовых циклов.
Определение
Цикл - это функция, принимающая в качестве аргументов некоторые условия, а затем, в зависимости от истинности или ложности данных условий, запускающая или же не запускающая команды из своего тела.
Самым базовым циклом в языке программирования С/С++, как и во многих других высокоуровневых языках программирования, является цикл if (от англ. if - если).
Цикл if выполняет ровно то, что он означает: "Если условие в аргументе истинно, то команды из цикла будут выполнены, а если ложно - не будут".
Разновидности условий
В качестве условий могут выступать совершенно любые математические выражения, которые могут быть абсолютно точно истинными или же абсолютно точки ложными. Например, условие "Среди чисел от 1 до 1000 есть число, которое делится на все положительные однозначные числа" указать в цикле if нельзя.
Условие должно быть односложным и легкопроверяемым компьютером с точки зрения вычисления. Условия могут быть как на созданные ранее переменные, так и на конкретные числа. Рассмотрим самые популярные разновидности условий:
Выражения со знаками:
В программировании существуют точно такие же знаки, что и в математике:
- Больше >
- Меньше
- Больше или равно >=
- Меньше или равно
- Равно ==
- Не равно !=
Обратите внимание, что знак "равно" в условии цикла if состоит из двух символов равно == . А знак "не равно" состоит из восклицательного знака ! , который в языке С/С++ обозначает отрицание и символа равно: != .
Условие пишется в круглых скобочках через пробел после названия цикла:
Выражения, требующие вычисления:
Но порой требуется сравнить не просто переменные или числа, а результаты некоторых операций над ними. Например, будет ли число a больше числа b после прибавления к нему числа c .
Для этого, конечно можно создать дополнительную переменную, положить в неё сумму, а потом уже сравнить эту сумму и число c в цикле if :
Однако это не очень удобно с точки зрения лишнего кода, а также использования памяти, необходимо для создания новой переменной.
Данное сравнение можно осуществить гораздо удобней, сразу сравнив результат суммы и число:
Внутри цикла if можно вычислять выражения любой сложности:
Проверка делимости одного числа на другое:
Операция взятия остатка может пригодиться для проверки делимости одного числа на другое. Если число a делится на число b , то остаток при делении числа a на число b равен нулю.
Поэтому проверить делимость числа a на число b в программе можно следующим образом:
Сложные условия в цикле if:
Иногда необходимо проверить сразу несколько условий. Например, что число чётное и положительное. В таком случае можно воспользоваться двумя циклами if :
Но иногда условия могут быть сложней: проверить, что число чётное, положительное и не делится на 7. И создавать для этого три вложенных друг в друга цикла if достаточно долго. Вместо этого можно воспользоваться одним циклом if со сложным условием, состоящем из нескольких простых:
Для связки простых условий можно использовать связки "И" или "ИЛИ". Связка "И" обозначается двумя символами амперсанта && , а связка "ИЛИ" - двумя вертикальными палочками || . В примере выше использована связка "И".
Чтобы в одном цикле if проверить, что число кратно 11 или 13, необходимо воспользоваться связкой "ИЛИ":
Иерархия простых условий в составе сложного:
Важно понимать, что связка "ИЛИ" относится к связке "И" точно также, как операция сложения к операции умножения.
Допустим, необходимо проверить условие, что число положительное, чётное и при этом кратно или 11 или 13.
Можно написать просто:
И это будет неправильно. Перейдём от условий к выражениям с операциями сложения и умножения:
(a>0) \times (a \% 2 == 0) \times (a \% 11 == 0) + (a \% 13 == 0)
Совершенно логично, что сначала будет произведено умножение, а только потом сложение полученного произведения с последним слагаемым. Но ведь нам необходимо, чтобы число гарантированно было чётно и положительно, и только потом кратно 11 или 13.
В стандартной арифметике для этого используются скобки:
(a>0) \times (a \% 2 == 0) \times \Big( (a \% 11 == 0) + (a \% 13 == 0)\Big)
В языке программирования происходит тоже самое. Чтобы условие работало корректно, необходимо добавить скобки:
Конструкция else
Чаще всего необходимо сделать что-либо не только, когда выполнено условие, но и когда оно не выполнено. Использовать для этого второй точно такой же цикл if , только с противоположным условием не рационально.
Для этого и была придумана конструкция else , которая определяет область невыполнимости условия цикла if . Данная конструкция образована от слова else (иначе - англ.).
Допустим, необходимо разделить число на 2 и выписать его на экран, если оно чётное, и просто выписать его на экран, если нечётное. Для этого и нужно использовать else :
Всё просто: "Если число a чётно, делим его на 2 и выписываем, иначе просто выписываем его".
Конструкция else if
Данная конструкция может быть использована и в ещё более сложном условии. Например, необходимо сделать что-то конкретное, в зависимости от остатка при делении на 3. Так как при делении на 3 может быть три остатка (0, 1 и 2), обыкновенно конструкцией "если. иначе. " ограничиться нельзя. В таком случае можно воспользоваться:
Взятие остатка от отрицательного числа
Как уже было замечено, в языке программирования С/С++ операция взятия остатка от отрицательных чисел работает не так, как принято у математиков. Если есть вероятность, что программному коду нужно будет работать с отрицательными числами, необходимо не просто брать остаток при делении одного числа на другое, а осуществлять проверку, с положительным или отрицательным числом идёт работа:
В представленном выше коде осуществляется проверка знака переменной a и в случае, если число a отрицательно, чтобы остаток был таким, как принято у математиков, к остатку прибавляется то, число, на которое делится переменная a .
Однако можно поступить проще. Так как ясно, что в языке программирования С/С++ при делении с остатком отрицательных чисел получается остаток, равный разности принятого у математиков остатка и делителя.
Например, число -12 при делении на 5 в стандартной операции даст остаток -2, а в общепринятой - 3, а число 3 как раз получается прибавлением -2 и 5.
Таким образом, например, при нахождении чисел, имеющих остаток 4 при делении на 11, можно написать следующее условие в цикле if :
В основу статьи легли мои собственные выработанные нелегким путем знания о принципах работы и правильном использовании целых чисел в C/C++. Помимо самих правил, я решил привести список распространенных заблуждений и сделать небольшое сравнение системы целочисленных типов в нескольких передовых языках. Все изложение строилось вокруг баланса между краткостью и полноценностью, чтобы не усложнять восприятие и при этом отчетливо передать важные детали.
Всякий раз, когда я читаю или пишу код на C/C++, мне приходится вспоминать и применять эти правила в тех или иных ситуациях, например при выборе подходящего типа для локальной переменной/элемента массива/поля структуры, при преобразовании типов, а также в любых арифметических операциях или сравнениях. Обратите внимание, что типы чисел с плавающей запятой мы затрагивать не будем, так как это большей частью относится к анализу и обработке ошибок аппроксимации, вызванных округлением. В противоположность этому, математика целых чисел лежит в основе как программирования, так и компьютерной науки в целом, и в теории вычисления здесь всегда точны (не считая проблем реализации вроде переполнения).
Типы данных
Базовые целочисленные типы
Целочисленные типы устанавливаются с помощью допустимой последовательности ключевых слов, взятых из набора .
Несмотря на то, что битовая ширина каждого базового целочисленного типа определяется реализацией (т.е. зависит от компилятора и платформы), стандартом закреплены следующие их свойства:
- char : минимум 8 бит в ширину;
- short : минимум 16 бит и при этом не меньше char ;
- int : минимум 16 бит и при этом не меньше short ;
- long : минимум 32 бит и при этом не меньше int ;
- long long : минимум 64 бит и при этом не меньше long .
Наличие знака
- Стандартный сhar может иметь знак или быть беззнаковым, что зависит от реализации.
- Стандартные short , int , long и long long идут со знаком. Беззнаковыми их можно сделать, добавив ключевое слово unsigned .
- Числа со знаком можно кодировать в двоичном формате в виде дополнительного кода, обратного или как величину со знаком. Это определяется реализацией. Заметьте, что обратный код и величина со знаком имеют различные шаблоны битов для отрицательного нуля и положительного, в то время как дополнительный код имеет уникальный нуль.
- Символьные литералы (в одинарных кавычках) имеют тип ( signed ) int в C, но ( signed или unsigned ) char в C++.
Дополнительные правила
- sizeof(char) всегда равен 1, независимо от битовой ширины char .
- Битовая ширина не обязательно должна отличаться. Например, допустимо использовать char , short и int , каждый шириной в 32 бита.
- Битовая ширина должна быть кратна 2. Например, int может иметь ширину 36 бит.
- Есть разные способы написания целочисленного типа. К примеру, в каждой следующей строке перечислен набор синонимов:
- int , signed , signed int , int signed ;
- short , short int , short signed , short signed int ;
- unsigned long long , long unsigned int long , int long long unsigned .
Типы из стандартных библиотек
- size_t (определен в stddef.h) является беззнаковым и содержит не менее 16 бит. При этом не гарантируется, что его ширина будет как минимум равна int .
- ptrdiff_t (определен в stddef.h) является целочисленным типом со знаком. Вычитание двух указателей будет давать этот тип. При этом не стоит ожидать, что вычитание двух указателей даст int .
- В stdint.h определена конкретная ширина типов: uint8_t , int8_t , 16 , 32 и 64 . Будьте внимательны к операциям, подразумевающим продвижение типов. Например, uint8_t + uint8_t даст int (со знаком и шириной не менее 16 бит), а не uint8_t , как можно было предположить.
Преобразования
Представим, что значение исходного целочисленного типа нужно преобразовать в значение целевого целочисленного типа. Такая ситуация может возникнуть при явном приведении, неявном приведении в процессе присваивания или при продвижении типов.
Как происходит преобразование?
Главный принцип в том, что, если целевой тип может содержать значение исходного типа, то это значение семантически сохраняется.
- Когда исходный тип расширяется до целевого типа с аналогичной знаковой характеристикой (например, signed char -> int или unsigned short -> unsigned long ), каждое исходное значение после преобразования сохраняется.
- Даже если исходный и целевой типы имеют разные диапазоны, все значения в их пересекающейся части будут сохранены. Например, int , содержащий значение в диапазоне [0, 255] , будет без потерь преобразован в unsigned char .
- При преобразовании в беззнаковый тип новое значение равняется старому значению по модулю 2 целевая ширина в битах . Объяснение:
- Если исходный тип беззнаковый и шире целевого, тогда старшие биты отбрасываются.
- Если исходный тип имеет знак, тогда в процессе преобразования берется исходное значение, и из него/к нему вычитается/прибавляется 2 целевая ширина в битах до тех пор, пока новое значение не впишется в диапазон целевого типа. Более того, если число со знаком представлено в дополнительном коде, то в процессе преобразования старшие биты отбрасываются, как и в случае с беззнаковыми числами.
- Если исходное значение вписывается в диапазон целевого типа, тогда процесс преобразования (например, расширение знака) производит целевое значение, семантически равное исходному.
- Если же оно не вписывается, тогда поведение будет определяться реализацией и может вызвать исключение (к примеру, прерывание из-за переполнения).
Арифметика
Продвижение/преобразование
- Унарный арифметический оператор применяется только к одному операнду. Примеры: - , ~ .
- Бинарный оператор применяется к двум операндам. Примеры: + , * , & . .
- Если операнд имеет тип bool , char или short (как signed , так и unsigned ), тогда он продвигается до int ( signed ), если int может содержать все значения исходного типа. В противном случае он продвигается до unsigned int . Процесс продвижения происходит без потерь. Примеры:
- В реализации присутствуют 16-битный short и 24-битный int . Если переменные x и y имеют тип unsigned short , то операция x & y продвигает оба операнда до signed int .
- В реализации присутствуют 32-битный char и 32-битный int . Если переменные x и y имеют тип unsigned char , то операция x – y продвигает оба операнда до unsigned int .
- (long) + (long) → (long) ;
- (unsigned int) * (int) → (unsigned int) ;
- (unsigned long) / (int) → (unsigned long) ;
- если int является 32-битным, а long 64-битным: (unsigned int) % (long) → (long) ;
- если int и long оба являются 32-битными: (unsigned int) % (long) → (unsigned long) .
Неопределенное поведение
Знаковое переполнение:
- При выполнении арифметических операций над целочисленным типом переполнение считается неопределенным поведением (UB). Такое поведение может вызывать верные, несогласованные и/или неверные действия как сразу, так и в дальнейшем.
- При выполнении арифметики над беззнаковым целым (после продвижений и преобразований) любое переполнение гарантированно вызовет оборот значения. Например, UINT_MAX + 1 == 0 .
- Выполнение арифметики над беззнаковыми целыми фиксированного размера может привести к едва уловимым ошибкам. Например:
- Пусть uint16_t = unsigned short , и int равен 32-битам. Тогда uint16_t x=0xFFFF , y=0xFFFF , z=x*y ; x и y будут продвинуты до int , и x * y приведет к переполнению int , вызвав неопределенное поведение.
- Пусть uint32_t = unsigned char , и int равен 33-битам. Тогда uint32_t x=0xFFFFFFFF , y=0xFFFFFFFF , z=x+y ; x и y будут продвинуты до int , и x + y приведет к переполнению int , то есть неопределенному поведению.
- Чтобы обеспечить безопасную арифметику с беззнаковыми целыми, нужно либо прибавить 0U , либо умножить на 1U в качестве пустой операции. Например: 0U + x + y или 1U * x * y . Это гарантирует, что операнды будут продвинуты как минимум до ранга int и при этом останутся без знаков.
- Деление на нуль и остаток с делителем нуля также относятся к неопределенному поведению.
- Беззнаковое деление/остаток не имеют других особых случаев.
- Деление со знаком может вызывать переполнение, например INT_MIN / -1 .
- Остаток со знаком при отрицательных операндах может вызывать сложности, так как некоторые части являются однообразными, в то время как другие определяются реализацией.
- Неопределенным поведением считается битовый сдвиг ( >) на размер, который либо отрицателен, либо равен или больше битовой ширины.
- Левый сдвиг беззнакового операнда (после продвижения/преобразования) считается определенным правильно и отклонений в поведении не вызывает.
- Левый сдвиг операнда со знаком, содержащего неотрицательное значение, вследствие которого 1 бит переходит в знаковый бит, является неопределенным поведением.
- Левый сдвиг отрицательного значения относится к неопределенному поведению.
- Правый сдвиг неотрицательного значения (в типе операнда без знака или со знаком) считается определенным правильно и отклонений в поведении не вызывает.
- Правый сдвиг отрицательного значения определяется реализацией.
Счетчик цикла
Выбор типа
Предположим, что у нас есть массив, в котором нужно обработать каждый элемент последовательно. Длина массива хранится в переменной len типа T0 . Как нужно объявить переменную счетчика цикла i типа T1 ?
- Самым простым решением будет использовать тот же тип, что и у переменной длины. Например:
- Говоря обобщенно, переменная счетчика типа T1 будет работать верно, если диапазон T1 будет являться (не строго) надмножетсвом диапазона T0 . Например, если len имеет тип uint16_t , тогда отсчет с использованием signed long (не менее 32 бит) сработает.
- Говоря же более конкретно, счетчик цикла должен просто покрывать всю фактическую длину. Например, если len типа int гарантированно будет иметь значение в диапазоне [3,50] (обусловленное логикой приложения), тогда допустимо отсчитывать цикл, используя char без знака или со знаком (в котором однозначно можно представить диапазон [0,127] ).
- Нежелательно использовать переменную длины и переменную счетчика с разной знаковостью. В этом случае сравнение вызовет неявное сложное преобразование, сопровождаемое характерными для платформы проблемами. К примеру, не стоит писать такой код:
Отсчет вниз
Для циклов, ведущих отсчет вниз, более естественным будет использовать счетчик со знаком, потому что тогда можно написать:
При этом для беззнакового счетчика код будет таким:
Примечание: сравнение i >= 0 имеет смысл только, когда i является числом со знаком, но всегда будет давать true , если оно будет беззнаковым. Поэтому, когда это выражение встречается в беззнаковом контексте, значит, автор кода скорее всего допустил ошибку в логике.Заблуждения
Все пункты приведенного ниже списка являются мифами. Не опирайтесь на эти ложные убеждения, если хотите писать корректный и портируемый код.
6.5.5: 6 Когда целые числа делятся, результатом оператора/является алгебраическое отношение с любой дробной частью, отброшенной. (105) Если частное a/b представимо, выражение (a/b) * b + a% b равно a; в противном случае поведение как a/b, так и a% b undefined.
105) Это часто называют "усечением к нулю".
Другим способом определения деления является округление до -оо. Это называется евклидовым делением. Python, похоже, использует еще одно определение, в соответствии с user3307862 ссылка.
Оператор % , правильно названный "остатком", определяется относительно соответствующего деления, поэтому он либо всегда находится в [0..b), либо в (-b..b) в зависимости от определения / .
При оценке целых чисел как логических в C / C ++ отрицательные числа являются истинными или ложными? Всегда ли они верны / неверны независимо от компиляторов?
Решение
Все ненулевые значения будут преобразованы в true и нулевые значения до false , С отрицательными числами, отличными от нуля, они преобразуются в true ,
Цитирование из стандарта C ++ 11 (выделено мое):
4.12 Булевы преобразования [conv.bool]
1 Значение арифметики, перечисления с незаданной областью, указателя или указателя
Тип члена может быть преобразован в тип значения типа bool. Ноль
значение, значение нулевого указателя или значение указателя нулевого элемента преобразуется
в false ; любое другое значение преобразуется в true , Prvalue типа
std :: nullptr_t может быть преобразован в значение типа bool;
результирующее значение ложно.Всегда ли они верны / неверны независимо от компиляторов?
Вы получите вышеуказанную гарантию только тогда, когда ваш компилятор соответствует стандартам или, по крайней мере, соответствует этой специфической части стандарта. На практике у всех компиляторов есть это стандартное поведение, поэтому не о чем беспокоиться.
Другие решения
Вы можете проверить это самостоятельно, скомпилировав это:
$ gcc -Wall -педантика test.c -o test-c
$ g ++ -Wall -pedantic test.c -o test-cpp
$ ./test-c
-1 верно
$ ./test-cpp
-1 верноКраткий ответ: Да, отрицательные значения и любые ненулевые значения в целом рассматриваются как истинные при использовании в качестве условий.
Для C существует ряд контекстов, в которых выражение рассматривается как условие. Условия не обязательно типа bool или же _Bool ; этот тип был добавлен к языку только по стандарту 1999 года.
Наиболее очевидным из этих контекстов является выражение в if Заявление, но есть и другие примеры: while , do-while второе выражение в for заголовок, первый операнд ?: условный оператор и операнд (ы) ! , && , а также || операторы. (Я считать это исчерпывающий список, но я не уверен.)
В обеих формах первое подсостояние выполняется, если выражение сравнивается с неравным 0.
Что означает, что это:
(добавление дополнительных скобок, чтобы избежать проблем с приоритетом операторов). Смысл понятен, если foo имеет тип int , Если foo имеет некоторый тип с плавающей точкой, 0 преобразуется в тот же тип (который может вызвать некоторые тонкости, если значение оказывается отрицательным нулем или NaN). И если foo это указатель, 0 трактуется как константа нулевого указателя; if (ptr) эквивалентно if (ptr != NULL) (при условии определения NULL видно).
Для C ++ правила сформулированы немного по-другому, но эффект тот же. Условие в C ++ if оператор преобразуется в тип bool (в отличие от C, тип bool был встроен в C ++ с самого начала своей истории). Преобразование значения любого скалярного типа в bool определяется стандартом C ++ как:
Нулевое значение, нулевое значение указателя или нулевое значение указателя элемента
конвертировано в ложный; любое другое значение преобразуется в true.
тип значения станд :: nullptr_t может быть преобразовано в значение
тип BOOL; результирующее значение ложный.Таким образом, как в C, так и в C ++ любое скалярное значение (т. Е. Целое число, число с плавающей запятой или указатель) может использоваться в качестве условия, и условие ложно, если скаляр равен нулю, и истинно, если оно не равно нулю. , С определяет это как сравнение неравенства с 0 ; C ++ определяет это как преобразование в bool — но результат тот же.
в C или C ++ (если вам действительно не нужно сравнивать его с этим одним значением); просто пиши:
Все, что не 0 будет преобразован в true (1 в случае С) zero значение будет преобразовано в false (0 в случае C). Что касается С если мы посмотрим на Проект стандарта C99 раздел 6.3.1.2 Логический тип параграф 1 говорит:
Когда любое скалярное значение преобразуется в _Bool, результат равен 0, если значение сравнивается равным
до 0; в противном случае результат равен 1.Ради полноты, если мы посмотрим на раздел 7.16 Логический тип и значения параграф 2 говорит:
в отношении C ++ проект стандарта C ++ в разделе 4.12 Булевы преобразования параграф 1 говорит (акцент мой):
Значение арифметики, перечисление с незаданной областью, указатель или указатель на тип элемента может быть преобразовано в значение типа bool. Нулевое значение, значение нулевого указателя или значение указателя нулевого элемента преобразуется в ложь; любое другое значение преобразуется в true.[…]
Это должно сохраняться независимо от того, какой компилятор вы используете.
С другой стороны, предположим, что вы работаете на 16-битной машине:
С другой стороны,
Таким образом, отрицательные числа не всегда ложные, за исключением того, что они всегда есть.
Читайте также: