mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-08 12:06:27 +08:00
deploy
This commit is contained in:
@@ -4273,7 +4273,7 @@
|
||||
<li>Целочисленные типы <code>byte</code> , <code>short</code> , <code>int</code> , <code>long</code> .</li>
|
||||
<li>Типы с плавающей точкой <code>float</code> , <code>double</code> , используемые для представления дробных чисел.</li>
|
||||
<li>Символьный тип <code>char</code> , используемый для представления букв, знаков препинания и даже эмодзи в разных языках.</li>
|
||||
<li>Логический тип <code>bool</code> , используемый для представления суждений "да" и "нет".</li>
|
||||
<li>Логический тип <code>bool</code> , используемый для представления суждений «да» и «нет».</li>
|
||||
</ul>
|
||||
<p><strong>Базовые типы данных хранятся в компьютере в двоичной форме</strong>. Один двоичный разряд равен <span class="arithmatex">\(1\)</span> биту. В большинстве современных операционных систем <span class="arithmatex">\(1\)</span> байт (byte) состоит из <span class="arithmatex">\(8\)</span> битов (bit).</p>
|
||||
<p>Диапазон значений базовых типов данных зависит от объема занимаемого ими пространства. Ниже в качестве примера используется Java.</p>
|
||||
@@ -4281,7 +4281,7 @@
|
||||
<li>Целочисленный тип <code>byte</code> занимает <span class="arithmatex">\(1\)</span> байт = <span class="arithmatex">\(8\)</span> бит и может представлять <span class="arithmatex">\(2^{8}\)</span> чисел.</li>
|
||||
<li>Целочисленный тип <code>int</code> занимает <span class="arithmatex">\(4\)</span> байта = <span class="arithmatex">\(32\)</span> бита и может представлять <span class="arithmatex">\(2^{32}\)</span> чисел.</li>
|
||||
</ul>
|
||||
<p>В таблице 3-1 перечислены объем памяти, диапазон значений и значения по умолчанию для различных базовых типов данных в Java. Эту таблицу не нужно заучивать наизусть; достаточно иметь общее представление и при необходимости обращаться к ней.</p>
|
||||
<p>В таблице 3-1 перечислены объем памяти, диапазон значений и значения по умолчанию для различных базовых типов данных в Java. Эту таблицу не нужно заучивать наизусть. Достаточно иметь общее представление и при необходимости обращаться к ней.</p>
|
||||
<p align="center"> Таблица 3-1 Объем памяти и диапазоны значений базовых типов данных </p>
|
||||
|
||||
<div class="center-table">
|
||||
@@ -4364,16 +4364,16 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>Обрати внимание: приведенная выше таблица относится именно к базовым типам данных Java. В каждом языке программирования свои определения типов, поэтому объем памяти, диапазон значений и значения по умолчанию могут различаться.</p>
|
||||
<p>Обрати внимание: в таблице 3-1 приведены данные именно для базовых типов данных Java. В каждом языке программирования свои определения типов, поэтому объем памяти, диапазон значений и значения по умолчанию могут различаться.</p>
|
||||
<ul>
|
||||
<li>В Python целочисленный тип <code>int</code> может иметь произвольный размер, ограниченный только доступной памятью; тип <code>float</code> является 64-битным числом двойной точности; типа <code>char</code> нет, а отдельный символ на деле является строкой <code>str</code> длины 1.</li>
|
||||
<li>В C и C++ размер базовых типов данных явно не зафиксирован и зависит от реализации и платформы. таблица 3-1 соответствует модели данных LP64 <a href="https://en.cppreference.com/w/cpp/language/types#Properties">data model</a>, используемой в 64-битных Unix-системах, включая Linux и macOS.</li>
|
||||
<li>Размер символа <code>char</code> в C и C++ составляет 1 байт, а в большинстве других языков программирования зависит от конкретного способа кодирования символов; подробнее это рассматривается в разделе "Кодирование символов".</li>
|
||||
<li>В Python целочисленный тип <code>int</code> может иметь произвольный размер, ограниченный только доступной памятью. Тип <code>float</code> является 64-битным числом двойной точности. Типа <code>char</code> нет, а отдельный символ на деле является строкой <code>str</code> длины 1.</li>
|
||||
<li>В C и C++ размер базовых типов данных явно не зафиксирован и зависит от реализации и платформы. В таблице 3-1 приведены данные для модели LP64 <a href="https://en.cppreference.com/w/cpp/language/types#Properties">data model</a>, используемой в 64-битных Unix-системах, включая Linux и macOS.</li>
|
||||
<li>Размер символа <code>char</code> в C и C++ составляет 1 байт, а в большинстве других языков программирования зависит от конкретного способа кодирования символов. Подробнее это рассматривается в разделе «Кодирование символов».</li>
|
||||
<li>Хотя для представления логического значения достаточно 1 бита ( <span class="arithmatex">\(0\)</span> или <span class="arithmatex">\(1\)</span> ), в памяти оно обычно хранится как 1 байт. Это связано с тем, что современные CPU обычно используют 1 байт как минимальную адресуемую единицу памяти.</li>
|
||||
</ul>
|
||||
<p>Какова же связь между базовыми типами данных и структурами данных? Мы знаем, что структура данных - это способ организации и хранения данных в компьютере. Подлежащее в этой фразе - "структура", а не "данные".</p>
|
||||
<p>Если мы хотим представить "ряд чисел", то естественно подумаем об использовании массива. Это связано с тем, что линейная структура массива может выразить отношения соседства и порядка между числами, а то, что именно хранится внутри - целые <code>int</code> , вещественные <code>float</code> или символы <code>char</code> , - к "структуре данных" отношения не имеет.</p>
|
||||
<p>Иными словами, <strong>базовые типы данных задают "тип содержимого" данных, а структуры данных задают "способ организации" данных</strong>. Например, в следующем коде мы используем одну и ту же структуру данных (массив) для хранения и представления различных базовых типов данных, включая <code>int</code> , <code>float</code> , <code>char</code> , <code>bool</code> и т.д.</p>
|
||||
<p>Какова же связь между базовыми типами данных и структурами данных? Мы знаем, что структура данных - это способ организации и хранения данных в компьютере. Подлежащее в этой фразе - «структура», а не «данные».</p>
|
||||
<p>Если мы хотим представить «ряд чисел», то естественно подумаем об использовании массива. Это связано с тем, что линейная структура массива может выразить отношения соседства и порядка между числами, а то, что именно хранится внутри - целые <code>int</code> , вещественные <code>float</code> или символы <code>char</code> , - к «структуре данных» отношения не имеет.</p>
|
||||
<p>Иными словами, <strong>базовые типы данных задают «тип содержимого» данных, а структуры данных задают «способ организации» данных</strong>. Например, в следующем коде мы используем одну и ту же структуру данных (массив) для хранения и представления различных базовых типов данных, включая <code>int</code> , <code>float</code> , <code>char</code> , <code>bool</code> и т.д.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:13"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
|
||||
@@ -4423,9 +4423,9 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="34">3.4 Кодирование символов *<a class="headerlink" href="#34" title="Permanent link">¶</a></h1>
|
||||
<p>В компьютере все данные хранятся в виде двоичных чисел, и символьный тип данных <code>char</code> не является исключением. Для представления символов необходимо задать "таблицу символов", которая устанавливает взаимно-однозначное соответствие между каждым символом и двоичным числом. С помощью этой таблицы компьютер может преобразовывать двоичные числа в символы.</p>
|
||||
<p>В компьютере все данные хранятся в виде двоичных чисел, и символьный тип данных <code>char</code> не является исключением. Для представления символов необходимо задать «таблицу символов», которая устанавливает взаимно-однозначное соответствие между каждым символом и двоичным числом. С помощью этой таблицы компьютер может преобразовывать двоичные числа в символы.</p>
|
||||
<h2 id="341-ascii">3.4.1 Таблица символов ASCII<a class="headerlink" href="#341-ascii" title="Permanent link">¶</a></h2>
|
||||
<p><u>Код ASCII</u> - это самая ранняя таблица символов; ее полное название - American Standard Code for Information Interchange (американский стандартный код обмена информацией). Для представления символов в ней используются 7 двоичных битов (нижние 7 битов одного байта), что позволяет закодировать до 128 различных символов. Как показано на рисунке 3-6, ASCII включает заглавные и строчные буквы английского алфавита, цифры 0 ~ 9, некоторые знаки препинания, а также некоторые управляющие символы (например перевод строки и табуляцию).</p>
|
||||
<p><u>Код ASCII</u> - это самая ранняя таблица символов. Ее полное название - American Standard Code for Information Interchange (американский стандартный код обмена информацией). Для представления символов в ней используются 7 двоичных битов (нижние 7 битов одного байта), что позволяет закодировать до 128 различных символов. Как показано на рисунке 3-6, ASCII включает заглавные и строчные буквы английского алфавита, цифры 0 ~ 9, некоторые знаки препинания, а также некоторые управляющие символы (например перевод строки и табуляцию).</p>
|
||||
<p><img alt="Таблица ASCII" class="animation-figure" src="../character_encoding.assets/ascii_table.png" /></p>
|
||||
<p align="center"> Рисунок 3-6 Таблица ASCII </p>
|
||||
|
||||
@@ -4435,11 +4435,11 @@
|
||||
<p>Позже люди обнаружили, что <strong>кодов EASCII все равно недостаточно для количества символов во многих языках</strong>. Например, китайских иероглифов существует почти сто тысяч, а в повседневном употреблении нужны тысячи. В 1980 году Государственное управление стандартов Китая выпустило таблицу символов <u>GB2312</u>, включающую 6763 иероглифа, что в основном удовлетворило потребности компьютерной обработки китайского текста.</p>
|
||||
<p>Однако GB2312 не умеет работать с некоторыми редкими иероглифами и традиционными формами письма. Таблица символов <u>GBK</u> представляет собой расширение GB2312 и в общей сложности содержит 21886 иероглифов. В схеме кодирования GBK символы ASCII представляются одним байтом, а китайские иероглифы - двумя байтами.</p>
|
||||
<h2 id="343-unicode">3.4.3 Таблица символов Unicode<a class="headerlink" href="#343-unicode" title="Permanent link">¶</a></h2>
|
||||
<p>С бурным развитием компьютерной техники таблицы символов и стандарты кодирования начали стремительно множиться, и это породило множество проблем. С одной стороны, такие таблицы обычно определяли символы только для конкретных языков и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов кодирования; если две машины использовали разные стандарты, при обмене информацией возникали искажения текста.</p>
|
||||
<p>С бурным развитием компьютерной техники таблицы символов и стандарты кодирования начали стремительно множиться, и это породило множество проблем. С одной стороны, такие таблицы обычно определяли символы только для конкретных языков и не могли нормально работать в многоязычной среде. С другой стороны, для одного и того же языка существовало несколько стандартов кодирования. Если две машины использовали разные стандарты, при обмене информацией возникали искажения текста.</p>
|
||||
<p>Исследователи той эпохи задумались: <strong>если создать достаточно полную таблицу символов, которая включит все языки и знаки мира, разве это не решит проблемы многоязычной среды и искаженного текста</strong>? Под влиянием этой идеи и появилась большая и всеобъемлющая таблица символов Unicode.</p>
|
||||
<p><u>Unicode</u> по-китайски называется "единый код" и теоретически способен вместить более миллиона символов. Его цель - собрать символы со всего мира в единую таблицу символов, предоставить универсальный стандарт для обработки и отображения текстов на разных языках и уменьшить количество проблем с искажением текста, вызванных различиями стандартов кодирования. С момента публикации в 1991 году Unicode непрерывно расширялся, добавляя новые языки и символы. По состоянию на сентябрь 2022 года Unicode уже включал 149186 символов, в том числе буквы разных языков, знаки, а также эмодзи.</p>
|
||||
<p>Как универсальный набор символов, Unicode по сути присваивает каждому символу уникальную "кодовую точку" (числовой идентификатор символа), диапазон которой составляет от U+0000 до U+10FFFF, образуя единое пространство нумерации символов. Однако <strong>Unicode не определяет, как именно хранить эти кодовые точки в компьютере</strong>. Тут неизбежно возникает вопрос: если в одном тексте одновременно встречаются кодовые точки Unicode разной длины, как система должна разбирать символы? Например, если дан код длиной 2 байта, как понять, является ли это одним 2-байтовым символом или двумя 1-байтовыми?</p>
|
||||
<p>Для этой проблемы <strong>прямолинейное решение состоит в том, чтобы хранить все символы в кодировке одинаковой длины</strong>. Как показано на рисунке 3-7, каждый символ в "Hello" занимает 1 байт, а каждый символ в "алгоритм" занимает 2 байта. Мы можем дополнить старшие биты нулями и закодировать все символы в "Hello алгоритм" в виде 2-байтовых единиц. Тогда система сможет считывать по одному символу каждые 2 байта и восстановить эту фразу.</p>
|
||||
<p><u>Unicode</u> по-китайски называется «единый код» и теоретически способен вместить более миллиона символов. Его цель - собрать символы со всего мира в единую таблицу символов, предоставить универсальный стандарт для обработки и отображения текстов на разных языках и уменьшить количество проблем с искажением текста, вызванных различиями стандартов кодирования. С момента публикации в 1991 году Unicode непрерывно расширялся, добавляя новые языки и символы. По состоянию на сентябрь 2022 года Unicode уже включал 149186 символов, в том числе буквы разных языков, знаки, а также эмодзи.</p>
|
||||
<p>Как универсальный набор символов, Unicode по сути присваивает каждому символу уникальную «кодовую точку» (числовой идентификатор символа), диапазон которой составляет от U+0000 до U+10FFFF, образуя единое пространство нумерации символов. Однако <strong>Unicode не определяет, как именно хранить эти кодовые точки в компьютере</strong>. Тут неизбежно возникает вопрос: если в одном тексте одновременно встречаются кодовые точки Unicode разной длины, как система должна разбирать символы? Например, если дан код длиной 2 байта, как понять, является ли это одним 2-байтовым символом или двумя 1-байтовыми?</p>
|
||||
<p>Для этой проблемы <strong>прямолинейное решение состоит в том, чтобы хранить все символы в кодировке одинаковой длины</strong>. Как показано на рисунке 3-7, каждый символ в «Hello» занимает 1 байт, а каждый символ в «алгоритм» занимает 2 байта. Мы можем дополнить старшие биты нулями и закодировать все символы в «Hello алгоритм» в виде 2-байтовых единиц. Тогда система сможет считывать по одному символу каждые 2 байта и восстановить эту фразу.</p>
|
||||
<p><img alt="Пример кодирования Unicode" class="animation-figure" src="../character_encoding.assets/unicode_hello_algo.png" /></p>
|
||||
<p align="center"> Рисунок 3-7 Пример кодирования Unicode </p>
|
||||
|
||||
@@ -4449,9 +4449,9 @@
|
||||
<p>Правила кодирования UTF-8 не слишком сложны и делятся на два случая.</p>
|
||||
<ul>
|
||||
<li>Для символов длиной 1 байт старший бит устанавливается в <span class="arithmatex">\(0\)</span> , а оставшиеся 7 битов содержат кодовую точку Unicode. Стоит отметить, что символы ASCII занимают первые 128 кодовых точек в наборе Unicode. Иными словами, <strong>кодировка UTF-8 обратно совместима с ASCII</strong>. Это означает, что мы можем использовать UTF-8 для разбора очень старых ASCII-текстов.</li>
|
||||
<li>Для символов длиной <span class="arithmatex">\(n\)</span> байт (где <span class="arithmatex">\(n > 1\)</span>) старшие <span class="arithmatex">\(n\)</span> битов первого байта устанавливаются в <span class="arithmatex">\(1\)</span> , а <span class="arithmatex">\((n + 1)\)</span>-й бит устанавливается в <span class="arithmatex">\(0\)</span> ; начиная со второго байта, старшие 2 бита каждого байта устанавливаются в <span class="arithmatex">\(10\)</span> ; все остальные биты используются для заполнения кодовой точки Unicode соответствующего символа.</li>
|
||||
<li>Для символов длиной <span class="arithmatex">\(n\)</span> байт (где <span class="arithmatex">\(n > 1\)</span>) старшие <span class="arithmatex">\(n\)</span> битов первого байта устанавливаются в <span class="arithmatex">\(1\)</span> , а <span class="arithmatex">\((n + 1)\)</span>-й бит устанавливается в <span class="arithmatex">\(0\)</span>. Начиная со второго байта, старшие 2 бита каждого байта устанавливаются в <span class="arithmatex">\(10\)</span>. Все остальные биты используются для заполнения кодовой точки Unicode соответствующего символа.</li>
|
||||
</ul>
|
||||
<p>На рисунке 3-8 показана UTF-8-кодировка для строки "Hello алгоритм". Можно заметить, что поскольку старшие <span class="arithmatex">\(n\)</span> битов установлены в <span class="arithmatex">\(1\)</span> , система может определить длину символа как <span class="arithmatex">\(n\)</span> , подсчитав число ведущих единиц.</p>
|
||||
<p>На рисунке 3-8 показана UTF-8-кодировка для строки «Hello алгоритм». Можно заметить, что поскольку старшие <span class="arithmatex">\(n\)</span> битов установлены в <span class="arithmatex">\(1\)</span> , система может определить длину символа как <span class="arithmatex">\(n\)</span> , подсчитав число ведущих единиц.</p>
|
||||
<p>Но почему старшие 2 бита всех остальных байтов устанавливаются в <span class="arithmatex">\(10\)</span> ? На самом деле это <span class="arithmatex">\(10\)</span> играет роль контрольного маркера. Если система начнет разбирать текст с неверного байта, префикс <span class="arithmatex">\(10\)</span> поможет быстро обнаружить аномалию.</p>
|
||||
<p>Причина выбора <span class="arithmatex">\(10\)</span> в качестве контрольного маркера в том, что по правилам UTF-8 символ не может иметь старшие два бита, равные <span class="arithmatex">\(10\)</span> . Это можно доказать от противного: если предположить, что у некоторого символа старшие два бита равны <span class="arithmatex">\(10\)</span> , то длина такого символа должна быть 1 байт, то есть это ASCII. Но у ASCII старший бит обязан быть <span class="arithmatex">\(0\)</span> , что противоречит предположению.</p>
|
||||
<p><img alt="Пример кодировки UTF-8" class="animation-figure" src="../character_encoding.assets/utf-8_hello_algo.png" /></p>
|
||||
@@ -4459,10 +4459,10 @@
|
||||
|
||||
<p>Помимо UTF-8, распространены еще два следующих способа кодирования.</p>
|
||||
<ul>
|
||||
<li><strong>Кодировка UTF-16</strong>: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представляются 2 байтами; небольшая часть символов требует 4 байта. Для 2-байтовых символов кодировка UTF-16 совпадает с кодовой точкой Unicode.</li>
|
||||
<li><strong>Кодировка UTF-16</strong>: использует 2 или 4 байта для представления символа. Все символы ASCII и часто используемые неанглийские символы представляются 2 байтами. Небольшая часть символов требует 4 байта. Для 2-байтовых символов кодировка UTF-16 совпадает с кодовой точкой Unicode.</li>
|
||||
<li><strong>Кодировка UTF-32</strong>: каждый символ занимает 4 байта. Это означает, что UTF-32 требует больше места, чем UTF-8 и UTF-16, особенно в текстах с большой долей ASCII-символов.</li>
|
||||
</ul>
|
||||
<p>С точки зрения занимаемого места UTF-8 очень эффективна для английских символов, потому что им нужен всего 1 байт; а для некоторых неанглийских символов (например китайских) UTF-16 может быть эффективнее, потому что ей требуется только 2 байта, тогда как UTF-8 может потребовать 3 байта.</p>
|
||||
<p>С точки зрения занимаемого места UTF-8 очень эффективна для английских символов, потому что им нужен всего 1 байт. А для некоторых неанглийских символов (например китайских) UTF-16 может быть эффективнее, потому что ей требуется только 2 байта, тогда как UTF-8 может потребовать 3 байта.</p>
|
||||
<p>С точки зрения совместимости у UTF-8 наилучшая универсальность, и многие инструменты и библиотеки в первую очередь поддерживают именно UTF-8.</p>
|
||||
<h2 id="345">3.4.5 Кодирование символов в языках программирования<a class="headerlink" href="#345" title="Permanent link">¶</a></h2>
|
||||
<p>Для большинства языков программирования прошлого строки во время выполнения программы использовали фиксированные по длине кодировки, такие как UTF-16 или UTF-32. При кодировке фиксированной длины строку можно обрабатывать как массив, и такой подход дает следующие преимущества.</p>
|
||||
@@ -4473,14 +4473,14 @@
|
||||
</ul>
|
||||
<p>Вообще говоря, проектирование схем кодирования символов в языках программирования - очень интересная тема, в которой учитывается множество факторов.</p>
|
||||
<ul>
|
||||
<li>Тип <code>String</code> в Java использует кодировку UTF-16, и каждый символ занимает 2 байта. Это связано с тем, что на раннем этапе проектирования Java считалось, что 16 битов достаточно для представления всех возможных символов. Но это оказалось неверным предположением. Позднее Unicode вышел за пределы 16 битов, поэтому символы в Java теперь могут представляться парой 16-битных значений (так называемой "суррогатной парой").</li>
|
||||
<li>Тип <code>String</code> в Java использует кодировку UTF-16, и каждый символ занимает 2 байта. Это связано с тем, что на раннем этапе проектирования Java считалось, что 16 битов достаточно для представления всех возможных символов. Но это оказалось неверным предположением. Позднее Unicode вышел за пределы 16 битов, поэтому символы в Java теперь могут представляться парой 16-битных значений (так называемой «суррогатной парой»).</li>
|
||||
<li>Строки в JavaScript и TypeScript используют UTF-16 по причинам, похожим на Java. Когда Netscape впервые выпустила JavaScript в 1995 году, Unicode еще находился на ранней стадии развития, и 16-битного кодирования тогда было достаточно для представления всех символов Unicode.</li>
|
||||
<li>C# использует UTF-16 главным образом потому, что платформа .NET была разработана Microsoft, а многие технологии Microsoft (включая Windows) широко используют именно UTF-16.</li>
|
||||
</ul>
|
||||
<p>Из-за недооценки общего числа символов перечисленным выше языкам пришлось использовать "суррогатные пары" для представления Unicode-символов длиной больше 16 бит. Это вынужденный компромисс. С одной стороны, в строках с суррогатными парами один символ может занимать 2 байта или 4 байта, из-за чего теряется преимущество кодировки фиксированной длины. С другой стороны, обработка суррогатных пар требует дополнительного кода, что повышает сложность разработки и отладки.</p>
|
||||
<p>Из-за недооценки общего числа символов перечисленным выше языкам пришлось использовать «суррогатные пары» для представления Unicode-символов длиной больше 16 бит. Это вынужденный компромисс. С одной стороны, в строках с суррогатными парами один символ может занимать 2 байта или 4 байта, из-за чего теряется преимущество кодировки фиксированной длины. С другой стороны, обработка суррогатных пар требует дополнительного кода, что повышает сложность разработки и отладки.</p>
|
||||
<p>По этим причинам некоторые языки программирования предложили иные схемы кодирования.</p>
|
||||
<ul>
|
||||
<li><code>str</code> в Python использует Unicode и гибкое строковое представление, где длина хранимого символа зависит от наибольшей кодовой точки Unicode в строке. Если все символы строки принадлежат ASCII, каждый символ занимает 1 байт; если есть символы за пределами ASCII, но все они лежат в базовой многоязычной плоскости (BMP), каждый символ занимает 2 байта; если встречаются символы за пределами BMP, каждый символ занимает 4 байта.</li>
|
||||
<li><code>str</code> в Python использует Unicode и гибкое строковое представление, где длина хранимого символа зависит от наибольшей кодовой точки Unicode в строке. Если все символы строки принадлежат ASCII, каждый символ занимает 1 байт. Если есть символы за пределами ASCII, но все они лежат в базовой многоязычной плоскости (BMP), каждый символ занимает 2 байта. Если встречаются символы за пределами BMP, каждый символ занимает 4 байта.</li>
|
||||
<li>Тип <code>string</code> в Go внутри использует кодировку UTF-8. Язык Go также предоставляет тип <code>rune</code>, предназначенный для представления одной кодовой точки Unicode.</li>
|
||||
<li>Типы <code>str</code> и <code>String</code> в Rust внутри используют UTF-8. В Rust также есть тип <code>char</code>, представляющий одну кодовую точку Unicode.</li>
|
||||
</ul>
|
||||
|
||||
@@ -4359,16 +4359,16 @@
|
||||
<h1 id="31">3.1 Классификация структур данных<a class="headerlink" href="#31" title="Permanent link">¶</a></h1>
|
||||
<p>К распространенным структурам данных относятся массивы, связные списки, стеки, очереди, хеш-таблицы, деревья, кучи и графы. Их можно классифицировать по двум измерениям: логической структуре и физической структуре.</p>
|
||||
<h2 id="311">3.1.1 Логическая структура: линейная и нелинейная<a class="headerlink" href="#311" title="Permanent link">¶</a></h2>
|
||||
<p><strong>Логическая структура раскрывает логические отношения между элементами данных</strong>. В массивах и связных списках данные расположены в определенном порядке, что отражает линейные отношения между элементами. В деревьях данные расположены по уровням сверху вниз, что демонстрирует отношения "предок" и "потомок". Графы состоят из вершин и ребер, отражая сложные сетевые отношения.</p>
|
||||
<p><strong>Логическая структура раскрывает логические отношения между элементами данных</strong>. В массивах и связных списках данные расположены в определенном порядке, что отражает линейные отношения между элементами. В деревьях данные расположены по уровням сверху вниз, что демонстрирует отношения «предок» и «потомок». Графы состоят из вершин и ребер, отражая сложные сетевые отношения.</p>
|
||||
<p>Как показано на рисунке 3-1, логические структуры делятся на две большие категории: линейные и нелинейные. Линейные структуры более интуитивны, поскольку в них данные расположены линейно и логически связаны. Нелинейные структуры, напротив, представляют собой нелинейное расположение элементов данных.</p>
|
||||
<ul>
|
||||
<li><strong>Линейные структуры данных</strong>: массивы, связные списки, стеки, очереди, хеш-таблицы, в которых элементы связаны отношением "один к одному".</li>
|
||||
<li><strong>Линейные структуры данных</strong>: массивы, связные списки, стеки, очереди, хеш-таблицы, в которых элементы связаны отношением «один к одному».</li>
|
||||
<li><strong>Нелинейные структуры данных</strong>: деревья, кучи, графы, хеш-таблицы.</li>
|
||||
</ul>
|
||||
<p>Нелинейные структуры данных можно дополнительно разделить на древовидные и сетевые.</p>
|
||||
<ul>
|
||||
<li><strong>Древовидные структуры</strong>: деревья, кучи, хеш-таблицы, в которых элементы связаны отношением "один ко многим".</li>
|
||||
<li><strong>Сетевые структуры</strong>: графы, в которых элементы связаны отношением "многие ко многим".</li>
|
||||
<li><strong>Древовидные структуры</strong>: деревья, кучи, хеш-таблицы, в которых элементы связаны отношением «один ко многим».</li>
|
||||
<li><strong>Сетевые структуры</strong>: графы, в которых элементы связаны отношением «многие ко многим».</li>
|
||||
</ul>
|
||||
<p><img alt="Линейные и нелинейные структуры данных" class="animation-figure" src="../classification_of_data_structure.assets/classification_logic_structure.png" /></p>
|
||||
<p align="center"> Рисунок 3-1 Линейные и нелинейные структуры данных </p>
|
||||
@@ -4381,19 +4381,19 @@
|
||||
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Стоит отметить, что сравнение памяти с таблицей Excel - это упрощенная аналогия; реальный механизм работы памяти гораздо сложнее и включает такие понятия, как адресное пространство, управление памятью, кэш-механизмы, виртуальная и физическая память.</p>
|
||||
<p>Стоит отметить, что сравнение памяти с таблицей Excel - это упрощенная аналогия. Реальный механизм работы памяти гораздо сложнее и включает такие понятия, как адресное пространство, управление памятью, кэш-механизмы, виртуальная и физическая память.</p>
|
||||
</div>
|
||||
<p>Память - общий ресурс для всех программ. Когда некоторый участок памяти занят одной программой, другие программы обычно не могут использовать его одновременно. <strong>Поэтому при проектировании структур данных и алгоритмов память занимает важное место</strong>. Например, пиковое потребление памяти алгоритмом не должно превышать объем доступной свободной памяти системы; если не хватает непрерывных крупных участков памяти, выбранная структура данных должна уметь размещаться в разрозненных областях памяти.</p>
|
||||
<p>Память - общий ресурс для всех программ. Когда некоторый участок памяти занят одной программой, другие программы обычно не могут использовать его одновременно. <strong>Поэтому при проектировании структур данных и алгоритмов память занимает важное место</strong>. Например, пиковое потребление памяти алгоритмом не должно превышать объем доступной свободной памяти системы. Если не хватает непрерывных крупных участков памяти, выбранная структура данных должна уметь размещаться в разрозненных областях памяти.</p>
|
||||
<p>Как показано на рисунке 3-3, <strong>физическая структура отражает способ хранения данных в памяти компьютера</strong>. Ее можно разделить на хранение в непрерывном пространстве (массивы) и хранение в разрозненном пространстве (связные списки). Физическая структура на низком уровне определяет способы доступа к данным, их обновления, вставки и удаления. Эти два типа физических структур взаимно дополняют друг друга по временной и пространственной эффективности.</p>
|
||||
<p><img alt="Хранение в непрерывном и разрозненном пространстве" class="animation-figure" src="../classification_of_data_structure.assets/classification_phisical_structure.png" /></p>
|
||||
<p align="center"> Рисунок 3-3 Хранение в непрерывном и разрозненном пространстве </p>
|
||||
|
||||
<p>Стоит отметить, что <strong>все структуры данных реализуются на основе массивов, связных списков или их комбинации</strong>. Например, стек и очередь можно реализовать как с помощью массивов, так и с помощью связных списков; реализация хеш-таблицы также может одновременно включать массивы и связные списки.</p>
|
||||
<p>Стоит отметить, что <strong>все структуры данных реализуются на основе массивов, связных списков или их комбинации</strong>. Например, стек и очередь можно реализовать как с помощью массивов, так и с помощью связных списков. Реализация хеш-таблицы также может одновременно включать массивы и связные списки.</p>
|
||||
<ul>
|
||||
<li><strong>Можно реализовать на основе массивов</strong>: стеки, очереди, хеш-таблицы, деревья, кучи, графы, матрицы, тензоры (массивы размерности <span class="arithmatex">\(\geq 3\)</span> ) и т.д.</li>
|
||||
<li><strong>Можно реализовать на основе связных списков</strong>: стеки, очереди, хеш-таблицы, деревья, кучи, графы и т.д.</li>
|
||||
</ul>
|
||||
<p>После инициализации длину связного списка все еще можно изменять во время выполнения программы, поэтому его также называют "динамической структурой данных". Длина массива после инициализации неизменна, поэтому его также называют "статической структурой данных". Стоит отметить, что массив может изменять длину за счет повторного выделения памяти, тем самым приобретая определенную "динамичность".</p>
|
||||
<p>После инициализации длину связного списка все еще можно изменять во время выполнения программы, поэтому его также называют «динамической структурой данных». Длина массива после инициализации неизменна, поэтому его также называют «статической структурой данных». Стоит отметить, что массив может изменять длину за счет повторного выделения памяти, тем самым приобретая определенную «динамичность».</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Если тебе пока трудно понять физическую структуру, рекомендуется сначала прочитать следующую главу, а затем вернуться к этому разделу.</p>
|
||||
|
||||
@@ -4363,11 +4363,11 @@
|
||||
</div>
|
||||
<h2 id="331">3.3.1 Прямой, обратный и дополнительный коды<a class="headerlink" href="#331" title="Permanent link">¶</a></h2>
|
||||
<p>В таблице из предыдущего раздела можно заметить, что все целочисленные типы могут представлять на одно отрицательное число больше, чем положительных. Например, диапазон <code>byte</code> равен <span class="arithmatex">\([-128, 127]\)</span> . Это выглядит не слишком интуитивно, и внутренняя причина связана с прямым, обратным и дополнительным кодами.</p>
|
||||
<p>Прежде всего нужно отметить, что <strong>числа хранятся в компьютере в виде "дополнительного кода"</strong>. Прежде чем разбирать причины такого решения, сначала дадим определения всем трем способам представления.</p>
|
||||
<p>Прежде всего нужно отметить, что <strong>числа хранятся в компьютере в виде «дополнительного кода»</strong>. Прежде чем разбирать причины такого решения, сначала дадим определения всем трем способам представления.</p>
|
||||
<ul>
|
||||
<li><strong>Прямой код</strong>: старший бит двоичного представления числа рассматривается как знаковый, где <span class="arithmatex">\(0\)</span> означает положительное число, а <span class="arithmatex">\(1\)</span> - отрицательное; остальные биты представляют значение числа.</li>
|
||||
<li><strong>Обратный код</strong>: для положительного числа обратный код совпадает с прямым; для отрицательного числа он получается инверсией всех битов прямого кода, кроме знакового бита.</li>
|
||||
<li><strong>Дополнительный код</strong>: для положительного числа дополнительный код совпадает с прямым; для отрицательного числа он получается добавлением <span class="arithmatex">\(1\)</span> к его обратному коду.</li>
|
||||
<li><strong>Прямой код</strong>: старший бит двоичного представления числа рассматривается как знаковый, где <span class="arithmatex">\(0\)</span> означает положительное число, а <span class="arithmatex">\(1\)</span> - отрицательное. Остальные биты представляют значение числа.</li>
|
||||
<li><strong>Обратный код</strong>: для положительного числа обратный код совпадает с прямым. Для отрицательного числа он получается инверсией всех битов прямого кода, кроме знакового бита.</li>
|
||||
<li><strong>Дополнительный код</strong>: для положительного числа дополнительный код совпадает с прямым. Для отрицательного числа он получается добавлением <span class="arithmatex">\(1\)</span> к его обратному коду.</li>
|
||||
</ul>
|
||||
<p>На рисунке 3-4 показаны способы преобразования между прямым, обратным и дополнительным кодами.</p>
|
||||
<p><img alt="Преобразования между прямым, обратным и дополнительным кодами" class="animation-figure" src="../number_encoding.assets/1s_2s_complement.png" /></p>
|
||||
@@ -4377,8 +4377,8 @@
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
& 1 + (-2) \newline
|
||||
& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline
|
||||
& = 1000 \; 0011 \newline
|
||||
& \rightarrow 0000 \. 0001 + 1000 \. 0010 \newline
|
||||
& = 1000 \. 0011 \newline
|
||||
& \rightarrow -3
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
@@ -4386,45 +4386,45 @@
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
& 1 + (-2) \newline
|
||||
& \rightarrow 0000 \; 0001 \; \text{(прямой код)} + 1000 \; 0010 \; \text{(прямой код)} \newline
|
||||
& = 0000 \; 0001 \; \text{(обратный код)} + 1111 \; 1101 \; \text{(обратный код)} \newline
|
||||
& = 1111 \; 1110 \; \text{(обратный код)} \newline
|
||||
& = 1000 \; 0001 \; \text{(прямой код)} \newline
|
||||
& \rightarrow 0000 \. 0001 \. \text{(прямой код)} + 1000 \. 0010 \. \text{(прямой код)} \newline
|
||||
& = 0000 \. 0001 \. \text{(обратный код)} + 1111 \. 1101 \. \text{(обратный код)} \newline
|
||||
& = 1111 \. 1110 \. \text{(обратный код)} \newline
|
||||
& = 1000 \. 0001 \. \text{(прямой код)} \newline
|
||||
& \rightarrow -1
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>С другой стороны, **в прямом коде у нуля есть два представления: <span class="arithmatex">\(+0\)</span> и <span class="arithmatex">\(-0\)</span> **. Это означает, что числу ноль соответствуют два разных двоичных кода, что может приводить к неоднозначности. Например, если в условном выражении не различать положительный и отрицательный ноль, можно получить ошибочный результат. А если специально обрабатывать такую неоднозначность, придется вводить дополнительные проверки, что может снизить вычислительную эффективность компьютера.</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
+0 & \rightarrow 0000 \; 0000 \newline
|
||||
-0 & \rightarrow 1000 \; 0000
|
||||
+0 & \rightarrow 0000 \. 0000 \newline
|
||||
-0 & \rightarrow 1000 \. 0000
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>Как и прямой код, обратный код тоже страдает от неоднозначности положительного и отрицательного нуля, поэтому компьютеры ввели <u>дополнительный код (2's complement)</u>. Сначала посмотрим на процесс преобразования отрицательного нуля из прямого кода в обратный, а затем в дополнительный:</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
-0 \rightarrow \; & 1000 \; 0000 \; \text{(прямой код)} \newline
|
||||
= \; & 1111 \; 1111 \; \text{(обратный код)} \newline
|
||||
= 1 \; & 0000 \; 0000 \; \text{(дополнительный код)} \newline
|
||||
-0 \rightarrow \. & 1000 \. 0000 \. \text{(прямой код)} \newline
|
||||
= \. & 1111 \. 1111 \. \text{(обратный код)} \newline
|
||||
= 1 \. & 0000 \. 0000 \. \text{(дополнительный код)} \newline
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>При добавлении <span class="arithmatex">\(1\)</span> к обратному коду отрицательного нуля возникает перенос, но длина типа <code>byte</code> составляет всего 8 бит, поэтому переполнившаяся в 9-й бит единица отбрасывается. Иными словами, <strong>дополнительный код отрицательного нуля равен <span class="arithmatex">\(0000 \; 0000\)</span> и совпадает с дополнительным кодом положительного нуля</strong>. Значит, в представлении дополнительного кода существует только один ноль, и проблема неоднозначности положительного и отрицательного нуля тем самым устраняется.</p>
|
||||
<p>При добавлении <span class="arithmatex">\(1\)</span> к обратному коду отрицательного нуля возникает перенос, но длина типа <code>byte</code> составляет всего 8 бит, поэтому переполнившаяся в 9-й бит единица отбрасывается. Иными словами, <strong>дополнительный код отрицательного нуля равен <span class="arithmatex">\(0000 \. 0000\)</span> и совпадает с дополнительным кодом положительного нуля</strong>. Значит, в представлении дополнительного кода существует только один ноль, и проблема неоднозначности положительного и отрицательного нуля тем самым устраняется.</p>
|
||||
<p>Остается последний вопрос: диапазон типа <code>byte</code> равен <span class="arithmatex">\([-128, 127]\)</span> , откуда берется лишнее отрицательное число <span class="arithmatex">\(-128\)</span> ? Мы замечаем, что у всех целых чисел из интервала <span class="arithmatex">\([-127, +127]\)</span> есть соответствующие прямой, обратный и дополнительный коды, а прямой и дополнительный коды можно преобразовывать друг в друга.</p>
|
||||
<p>Однако <strong>дополнительный код <span class="arithmatex">\(1000 \; 0000\)</span> является исключением: у него нет соответствующего прямого кода</strong>. Согласно правилу преобразования, прямой код для этого дополнительного кода должен быть равен <span class="arithmatex">\(0000 \; 0000\)</span> . Это очевидное противоречие, потому что такой прямой код обозначает число <span class="arithmatex">\(0\)</span> , а его дополнительный код должен совпадать с ним самим. Компьютер просто определяет, что этот особый дополнительный код <span class="arithmatex">\(1000 \; 0000\)</span> представляет число <span class="arithmatex">\(-128\)</span> . На самом деле результат вычисления <span class="arithmatex">\((-1) + (-127)\)</span> в дополнительном коде как раз и равен <span class="arithmatex">\(-128\)</span> .</p>
|
||||
<p>Однако <strong>дополнительный код <span class="arithmatex">\(1000 \. 0000\)</span> является исключением: у него нет соответствующего прямого кода</strong>. Согласно правилу преобразования, прямой код для этого дополнительного кода должен быть равен <span class="arithmatex">\(0000 \. 0000\)</span> . Это очевидное противоречие, потому что такой прямой код обозначает число <span class="arithmatex">\(0\)</span> , а его дополнительный код должен совпадать с ним самим. Компьютер просто определяет, что этот особый дополнительный код <span class="arithmatex">\(1000 \. 0000\)</span> представляет число <span class="arithmatex">\(-128\)</span> . На самом деле результат вычисления <span class="arithmatex">\((-1) + (-127)\)</span> в дополнительном коде как раз и равен <span class="arithmatex">\(-128\)</span> .</p>
|
||||
<div class="arithmatex">\[
|
||||
\begin{aligned}
|
||||
& (-127) + (-1) \newline
|
||||
& \rightarrow 1111 \; 1111 \; \text{(прямой код)} + 1000 \; 0001 \; \text{(прямой код)} \newline
|
||||
& = 1000 \; 0000 \; \text{(обратный код)} + 1111 \; 1110 \; \text{(обратный код)} \newline
|
||||
& = 1000 \; 0001 \; \text{(дополнительный код)} + 1111 \; 1111 \; \text{(дополнительный код)} \newline
|
||||
& = 1000 \; 0000 \; \text{(дополнительный код)} \newline
|
||||
& \rightarrow 1111 \. 1111 \. \text{(прямой код)} + 1000 \. 0001 \. \text{(прямой код)} \newline
|
||||
& = 1000 \. 0000 \. \text{(обратный код)} + 1111 \. 1110 \. \text{(обратный код)} \newline
|
||||
& = 1000 \. 0001 \. \text{(дополнительный код)} + 1111 \. 1111 \. \text{(дополнительный код)} \newline
|
||||
& = 1000 \. 0000 \. \text{(дополнительный код)} \newline
|
||||
& \rightarrow -128
|
||||
\end{aligned}
|
||||
\]</div>
|
||||
<p>Ты, вероятно, уже заметил, что все приведенные выше вычисления были операциями сложения. Это указывает на важный факт: <strong>аппаратные схемы внутри компьютера в основном проектируются на основе операций сложения</strong>. Причина в том, что сложение по сравнению с другими операциями (например умножением, делением и вычитанием) проще реализуется на аппаратном уровне, легче распараллеливается и выполняется быстрее.</p>
|
||||
<p>Обрати внимание: это не означает, что компьютер умеет только складывать. <strong>Комбинируя сложение с некоторыми базовыми логическими операциями, компьютер может реализовать и другие математические операции</strong>. Например, вычитание <span class="arithmatex">\(a - b\)</span> можно преобразовать в сложение <span class="arithmatex">\(a + (-b)\)</span> ; умножение и деление можно свести к многократному сложению или вычитанию.</p>
|
||||
<p>Обрати внимание: это не означает, что компьютер умеет только складывать. <strong>Комбинируя сложение с некоторыми базовыми логическими операциями, компьютер может реализовать и другие математические операции</strong>. Например, вычитание <span class="arithmatex">\(a - b\)</span> можно преобразовать в сложение <span class="arithmatex">\(a + (-b)\)</span>. Умножение и деление можно свести к многократному сложению или вычитанию.</p>
|
||||
<p>Теперь можно подвести итог, почему компьютеры используют дополнительный код: с представлением в дополнительном коде компьютер может использовать одни и те же схемы и операции для сложения положительных и отрицательных чисел, без необходимости проектировать специальные аппаратные схемы для вычитания и без особой обработки неоднозначности положительного и отрицательного нуля. Это значительно упрощает аппаратную архитектуру и повышает эффективность вычислений.</p>
|
||||
<p>Идея дополнительного кода очень изящна; из-за ограничений по объему мы на этом остановимся. Если тебе интересно, стоит изучить эту тему глубже.</p>
|
||||
<p>Идея дополнительного кода очень изящна. Из-за ограничений по объему мы на этом остановимся. Если тебе интересно, стоит изучить эту тему глубже.</p>
|
||||
<h2 id="332">3.3.2 Кодирование чисел с плавающей точкой<a class="headerlink" href="#332" title="Permanent link">¶</a></h2>
|
||||
<p>Внимательный читатель может заметить: <code>int</code> и <code>float</code> имеют одинаковую длину, по 4 байта , но почему диапазон значений у <code>float</code> намного больше, чем у <code>int</code> ? Это выглядит парадоксально, ведь <code>float</code> должен еще представлять дробные числа, а значит диапазон вроде бы должен быть меньше.</p>
|
||||
<p>На самом деле <strong>это связано с тем, что число с плавающей точкой <code>float</code> использует другой способ представления</strong>. Обозначим двоичное число длиной 32 бита как:</p>
|
||||
@@ -4455,12 +4455,12 @@ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0
|
||||
<p><img alt="Пример вычисления float по стандарту IEEE 754" class="animation-figure" src="../number_encoding.assets/ieee_754_float.png" /></p>
|
||||
<p align="center"> Рисунок 3-5 Пример вычисления float по стандарту IEEE 754 </p>
|
||||
|
||||
<p>Посмотрим на рисунок 3-5: если взять пример <span class="arithmatex">\(\mathrm{S} = 0\)</span> , <span class="arithmatex">\(\mathrm{E} = 124\)</span> , <span class="arithmatex">\(\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\)</span> , то получим:</p>
|
||||
<p>Как видно на рисунке 3-5, если взять пример <span class="arithmatex">\(\mathrm{S} = 0\)</span> , <span class="arithmatex">\(\mathrm{E} = 124\)</span> , <span class="arithmatex">\(\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\)</span> , то получим:</p>
|
||||
<div class="arithmatex">\[
|
||||
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
|
||||
\]</div>
|
||||
<p>Теперь мы можем ответить на исходный вопрос: <strong>в представлении <code>float</code> присутствуют биты экспоненты, поэтому его диапазон значений намного больше, чем у <code>int</code></strong>. Согласно приведенным выше вычислениям, максимально возможное положительное число для <code>float</code> равно <span class="arithmatex">\(2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}\)</span> ; если изменить бит знака, получим минимальное отрицательное число.</p>
|
||||
<p><strong>Хотя число с плавающей точкой <code>float</code> расширяет диапазон значений, побочным эффектом становится потеря точности</strong>. Целочисленный тип <code>int</code> использует все 32 бита для представления числа, и числа распределены равномерно; а из-за существования битов экспоненты у <code>float</code> чем больше число, тем больше обычно становится разница между двумя соседними представимыми значениями.</p>
|
||||
<p>Теперь мы можем ответить на исходный вопрос: <strong>в представлении <code>float</code> присутствуют биты экспоненты, поэтому его диапазон значений намного больше, чем у <code>int</code></strong>. Согласно приведенным выше вычислениям, максимально возможное положительное число для <code>float</code> равно <span class="arithmatex">\(2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}\)</span>. Если изменить бит знака, получим минимальное отрицательное число.</p>
|
||||
<p><strong>Хотя число с плавающей точкой <code>float</code> расширяет диапазон значений, побочным эффектом становится потеря точности</strong>. Целочисленный тип <code>int</code> использует все 32 бита для представления числа, и числа распределены равномерно. А из-за существования битов экспоненты у <code>float</code> чем больше число, тем больше обычно становится разница между двумя соседними представимыми значениями.</p>
|
||||
<p>Как показано в таблице 3-2, значения экспоненты <span class="arithmatex">\(\mathrm{E} = 0\)</span> и <span class="arithmatex">\(\mathrm{E} = 255\)</span> имеют специальный смысл и <strong>используются для представления нуля, бесконечности, <span class="arithmatex">\(\mathrm{NaN}\)</span> и т.д.</strong></p>
|
||||
<p align="center"> Таблица 3-2 Значение поля экспоненты </p>
|
||||
|
||||
|
||||
@@ -4372,32 +4372,32 @@
|
||||
</ul>
|
||||
<h3 id="2-q-a">2. Q & A<a class="headerlink" href="#2-q-a" title="Permanent link">¶</a></h3>
|
||||
<p><strong>Q</strong>: Почему хеш-таблица одновременно включает линейные и нелинейные структуры данных?</p>
|
||||
<p>В основе хеш-таблицы лежит массив, а для разрешения коллизий мы можем использовать "цепочки адресации" (об этом будет рассказано в последующем разделе "Хеш-коллизии"): каждый бакет массива указывает на связный список, а если длина списка превышает некоторый порог, он может быть преобразован в дерево (обычно в красно-черное дерево).</p>
|
||||
<p>В основе хеш-таблицы лежит массив, а для разрешения коллизий мы можем использовать «цепочки адресации» (об этом будет рассказано в последующем разделе «Хеш-коллизии»): каждый бакет массива указывает на связный список, а если длина списка превышает некоторый порог, он может быть преобразован в дерево (обычно в красно-черное дерево).</p>
|
||||
<p>С точки зрения хранения данных в основе хеш-таблицы находится массив, где каждый слот бакета может содержать либо отдельное значение, либо связный список, либо дерево. Поэтому хеш-таблица действительно может одновременно включать линейные структуры данных (массивы, списки) и нелинейные структуры данных (деревья).</p>
|
||||
<p><strong>Q</strong>: Длина типа <code>char</code> равна 1 байту?</p>
|
||||
<p>Длина типа <code>char</code> определяется используемым в языке программирования способом кодирования. Например, Java, JavaScript, TypeScript и C# используют кодировку UTF-16 (для хранения кодовых точек Unicode), поэтому длина <code>char</code> у них равна 2 байтам.</p>
|
||||
<p><strong>Q</strong>: Не является ли двусмысленным утверждение, что структуры данных, реализованные на основе массива, также называются "статическими структурами данных"? Ведь стек тоже поддерживает операции push и pop, а они явно "динамические".</p>
|
||||
<p>Стек действительно может поддерживать динамические операции над данными, но сама структура данных при этом остается "статической" (ее длина неизменна). Хотя структуры на основе массива могут динамически добавлять и удалять элементы, их емкость фиксирована. Если количество данных превышает заранее выделенный размер, приходится создавать новый, более крупный массив и копировать в него содержимое старого.</p>
|
||||
<p><strong>Q</strong>: При построении стека (очереди) его размер не задается явно, почему же его относят к "статическим структурам данных"?</p>
|
||||
<p>В языках высокого уровня нам не нужно вручную задавать начальную емкость стека (очереди): это автоматически делает сама реализация класса. Например, начальная емкость <code>ArrayList</code> в Java обычно равна 10. Кроме того, автоматом реализуется и расширение емкости. Подробнее это рассматривается в последующем разделе о "списках".</p>
|
||||
<p><strong>Q</strong>: Если метод преобразования из прямого кода в дополнительный - это "сначала инвертировать, затем прибавить 1", то обратное преобразование из дополнительного кода в прямой, по идее, должно быть обратной операцией "сначала вычесть 1, затем инвертировать". Почему же дополнительный код также можно перевести в прямой тем же способом "сначала инвертировать, затем прибавить 1"?</p>
|
||||
<p>Это связано с тем, что взаимное преобразование прямого и дополнительного кодов по сути является вычислением "дополнения". Сначала дадим определение дополнения: если <span class="arithmatex">\(a + b = c\)</span> , то говорят, что <span class="arithmatex">\(a\)</span> является дополнением числа <span class="arithmatex">\(b\)</span> до <span class="arithmatex">\(c\)</span> ; аналогично, <span class="arithmatex">\(b\)</span> является дополнением числа <span class="arithmatex">\(a\)</span> до <span class="arithmatex">\(c\)</span> .</p>
|
||||
<p>Для двоичного числа длины <span class="arithmatex">\(n = 4\)</span> со значением <span class="arithmatex">\(0010\)</span> , если рассматривать его как прямой код (не учитывая знаковый бит), то его дополнительный код получается правилом "сначала инвертировать, затем прибавить 1":</p>
|
||||
<p><strong>Q</strong>: Не является ли двусмысленным утверждение, что структуры данных, реализованные на основе массива, также называются «статическими структурами данных»? Ведь стек тоже поддерживает операции push и pop, а они явно «динамические».</p>
|
||||
<p>Стек действительно может поддерживать динамические операции над данными, но сама структура данных при этом остается «статической» (ее длина неизменна). Хотя структуры на основе массива могут динамически добавлять и удалять элементы, их емкость фиксирована. Если количество данных превышает заранее выделенный размер, приходится создавать новый, более крупный массив и копировать в него содержимое старого.</p>
|
||||
<p><strong>Q</strong>: При построении стека (очереди) его размер не задается явно, почему же его относят к «статическим структурам данных»?</p>
|
||||
<p>В языках высокого уровня нам не нужно вручную задавать начальную емкость стека (очереди): это автоматически делает сама реализация класса. Например, начальная емкость <code>ArrayList</code> в Java обычно равна 10. Кроме того, автоматом реализуется и расширение емкости. Подробнее это рассматривается в последующем разделе о «списках».</p>
|
||||
<p><strong>Q</strong>: Если метод преобразования из прямого кода в дополнительный - это «сначала инвертировать, затем прибавить 1», то обратное преобразование из дополнительного кода в прямой, по идее, должно быть обратной операцией «сначала вычесть 1, затем инвертировать». Почему же дополнительный код также можно перевести в прямой тем же способом «сначала инвертировать, затем прибавить 1»?</p>
|
||||
<p>Это связано с тем, что взаимное преобразование прямого и дополнительного кодов по сути является вычислением «дополнения». Сначала дадим определение дополнения: если <span class="arithmatex">\(a + b = c\)</span> , то говорят, что <span class="arithmatex">\(a\)</span> является дополнением числа <span class="arithmatex">\(b\)</span> до <span class="arithmatex">\(c\)</span>. Аналогично, <span class="arithmatex">\(b\)</span> является дополнением числа <span class="arithmatex">\(a\)</span> до <span class="arithmatex">\(c\)</span> .</p>
|
||||
<p>Для двоичного числа длины <span class="arithmatex">\(n = 4\)</span> со значением <span class="arithmatex">\(0010\)</span> , если рассматривать его как прямой код (не учитывая знаковый бит), то его дополнительный код получается правилом «сначала инвертировать, затем прибавить 1»:</p>
|
||||
<div class="arithmatex">\[
|
||||
0010 \rightarrow 1101 \rightarrow 1110
|
||||
\]</div>
|
||||
<p>Мы видим, что сумма прямого и дополнительного кодов равна <span class="arithmatex">\(0010 + 1110 = 10000\)</span> , то есть дополнительный код <span class="arithmatex">\(1110\)</span> является "дополнением" прямого кода <span class="arithmatex">\(0010\)</span> до <span class="arithmatex">\(10000\)</span> . **Это означает, что описанная выше операция "сначала инвертировать, затем прибавить 1" на самом деле вычисляет дополнение до <span class="arithmatex">\(10000\)</span> **.</p>
|
||||
<p>Тогда чему равно "дополнение" дополнительного кода <span class="arithmatex">\(1110\)</span> до <span class="arithmatex">\(10000\)</span> ? Мы снова можем получить его правилом "сначала инвертировать, затем прибавить 1":</p>
|
||||
<p>Мы видим, что сумма прямого и дополнительного кодов равна <span class="arithmatex">\(0010 + 1110 = 10000\)</span> , то есть дополнительный код <span class="arithmatex">\(1110\)</span> является «дополнением» прямого кода <span class="arithmatex">\(0010\)</span> до <span class="arithmatex">\(10000\)</span> . **Это означает, что описанная выше операция «сначала инвертировать, затем прибавить 1» на самом деле вычисляет дополнение до <span class="arithmatex">\(10000\)</span> **.</p>
|
||||
<p>Тогда чему равно «дополнение» дополнительного кода <span class="arithmatex">\(1110\)</span> до <span class="arithmatex">\(10000\)</span> ? Мы снова можем получить его правилом «сначала инвертировать, затем прибавить 1»:</p>
|
||||
<div class="arithmatex">\[
|
||||
1110 \rightarrow 0001 \rightarrow 0010
|
||||
\]</div>
|
||||
<p>Иначе говоря, прямой и дополнительный коды являются взаимными "дополнениями" друг друга до <span class="arithmatex">\(10000\)</span> , поэтому и "прямой код -> дополнительный код", и "дополнительный код -> прямой код" можно реализовать одной и той же операцией (сначала инвертировать, затем прибавить 1).</p>
|
||||
<p>Разумеется, можно получить прямой код из дополнительного кода <span class="arithmatex">\(1110\)</span> и обратной операцией, то есть "сначала вычесть 1, затем инвертировать":</p>
|
||||
<p>Иначе говоря, прямой и дополнительный коды являются взаимными «дополнениями» друг друга до <span class="arithmatex">\(10000\)</span> , поэтому и «прямой код -> дополнительный код», и «дополнительный код -> прямой код» можно реализовать одной и той же операцией (сначала инвертировать, затем прибавить 1).</p>
|
||||
<p>Разумеется, можно получить прямой код из дополнительного кода <span class="arithmatex">\(1110\)</span> и обратной операцией, то есть «сначала вычесть 1, затем инвертировать»:</p>
|
||||
<div class="arithmatex">\[
|
||||
1110 \rightarrow 1101 \rightarrow 0010
|
||||
\]</div>
|
||||
<p>В итоге и "сначала инвертировать, затем прибавить 1", и "сначала вычесть 1, затем инвертировать" - это два эквивалентных способа вычисления дополнения до <span class="arithmatex">\(10000\)</span> .</p>
|
||||
<p>По сути операция "инвертировать" сама по себе вычисляет дополнение до <span class="arithmatex">\(1111\)</span> (потому что всегда выполняется <code>прямой код + обратный код = 1111</code> ); а дополнительный код, получающийся после добавления 1 к обратному коду, и есть дополнение до <span class="arithmatex">\(10000\)</span> .</p>
|
||||
<p>В итоге и «сначала инвертировать, затем прибавить 1», и «сначала вычесть 1, затем инвертировать» - это два эквивалентных способа вычисления дополнения до <span class="arithmatex">\(10000\)</span> .</p>
|
||||
<p>По сути операция «инвертировать» сама по себе вычисляет дополнение до <span class="arithmatex">\(1111\)</span> (потому что всегда выполняется <code>прямой код + обратный код = 1111</code> ). А дополнительный код, получающийся после добавления 1 к обратному коду, и есть дополнение до <span class="arithmatex">\(10000\)</span> .</p>
|
||||
<p>Приведенный выше пример использовал <span class="arithmatex">\(n = 4\)</span> , но его можно обобщить на двоичные числа любой длины.</p>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
Reference in New Issue
Block a user