WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!


Pages:     | 1 |   ...   | 2 | 3 || 5 | 6 |   ...   | 17 |

int a [ ][2][2] = { 1, 2, 3, 4, 5, 6, 7, 8 }; // массив a [2][2][2].

Инициализирующее выражение может иметь вид, отражающий факт, что массив является, например, двумерным:

int c[2][3]={ {1, 7}, {-5, 3} };

В этом случае в матрице c инициализированы нулевой и первой столбцы, а второй столбец, то есть элементы c[0][2] и c[1][2], – инициализируется нулями.

5.6. Указатели и многомерные массивы Рассмотрим разницу между объектами a и b, описанными следующим образом:

int a[10][10];

int * b[10];

И a и b можно использовать сходным образом в том смысле, что как a[5][5], так и b[5][5] являются обращениями к отдельному значению типа int. Но a – настоящий массив: под него отводится 100 ячеек памяти и для нахождения любого указанного элемента проводятся обычные вычисления с индексами, которые требуют умножения. Для b описание выделяет только 10 указателей. Каждый из них должен быть установлен так, чтобы он указывал на массив целых.

Если предположить, что каждый из них указывает на массив из элементов, то тогда где-то будет отведено 100 ячеек памяти плюс еще 10 ячеек для указателей. Таким образом, массив указателей использует несколько больший объем памяти и может требовать наличие явного шага инициализации. Но при этом возникают 2 преимущества: доступ к элементу осуществляется косвенно через указатель, а не посредством умножения и сложения, и строки массива могут иметь различные длины. Это означает, что каждый элемент b не должен обязательно указывать на вектор из 10 элементов. Эту разницу можно увидеть в следующем примере:

char day [5][12] = { “понедельник”, // В каждой строке 12 символов.

“вторник”, “среда”, “четверг”, “пятница” };

Здесь константные указатели day[0], day[1], …, day[4] адресуют участки памяти одинаковой длины 12 байт каждый:

day day +1 day + 2 day + 3 day + char * day1[2] = { “суббота”, // 7 символов + ‘/0’ “воскресенье”}; // 11 символов + ‘/0’ day1[0] day1[1] с у б б о т а \0 в о с к р е с е н ь е \Здесь переменные-указатели day1[0] и day1[1] адресуют участки памяти соответственно в 8 и 12 байт.

6. Операция sizeof Эта операция выполняется на стадии компиляции. Результатом этой операции является число байтов, необходимое для размещения объекта в памяти. Существует два варианта синтаксиса этой операции.

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

sizeof ( float ) sizeof ( int ) Во втором операнд задает некоторое выражение и здесь использование скобок необязательно:

sizeof a;

sizeof *ip;

sizeof array[ i ];

Заметим, что при получении размеров массивов, несмотря на то, что имя массива является указателем, результатом операции sizeof array, где array – имя некоторого массива, является длина в байтах этого массива. Это свойство можно использовать для вычисления числа элементов в массиве:

const n = 20;

int array [n];

...

int num=sizeof array / sizeof(int) // num = = 20.

Результатом операции sizeof над ссылкой является длина типа, с которым сопоставлена ссылка, т.е. sizeof( double & ) и sizeof( double ) эквивалентны.

7. Операции для работы с динамической памятью 7.1. Операция выделения памяти new Часто выражение, содержащее операцию new, имеет следующий вид:

указатель_на_тип_= new имя_типа (инициализатор) Инициализатор – это необязательное инициализирующее выражение, которое может использоваться для всех типов, кроме массивов.

При выполнении оператора int *ip = new int;

создаются 2 объекта: динамический безымянный объект и указатель на него с именем ip, значением которого является адрес динамического объекта. Можно создать и другой указатель на тот же динамический объект:

int *other=ip;

ip int other Если указателю ip присвоить другое значение, то можно потерять доступ к динамическому объекту:

int *ip=new (int);

int i=0;

ip=&i;

ip int i Теперь динамический объект по-прежнему будет существовать, но обратится к нему уже нельзя. Такие объекты называются мусором.

При выделении памяти объект можно инициализировать:

int *ip = new int(3);

Можно динамически распределить память и под массив:

double *mas = new double [50];

mas.....

Теперь с этой динамически выделенной памятью можно работать как с обычным массивом:

*(mas+5) = 3.27;

mas[6] = mas[5] + sin(mas[5]);

В случае успешного завершения операция new возвращает указатель со значением, отличным от нуля.

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

7.2. Операция освобождения памяти delete Операция delete освобождает для дальнейшего использования в программе участок памяти, ранее выделенной операцией new:

delete ip; // Удаляет динамический объект типа int, // если было ip = new int;

delete mas; // удаляет динамический массив длиной 50, если было // double *mas = new double[50];

Совершенно безопасно применять операцию к указателю NULL.

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

Чтобы избежать подобных ошибок, можно применять следующую конструкцию:

int *ip=new int[500];

...

if (ip){delete ip; ip=NULL;} else { cout <<“ память уже освобождена \n”; } Пример:



Распределить память для матрицы из m строк и n столбцов:

int m, n;

cout<<”Задайте число строк и столбцов матрицы: \n”;

cin>>m>>n;

double **a = new double *[m]; // массив из m указателей на double for (int i = 0; i

a[i][j] или *(a[i] + j) или *(*(a + i) + j) Изобразить распределение памяти, соответствующее вышеприведенному фрагменту, можно следующим образом:

0 … n-2 n-a a[0] … a[1] … … … … … … a[m-2] … a[m-1] … Освободить память здесь можно так:

for (i=0; i

delete a;

Или так:

for(i=0; i

8. Объявления и определения Любое имя, за исключением имён меток, должно быть объявлено в программе:

int i, j;

double d=7.3;

extern int m;

typedef unsigned int size_t;

int add (int a, int b){ return a+b;} void func (char*, int);

После этих объявлений компилятор знает, что i, j, m – имена переменных типа int, d – имя переменного типа double, size_t – имя типа, а add и func – имена функций (о функциях см. раздел 16).

В объявлении с именем может сопоставляться не только некоторый тип. С именем может быть сопоставлен некоторый элемент, идентификатором которого оно является. Например, при объявлении int i, j;

для переменных i, j отводится память, с этими именами сопоставляются участки памяти по 2 байта, которые можно использовать для хранения значений переменных. С именем size_t сопоставлено имя конкретного типа (unsigned int), синонимом которого является теперь size_t; с именем add сопоставлен код функции. Все такие объявления называют определениями. Однако не все объявления являются определениями. Из всех вышеприведённых объявлений два не являются определениями:

extern int m;

void func (char *, int);

В первом из этих объявлений сообщается только, что переменная m имеет тип int, память же для неё должна выделяться где-то в другом месте, о чём говорит ключевое слово extern.

Второе объявление говорит, что func есть имя функции с двумя аргументами, первый из которых – указатель на char, а второй – int, а сама функция не возвращает никакого значения. Такое объявление называют прототипом функции. Сама же функция func должна быть определена вместе со своим телом где-то в другом месте.

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

9. Область существования имени После объявления имя можно использовать. Однако оно, как правило, может быть использовано только в некоторой части программы, которая называется областью существования имени.

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

Компилятор для каждого исходного файла создаёт объектный код (файл с расширением ".obj"). Затем все объектные файлы (вместе с библиотечными) объединяются компоновщиком в исполняемый, или загрузочный модуль, который имеет имя файла проекта и расширение ".ехе".

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

9.1. Компиляция, компоновка, библиотеки Рассмотрим в связи с этим несколько подробнее саму технологию подготовки программ.

Изобразить ее схематически можно так (рис. 1):

Заголовочный файл Редактирование текста stdlib.h Исходный текст P.cpp Prog.cpp Компилятор Компилятор *.obj Препроцессор Лексический анализ Синтаксический анализ Библиотечные файлы Семантический анализ Генерация кода, оптимизация Объектный модуль prog.obj p.obj Компоновщик Исполняемая программа, загрузочный моИсполняемая программа, загрудуль, программный файл.

зочный модуль, программный prog.exe файл Рис. 1. Подготовка программы к выполнению Подготовка программы начинается с редактирования файла, содержащего текст этой программы, который имеет стандартное РАСШИРЕНИЕ ".cpp". Затем выполняется его компиляция, которая включает в себя несколько фаз: препроцессор, лексический, синтаксический, семантический анализ, генерация кода и его оптимизация. В результате компиляции получается объектный модуль – некий "полуфабрикат" готовой программы, который потом участвует в ее сборке. Файл объектного модуля имеет стандартное расширение ".obj". Компоновка (сборка) программы заключается в объединении одного или нескольких объектных модулей программы и объектных модулей, взятых из библиотечных файлов и содержащих стандартные функции и другие полезные вещи. В результате получается исполняемая программа в виде отдельного файла (загрузочный модуль, программный файл) со стандартным расширением -".exe", который затем загружается в память и выполняется.

9.1.1. Компиляция и ее фазы Собственно компиляция начинается с лексического анализа программы. ЛЕКСИКА языка программирования – это правила "правописания слов" программы таких, как идентификатры, константы, служебные слова, комментарии. Лексический анализ разбивает текст программы на указанные элементы. Особенность любой лексики – ее элементы представляют собой регулярные линейные последовательности символов.





Например, ИДЕНТИФИКАТОР -это произвольная последовательность букв, цифр и символа "_", начинающаяся с буквы или "_".

СИНТАКСИС языка программирования – это правила составления предложений языка из отдельных слов. Такими предложениями являются операции, операторы, определения функций и переменных. Особенностью синтаксиса является принцип вложенности (рекурсивность) правил построения предложений. Это значит, что элемент синтаксиса языка в своем определении прямо или косвенно в одной из его частей содержит сам себя. Например, в определении оператора цикла телом цикла является оператор, частным случаем которого является все тот же оператор цикла.

СЕМАНТИКА языка программирования – это смысл, который закладывается в каждую конструкцию языка. Семантический анализ – это проверка смысловой правильности конструкции. Например, если мы в выражении используем переменную, то она должна быть определена ранее по тексту программы, а из этого определения может быть получен ее тип. Исходя из типа переменной, можно говорить о допустимости операции с данной переменной.

ГЕНЕРАЦИЯ КОДА – это преобразование элементарных действий, полученных в результате лексического, синтаксического и семантического анализа программы, в некоторое внутреннее представление. Это могут быть коды команд, адреса и содержимое памяти данных, текст программы на языке Ассемблера либо стандартизованный промежуточный код (например, так называемый P-код). В процессе генерации кода производится и его оптимизация.

9.1.2. Модульное программирование, компоновка Полученный в результате трансляции ОБЪЕКТНЫЙ МОДУЛЬ включает в себя готовые к выполнению коды команд, адреса и содержимое памяти данных. Но это касается только собственных внутренних объектов программы (функций и переменных). Обращение к внешним функциям и переменным, отсутствующим в данном фрагменте программы, не может быть полностью переведено во внутреннее представление и остается в объектном модуле в исходном (текстовом) виде. Но если эти функции и переменные отсутствуют, значит они должны быть каким-то образом получены в других объектных модулях. Самый естественный способ -написать их на том же самом С++ и откомпилировать.

Это и есть принцип МОДУЛЬНОГО ПРОГРАММИРОВАНИЯ – представление текста программы в виде нескольких файлов, каждый из которых транслируется отдельно. С модульным программированием мы сталкиваемся в двух случаях:

когда сами пишем модульную программу;

когда используем стандартные библиотечные функции.

БИБЛИОТЕКА ОБЪЕКТНЫХ МОДУЛЕЙ – это файл (библиотечный файл), содержащий набор объектных модулей и собственный внутренний каталог. Объектные модули библиотеки извлекаются из нее целиком при наличии в них требуемых внешних функций и переменных и используются в процессе компоновки программы.

КОМПОНОВКА – это процесс сборки программы из объектных модулей, в котором производится их объединение в исполняемую программу и связывание вызовов внешних функций и их внутреннего представления (кодов), расположенных в различных объектных модулях.

Этот этап выполняет специальная программа – LINKER.

9.2. Виды областей существования имени Вернемся к понятию "область существования имени". Можно выделить 5 видов областей существования имени.

Область существования БЛОК. Напомним, что блок – это фрагмент программы, заключённый в фигурные скобки { }, например if (a != 5) { int j=0;

double k = 3.5;

a++;

… } Заметим, что тело любой функции является блоком.

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

int f1 (int i){ return i; } Имя i имеет область существования «блок». Область существования «блок» распространяется и на вложенные блоки.

Область существования ФУНКЦИЯ. Эту область существования имеют только имена меток перехода, используемые оператором goto:

void f (){….

… goto lab;

… {… lab:...} … } Область существования ПРОТОТИП ФУНКЦИИ. Прототип функции есть объявление функции, не являющееся её определением и имеющий, например, вид int F(int a, double b, char* str);

Область существования «прототип» заключена между открывающей и закрывающей круглыми скобками. Иначе говоря, имена a, b, str в примере определены только внутри круглых скобок. Из этого следует, что в прототипах можно использовать для аргументов любые имена или не использовать их совсем:

int F(int, double, char*);

Область существования ФАЙЛ. Область существования «файл» имеют имена, объявленные вне любого блока и класса. Такие имена называют глобальными. Глобальные имена определены от точки их объявления и до конца файла, где встретилось их объявление. Примером таких имён являются имена функций.

#include int a, b, c[40]; // Глобальные имена;

int f1() // локальное имя f1;

{int i; // локальное имя;

… } int count; // глобальное имя;

void f2() { … } // глобальное имя f2.

Область существования КЛАСС.

Такую область существования имеют имена, объявленные в классах (см. раздел 18). Эти имена определены во всем классе, в котором они объявлены, независимо от точки их объявления.

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

Pages:     | 1 |   ...   | 2 | 3 || 5 | 6 |   ...   | 17 |










© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.