Адрес переменной и значение переменной по адресу 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Адрес переменной и значение переменной по адресу



Второй частью ссылки является URL - адрес. Это не что иное, как адрес Web - страницы, которая будет загружена, если щелкнуть на указателе ссылки кнопкой мыши. Указание адреса может быть относительным или абсолютным:

 

Относительные указатели. URL - адрес файла, расположенный на том же компьютере, что и документ, в котором находится указатель этой ссылки, называется относительным. Это означает, что если браузер загрузил страницу, находящуюся по адресу http://www.mysite.com/page, то относительный адрес /picture подразумевает адрес http://www.mysite.com/page/picture, т.е. подкаталог, расположенный на той же машине. Относительные адреса удобны в использовании. Намного проще вставить только имя файла, а не весь длинный URL - адрес. Они также позволяют перемещать файлы в пределах сервера без больших изменений в межстраничной адресации.

 

Абсолютные указатели. URL - адрес, полностью определяющий компьютер, каталог и файл, называется абсолютным. В отличие от относительных адресов, абсолютные могут ссылаться на файлы, расположенные на других компьютерах.

Рассмотрим две переменные: целочисленную переменную x и указатель на целочисленную переменную.

int x;int *p;

Чтобы получить адрес переменной, нужно перед ее именем написать амперсанд.

p = &x;

Данная конструкция будет выполняться справа налево. Сначала с помощью оператора &, примененного к переменной x, будет получен адрес x. Затем адрес x будет сохранен в указателе p.

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

int y = *p;

Такая операция в русском языке называется не слишком благозвучным словом «разыменование». В английском — dereference.

В данном примере с помощью оператора * мы получим то значение, которое находится в памяти по адресу p. Затем мы сохраним его в переменную y. В итоге получится, что значения x и y совпадают.

Все это несложно увидеть на экране.

#include int main(void) { int x; int y; int *p; x = 13; y = 0; p = &x; y = *p; printf("Value of x\t%d", x); printf("Address of x\t%p", &x); printf("\n"); printf("Value of p\t%p", p); printf("Address of p\t%p", &p); printf("\n"); printf("Value of y\t%d", y); printf("Address of y\t%p", &y); printf("\n"); return 0;}

Билет №16

Динамическое выделение памяти.

Динамические объекты не имеют собственных имен, и действия над ними производятсякосвенно, с помощью указателей; · выделение и освобождение памяти под статические объекты производится компилятором автоматически.Выделение и освобождение памяти под статические объекты производится компилятором автоматически. Программисту не нужно самому заботиться об этом. Выделение и освобождение памяти под динамические объекты целиком и полностью возлагается на программиста. Это достаточно сложная задача, при решении которой легко наделать ошибок. Для манипуляции динамически выделяемой памятью служат операторы new и delete.

Оператор new имеет две формы. Первая форма выделяет память под единичный объект определенного типа:

int *pint = new int(1024);

Здесь оператор new выделяет память под безымянный объект типа int, инициализирует его значением 1024 и возвращает адрес созданного объекта. Этот адрес используется для инициализации указателя pint. Все действия над таким безымянным объектом производятся путем разыменовывания данного указателя, т.к. явно манипулировать динамическим объектом невозможно.

Вторая форма оператора new выделяет память под массив заданного размера, состоящий из элементов определенного типа:

int *pia = new int[4];

В этом примере память выделяется под массив из четырех элементов типа int. К сожалению, данная форма оператора new не позволяет инициализировать элементы массива.

Некоторую путаницу вносит то, что обе формы оператора new возвращают одинаковый указатель, в нашем примере это указатель на целое. И pint, иpia объявлены совершенно одинаково, однако pint указывает на единственный объект типа int, а pia – на первый элемент массива из четырех объектов типа int.

Когда динамический объект больше не нужен, мы должны явным образом освободить отведенную под него память. Это делается с помощью оператора delete, имеющего, как и new, две формы – для единичного объекта и для массива:

// освобождение единичного объекта delete pint; // освобождение массива

delete[] pia;

Что случится, если мы забудем освободить выделенную память? Память будет расходоваться впустую, она окажется неиспользуемой, однако возвратить ее системе нельзя, поскольку у нас нет указателя на нее. Такое явление получило специальное название утечка памяти. В конце концов программа аварийно завершится из-за нехватки памяти (если, конечно, она будет работать достаточно долго). Небольшая утечка трудно поддается обнаружению, но существуют утилиты, помогающие это сделать.

Билет №17

Многомерные массивы.

Многомерные массивы в C++ рассматриваются как массивы, элементами которых являются массивы.

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

int MyArray1[10]; // Одномерный массив размерности 10.int MyArray2[20][10]; // 20 одномерных массивов размерности 10.int MyArray3[30][20][10]; // 30 двумерных массивов размерности 20*10.

По крайней мере, для Borland C++ 4.5, элементы многомерного массива располагаются в памяти в порядке возрастания самого правого индекса, т.е. самый младший адрес имеют элементы

MyArray1[0],MyArray2[0][0],MyArray3[0][0][0],

затем элементы

MyArray1[1],MyArray2[0][1],MyArray3[0][0][1]

и т.д.

Многомерный массив подобно одномерному массиву может быть проинициализирован с помощью списка инициализаторов. Первыми инициализируются элементы с самыми маленькими индексами:

int MyArray[3][3][3] = {0,1,2,3,4,5,6,7,8,9,10,11};

Начальные значения получают следующие элементы трёхмерного массива:

MyArray[0][0][0] == 0MyArray[0][0][1] == 1MyArray[0][0][2] == 2MyArray[0][1][0] == 3MyArray[0][1][1] == 4MyArray[0][1][2] == 5MyArray[0][2][0] == 6MyArray[0][2][1] == 7MyArray[0][2][2] == 8MyArray[1][0][0] == 9MyArray[1][0][1] == 10MyArray[1][0][2] == 11

Остальные элементы массива получают начальные значения в соответствии со статусом массива (в глобальном массиве значения остальных элементов равны 0, в локальном массиве элементам присваиваются неопределённые значения).

Дополнительные фигурные скобки в инициализаторе позволяют инициализировать отдельные фрагменты многомерного массива. Каждая пара фигурных скобок специфицирует значения, относящиеся к одной определённой размерности. Пустые фигурные скобки не допускаются (и это означает, что в C++ реализован жёсткий алгоритм инициализации массивов):

int MyArray[3][3][3] = { {{0,1}}, {{100},{200,210},{300}}, {{1000},{2000,2100},{3000,3100,3200}} };

В результате выполнения этого оператора определения будут означены следующие элементы массива MyArray:

MyArray[0][0][0] == 0MyArray[0][0][1] == 1MyArray[1][0][0] == 100MyArray[1][1][0] == 200MyArray[1][1][1] == 210MyArray[1][2][0] == 300MyArray[2][0][0] == 1000MyArray[2][1][0] == 2000MyArray[2][1][1] == 2100MyArray[2][2][0] == 3000MyArray[2][2][1] == 3100MyArray[2][2][2] == 3200

По аналогии с одномерным массивом, при явной инициализации массива входящего в состав многомерного массива его самая левая размерность может не указываться. Она определяется на основе инициализатора.

int MyArray[ ][3][3] = { {{0,1}}, {{100},{200,210},{300}}, {{1000},{2000,2100}} };

Транслятор понимает, что речь идёт об определении массива размерности 3*3*3.

А в таком случае

int MyArray[ ][3][3] = { {{0,1}}, {{100},{200,210},{300}}, {{1000},{2000,2100}}, {{10000}} };

предполагается размерность 4*3*3. В результате MyArray оказывается массивом из четырёх частично проинициализированных двумерных массивов. Следует помнить, что в C++ нет принципиальной разницы между массивом массивов произвольной размерности и обычным одномерным массивом. Потому и простор для творчества в деле инициализации многомерных массивов ограничивается левым индексом.

Билет №18

Работа со строками.

Строка в С++ - это массив символов, оканчивающийся нулевым символом ('\0').

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

char str1[10] = "string1"; // объявление строки с помощью массива символов

Итак, теперь подробнее. Вы уже знаете, что массив - это набор однородных значений. Так вот строка есть не что иное, как набор символов, и, соответственно, для хранения строк можно использовать символьные массивы. Например, строка "QWERTY" имет тип char[7], а пустая строка "" имеет тип char[1]. Почему char[1]? Именно потому, что любая строка завершается так называемым нулевым символом, то есть символом, код которого в ASCII-таблице равен 0 (этот символ также является escape-символом и его символьный эквивалент представляется как '\0'). Благодаря этому свойству Вы всегда можете определить конец строки, если у Вас строка занимает меньшее количество символов, чем то количество, которое было указано в квадратных скобках при оъявлении массива, т.е. определить фактическую длину строки, хранящейся в массиве.

Одна из замечательных особенностей при работе со строками - это возможность упрощенной начальной инициализации. Например,

Объявление char str[] = "ABCDE"; присваивает переменной-строке

начальное значение "ABCDE". А точнее, создает массив из 6 символов:

'A','B','C','D','E' и символа '\0'.

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

Сразу необходимо отметить, что С++ сам автоматически сделает последний элемент массива нулевым символом (а Вы уже помните, что любая строка обязательно заканчивается нулевым символом), то есть, хотя в данном случае Вы массиву str присваиваете строку "ABCDE", длина которой составляет 5 символов, C++ выделяет память под 6 символов, записывает туда строку и затем в последний (пятый при счете от 0) записывает нулевой символ.

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

Объявление char str[10] = "ABCDE"; создает массив из 10 символов

и первые пять элементов этого массива принимают значения 'A','B','C','D'

и 'E' соответственно, остальные символы будут ноль-символы.

В данном случае в первые 5 элементов массива записывается строка "ABCDE", а всем остальным элементам присваиваются нули.

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

char str[]={'A','B','C','D','E','\0'};

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

Типичная ошибка программирования. Не выделяется достаточно места в массиве символов для хранения нулевого символа, завершающего строку.

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

Типичная ошибка программирования. Путают символьные и строковые константы.

Символьная константа - это один символ, заключенный в апострофы, например: 'A' или '\n'. Строковая константа - это последовательность символов, заключенная в двойные кавычки. В числе символов строки могут находится любые символьные константы, например, "Visual C++\n" состоит из следующих символов: 'V', 'i', 's', 'u', 'a', 'l', ' ', 'C', '+', '+', '\n', '\0'. Таким образом, "A" - это строковая константа и состоит из двух символов: 'A' и '\0'. Соседние строковые константы транслятором "склеиваются", например: "АБВ" "ГДЕ" означает то же, что "АБВГДЕ".

Рассмотрим небольшой пример:

//Задана строка, скопировать ее в символьный массив.

#include<iostream.h>

void main()

{

char str1 [ ] = "1234567890", /* объявляем символьный массив str1 и

инициализируем его */

str2[11]; // объявляем символьный массив без инициализации

 

/* в цикле пока не встретится конец строки присваиваем текущему элементу

массива str2 символ из массива str1

*/

for(int i = 0; str1[i]!= '\0'; i++) str2[i] = str1[i];

 

// копируем ноль-символ в str2.

str2 [i] = '\0';

 

cout << str2 << '\n'; // вывод строки на экран

}

Обратите внимание, выход из цикла происходит, когда str1[i] равно ноль-символу, т.е. ноль-символ не копируется в str2, следовательно это нужно сделать за циклом.

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

cin >> Имя_массива;

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

cout << Имя_массива;

Следует сразу отметить, что при вводе с клавиатуры строки оператор cin автоматически добавляет в конец строки нулевой символ, так что Вы должны учитывать этот факт при указании количества элементов при объявлении массива.

Например,

#include <iostream.h>

void main()

{

char str[31]; // объявление символьного массива

cout<<"Enter a string (max 30 symbols):";

cin>>str; // ввод строки

cout<<"\nYou enter string:"<<str; // вывод строки

}

Как видите в данном примере выделяется память под 31 символ, но пользователю в приглашении указывается, что он может ввести строку с размером максимум 30 символов, учитывая тот факт, что оператор cin добавит еще один нулевой символ в конец строки автоматически, и под него также необходимо предусмотреть выделение памяти. Далее после приглашения вводим сразу всю строку с клавиатуры в массив и затем с соответствующим сообщением выводим всю строку на экран монитора.

Билет №19

Модульная структура программы на C++.

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

· синтаксическую спецификацию его входов, позволяющую построить на используемом языке программирования синтаксически правильное обращение к нему (к любому его входу);

· функциональную спецификацию модуля (описание семантики функций, выполняемых этим модулем по каждому из его входов).

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

Метод восходящей разработки

Данный метод заключается в следующем. Сначала строится модульная структура программы в виде дерева. Затем поочередно программируются модули программы, начиная с модулей самого нижнего уровня (листья дерева модульной структуры программы), в таком порядке, чтобы для каждого программируемого модуля были уже запрограммированы все модули, к которым он может обращаться. После того, как все модули программы запрограммированы, производится их поочередное тестирование и отладка в принципе в таком же (восходящем) порядке, в каком велось их программирование. Такой порядок разработки программы на первый взгляд кажется вполне естественным: каждый модуль при программировании выражается через уже запрограммированные непосредственно подчиненные модули, а при тестировании использует уже отлаженные модули. Однако, современная технология не рекомендует такой порядок разработки программы. Во-первых, для программирования какого-либо модуля совсем не требуется наличия текстов используемых им модулей - для этого достаточно, чтобы каждый используемый модуль был лишь специфицирован (в объеме, позволяющем построить правильное обращение к нему), а для тестирования его возможно (и даже, как мы покажем ниже, полезно) используемые модули заменять их имитаторами (заглушками). Во-вторых, каждая программа в какой-то степени подчиняется некоторым внутренним для нее, но глобальным для ее модулей соображениям (принципам реализации, предположениям, структурам данных и т.п.), что определяет ее концептуальную целостность и формируется в процессе ее разработки. При восходящей разработке эта глобальная информация для модулей нижних уровней еще не ясна в полном объеме, поэтому очень часто приходится их перепрограммировать, когда при программировании других модулей производится существенное уточнение этой глобальной информации (например, изменяется глобальная структура данных). В-третьих, при восходящем тестировании для каждого модуля (кроме головного) приходится создавать ведущую программу (модуль), которая должна подготовить для тестируемого модуля необходимое состояние информационной среды и произвести требуемое обращение к нему. Это приводит к большому объему «отладочного» программирования и в то же время не дает никакой гарантии, что тестирование модулей производилось именно в тех условиях, в которых они будут выполняться в рабочей программе.

Метод нисходящей разработки

Метод нисходящей разработки заключается в следующем. Как и в предыдущем методе сначала строится модульная структура программы в виде дерева. Затем поочередно программируются модули программы, начиная с модуля самого верхнего уровня (головного), переходя к программированию какого-либо другого модуля только в том случае, если уже запрограммирован модуль, который к нему обращается. После того, как все модули программы запрограммированы, производится их поочередное тестирование и отладка в таком же (нисходящем) порядке. При этом первым тестируется головной модуль программы, который представляет всю тестируемую программу и поэтому тестируется при «естественном» состоянии информационной среды, при котором начинает выполняться эта программа. При этом те модули, к которым может обращаться головной, заменяются их имитаторами (так называемыми заглушками [5]). Каждый имитатор модуля представляется весьма простым программным фрагментом, который, в основном, сигнализирует о самом факте обращения к имитируемому модулю, производит необходимую для правильной работы программы обработку значений его входных параметров (иногда с их распечаткой) и выдает, если это необходимо, заранее запасенный подходящий результат. После завершения тестирования и отладки головного и любого последующего модуля производится переход к тестированию одного из модулей, которые в данный момент представлены имитаторами, если таковые имеются. Для этого имитатор выбранного для тестирования модуля заменяется самим этим модулем и, кроме того, добавляются имитаторы тех модулей, к которым может обращаться выбранный для тестирования модуль. При этом каждый такой модуль будет тестироваться при «естественных» состояниях информационной среды, возникающих к моменту обращения к этому модулю при выполнении тестируемой программы. Таким образом, большой объем «отладочного» программирования при восходящем тестировании заменяется программированием достаточно простых имитаторов используемых в программе модулей. Кроме того, имитаторы удобно использовать для того, чтобы подыгрывать процессу подбора тестов путем задания нужных результатов, выдаваемых имитаторами. При таком порядке разработки программы вся необходимая глобальная информация формируется своевременно, т.е. ликвидируется весьма неприятный источник просчетов при программировании модулей. Некоторым недостатком нисходящей разработки, приводящим к определенным затруднениям при ее применении, является необходимость абстрагироваться от базовых возможностей используемого языка программирования, выдумывая абстрактные операции, которые позже нужно будет реализовать с помощью выделенных в программе модулей. Однако способность к таким абстракциям представляется необходимым условием разработки больших программных средств, поэтому ее нужно развивать.

Билет №1



Поделиться:


Последнее изменение этой страницы: 2016-08-16; просмотров: 586; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 107.23.157.16 (0.066 с.)