Несколько замечаний о назначении программирования

Язык программирования С

Основы Си++

Несколько замечаний о назначении программирования

Программирование – это техническая творческая деятельность, цель которой

заключается в решении важных для человека задач или выполнении определенных

действий с помощью компьютера. На рис. 1 представлена идеализированная схема

решения типичной задачи программирования.

Подробное описание

задачи или необходимых

действий

КОМПЬЮТЕР

Решение задачи или

выполнение действий

Рис. 1. Схема решения задачи с помощью компьютера.

В рамках такой схемы необходимыми компонентами компьютера являются

центральный процессор, устройства ввода/вывода и память (рис. 2).

Рис. 2. Основные компоненты компьютера.

Конечно, в действительности дело обстоит не так просто, как показано на

рис. 1. Например, "подробное описание (спецификация) задачи" на естественном язы-

ке для компьютера не годится (в настоящее время). Более того, для решения задачи на

компьютере недостаточно полного описания задачи, необходимо также снабдить

компьютер информацией о том, как именно следует решать задачу – т.е. составить ал-

горитм. Для описания алгоритмов решения задач или алгоритмов выполнения каких-

либо действий (например, управление роботом-манипулятором) с помощью компью-

тера применяются языки программирования.

На рис. 3 показана более подробная схема решения задачи с помощью компью-

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

люстрация этой схемы на конкретном примере приведена в таблице 1.

Существует большое количество различных языков программирования и много

способов их классификации. Например, "языками высокого уровня" считаются те

языки, синтаксис которых сравнительно близок к естественному языку, в то время как

синтаксис "низкоуровневых" языков содержит много технических подробностей, свя-

занных с устройством компьютера и процессора.

7

Несколько замечаний о назначении программирования - student2.ru  

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

Таблица 1. Основные этапы решения задачи по проверке числа на простоту.

Спецификация задачи

Алгоритм

Описание алгоритма на

Языке высокого уровня

Объектный код (внут-

Ренний код конкретного

компьютера)

Исполняемый файл для

Конкретного компьютера

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

Ввести x

Для каждого целого числа z из диапазоне от 1 до x

Если остаток от деления x на z равен 0, то

вывести сообщение "число не простое" и закончить работу

Если такого числа z не найдено, то

вывести сообщение "число простое" и закончить работу

#include <iostream.h>

int main()

{

int x;

cout << "Введите число:\n";

cin>> x;

for (int z=2; z<x; z++)

if (x % z == 0)

{

cout << "Это не простое число.\n";

return 0;

}

cout << "Это простое число.\n";

return 0;

}

Двоичные команды процессора (частично)

Двоичные команды процессора (полностью)

"Императивные" или "процедурные" языки позволяют программисту описать, в

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

таким образом, решать задачу и выдавать на экран результат. "Декларативные" языки

предназначены больше для описания самой задачи и желаемого результата, а не дей-

ствий компьютера.

"Объектно-ориентированные языки" рассчитаны на применение особого под-

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

с характерным для них "поведением" и взаимодействующие между собой. Один из

Несколько замечаний о назначении программирования - student2.ru

Несколько замечаний о назначении программирования - student2.ru  
Несколько замечаний о назначении программирования - student2.ru Несколько замечаний о назначении программирования - student2.ru Несколько замечаний о назначении программирования - student2.ru Несколько замечаний о назначении программирования - student2.ru Несколько замечаний о назначении программирования - student2.ru

первых объектно-ориентированных языков – Смоллток, он предназначен исключи-

тельно для объектно-ориентированного программирования. В отличие от него, язык

Си++ обладает как объектно-ориентированными возможностями, так и средствами

традиционного процедурного программирования.

Радикальные приверженцы различных языков и стилей программирования

иногда делают экстравагантные заявления, выделяющие семейство языков или один

язык как исключительный и идеально подходящий для любых задач. Например, до-

вольно распространено мнение, что объектно-ориентированный подход наиболее

близок к способу решения задач человеком. По этому поводу вы со временем сможе-

те составить собственное мнение, т.к. абсолютно истинного, очевидно, нет.

2. Происхождение языка Си++

Язык Си++ был разработан в начале 1980-х гг. Бьерном Страуструпом из ком-

пании AT&T Bell Laboratories. Си++ основан на языке Си. Два символа "++" в назва-

нии – это игра слов, символами "++" в языке Си обозначается операция инкремента

(увеличение значения переменной на 1). Т.о., Си++ был задуман как язык Си с рас-

ширенными возможностями. Большая часть языка Си вошла в Си++ как подмножест-

во, поэтому многие программы на Си можно скомпилировать (т.е. превратить в набор

низкоуровневых команд, которые компьютер может непосредственно выполнять) с

помощью компилятора Си++.

При классификации языков программирования язык Си вызывает некоторые

трудности. По сравнению с ассемблером, это высокоуровневый язык. Однако Си со-

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

компьютера. Поэтому язык Си отлично подходит для написания эффективных "сис-

темных" программ. Но программы других типов на Си могут оказаться довольно

сложными для понимания, и есть ряд ошибок, которым программы на Си особенно

подвержены. Дополнительные объектно-ориентированные возможности Си++ были

добавлены в Си, в частности, для устранения этих недостатков.

3. Стандарт ANSI Си++

Национальный Институт Стандартизации США (American National Standards

Institution, ANSI) разработал "официальные" стандарты для многих языков програм-

мирования, в том числе для Си и Си++. Эти стандарты стали общепринятыми и они

имеют очень большое значение. Программу, целиком написанную на ANSI Си++, га-

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

лятор ANSI Си++. Другими словами, стандарт гарантирует переносимость программ

на языке ANSI Си++.

В действительности большинство версий Си++ представляют собой стандарт-

ный ANSI Си++, дополненный некоторыми машинно-зависимыми возможностями.

Эти специфические средства предназначены для облегчения взаимодействия про-

грамм с конкретными операционными системами. Вообще, в программах, которые

должны быть переносимыми, подобными специфическими возможностями следует

пользоваться как можно реже. В таких случаях части программы на Си++, в которых

используются не-ANSI компоненты языка, целесообразно особым образом помечать,

так, чтобы их легко можно было отделить от основной части программы и модифи-

цировать для других компьютеров и операционных систем.

4. Средаразработки Microsoft Developer Studio Visual С++

Известно, что лучший способ изучения языка программирования заключается в

том, чтобы писать на нем программы и проверять, как они работают на компьютере.

Для этого необходимы несколько программ:

 Текстовый редактор, с помощью которого можно набирать и редактировать

исходный текст программ на Си++.

 Компилятор. Эта программа выполняет преобразование исходного текста в

машинные команды, которые компьютер может непосредственно выпол-

нять.

 Компоновщик, который собирает отдельные скомпилированные части про-

граммы в единое целое и, при необходимости, добавляет к ним компоненты

из готовых библиотек. В результате компоновки получается готовая к за-

пуску программа – исполняемый файл.

 Отладчик, с помощью которого легче искать ошибки в программе. Ошибки

могут обнаружиться как при компиляции, так и во время работы програм-

мы.

В данном курсе изучения Си++ практические упражнения предполагается вы-

полнять в среде разработки программMicrosoft Developer Studio Visual C++для

IBM-совместимых ПК под управлениемWindows 95/NT. В этом пакете интегрирова-

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

удобную среду программирования. Краткое описание работы со средойVisual C++

приведено в Приложении.

5. Пример программы на Си++

Ниже приведен исходный текст простой программы на Си++.

В языке Си++ с двойной косой черты начинаются комментарии

(например, как эта строка). Компилятор игнорирует комментарии,

начиная от первой черты и до конца строки.

Второй способ записи комментариев – после косой черты со звездочкой.

После текста комментария надо поставить звездочку, а затем – косую

черту. Комментарии, записанные подобным образом, могут занимать

больше одной строки. */

/* В программе ОБЯЗАТЕЛЬНО должно быть достаточное количество

комментариев! */

/* Эта программа запрашивает у пользователя текущий год, возраст

пользователя и еще один год. Затем программа вычисляет возраст

пользователя, который будет у него во втором введенном году.*/

#include <iostream.h>

int main()

{

int year_now, age_now, another_year, another_age;

cout << "Введите текущий год и нажмите ENTER.\n";

cin >> year_now;

cout << "Введите свой возраст (в годах).\n";

cin >> age_now;

//
//
//
/*

cout << "Введите год, для которого вы хотите узнать свой возраст.\n";

cin>> another_year;

another_age = another_year - (year_now - age_now);

if (another_age >= 0)

{

cout<< "В " <<another_year<< " году вам будет ";

cout<< another_age << "\n";

}

else

{

cout << "В " << another_year << " вы еще не родились!\n";

}

return 0;

Программа 5.1.

Некоторые свойства программы 5.1 являются обычными для большинства про-

грамм на Си++. Программа начинается (после комментариев) с оператора

#include <iostream.h>

Этот оператор называется "директивой include". До компилятора исходный

текст обрабатывается препроцессором – специальной программой, которая модифи-

цирует текст программы по специальным командам – директивам. Директивы пре-

процессора начинаются с символа "#". Директива include предназначена для вклю-

чения в исходный текст содержимого другого файла. Например, в программу 5.1

включается файл iostream.h, содержащий описания функций стандартной библиоте-

ки ввода/вывода для работы с клавиатурой и экраном. (Стандартные библиотеки язы-

ка Си++ будут рассматриваться позже).

Алгоритм, записанный в программе 5.1, очень простой. Поэтому структуру

программы легко представить в виде списка последовательно выполняемых команд

(операторов). Схематично программу, содержащуюся после директивы #include,

можно представить в виде:

int main()

{

Первый оператор;

...

...

Последний оператор;

return 0;

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

ратор в теле программы завершается точкой с запятой. В хорошо разработанной

большой программе большинство операторов являются обращениями (вызовами) к

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

файлах. Каждая подпрограмма (функция) имеет структуру, подобную функции

main(). Но функция main() в каждой программе только одна. Именно с нее начина-

ется выполнение программы. (Подробнее функции будут рассматриваться далее.)

В конце функции main() записана строка:

}
}

return 0;

Эта строка значит "вернуть операционной системе в качестве сигнала об ус-

пешном завершении программы значение 0". Оператор возврата return применяется

не только при завершении программы, но и при завершении отдельных подпрограмм.

В любом случае этот оператор возвращает определенное значение на более высокий

уровень управления.

В программе-примере используются четыре переменные:

year_now, age_now, another_year и another_age

Переменные в программировании отличаются от математических переменных.

Они используются как символические имена "фрагментов оперативной памяти ком-

пьютера". При выполнении программы в различные моменты времени переменные

могут хранить различные значения. В программе 5.1 первое упоминание четырех пе-

ременных содержится в строке с оператором описания переменных:

int year_now, age_now, another_year, another_age;

Этот оператор уведомляет компилятор, что для хранения четырех переменных

типа "целое число" (integer – int) требуется выделить необходимое количество памя-

ти. Эта область памяти будет зарезервирована в течение выполнения оставшейся час-

ти программы. Переменные всегда должны быть описаны до первого использования.

В программировании хорошим стилем считается описание всех переменных, исполь-

зуемых в подпрограмме, в начале этой подпрограммы. В Си++ есть несколько раз-

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

Else

{

cout << "Вы сдали тест нечестно!\n";

total_test_score = 0;

}

...

...

Массивы

Назначение массивов

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

ших объемов данных. Для постоянного хранения этих данных удобно пользоваться

файлами. Например, в программе для ввода и сортировки длинных числовых списков

данные можно ввести с клавиатуры один раз и сохранить в файле для последующего

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

представления больших объемов данных внутри программ. Для этой цели в Си++

часто применяются массивы – простейшая разновидность структурных типов дан-

ных (о более сложных структурах данных будет говориться в следующих лекциях).

Массив – это набор переменных одного типа ("int", "char" и др.). При объявле-

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

обращения к которым в программе применяется одно и то же имя. В то же время мас-

сив позволяет получить прямой доступ к своим отдельным элементам.

1.1 Объявление массивов

Оператор описания массива имеет следующий синтаксис:

<тип данных><имя переменной>[<целое значение>];

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

работанных в течении недели группой из 6-ти сотрудников. Для хранения этих дан-

ных можно объявить массив:

int hours[6];

или, лучше, задать численность группы с помощью специальной константы:

const int NO_OF_EMPLOYEES = 6;

int hours[NO_OF_EMPLOYEES];

Если подобные массивы будут часто встречаться в программе, то целесообраз-

ноопределитьновыйтип:

const int NO_OF_EMPLOYEES = 6;

typedef int Hours_array[NO_OF_EMPLOYEES];

Hours_array hours;

Hours_array hours_week_two;

В любом из трех перечисленных вариантов, в программе будет объявлен мас-

сив из 6 элементов типа "int", к которым можно обращаться с помощью имен:

hours[0]

hours[1]

hours[2]

hours[3]

hours[4]

hours[5]

Каждое из этих имен является именем элемента массива. Числа 0, ..., 5 назы-

ваются индексами элементов. Отличительная особенность массива заключается в том,

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

довательные ячейки памяти (рис. 1).

Рис. 1.. Расположение элементов массива в оперативной памяти (направление

сверху вниз соответствует возрастанию адресов ячеек памяти).

1.2 Использование элементов массивов в выражениях

С элементами объявленного массива можно выполнять все действия, допусти-

мые для обычных переменных этого типа (выше был приведен пример целочисленно-

го массива, т.е. типа "int"). Например, возможны операторы присваивания наподо-

бие:

hours[4] = 34;

hours[5] = hours[4]/2;

или логические выражения с участием элементов массива:

if (number < 4 && hours[number] >= 40) { ...

Присвоить значения набору элементов массива часто бывает удобно с помо-

щью циклов "for" или "while". В программе 1.1 в цикле у оператора запрашивается

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

естественной может показаться нумерация сотрудников от 1 до 6, а не от 0 до 5, но

необходимо помнить, что индексация массивов в Си++ начинается с 0. Поэтому про-

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

соответствующего элемента массива.

#include <iostream.h>

const int NO_OF_EMPLOYEES = 6;

typedef int Hours_array[NO_OF_EMPLOYEES];

int main()

{

Hours_array hours;

int count;

for ( count = 1; count <= NO_OF_EMPLOYEES; count++ )

{

cout << "Сколько часов отработал сотрудник";

cout<< " номер " << count << "?: ";

cin>> hours[count - 1];

}

return 0;

Программа 1.1.

В типичном сеансе работы программа 1.1 выведет на экран подобные сообщения:

Сколько

Сколько

Сколько

Сколько

часов

часов

часов

часов

отработал

отработал

отработал

отработал

сотрудник

сотрудник

сотрудник

сотрудник

номер

номер

номер

номер

1?:

2?:

3?:

4?:



Несколько замечаний о назначении программирования - student2.ru  
}

Сколько часов отработал сотрудник номер 5?:38

Сколько часов отработал сотрудник номер 6?:37

На рис. 2. показано состояние целочисленного массива после ввода этих данных.

Рис. 2.. Состояние массива после присвоения значений его элементам.

Полезно разобраться, что произошло бы, если бы в программе 1.1 внутри цикла

"for" в операторе "cin ..." отсутствовало бы вычитание 1 из переменной "count".

Компилятор Си++ (в отличие, например, от Паскаля) не обнаруживает ошибки выхо-

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

ним оказался бы в состоянии, показанном на рис. 3.

Рис. 3.. Ошибка выхода за пределы массива.

Другими словами, значение "37" было бы размещено в ячейке памяти, доста-

точной для хранения целого числа, которая расположена сразу после массива "hours".

Это чрезвычайно нежелательная ситуация, потому что компилятор может зарезерви-

ровать эту ячейку памяти для другой переменной (например, для переменной

"count").

Массивы могут быть любого типа, не обязательно типа "int". Ниже приведена

программа 1.2, в которой символьный ("char") массив применяется для печати собст-

венного исходного файла на экране в обратном порядке.

#include <iostream.h>

#include <fstream.h>

const int MAX_LEN = 1000;

typedef char File_array[MAX_LEN];

int main()

{

char character;

File_array file;

int count;

ifstream in_stream;

in_stream.open("prg6_1_2.cpp");

in_stream.get(character);

for ( count = 0; !in_stream.eof() && count < MAX_LEN; count++ )

{

file[count] = character;

Несколько замечаний о назначении программирования - student2.ru  
Несколько замечаний о назначении программирования - student2.ru  

in_stream.get(character);

}

in_stream.close();

while (count > 0)

cout<< file[--count];

return 0;

Программа 1.2.

В заголовке цикла "for" обратите внимание на условие "... && count < MAX

...", специально предусмотренное для предотвращения выхода за пределы массива.

Фрагмент программы 2.1.

Эту функцию можно сделать более универсальной, если размер массива не

фиксировать в ее определении, а передать в качестве второго параметра:

float average( int list[], int length )

{

float total = 0;

int count;

for ( count = 0 ; count < length; count++ )

total += float(list[count]);

return ( total/length );

}

В этом примере показан очень распространенный способ оформления функций,

работающих с массивами: в одном параметре передается длина массива, а в другом –

сам массив, причем в описании параметра-массива не указывается длина массива (на-

пример, "int list[]").

Параметры-массивы всегда передаются по ссылке (а не по значению), хотя при

их описании в заголовке функции символ "&" не указывается. Правило "массивы все-

}
;

гда передаются по ссылке" введено для того, чтобы функции не делали собственных

внутренних копий переданных им массивов – для больших массивов это могло бы

привести к нерациональным затратам памяти. Следовательно, как и параметры по

ссылке, рассматривавшиеся в 3-ей лекции, все изменения элементов параметров-

массивов внутри функций будут видны и в вызывающей функции.

Далее показана функция (фрагмент программы 2.2), принимающая в качестве

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

вен сумме двух соответствующих элементов первого и второго массивов.

void add_lists( int first[], int second[], int total[],

int length )

int count;

for ( count = 0; count < length; count++ )

total[count] = first[count] + second[count];

Фрагмент программы 2.2.

В целях безопасности, для защиты от случайного изменения элементов масси-

ва, в описание первых двух параметров функции добавим модификатор типа "const":

void add_lists( const int fst[], const int snd[], int tot[],

int len )

int count;

for ( count = 0; count < len; count++ )

tot[count] = fst[count] + snd[count];

Теперь компилятор не будет обрабатывать ни один оператор в определении

функции, который пытается модифицировать элементы константных массивов "fst"

или "snd". Фактически, ограничение, накладываемое модификатором "const" в дан-

ном контексте, в некоторых ситуациях оказывается слишком строгим. Например,

компилятор выдаст ошибку при обработке следующих двух функций:

void no_effect( const int list[] )

{

do_nothing( list );

}

void do_nothing( int list[] )

{

;

}

Фрагмент программы 2.3.

Ошибка компиляции программы 2.3 объясняется тем, что, хотя фактически

функция "do_nothing(...)" не выполняет никаких действий, но в ее заголовке отсут-

ствует модификатор "const". Когда компилятор в теле функции "no_effect(...)"

встретит вызов функции "do_nothing(...)", то он обратится только к заголовку функ-

{
}
{
}

ции "do_nothing(...)", и выдаст ошибку о невозможности передачи константного

массива в эту функцию.

Сортировка массивов

По отношению к массивам часто возникает задача сортировки по убыванию

или возрастанию. Разработано много различных алгоритмов сортировки, например,

пузырьковая сортировка и быстрая сортировка. В этом параграфе кратко рассматри-

вается один из простейших алгоритмов сортировки – сортировка методом выбора

наименьшего элемента.

Основные действия алгоритма для сортировки массива длиной L заключаются

в следующем:

Для каждого значения индекса i выполнить два действия:

Среди элементов с индексами от i до (L-1) найти

элемент с минимальным значением.

Обменять значения i-го и минимального элемен-

тов.

Работу этого алгоритма рассмотрим на примере сортировки массива из 5 целых

чисел:

int a[5];

Значения элементов неотсортированного массива показаны на рис. 4.

Рис. 4.. Начальное состояние массива.

В процессе сортировки методом выбора наименьшего элемента массив будет

последовательно переходить в состояния, показанные на рис. 5 (слева направо). Каж-

дое состояние получается из предыдущего путем перестановки двух элементов, поме-

ченных на рис. 5 кружками.

Рис. 5.. Последовательные шаги сортировки массива методом выбора наи-

меньшего элемента.

На Си++ алгоритм сортировки можно реализовать в виде трех функций. Функ-

ция высокого уровня будет называться "selection_sort(...)" (у нее два параметра –

сортируемый массив и его длина). Сначала эта функция вызывает вспомогательную

функцию "minimum_from(array,position,length)", котораявозвращаетиндексмини-

мального элемента массива "array", расположенного в диапазоне между индексом

"position" и концом массива. Затем для обмена двух элементов массива вызывается

функция "swap(...)".

void selection_sort( int a[], int length )

Несколько замечаний о назначении программирования - student2.ru  
Несколько замечаний о назначении программирования - student2.ru  
1)
2)

{

for ( int count = 0; count < length - 1; count++ )

swap( a[count], a[minimum_from(a,count,length)] );

}

int minimum_from( int a[], int position, int length )

{

int min_index = position;

for ( int count = position + 1; count < length; count++ )

if ( a[count] < a[min_index] )

min_index = count;

return min_index;

}

void swap( int& first, int& second )

int temp = first;

first = second;

second = temp;

Фрагмент программы 3.1.

Двумерные массивы

Массивы в Си++ могут иметь более одной размерности. В данном параграфе

кратко описаны двумерные массивы. Они широко применяются для хранения дву-

мерных структур данных, например, растровых изображений и матриц.

При обработке изображений часто используются битовые маски – вспомога-

тельные двуцветные (черно-белые) изображения, состоящие только из 0 (белая точка)

и 1 (черная точка) (или, логических значений "false" и "true"). Предположим, что

требуется маска размером 64х32 пиксела. Для описания соответствующего массива

возможны операторы:

const int BITMAP_HEIGHT = 64;

const int BITMAP_WIDTH = 32;

bool bitmap[BITMAP_HEIGHT][BITMAP_WIDTH];

При обращении к элементам массива "bitmap" необходимо указывать два ин-

декса. Например, чтобы изменить значение 2-го пиксела слева в 4-й строке надо запи-

сать оператор:

bitmap[3][1] = true;

Все сказанное во 2-м параграфе относительно одномерных массивов как пара-

метров функций верно и для двумерных массивов, но у них есть еще одна особен-

ность. В прототипах и заголовках функций можно опускать первую размерность мно-

гомерного массива-параметра (т.е. записывать пустые квадратные скобки "[]"), но все

остальные размерности надо обязательно указывать. Далее в качестве примера приве-

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

void clear_bitmap( bool bitmap[][BITMAP_WIDTH],

int bitmap_height )

{

for ( int row = 0; row < bitmap_height; row++ )

{
}

for ( int column = 0; column < BITMAP_WIDTH; column++ )

bitmap[row][column] = false;

}

Указатели

Назначение указателей

Язык Си++ унаследовал от языка Си мощные средства для работы с оператив-

ной памятью: динамическое выделение и освобождение блоков памяти, доступ к от-

дельным ячейкам памяти по их адресам. Эти возможности делают язык Си++, как и

Си, удобным для разработки системного программного обеспечения и прикладных

программ, в которых применяются динамические структуры данных (т.е. такие, раз-

мер которых не известен на этапе компиляции и может меняться во время выполне-

ния программы).

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

компилятор резервировал для каждой из них некоторое количество памяти (в соот-

ветствии с типом данных) еще на этапе компиляции. В начале выполнения блока опе-

раторов, внутри которого объявлена переменная, автоматически выполняется выде-

ление памяти для этой переменной, а при выходе из блока – освобождение памяти.

В данной лекции подробно рассматривается понятие указателя, – средства, ко-

торое дает программисту наиболее гибкий способ контроля операций выделе-

ния/освобождения памяти во время выполнения программ.

1.1 Объявление указателей

Указателем называется адрес переменной в оперативной памяти. Переменная

указательного типа (часто она называется переменная-указатель или просто указа-

тель) – это переменная, размер которой достаточен для хранения адреса оперативной

памяти. Переменные-указатели объявляются с помощью символа "*", который добав-

ляется после названия обычного типа данных. Например, оператор описания (его

можно прочитать как "указатель на целочисленную переменную"):

int* number_ptr;

объявляет переменную-указатель "number_ptr", которая может хранить адреса пере-

менных типа "int".

Если в программе используется много однотипных указателей, то дляих объ-

явления можно ввести новый тип. Это делается с помощью оператора описания ново-

го типа "typedef". Например, если записать оператор:

typedef int* IntPtrType;

то в программе можно будет объявлять сразу несколько переменных-указателей, не

применяя для каждой из них символ "*":

IntPtrType number_ptr1, number_ptr2, number_ptr3;

1.2 Применение символов "*" и "&" в операторах присваивания значений указателям

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

указательных переменных одного типа (например, "int"). Для получения значения

переменной по ее указателю предназначена операция разыменования указателя "*". У

нее есть обратная операция – операция взятия адреса "&". Упрощенно, операция "*"

означает "получить значение переменной, расположенной по этому адресу", а опера-

ция "&" – "получить адрес этой переменной". Для пояснения смысла этих операций

далее приводится программа 1.1.

#include <iostream.h>

typedef int* IntPtrType;

int main()

IntPtrType ptr_a, ptr_b;

int num_c = 4, num_d = 7;

ptr_a = &num_c;

ptr_b = ptr_a;

/* СТРОКА 10 */

/* СТРОКА 11 */

cout<< *ptr_a << " " << *ptr_b << "\n";

ptr_b = &num_d; /* СТРОКА 13 */

cout<< *ptr_a << " " << *ptr_b << "\n";

*ptr_a = *ptr_b; /* СТРОКА 15 */

cout<< *ptr_a << " " << *ptr_b << "\n";

cout<< num_c << " " << *&*&*&num_c << "\n";

return 0;

Программа 1.1.

Программа 1.1 выдает на экран следующие сообщения:




Графически состояние программы 1.1 после выполнения операторов присваи-

вания в 10-11-й строках, 15-й и 19-й показано, соответственно, на рис. 1, 2 и 3.

Рис. 1.. Состояние про-

граммы 1.1 после выпол-

нения 10-й и 11-й строк.

Рис. 2.. Состояние про-

граммы 1.1 после выпол-

нения 13-й строки.

Рис. 3.. Состояние про-

граммы 1.1 после выпол-

нения 15-й строки.

Операции "*" и "&", в некотором смысле, являются взаимно обратными, так что

выражение "*&*&*&num_c" означает то же самое, что и "num_c".

1.3 Операторы "new" и "delete". Константа "NULL"

Рассмотрим оператор присваивания в 10-й строке программы 1.1:

ptr_a = &num_c;

Можно считать, что после выполнения этого оператора у переменной "num_c"

появляется еще одно имя – "*ptr_a". Часто в программах бывает удобно пользоваться

переменными, у которых есть только такие имена – динамическими переменными. Не-

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

через указатели с помощью операции разыменования (например, "*ptr_a" и "*ptr_b").

Динамические переменные "создаются" с помощью оператора распределения

динамической памяти "new", а "уничтожаются" (т.е. занимаемая ими память освобож-

Несколько замечаний о назначении программирования - student2.ru  
Несколько замечаний о назначении программирования - student2.ru  
Несколько замечаний о назначении программирования - student2.ru  
{
}

дается для дальнейшего использования) с помощью оператора "delete". Действие

этих операторов показано в программе 1.2 (она очень похожа на программу 1.1).

#include <iostream.h>

typedef int* IntPtrType;

int main()

{

IntPtrType ptr_a, ptr_b;

ptr_a = new int;

*ptr_a = 4;

ptr_b = ptr_a;

/* СТРОКА 7 */

/* СТРОКА 9 */

/* СТРОКА 10 */

/* СТРОКА 11 */

cout << *ptr_a << " " << *ptr_b << "\n";

ptr_b = new int;

*ptr_b = 7;

/* СТРОКА 15 */

/* СТРОКА 16 */

cout << *ptr_a << " " << *ptr_b << "\n";

delete ptr_a;

ptr_a = ptr_b;

/* СТРОКА 21 */

cout << *ptr_a << " " << *ptr_b << "\n";

delete ptr_a;

/* СТРОКА 25 */

return 0;

}

Программа 1.2.

Программа 1.2 печатает на экране следующие сообщения:

4 4

4 7

7 7

На рисунках 4-8 показаны состояния программы 1.2 после выполнения 7-й, 9-

11-й, 15-16-й, 21-й и 25-й строк.

Рис. 4.. Состояние про-

граммы 1.2 после выпол-

нения 7-й строки с описа-

ниями переменных.

Наши рекомендации