Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Вопрос1
Массивы на языке С++. Описание и использование. Массивы
и указатели. Строковые литералы
Массив это структура данных, которая содержит множество значений, относящихся к одному и тому же типу. Например, массив может содержать 60 значений типа int, которые представляют информацию об объемах продаж за пять лет, 12 значений типа short, представляющих число дней в каждом месяце, или 365 значений типа float, которые указывают ваши ежедневные расходы на питание в течение года. Каждое значение сохраняется в отдельном элементе массива, и компьютер сохраняет все элементы массива в памяти последовательно друг за другом.
Чтобы создать массив, вы используете оператор объявления. Объявление массива должно описывать три вещи:
• Тип значений каждого элемента.
• Имя массива.
• Количество элементов в массиве.
В С++ это достигается модификацией объявления простой переменной и добавлением квадратных скобок, в которых указано число элементов. Например, следующее объявление
short months[12]; // создает массив из 12 элементов типа short
создает массив months, который имеет 12 элементов, каждый из которых может хранить одно значение типа short. По сути, каждый элемент это переменная, которую можно трактовать как простую переменную.
Так выглядит общая форма объявления массива:
typeName arrayName[arraySize];
Выражение arraysize, представляющее количество элементов, должно быть целочисленной константой, такой как 10, либо значением const или константным выражением вроде 8 * sizeof(int),B котором все величины известны на момент компиляции. В частности, arraysize не может быть переменной, чье значение устанавливается во время работы программы. Однако позднее в этой главе вы узнаете, как, используя операцию new, можно обойти это ограничение.
В С++ существует несколько правил, касающихся инициализации массивов. Они ограничивают, когда вы можете ее осуществлять, и определяют, что случится, если количество элементов массива не соответствует количеству элементов инициализатора. Рассмотрим эти правила.
Вы можете использовать инициализацию только при объявлении массива. Ее нельзя выполнить позже, и нельзя присвоить один массив другому:
int cards [4] = {3, б, 8, 10}; // все в порядке
int hand [4]; // все в порядке
hand[4] = {5, 6, 7, 9}; //не допускается
hand = cards; //не допускается
Однако можно использовать индексы и присваивать значения элементам массива индивидуально.
При инициализации массива можно указать меньше значений, чем в массиве объявлено элементов. Например, следующий оператор инициализирует только первые два элемента массива hotelTips:
float hotelTips[5] = {5.0, 2.5};
Если вы инициализируете массив частично, то компилятор присваивает остальным элементом нулевые значения. То есть, очень легко инициализировать весь массив нулями для этого просто нужно явно инициализировать нулем его первый элемент, а инициализацию остальных предоставить компилятору:
long totals [500] = {0};
Следует отметить, что если вы инициализируете его с помощью {1} вместо { 0}, то только первый элемент устанавливается в 1; остальные по-прежнему устанавливаются в 0.
Если вы оставите квадратные скобки пустыми при инициализации массива, то компилятор С++ пересчитает элементы за вас. Предположим, к примеру, что у вас есть такое объявление:
short things [] = {1, 5, 3, 8};
Компилятор сделает things массивом из пяти элементов.
Позволять ли компилятору делать это
Обычно позволять компилятору считать количество элементов плохая привычка, потому что количество, которое он посчитает, может отличаться от того, что вы думаете. Однако, как вы вскоре убедитесь, такой подход может оказаться безопасным при инициализации символьных массивов как строк. И если ваш основной принцип состоит в том, что программа, а не вы, должна знать, какой размер у массивов, то можно сделать нечто вроде такого:
short things [ ] = {1, 5, 3, 8};
int num_elements = sizeof things / sizeof (short);
Удобно это или нет зависит от обстоятельств.
Стандартная библиотека шаблонов С++ (STL) предлагает альтернативу массивам шаблонный класс vector. Это более сложная и гибкая конструкция, нежели встроенный составной тип массива. В главе 16 обсуждается STL и шаблонный класс vector.
Указатели придуманы с целью непосредственного отражения механизмов адресации компьютеров, на которых исполняются программы.
Гарантируется, что нет объектов с нулевым адресом. Следовательно, указатель, равный нулю можно интерпретировать как указатель, который ни на что не ссылается.
Указатели на массивы
Имя массива можно рассматривать как указатель на его первый элемент.
Рассмотрим пример:
1: |
int v[] = { 1,2,3,4 }; int *p1 = v; // Указатель на первый элемент int *p2 = &v[0] // Указатель на первый элемент int *p3 = &v[4] // Указатель на элемент, следующий за последним |
В противоположность указателю на массив, определение массива указателей выглядит следующим образом:
1: |
int *ap[15]; // Массив из 15 указателей на int |
Результат применения операторов -, +, --, ++ к указателю зависит от типа объекта, на который ссылается указатель. Если к указателю p типа T* применяется арифметическая операция, предполагается, что он указывает на элемент массива объектов типа Т; p+1 указывает на следующий элемент массива, а p-1 на предыдущий. То есть целое значение p+1 будет на sizeof(T) больше, чем целое значение р.
Рассмотрим пример обнуления элементов массива с использованием индексов
1: |
int arr[ArraySize]; for (int i=0; i < ArraySize; ++i) arr[i] = 0; |
и с использованием указателей:
1: |
int arr[ArraySize]; int *p=arr; for (int i=0; i < ArraySize; ++i) *p++ = 0; |
Интересна запись *p++. Унарные операторы * и ++ имеют одинаковый приоритет, однако они правоассоциативны. То есть в данном случае первым будет выполняться оператор ++, увеличивающий значение указателя. Указатель будет сдвинут на следующий элемент массива. Но поскольку это оператор постинкремента, то для разыменования будет использовано старое значение указателя. Таким образом, в одном выражении записано сразу два действия: передвинуть указатель и разыменовать указатель. В объект, на который указывает указатель помещается ноль.
Указатели и константы
В операциях с указателями участвуют два объекта: сам указатель и объект, на который он ссылается. Помещение ключевого слова const перед объявлением указателя делает константным объект, а не указатель. Для объявления самого указателя в качестве константы, используется оператор объявления * const, а не просто *.
Примеры:
1: |
const int *p1; // указатель на константу типа int int const *p2; // указатель на константу типа int const int *const p4; // константный указатель на константу типа int int const *const p4; // константный указатель на константу типа int |
Первый и второй варианты записи являются синонимами и обозначают, что константой является объект, на который указывает указатель. То есть нельзя изменять значение, хранящееся в указываемом объекте.
Третий вариант указывает, что константен указатель, то есть его нельзя установить на другой объект в него нельзя занести другой адрес в памяти.
Четвёртый и пятый вариант являются синонимами и указывают, что константен как указатель, так и указываемый объект.
Указатели на строки
Поскольку текстовая строка имеет тип const char [] и является массивом, к ней применимы все ранее приведённые соображения о массивах и указателях.
Строковый литерал можно присвоить переменной типа char *. Это сделано для совместимости с ранними версиями языка C, в которых не было ключевого слова const.
Однако изменение строкового литерала через такой указатель является ошибкой:
1: |
void f() { char *p=”text”; p[3] = a; // ОШИБКА } |
Память под строковые литералы выделяется статически, поэтому их свободно можно возвращать в качестве значения функции:
1: |
const char *access(int i) { … return “access denied”; |
Будут ли одинаковые литералы записываться в одно место памяти или нет зависит от реализации.
Строки оканчиваются нуль-байтом (\0), что делает их удобными для использования указателей. Рассмотрим копирование строк:
1: |
const char src[]=”Строка, которую надо скопировать”; char dst[sizeof src]; const char *p_src = src; char *p_dst = dst; while (*p_dst++ = *p_src++) ; |
Создаются два указателя константный указатель p_src, который хранит адрес копируемого элемента и указатель p_dst, хранящий адрес, куда будет скопирован элемент. Копирование будет продолжаться до тех пор, пока не будет скопирован нуль-байт, завершающий строку.
Указатель на void
Указатель на объект любого типа можно присвоить переменной типа void*.
void* можно присваивать, сравнивать и явно преобразовать в указатель любого другого типа.
Массивы, как параметры функции
При передаче массива как аргумента функции происходит неявное преобразование имени массива в указатель на его начальный элемент с потерей информации о размере массива. Таким образом, массив всегда передаётся по указателю его копия не создаётся .
Указатели на функции
С функцией можно выполнить только две операции: вызывать её и получить её адрес. Адрес функции может быть использован для вызова функции. Например:
1: |
void error(int i); void (*p)(int); p=&error; (*p)(1); |
Разыменование указателя при вызове не обязательно. Также не обязательно пользоваться & для получения адреса функции.
Предыдущий пример может быть записан как:
1: |
void error(int i); void (*p)(int); p=error; p(1); |
Указатели и структуры
В случае, если указатель используется для хранения адреса объекта типа структуры, до доступ к полям структуры может быть осуществлён двумя способами (строки 4 и 5):
1: |
struct vec2 { double x, y; }; p_dir->x=0; |
В строке 4 используется полная форма записи: разыменование указателя и обращение к члену x объекта dir. В строке используется сокращённая форма записи с использованием оператора -> который обеспечивает прямое обращение к члену структуры указываемого объекта. Таким образом, строки 4 и 5 дают один и тот же результат.
Строковый литерал состоит из нуля или более символов из знака источника - набора окруженного в двойные кавычки ("). Строковый литерал представляет последовательность символов, которая, совместно формируют null-, принимаемых завершенную строку.
Строковые литералы могут содержать любой символ из графического символа источника - установка за исключением двойной кавычки ("), обратная косая черта (\) или символа новой строки. Они могут содержать такие же escape-последовательности, описанные в Символьные константы C++.
Строки C++ имеют следующие типы:
Изменение строковую константу результат не определен. Например:
Копировать
char *szStr = "1234";
szStr[2] = 'A'; // Results undefined
Символьные указатели и строковые литералы
При разрешении расширенного синтаксиса строковый литерал
совместим по присваиванию с типом PChar. Это означает, что пере-
менной типа PChar можно присвоить строковый литерал. Например:
var
P: PChar;
.
.
begin
P := 'Привет...';
end;
В результате такого присваивания указатель указывает на об-
ласть памяти, содержащую строку с завершающим нулем, являющуюся
копией строкового литерала. Компилятор записывает строковые лите-
ралы в сегмент данных, аналогично описанию "скрытых" типизирован-
ных констант:
const
TempString: array[0..14] of Char = 'Привет...'#0;
var
P: PChar;
.
.
begin
P := @TempString;
end;
Когда соответствующие формальные параметры имеют тип Char,
строковые литералы вы можете использовать как фактические пара-
метры при вызовах процедур и функций. Например, если имеется про-
цедура с описанием:
procedure PrintStr(Str: PChar);
то допустимы следующие вызовы процедуры:
procedure PrintStr('Строка для проверки');
PrintStr(#10#13);
Аналогично тому, как это происходит при присваивании, компи-
лятор генерирует строку с завершающим нулем, представляющую собой
копию литеральной строки в сегменте данных, и передает указатель
на эту область памяти в параметре Str процедуры PrintStr.
Наконец, типизированная константа типа PChar может инициали-
зироваться строковой константой. Это справедливо также для струк-
турных типов, таких как массивы PChar и записи, а также объекты
PChar.
const
Message: PChar = 'Program terminated';
Prompt: PChar = 'Enter values: ';
Digits; array [0..9] of PChar = {
'Zero', 'One', 'Two', 'Three', 'Four', 'Five',
'Six', 'Seven', Eight', 'Nine'};
Строковая выражение-константа всегда вычисляется как строка
Паскаля, даже если она инициализируется как типизированная конс-
танта типа PChar. Таким образом, строковое выражение-константа
всегда ограничено длиной в 255 символов.
2. Модель взаимодействия открытых систем (OSI).
Международной организацией по стандартизации (ISO) разработана система стандартных протоколов, получившая название модели взаимодействия открытых систем (OSI), часто называемая также эталонной семиуровневой логической моделью открытых систем.
Открытая система система, доступная для взаимодействия с другими системами в соответствии с принятыми стандартами.
Эта система протоколов базируется на разделении всех процедур взаимодействия на отдельные мелкие уровни, для каждого из которых легче создать стандартные алгоритмы их построения.
Модель OSI представляет собой самые общие рекомендации для построения стандартов совместимых сетевых программных продуктов, она же служит базой для производителей при разработке совместимого сетевого оборудования. В настоящее время модель взаимодействия открытых систем является наиболее популярной сетевой архитектурной моделью.
В общем случае сеть должна иметь 7 функциональных уровней (табл. 1.1).
Таблица 1.1. Уровни модели OSI
Уровень OSI |
Назначение |
Примеры протоколов |
7 Прикладной |
Обеспечивает прикладным процессам пользователя средства доступа к сетевым ресурсам; является интерфейсом между программами пользователя и сетью. Имеет интерфейс с пользователем |
Х.400, NCR HTTP, SMTP, FTP, FTAM, SAP, DNS, Telnet и т. д. |
6 Представления |
Устанавливает стандартные способы представления данных, которые удобны для всех взаимодействующих объектов прикладного уровня. Имеет интерфейс с прикладными программами |
X.226 |
5 Сеансовый |
Обеспечивает средства, необходимые сетевым объектам для организации, синхронизации и административного управления обменом данных между ними |
X.225, RPC, NetBEUI и т. д. |
4 Транспортный |
Обеспечивает надежную, экономичную и «прозрачную» передачу данных между взаимодействующими объектами сеансового уровня |
Х.224, TCP, UDP, NSP, SPX, SPP, RH и т. д. |
3 Сетевой |
Обеспечивает маршрутизацию передачи данных в сети, устанавливает логический канал между объектами для реализации протоколов транспортного уровня |
X.25, X.75, IP, IPX, IDP, TH, DNA-4 и т. д. |
2 Канальный |
Обеспечивает непосредственную связь объектов сетевого уровня, функциональные и процедурные средства ее поддержки для эффективной реализации протоколов сетевого уровня |
LAP-B, HDLC, SNAP, SDLC, IEEE 802.2 и т.д. |
1 Физический |
Формирует физическую среду передачи данных, устанавливает соединения объектов сети с этой средой |
Ethernet, Arcnet, Token Ring, IEEE 802.3, 5 |
Прикладной уровень (application) - управляет запуском программ пользователя, их выполнением, вводом-выводом данных, управлением терминалами, административным управлением сетью. На этом уровне обеспечивается предоставление пользователям различных услуг, связанных с запуском его программ. На этом уровне функционируют технологии, являющиеся как бы надстройкой над передачей данных.
Уровень представления (presentation) интерпретация и преобразование передаваемых в сети данных к виду, удобному для прикладных процессов. На практике многие функции этого уровня задействованы на прикладном уровне, поэтому протоколы уровня представлений не получили развития и во многих сетях практически не используются.
Сеансовый уровень (session) организация и проведение сеансов связи между прикладными процессами (инициализация и поддержание сеанса между абонентами сети, управление очередностью и режимами передачи данных). Многие функции этого уровня в части установления соединения и поддержания упорядоченного обмена данными на практике реализуются на транспортном уровне, поэтому протоколы сеансового уровня имеют ограниченное применение.
Транспортный уровень (transport) управление сегментированием данных и транспортировкой данных от источника к потребителю (т.е. обмен управляющей информацией и установление между абонентами логического канала, обеспечение качества передачи данных). Протоколы транспортного уровня развиты очень широко и интенсивно используются на практике. Большое внимание на этом уровне уделено контролю достоверности передаваемой информации.
Сетевой уровень (network) управление логическим каналом передачи данных в сети (адресация и маршрутизация данных). Каждый пользователь сети обязательно использует протоколы этого уровня и имеет свой уникальный сетевой адрес, используемый протоколами сетевого уровня. На этом уровне выполняется структуризация данных разбивка их на пакеты и присвоение пакетам сетевых адресов.
Канальный уровень (datalink) формирование и управление физическим каналом передачи данных между объектами сетевого уровня (установление, поддержание и разъединение логических каналов), обеспечение “прозрачности” физических соединений, контроля и исправления ошибок передачи.
Физический уровень (physical) установление, поддержание и расторжение соединений с физическим каналом сети. Управление выполняется на уровне битов цифровых (импульсы, их амплитуда, форма) и аналоговых (амплитуда, частота, фаза непрерывного сигнала).
Блоки информации, передаваемые между уровнями, имеют стандартный формат: заголовок (header), служебная информация, данные, концевик. Каждый уровень при передаче блока информации нижестоящему уровню снабжает его своим заголовком. Заголовок вышестоящего уровня воспринимается нижестоящим как передаваемые данные.
Средства каждого уровня отрабатывают протокол своего уровня и интерфейсы с соседними уровнями.
Указанные уровни управления можно по разным признакам объединять в группы:
- уровни 1, 2 и частично 3 реализуются в большей части за счет аппаратных средств; верхние уровни с 4 по 7 и частично 3 обеспечиваются программными средствами;
- уровни 1 и 2 ответственны за физические соединения; уровни 3-6 заняты организацией передачи, передачей и преобразованием информации в понятную для абонентской аппаратуры форму; уровень 7 обеспечивает выполнение прикладных программ пользователя.
3 Классическое определение вероятности
Вероятность - это одно из основных понятий теории вероятности.
Вероятностью события называется численная мера степени объективной возможности этого события. Вероятность события А обозначается Р (А).
Достоверным называется событие В, которое в результате опыта непременно должно произойти:
Р (В) = 1
Невозможным называется событие С, которое в результате опыта не может произойти:
Р (С) = 0
Вероятность любого события А заключена между нулем и единицей:
0 ≤ Р(А) ≤ 1
Полной группой событий называется несколько событий таких, что в результате опыта непременно должно произойти хотя бы одно из них. Сумма вероятностей событий, образующих полную группу, равна 1.
Несколько событий называются несовместимыми, если никакие два из них не могут появиться вместе. Пример.
Несколько событий называются равновозможными, если по условиям опыта нет оснований считать какое-либо из них более возможным, чем любое другое.
Если несколько событий: образуют полную группу, несовместны и равновозможны, то они называются случаями.
Случай называется благоприятным событию, если появление этого случая влечет за собой появление события.
Вероятность события А вычисляется по формуле:
,
где n общее число случаев, m число случаев, благоприятных событию А.
Итак, что же такое вероятность?
Определение: вероятностью события А называют отношение числа благоприятствующих этому событию случаев к общему числу всех равновозможных элементарных исходов, образующих полную группу.