Long long c что это
Перейти к содержимому

Long long c что это

  • автор:

Long long c что это

unsigned long long

Запоминать предельные значения, особенно для 4-х или 8-ми байтовых целых, вряд ли стоит, достаточно знать хотя бы какого порядка могут быть эти значения, например, тип int — приблизительно 2 · 10 9 .

На практике рекомендуется везде использовать основной целый тип, т.е. int . Дело в том, что данные основного целого типа практически всегда обрабатываются быстрее, чем данные других целых типов. Короткие типы ( char , short ) подойдут для хранения больших массивов чисел с целью экономии памяти при условии, что значения элементов не выходят за предельные для этих типов. Длинные типы необходимы в ситуации, когда не достаточно типа int .

Символьные типы

В стандарте C++ нет типа данных, который можно было бы считать действительно символьным. Для представления символьной информации есть два типа данных, пригодных для этой цели, — это типы char и wchar_t , хотя оба эти типа по сути своей вообще-то являются целыми типами. Например, можно взять символ ‘A’ и поделить его на число 2. Кстати, а что получится? Подсказка: символ пробела. Для «нормальных» символьных типов, например, в Паскале или C#, арифметические операции для символов запрещены.

Тип char используется для представления символов в соответствии с системой кодировки ASCII (American Standard Code for Information Interchange — Американский стандартный код обмена информации). Это семибитный код, его достаточно для кодировки 128 различных символов с кодами от 0 до 127. Символы с кодами от 128 до 255 используются для кодирования национальных шрифтов, символов псевдографики и др.

Тип wchar_t предназначен для работы с набором символов, для кодировки которых недостаточно 1 байта, например, Unicode. Размер типа wchar_t обычно равен 2 байтам. Если в программе необходимо использовать строковые константы типа wchar_t , то их записывают с префиксом L , например, L «Слово».

Логический тип

Логический (булевый) тип обозначается словом bool . Данные булевого типа могут принимать только два значения: true и false . Значение false обычно равно числу 0, значение true — числу 1. Под данные булевого типа отводится 1 байт.

Вещественные типы

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

Имеется три вещественных типа данных: float , double и long double . Основным считается тип double . Так, все математические функции по-умолчанию работают именно с типом double . В таблице ниже приведены основные характеристики вещественных типов:

Диапазон абсолютных величин

Точность, количество десятичных цифр

Контакты

Петрозаводск, пр. Ленина, 31 (IT-парк ПетрГУ), кабинет 211.

Замечания по содержимому и работе сайта принимаются по адресу webmaster@acm.petrsu.ru.

Последние обновления

11.10.2023

8.07.2023

9.04.2023

27.11.2022

13.10.2022

15.11.2021

3.03.2021

27.01.2021

14.11.2020

3.09.2020

Базовые типы данных в C++

Вид типа данных Название Размер Диапазон Комментарий
Логический bool 8 бит true / false Значение false тождественно равно нулю. Значение true — это любое другое значение, кроме нуля.
Символьный char 8 бит 0 – 255 Несмотря на то, что тип символьный, char хранит число — код символа в таблице ASCII.
Целочисленный int 32 бита Примерно
-2⋅10 9 – 2⋅10 9
Диапазон обусловлен количеством бит, которые кодируют значение числа. В данном случае их ровно 31, так как один бит остаётся на кодирование знака. Так как биты кодируют значение бинарным кодом, диапазон на самом деле такой: -2 31 – 2 31 — 1.
unsigned int 32 бита 0 – 4⋅10 9 Модификатор целочисленных типов unsigned делает тип данных беззнаковым, то есть теперь под кодирование значения числа используются все 32 бита. Соответственно, диапазон теперь не включает отрицательный чисел, зато сверху ограничен более высоким значением 2 32 — 1.
long long 64 бита Примерно
-9⋅10 18 – 9⋅10 18
-2 63 – 2 63 — 1
unsigned long long 64 бита 0 – 18⋅10 18 0 – 2 64 — 1
Вещественный (с плавающей запятой) double 64 бита 1,7E +/- 308
(15 знаков)
Этот тип данных хранит вещественные числа с точностью до 308 знаков после (или до) запятой, но не более 15 цифр после самой левой значащей цифры (не нуля).

copyright © 2010-2023 KTP PetrSU

Общаться с 64-битным long long в Visual C++

Тип данных long long — это единственный целочисленный тип данных в Visual С++, который весит 8 байт и может использовать значения выше 2^31 — 1. -2^31. Если у вас 64-битная система, то тип long тоже будет 8-байтовым. А в 32-разрядной системе приходится довольствоваться только им. Этот тип данных получил права только в последних версиях С++, например, в С++ 6.0 вы его найдёте, но работать с ним там одно удовольствие.

Несмотря на то, что у long long есть закреплённые права и обязанности. Использовать его всё равно следует по-особенному. Для начала следует учесть, что простое присваивание ему значений не прокатит. Если вы хотите присвоить значение переменной типа long long, то не забудьте приставить к числу две буквы LL (которые видимо символизируют, что число «longlong» 64-битное).

long long temp = 1100LL;

Далее: про соответствие типов. Думаю, когда человек первый раз «берёт в руки» этот тип данных ему становится интересно, а как он соотносится с типом int?

Отношения у long long c int очень интересные. Если вы хотите складывать два числа int, зная, что результат выйдет за диапазон int, то вы можете использовать long long. Сложите два int присвоите long long и все будут счастливы.

int a1 = 2000000000;
int a2 = 2000000000;
long long b = a1 + a2;

b равен 400000000. И здесь проблем возникнуть не должно.

С умножением int’ов дела гораздо интереснее. Если вы просто возьмёте две переменные типа int и перемножите их между собой, зная, что результат выйдет за пределы int, то ничего у вас не получится. Переменная типа long long будет просто равна нулю. В итоге, чтобы умножать в Visual C++ числа типа int и получить long long следует проделать сложения через цикл, т.е. если мы хотим умножить число а на число b, то всё будет выглядеть так.

long long temp = 0;
for (int i = 0; i < a; i++) temp += b;
>

И только тогда захочет быть произведением.

Синтаксическое пересечение C и C++

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

Однако C и C++ — это разные языки:

  1. Во-первых, их разрабатывают разные люди с разными целями.
  2. Во-вторых, они имеют разные компиляторы, несмотря на то, что обычно компании, имеющие компиляторы C++ также имеют компилятор C. Эти компиляторы имеют общий код, но они всё же не одинаковы — этот самый общий код также используется и для Go, и для D, и для Ada. Исключением из этого правила является Clang, где просто if ‘ами различаются C и C++. Правда, там ещё и Objective-C и нечто ещё.
  3. В-третьих, стили программирования на C и C++ кардинально отличаются, если вы пишете на них одинаково, вы дурачок.
  4. В-четвёртых, C не является подмножеством C++, случайная программа на C вообще не факт что будет корректна в C++. Правда, обычно придётся менять её не очень сильно. Примером такого отличия является код вида a ? b : c = 42 . В C — это (a ? b : c) = 42 , а в C++ — a ? b : (c = 42) .

Так вот, у нас всё будет обсуждаться в терминах C++: в местах отличий мы не будем обсуждать оба языка.

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

Типы данных.

Целочисленные.

Целочисленных типов в языке C++ девять:

  • char .
  • unsigned char .
  • signed char .
  • [signed] short [int] .
  • unsigned short [int] .
  • [signed] int или просто signed .
  • unsigned [int] .
  • [signed] long [int] .
  • unsigned long [int] .
  • [signed] long long [int] .
  • unsigned long long [int] .

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

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

Размер Стандарт 32 bit win64 linux64
char $1$ байт 8 8 8
short $\leqslant16$ бит 16 16 16
int $\leqslant16$ бит 32 32 32
long $\leqslant32$ бит 32 32 64
long long $\leqslant64$ бит 64 64 64

Надо понимать, что C++ поддерживает системы, в которых байт не равен 8 битам. В частности, на cppreference можно найти такую строку: «Note: this allows the extreme case in which bytes are sized 64 bits, all types (including char ) are 64 bits wide, and sizeof returns 1 for every type.»

Символьные типы.

Как следует из списка выше, char , unsigned char и signed char — это три разных типа.

Как проверить, одинаковые ли типы? Например, перегрузить функцию:

void foo(int) <> void foo(signed int) <> // Не скомпилируется, так как две функции с одинаковой сигнатурой. 
void foo(char) <> void foo(signed char) <> void foo(unsigned char) <> // Скомпилируется. 
Типы с фиксированным размером.

Несмотря на то, что стандарт не гарантирует ничего про размеры типов данных, существуют типы с фиксированным размером:

#inlcude int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t 

Следует использовать их, если хотим тип гарантированного размера. Эти типы существуют в том и только в том случае, если реализация имеет тип соответствующего размера. В частности, а патологическом случае указанном выше (где байт имеет 64 бита), из типов фиксированного размера существуют только int64_t и uint64_t .

Прочие полезные typedef ‘ы.

Нужно вам перебрать все значения в массиве:

for (/*. */ i = 0; i < N; i++) arr[i] = 42; 

Какого типа должно быть i ?

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

У size_t есть знаковый друг — ptrdiff_t — результат разности двух указателей.

Как выбирать целочисленный тип.
  • Если данные приходят из существующей функции или уходят в неё, то используем тот же тип, что там.
  • Если используется как размер/индекс или сдвиг в контейнере — size_t и ptrdiff_t .
  • Если знаем, оцениваем размер, используем тип фиксированного размера.

Типы с плавающей точкой.

Тип Размер (обычно)
float 32
double 64
long double 64/80/128

Разделение на мантиссу и экспоненту фиксировано в стандарте IEEE-754

Подробнее о них (про денормализованные числа, NaN, $\pm\infty$, отрицательный 0 и подобное) можно почитать на викиконспектах или узнать в курсе архитектуры ЭВМ.

Стоит заметить, что из-за особенностей чисел с плавающей точкой (обычно из-за NaN'ов, бесконечностей и нулей разного знака) операции вида 0 * a и a - a не могут быть заменены заменяются при компиляции на 0 (о части из них также можно почитать в приложенной ссылке на конспект по АрхЭВМ). Но можно прописать флаги компилятора, игнорирующие NaN и $\infty$ и тогда арифметические действия будут быстрее (но не будут соответствовать стандарту IEEE-754). Одним из примеров невозможности оптимизации является if (a == a) , что вернёт false , если a является NaN'ом:

Перечисляемый тип.

Самым простым составным типом является перечисляемый тип. В C++ рекомендуется использовать только строгий их вариант — enum class . Это кто? Это перечисление набора вариантов. Это лучше, чем набор констант, потому что enum class :

  • Безопаснее (в него сложно присвоить то, чего в нём нет).
  • Понятнее (если в функцию передаётся три разных int 'а, то вы легко их перепутаете, а перепутать три разных перечисляемых типа вам не дадут).
  • Сразу видно, какие константы связаны друг с другом.

Изнутри enum class — это просто целое число, причём тип этого числа можно явно указать при помощи такой конструкции:

enum class color : uint16_t < RED, GREEN, BLUE >; 

По умолчанию внутри enum class лежит int . Тот тип, который лежит внутри, называется underlying type. Но арифметика со строгими перечисляемыми типами не работает.

Есть ещё обычный enum , без слова class . Это тип-перечисление, который пришёл из Си. Но им лучше не пользоваться, потому что:

  • У enum не прописано, какой тип внутри (underlying type). Причём не прописано именно в стандарте. То есть это может отличаться от компилятора к компилятору.
  • Они неявно конвертируются в int . Мы не хотим неявных конверсий.
  • И им ещё можно не указывать спецификатор: можно просто red вместо color::red . Получается, они засоряют пространство имен. И, например, такой код не компилируется, потому что имена c конфликтуют (если заменить на enum class , то будет работать):
enum programming_languages < c, cpp >; enum letters < a, b, c >; 

Структуры и указатели:

Структура

struct point < float x; float y; float z; >; // Обращение к полям: void f (point p)

Структура — это способ сгруппировать набор данных в одну сущность. Структурами очень рекомендуется пользоваться: не надо таскать в разные места несгруппированные данные. Иначе ваш код раздуется до невероятных размеров и вообще обретёт форму спагетти. А ещё вы получите больший шанс ошибиться.

Данные структуры хранятся подряд (с точностью до выравнивания). Какое такое выравнивание? Сразу виден человек, не читавший конспект по ассемблеру. Процессоры либо не умеют, либо плохо читают $N$ байт, адрес начала которых не делится на $N$ (это называется невыравненное обращение к памяти/unaligned access). Поэтому компиляторы стараются располагать структуры так, чтобы данные в них были выравнены. Подробнее о невыравненном обращении можно почитать здесь.

struct mytype1 < char a; int b; >; struct mytype2 < int b; char a; >; 

Как несложно догадаться, первая структура занимает 8 байт, а не 5, потому что после a добавляются 3 байта. Более интересно, что вторая структура тоже занимает 8 байт, потому что когда вы положите такую структуру в массив, придётся после неё вставлять 3 байта.

Чтобы узнать, сколько места занимает структура (когда она лежит в массиве), есть оператор sizeof . Использование: sizeof(mytype1) , возвращает в нашем случае 8 .

А ещё есть оператор alignof . Он возвращает выравнивание структуры. Выравнивание структуры — это число, на которое должен делиться тот адрес, по которому мы размещаем структуру. Использование: alignof(mytype1) , возвращает в нашем случае 4.

Кстати, sizeof и alignof можно применять не только к типам, но и к значениям. Вызов этих функций от значения эквивалентен вызову от типа этого значения.

Объединение.

У нас есть тип, которых хранит первое, И второе, И третье. А что, если мы хотим хранить строго одно из нескольких значений? Специально для этого есть union , который этим и занимается. Пока struct хранит следующее поле по смещению относительно предыдущего, у union 'а всё хранится по одному смещению.

Важно: никакой информации о том, что хранится в данный момент, union не знает. Если вас это устраивает, вас это устраивает, а иначе вам нужно связать union с enum class в одну структуру (обращение не в ту альтернативу union 'а — undefined behaviour). И называется эта структура std::variant 🙂

Указатели и массивы.

Указатели.

Указатель — «номер ячейки памяти» (важно указывать, какой тип в это ячейке, эта информация используется на уровне компилятора). Все указатели имеют одинаковый размер - битность системы.

mytype *p1; // Объявление указателя на тип `mytype`. int a; int *p2 = &a; // `&` — взятие адреса переменной. *p2 = 42; // `*` — разыменования указателя (взять значение того, что в этой ячейке). point *p; // Вместо: (*p).x = 5; // Можно написать: p->x = 5; // Второе — просто сокращение для первого. 
C++-style массивы.
#include std::array arr; // Массив из 20 целых чисел. arr[2] = 123; // Обращение к элементу массива (0-based). int *p = arr.data(); // Указатель на первый элемент массива (может быть использовано для арифметики указателей). 
C-style массивы.
int a[10]; // Массив из 10 целых чисел. a[1] = 42; // Всё такое же 0-based обращение к элементу. 

У массивов из C (далее встроенные массивы) по сравнению с std::array есть существенные недостатки:

  • Встроенные массивы неявно конвертируются в указатели (что вызывает путаницу с тем, являются ли указатели и массивы одним и тем же или нет).
  • Встроенные массивы нельзя копировать (поэтому нельзя их в функцию передавать, например).
  • А если вы напишете встроенный массив в параметре функции, то он тоже неявно конвертируется в указатель:
void f(int a[10])<> // компилируется в void f(int* a)<> 
Арифметика указателей.
long long *p; int n; // Любой целочисленный тип. long long *q = +p; // `+p` — то же, что `p`. По полезности как писать `a = +1` вместо `a = 1`. p++; // Перейти к следующему объекту в памяти. p--; // Перейти к предыдущему. p += n; // Добавить к указателю `n`. p -= n; // Вычесть из указателя `n`. ptrdiff_t diff = p - q; // Разность указателей на одинаковый тип — количество элементов между ними. p[10] = -5; // `p[10]` равносильно `*(p + 10)`. 10[p] = -5; // Равносильно `*(10 + p)`. Так можно, но Безымянного Бога ради не делайте так. 
void* .

Есть особый указатель, пришедший из C — void* . Любой указатель неявно приводится в void* , а void* можно явно (в C — неявно) привести куда угодно. И это только ваша ответственность следить за тем, чтобы это приведение было корректно.

Используется void* во всяких интерфейсах из C, где неизвестен тип объекта (как то malloc или qsort ). В C++ он обычно не нужен.

Сочетание указателей и массивов.

Ещё с массивами из C есть вопрос: int* a[10] — это кто такой: массив указателей или указатель на массив? Первое. Второе — это int (*a)[10] . В общем случае суффиксные деклараторы имеют больший приоритет, чем префиксные (т.е. это в первую очередь массив чего-то, а во вторую «что-то — это указатели»). Ровно также работает использование: если вы пишете выражение x = *a[1] , то у вас сначала будет обращение к первому элементу, а потом его разыменовывание.

Но вообще люди обычно не пишут все эти скобки, а пишут что-то такое:

typedef int type[10]; type* a; // int (*a)[10]; 

Вопрос на засыпку: как хотим завести себе typedef , который будет являться типом переменной

int ***(***a[10][20][30])[40][50][60]; 
typedef int ***(***type[10][20][30])[40][50][60]; 

То есть никакой разницы, переменную вы объявляете или typedef делаете.

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

В ассемблере вы могли сделать что-то такое:

mov RBX, func ; . call RBX 

В C и C++, разумеется, так тоже можно:

void func(int) <> void main()

Это можно использовать для полиморфного поведения. Ещё сто́ит сказать, что указатель на функцию можно вызвать. И более того, функции неявно преобразуются в указатели на себя. Поэтому точно такой же код можно написать так:

void func(int) <> void main()

Указатели на функции подчиняются тем же правилам приоритета, что массивы и обычные указатели. При этом декларатор указателя на функцию считается суффиксным.

Следующий ужас, который мы можем увидеть — функция, возвращающая указатель на функцию. Это выглядит так:

void (*get_function())(int) 

То есть это как объявление указателя на функцию, но с круглыми скобками после имени (это же не сам указатель, а функция, возвращающая его). То есть возвращаемое значение пишется не слева от функции, а вокруг. Хотя на практике с таким не встречаются, а делают typedef .

Мем про switch.

void f(int a) < switch (a) < case 1: printf("1\n"); if (false) case 2: printf("2\n"); if (false) case 3: printf("3\n"); if (false) default: printf("x\n"); >> 

Код выводит 1 , 2 и 3 для соответствующих значений и x иначе. И непонятно, почему. А потому что case — это метки. И switch делает goto по ним. И если не опускать фигурные скобки в данной записи, то получится что-то такое:

void f(int a) < switch (a) < case 1: printf("1\n"); if (false) < case 2: printf("2\n"); >if (false) < case 3: printf("3\n"); >if (false) < default: printf("x\n"); >> > 

И теперь в целом понятно, что происходит, мы прыгаем внутрь if (false) . Так писать ни в коем случае не надо, но с точки зрения языка возможно.

lvalue, rvalue (until C++11).

Понятно, что мы не можем написать что-то типа 2 + 2 = 7 , хотя и слева, и справа — int . Но всё же, почему конкретно, как это в языке работает? А так что в языке есть две категории значений:

  • lvalue — то, что может стоять слева от оператора присваивания.
  • rvalue — то, что не может. Обычно временные объекты.

Ещё обычно у lvalue можно взять адрес, а у rvalue — нельзя.

&a; // ok. &5; // `5` — rvalue. &&a; // `&a` — rvalue. ++a; // Увеличивает и **возвращает по ссылке**. a++; // **Возвращает копию**, а потом увеличивает. a++++; // `a++` - rvalue. ++++a; // В C++ ok, в C — нет. ++a++; // `a++` — rvalue (суффиксный оператор имеет приоритет). +++a; // `+a` — rvalue (лексер работает жадно, воспринимая это как `++(+a)`). a = 4; // Присваивание возвращает **левый аргумент по ссылке**. (a = 5) = 6; // ok. a = b = c; // ok, `a = (b = c)`. 

Детали работы с числами.

Суффиксы констант.

Какой тип имеет 42 ? int . А если мы хотим другой?

Тип Пример
int 42
unsigned 42U
long 42L
unsigned long 42UL
long long 42LL
unsigned long long 42ULL
float 42.0f
double 42.0
long double 42.0L

Все суффиксы не зависят от регистра, но маленькую букву l можно легко перепутать с единицей, поэтому советуют писать большую.

Приведение типов.

Что будет, если складывать числа разного типа? А вот что. Упорядочим числа в такой ряд

  1. int .
  2. unsigned .
  3. long .
  4. unsigned long .
  5. long long .
  6. unsigned long long .

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

Нюансы проявляются в том, что будет, если складывать два char 'а, например. Будет int . Потому что все арифметические операции с числами меньше int 'а выполняются в типе int . Более того, вам даже операции делать не надо, при вызове функции это преобразование также происходит.

Поэтому, кстати, если вы хотите принимать в функцию все типы (чтобы они сохраняли численное значение), то вам хватит int и больше. Реализовать только long long и unsigned long long вы не можете, потому что long , например, не будет знать, куда ему конвертиться. А почему char и short будут? А потому что в языке есть 3 типа конверсий (exact match, promotion и convertion), каждый следующий хуже всех предыдущих, и если у вас есть два одинаково хороших варианта, то ошибка компиляции. Так вот конверсия из short 'а в int — promotion, а long в long long или unsigned long long convertion'ы. Про всё это подробно можно почитать тут.

На дробных числах promotion также есть (из float в double ), но все операции с float 'ами во float 'ах и осуществляются. Если вы совершаете операцию, один аргумент которой — целое число, а другой — число с плавающей точкой, то целое приводится к вещественному.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *