ПО ВЫСШЕМУ ОБРАЗОВАНИЮ
СТАРООСКОЛЬСКИЙ ФИЛИАЛ МОСКОВСКОГО ИНСТИТУТА
СТАЛИ И СПЛАВОВ
(ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ)
МЕТОДИЧЕСКОЕ ПОСОБИЕ
ПО КУРСАМ
«ПРОГРАММИРОВАНИЕ И ОСНОВЫАЛГОРИТМИЗАЦИИ »
«ОСНОВЫПРОГРАММИРОВАНИЯ »
Для студентов специальностей
- Промышленная электроника
Автоматизация технологических
Процессов и производств
Автоматизация электропривода
(Дневное, вечернее, заочное отделения)
Г.Старый Оскол
Г.
Московский Государственный Институт
Стали и Сплавов
(технологический университет)
Старооскольский филиал
Утверждено
Методическим советом
СТИ МИСиС
Бритик В.И.
Козырь О.Ф.
Программирование и основы алгоритмизации
методическое пособие
для студентов заочного отделения специальности
“Автоматизация технологических процессов
и производств”
Старый Оскол
Рецензент:
Тимофеев В.А.., доц. кафедры «ЭВМ» ХГТУРЭ, к.т.н.
Составители:
Бритик В.И., доц., к.т.н.
Козырь О.Ф., доц.
Методическое пособие работ по курсу “Программирование и основы алгоритмизации”
Ó Бритик В.И., Козырь О.Ф.
Содержание.
Введение…………………………………………………………………………
1. Общие методические указания…………………………………………….
2. Рабочая программа и методические указания к темам курса……………
3. Перечень лабораторных работ……………………………………………...
4. Список рекомендуемой литературы…………………………………………
Введение
Курс "Программирование и основы алгоритмизации" читается студентам очной, очно-заочной и заочной форм обучения специальности "Автоматизация технологических процессов и производств". Целью курса является изучение универсального языка программирования высокого уровня С++, приемов структурного программирования и получение навыков разработки алгоритмов обработки данных.
|
Базовым курсом для изучения данного курса является курс «Информатики», где студенты должны получить навыки составления элементарных и классических алгоритмов обработки данных, как нахождение суммы и произведения, а также максимального (минимального) элемента массива, обработка главной и побочной диагонали квадратной матрицы, различных методов сортировки («пузырька», Шелла, бинарных вставок и др.), знание простейшего языка программирования (например, QBasic). Естественным продолжением читаемого курса считаются дисциплины «Операционные системы», «Объектно-ориентированное программирование», «Технология разработки программного обеспечения», «Управление данными» и др. Знание приемов программирования необходимо и для многих специальных дисциплин «Алгоритмизация организационных задач СУ», «Моделирование технических систем» и т.д.
Общие методические указания.
Освоение дисциплиной рассчитано на один семестр, в течение которого читается курс лекций по дисциплине, выполняется четыре лабораторные работы и курсовая работа. Курс завершается сдачей экзамена.
Экзамен проводится по расписанию, составленному учебной частью. К экзамену допускаются студенты успешно защитившие все предложенные преподавателем лабораторные работы и курсовую работу. Экзаменационный билет содержит два теоретических и один практический вопросы. Практический вопрос представляет собой задачу, которую нужно реализовать на языке С++.
|
Объем дисциплины в часах и виды учебной работы представлены в таблице 1.
Таблица 1.
Вид учебной работы | Кол-во часов (5 семестр) |
Общая трудоемкость дисциплины | |
Аудиторные занятия | |
Лекции | |
Лабораторные работы (ЛР) | |
Самостоятельная работа | |
Вид итогового контроля: | экзамен |
Ориентировочное время на изучение теоретического курса (по темам) представлено в таблице 2.
Таблица 2.
№ п/п | Раздел дисциплины | Количество часов |
Технология программирования. Структурный подход в программировании. Правила кодирования документов и программ. Этапы создания программ. | ||
Состав языка С++. Основные типы данных | 0,5 | |
Структура программы на С++. | 0,5 | |
Переменные. Классы памяти. Выражения. Операции. | 0,5 | |
Базовые конструкции языка программирования. Виды программ. | 0,5 | |
Операторы. | ||
Ввод/ вывод данных: стандартный, форматированный и неформатированный. Оформление экрана. | 1,5 | |
Указатели. Ссылки. Массивы. | ||
Строки. Ввод/вывод строк. Операции со строками. | ||
Типы данных, создаваемые пользователем: структуры, смеси, перечисления, битовые поля. | 1,5 | |
Модульное программирование. Функции. |
Рабочая программа и методические указания к темам курса.
Типы данных
Основная цель любой программы состоит в обработке данных. Данные различного типа хранятся и обрабатываются по-разному. В любом алгоритмическом языке каждая константа, переменная, результат вычисления выражения или функции должны иметь определенный тип.
|
Тип данных определяет:
- внутреннее представление данных в памяти компьютера;
- множество значений, которые могут принимать величины этого типа;
- операции и функции, которые можно применять к величинам этого типа.
Все типы можно разделить на:
- основные (для представления целых, вещественных, логических и символьных величин);
- составные (массивы, перечисления, функции, структуры, ссылки, указатели, объединения, классы).
Основные (стандартные) типы данных называют часто арифметическими, поскольку их можно использовать в арифметических операциях. Для описания основных типов определены следующие ключевые слова:
- int (целый);
- char (символьный);
- wchar_t (расширенный символьный);
- bool (логический);
- float (вещественный);
- double (вещественный с двойной точностью).
Первые четыре типа называют целочисленными (целыми), последние два – типами с плавающей точкой..
Существует четыре спецификатора типа, уточняющих внутреннее представление и диапазон значений стандартных типов:
- short (короткий);
- long (длинный);
- signed (знаковый);
- unsigned (беззнаковый).
Кроме перечисленных, к основным типам языка относится и тип void (неопределенный тип), множество значений которого пусто. Он используется для определения функций, которые не возвращают значения, для указания пустого списка аргументов функции, как базовый тип для указателей и в операции приведения типов.
В таблице 1 представлены основные характеристики типов данных.
Таблица 1.
тип | Размер байт | Диапазон значений (min –max) |
bool | true и false | |
signed char | -128 - +127 | |
unsigned char | 0 - +255 | |
unsigned short int | 0 – +65 535 | |
signed short int | -32 768 – +32 767 | |
signed int | 2 (4) | -32 768 – +32767 (+2 147 483 648) |
signed long int | -2 147 483 648 – +2 147 483 648 | |
unsigned long int | 0 – +4 294 967 295 | |
float | 3.4e-38 – 3.4e+38 | |
double | 1.7e-308 – 1.7e+308 | |
long double | 3.4e-4932 – 3.4e+4932 |
2.2. Основные операторы языка С++
Операторы управления.
Условный оператор if. Используется для разветвления процесса на два направления. Имеет следующий вид:
if (выражание) оператор_1;
[else оператор_2;]
где выражение - выражение произвольного вида интерпретируемого языком С++ как логическое, т.е. любое отличное от нуля значение как True и False в противном случае. Сначала вычисляется выражение; если значение выражения отлично от нуля, то выполняется оператор_1 (или блок), иначе (равно нулю) – оператор_2 (блок). После этого управление передается на оператор, следующий за if. Ворая ветвь вместе с ключевым словом else может отсутствовать.
Например:
int i=0,j=0,k=0; //int i=j=k=0;
.......
if (m[j]<0)i++; //счётчик отрицательных элементов
else
k++; //счётчик положительных элементов
........
Операторы if..else могут быть вложенными. В этом случае операторы оператор_1 или оператор_2 могут быть представлены такой же конструкцией if..else.
Например:
int i=0,j=0,k=0,n=0;
................
if(m[j]==0)n++ //счётчик нулевых элементов
else
if(m[j]<0)i++; // cчётчик отрицательных элементов
................
Если требуется проверить несколько условий, то их объединяют знаками логических операций:
if (a<b && (a>b || a==0)) b++;
else {if (b<c) m=b; else m=c;}
Для устранения неоднозначности компилятор С интерпретирует вложенные if таким образом, что ветвь else соотносит к ближайшему предыдущему if.
Оператор множественного ветвления switch (переключатель). Предназначен для разветвления процесса вычислений на несколько направлений. Имеет следующую форму:
switch (выражение) {
case константное_выражение_1: [список_операторов_1] [break;]
case константное_выражение_2: [список_операторов_2] [break;]
...
case константное_выражение_n: [список_операторов_n] [break;]
[ default: операторы ] }
Оператор switch работает следующим образом: сначала вычисляется выражение (оно должно быть целочисленного типа char, int, unsigned int, long int, long unsigned), которое сравнивается со значениями константных выражений группы - константное_выражение_1 … константное_выражение n (которые должны быть одного и того же целочисленного типа). Если какое-либо из константных целочисленных выражений совпало со значением выражения, то выполняется соответствующий список_операторов (оператор) и управление (при наличии оператора break) передаётся следующему за оператором switch оператору; если выход из переключателя явно не задан (отсутствует операторы break или return в конце ветви), последовательно выполняются все остальные ветви.
Если соответствующего совпадения не обнаружено, то выполняются операторы ветви default (если он есть) и управление передаётся следующему после switch оператору.
Пример:
switch(getchar()) {
case ‘a’: puts(‘’введена буква а ‘’); break;
case ‘б’: puts(‘’ введена буква б ‘’); break;
case ‘c’: puts(‘’ введена буква с ‘’); break;
default: puts(‘’ ни одна из трёх букв а, б, с не введена’’); break;
}
При вводе с клавиатуры одного из символов «а», «б» или «с» на экран дисплея будет выведено соответствующее извещение. Например, при вводе символа «a» на экране дисплея будет отображено следующее сообщение
введена буква a
После чего управление будет передано следующему за switch оператору. Если будет введена буква отличная от «a», «b» или «с» на экран будет выдано сообщение:
ни одна из трёх букв а, б, с не введена.
Затем управление также передается следующему за switch оператору.
Приведенный выше пример без использования операторов break даст следующие результаты: при вводе символа «a» на экране дисплея будет отображено:
введена буква a
введена буква b
введена буква с
ни одна из трех букв a,b или с не введена;
при вводе символа «c» на экране будет отображено:
введена буква с
ни одна из трех букв a,b или с не введена.
Как видно из рассмотренного примера, отсутствие оператора break может привести к некорректным результатам, если это не предусмотрено специально.
Цикл с предусловием (while). В общем виде записывается:
While (выражение)
оператор;
Оператор while обеспечивает реализацию цикла с предусловием. Это означает, что оператор в теле цикла вообще не вычисляется, если вычисленное выражение имело нулевое значение (ложь), а управление передается следующему за циклом while оператору. Если выражение отлично от нуля (истинно), тогда вычисляется оператор, и управление передается обратно к началу цикла. В результате тело цикла оператора while - оператор, выполняется до тех пор, пока выражение примет значение ноль (ложь), а управление будет передано следующему за циклом оператору. Тип выражения должен быть арифметическим или приводимым к нему. Выражение вычисляется перед каждой итерацией цикла.
Пример:
int i=1,p=1;
while(i<=10){ //Вычисление 10! (факториал 10)
p*=i;
i++;
}
Распространенный прием программирования – организация бесконечного цикла с заголовком
while (true)
либо
while (1)
В круглых скобках после ключевого слова while можно вводить описание переменной. Областью ее действия является цикл.
while (int x = 0)
Цикл с постусловием (do while). В общем виде записывается:
do
оператор;
while(выражение);
В отличие от предыдущего, оператор do реализует цикл с постусловием, что гарантирует выполнение тела цикла хотя бы один раз, после чего производится вычисление выражения. Если значение выражения отлично от нуля (истинно), тогда управление передается обратно к началу оператора do и процесс вычисления повторяется. В том случае, когда значение выражения - ноль (ложь) управление передается следующему за оператором do оператору.
Пример:
int n=1,p1,p2;
do{
p1=1./n; n++;
p2=p1+1./n;
}
while(1./n<0.001);
Цикл с параметром (for). В общем случае записывается:
for (оператор_1; выражение_1; выражение_2)
оператор_2;
Оператор for работает следующим образом. Вначале выполняется оператор_1 (обычно это оператор инициализации), который в частном случае может быть пустым. Затем вычисляется выражение_1. Если значение выражения_1 имеет значение ноль (ложь), то управление передается следующему за оператором for оператору, и оператору_2, когда значение выражения_1 имеет ненулевое значение. После этого выполняется выражение_2 (чаще всего производящий модификацию параметра) и управление передается на выражение_1. Таким образом, оператор_2 -тело цикла - повторяется до тех пор, пока выражение_1 (условие окончания цикла) не примет значение 0.
Примеры:
//фрагмент программы определения минимального элемента вектора
int min=а[0]
for(int i=0,i<100,i++)
if (min>a[i]) min=a[i];.
Оператор_1 используется для объявления и присвоения начальных значений величинам, используемым в цикле. Он выполняется только один раз в начале исполнения цикла.
Выражение_2 выполняется после каждой итерации цикла.
Вместо оператор_1 и выражение_2 можно записать несколько операторов через запятую.
Операторы цикла взаимозаменяемы. Однако do while обычно используют, когда цикл требуется обязательно выполнить хотя бы раз (напр., ввод данных). Оператором while удобнее пользоваться в случаях, когда число итераций заранее не известно, очевидных параметров цикла нет или модификацию параметров удобнее записывать не в конце тела цикла. Оператор for предпочтительнее в большинстве остальных случаев, в т.ч. для организации циклов со счетчиками.
Операторы ввода-вывода.
Ввод и вывод как в С, так и в С++ не относятся непосредственно языку. Они обеспечиваются стандартными библиотеками. Для С++ такая библиотека называется iostream. Ввод, идущий с клавиатуры пользователя, называется стандартным входным потоком или стандартным вводом. Он связывается с предопределенным в iostream.h потоком cin. Вывод, направляемый на экран пользователя, называется стандартным выходным потоком или стандартным выводом. Он связывается с предопределенным в iostream.h потоком cout.
Операция вывода << направляет значение в стандартный выходной поток.
cout << index;
Для перехода на новую строку существуют два способа:
1) использовать определенный в iostream.h манипулятор endl. Манипулятор можно выводить в поток и при этом он меняет параметры вывода. Здесь endl вызовет переход на новую строку
cout << endl;
2)явно вывести в поток символ новой строки. В С++ он записывается двумя символами: '\n'.
cout << '\n';
Одиночные кавычки ограничивают символ. Такой символ может быть внутри строки символов, например оператор
cout << "Программа на С++\n";
вызовет переход на новую строку после вывода данного сообщения.
В одном операторе вывода можно соединять несколько операций. Например:
cout << "Значение index равно: " << index << endl;
Вывод осуществляется по порядку, считая слева направо.
Аналогично операция ввода (>>) читает значение из стандартного входного потока, например
cin >> index;
Такие операции тоже можно соединять в одном операторе. Например,
если в программе встретится следующий оператор:
cin >> i1 >> i2;
то программа будет ждать ввода с клавиатуры двух величин и первую из них поместит в переменную i1, а вторую - в переменную i2. Эти две вводимых величины можно разделять пробелом или табуляцией, а можно каждую из них вводить с новой строки - операция ввода сработает правильно.
Форматированный ввод/ вывод
Форматированный ввод/ вывод может быть выполнен благодаря использованию 2-х функций: scanf и printf, соответственно. В эти функции описаны заголовочном файле stdio.h.
Запишем эти функции в следующем виде:
printf(упр.текст.(форматная)строка[,список аргументов]);
scanf(упр.текст.(форматная) строка[,список аргументов]);
Список аргументов - это последовательность констант, переменных или выражений, значения которых выводятся на экран (для printf()) в соответствии с форматом управляющей строки.
Аргументы же scanf() должны быть указателями на соответствующие вводимые значения, для этого перед именем переменной записывается символ &.
Управляющая строка определяет количество и тип аргументов и содержит объекты трех видов:
- обычные символы, выводимые на экран без изменений;
- спецификации преобразования, каждая из которых вызывает вывод на экран (или ввод) значения очередного аргумента из списка аргументов;
- управляющие символьные константы.
Каждая спецификация преобразования начинается с символа % и заканчивается символом преобразования (типом). Между ними могут записываться флаг, ширина, точность и др.. Спецификация преобразования задается в виде последовательности:
%[флаг] [ширина] [.точность] [f | n | h | l] <тип>
[флаг]
- | выравнивание влево в пределах выводимого поля. Правая строка дополняется пробелами (по умалчиванию - выравнивается вправо). |
+ | выводится знак |
пробел | печатается пробел, если число положительное и знак ‘-‘ для отрицательного. |
# | выводится идентификатор систем исчисления(0-8ми ричная,0x-16тиричная ничего-10тиричная, с точкой - float) |
[ширина]- воздействует только на вывод
n | ширина поля. Если символов меньше чем ширина, то лишние символы заполняются пробелами. Если символов больше, то выводится сколько надо. |
0x | тоже что прежде, но для целого числа позиции слева заполняются символами 0 |
* | следующий аргумент задает ширину |
[.точность]- воздействует только на вывод
Ничего | по умолчанию |
.O | для d,i,o,u,x -по умолчанию. Для «e,E,f» -десятичная точка отсутствует. |
n | не более n знаков после точки, для «e,E,f». |
* | следующий аргумент из списка аргументов - точность |
--->[ F | N | h | l ] -модификатор
F | F - рассматривается как FAR - указатель |
n | рассматривается как near - указатель |
h | для d,i,o,u,x,X - аргумент является short int. |
L | для (d,i,o,u,x,E) - аргумент long int, для (e,E,f,g,G) аргумент для scanf - double |
<тип> (тип данных)
C | при вводе читается один байт - ссылка на char (один символ) |
C | при выводе переменная преобразуется к типу char (1 байт) |
Переменная int.
d | десятичное int со знаком |
i | десятичное int со знаком |
o | Восьмеричное int. unsigned |
u | десятичное int unsigned |
X, x | Шестнадцатеричное int unsigned; при выводе используются символы o-f (O -F) |
Переменная float
f | значение со знаком в форме с фиксированной точкой [-] dddd.dddd |
E, e | значение со знаком в фоpме [-] d.dddde[+|-]ddd, при вводе они e|E |
G, g | значение со знаком, в форме «f» или «e», в зависимости от значения |
Переменная char
S | ссылка на массив char - при вводе принимает символы без преобразования до тех пор, пока не достигнута специфицируемая точность или не достигнут символ \n или пробела. |
S | При выводе в поток передаются символы до «\0»или пока не достигнута специфицируемая точность. |
Наиболее часто используются следующие управляющие символы:
\a – кратковременный звуковой сигнал;
\n – перевод каретки;
\t – горизонтальная табуляция;
\b – возврат курсора на один шаг назад;
\r – возврат каретки.
Примеры:
printf ("Печатается данный текст");
курсор остается на этой же строке после символа «т».
printf ("печатается данный текст \n ");
курсор переходит на следующую строку в первую позицию.
Вывод строк puts(string) - аналогично printf(“%s\n”,string).
Необходимо отметить, что функция scanf прекращает работу, если:
- закончилась управляющая последовательность (форматная строка).
- очередной элемент ввода не удовлетворяет текущей спецификации преобразования или
не совпадает с символом образца.
- достигнут конец файла ввода.
Для 1,2 случая функция возвращает число введенных переменных (т.е. в ячейке scanf число введенных переменных). Для 3 случая функция возвращает значение EOF. Следует заметить, что функция scanf по формату S вводит символы вводимого потока до первого разделителя, поэтому часто приходится очищать буфер stdin с помощью функции fflush(stdin).
Пример:
int a,b,c,d;
a=20;
b=-252;
c=01777;
d=0xa7c;
printf(“a=%d\t b=%d\t c=%d\t d=%d\t”,a,b,c,d);
/* a=20 b=-252 c=1023 d=2684 */
2.2.3. Контрольные вопросы.
1. Как ввести начальные значения переменных.
2. Какое месторасположение операторов описания типа в программе.
3. Какие формы представления чисел существуют в С++.
4. Какая последовательность выполнения операций в выражениях.
5. Что понимается под термином «тело цикла ».
6. Какого типа могут быть метки в операторе swith.
7. Какие существуют формы записи условного оператора? В чём их различие?
Массивы и указатели
Указатели.
Указатели – это составной тип данных, предназначеный для хранения адресов областей памяти. В С++ различают три вида указателей, отличающиеся свойствами и набором допустимых значений:
- указатели на объект;
- на функцию;
- на void.
Указатель не является самостоятельным типом, он всегда связан с каким-либо другим конкретным типом.
В этом параграфе мы будем рассматривать у казатель на объект, содержащий адрес области памяти, в которой хранятся данные определенного типа (основного или составного).
Порядок работы с указателем:
1) Объявление указателя на объект:
тип *имя;
где тип может быть любым (кроме ссылки и битового поля), причем он может быть к этому моменту только объявлен, но еще не определен.
Звездочка относится непосредственно к имени, как признак указателя, поэтому при
Указатель может быть константой (не изменять своего значения) или переменной (изменять значение), а также указывать на константу или переменную. Указатель-константа объявляется с помощью спецификатора const и, как и любая константа инициализируется сразу же при объявлении. Указатель-переменная тоже может быть про инициализирована при объявлении:
Пример:
int i; // целая переменая
const float pi=3.14; // вещественная константа
int *ci; // указатель на целую переменную
const int *pci; // указатель на целую константу
int *const cp = &i; // указатель-константа на целую переменную
const float *const cpc = π // указатель-константа на целую
// константу
2) Инициализация указателя (присвоение начального значения).
Для инициализации указателя используется операция взятия адреса &.
Инициализатор записывается после имени указателя:
- в круглых скобках;
- после знака равенства.
Существуют следующие способы инициализации указателя:
· Присвоение указателю адреса существующего объекта:
- с помощью операции взятия адреса:
int a =5; // целая переменная
int *p = &a; // в указатель записывается адрес а
int *p (&a); // инициализация другим способом
- с помощью значения другого инициализированного указателя:
int *r = p; // r и p – указатели
- с помощью имени массива, который трактуется как адрес:
int b[10]; // массив
int *t = b; // присвоение указателю адреса начала массива
· присвоение указателю адреса области памяти в явном виде:
char* vp = (char *)0xb8000000; // 0xb8000000 – 16ричная константа
// (char *) – операция приведения типа: константа преобразуется
// к типу «указатель на char»
· выделение участка динамической памяти и присвоение ее адреса указателю:
- с помощью операции new:
int *n = new int; // выделение памяти под значение типа int и
// записывает адрес начала этого участка в переменную n
int *m = new int (10) // аналогично+заполнение участка значением 10
- с помощью функции malloc:
int *u = (int *) malloc (sizeof(int)); // аналогично int *n=new int;
3) Операции с указателями.
С указателями можно выполнять следующие операции:
- разыменование (разадресация);
- присваивание;
- сложение с константой;
- вычитание;
- инкремент (++);
- декремент (--);
- сравнение;
- приведение типов;
- взятия адреса.
Операция разыменования (*). Предназначена для доступа к величине, адрес которой хранится в указателе. Ее можно использовать и для получения и для изменения величины (значения).
Конструкция *имя_указателя может считаться именем переменной, на которую ссылается указатель. С ней допустимы все действия, определенные для величин соответствующего типа. На одну и ту же область памяти может ссылаться несколько указателей различного типа. Примененная к ним операция разыменования даст разные результаты. Таким образом, если в программе имеется объявление
int a,*p; // p - указатель на int
a=1;
.........
p=&a;
то выражение
++*p; // *p=*p+1; или a=a+1; (a=2)
т.е. означает - увеличить значение переменной целого типа a на единицу.
Арифметические операции (сложение с константой, вычитание, инкремент, декремент). Автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенными в памяти, например, с массивами.
Инкремент перемещает указатель к следующему элементу массива.
Декремент – к предыдущему.
Фактически значение указателя изменяется на величину sizeof(тип). Если указатель изменяется не какую-либо константу, то его значение изменяется на величину этой константы, умноженную на размер объекта данного типа, например
p=p+3; // p = p + 3 * 4(байта), где р – имя указателя.
В нашем примере мы увеличили значение указателя на 3*4=12 байт, т.е. «сдвинули» его вправо на три элемента типа int (размером по 4 байта каждый).
Разность двух указателей – это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается.
При записи выражений с указателями следует обращать внимание на приоритет операций. Пример:
int a, *p = &a; // определение переменной а и указателя р
*p++=10; // аналогично последовательности: a=10;(*p=10;) p++;
(*p)++; // аналогично: a++; (a=a+1;) теперь а=11.
Операция взятия адреса & применима к величинам, имеющим имя и размещенным в оперативной памяти. Таким образом нельзя получить адрес скалярного выражения или регистровой переменной.
Массивы
Если требуется работать с группой величин одного типа, их располагают в памяти последовательно и дают им общее имя, а различают по порядковому номеру. Такая последовательность однотипных величин называется массивом (вектором).
Различают массивы:
- одномерные (имеют одну размерность) – вектора;
- многомерные (две и более размерностей).
Из многомерных массивов наиболее часто работают с двумерными массивами (матрицами, таблицами).
Порядок работы с массивами следующий:
1) Объявление массивов (или их определение), при котором указывается тип элементов массива (а он у всех одинаков), имя массива и количество элементов или количество строк и столбцов для матрицы. При описании массивов каждая размерность задается отдельно в квадратных скобках. У матриц (двумерных массивов) первая размерность означает количество строк, вторая – количество столбцов. Например:
float a [10]; // описание вектора из 10 вещественных чисел
int B [3][4]; /* описание матрицы из 12 (3х4)целых элементов,
имеющей 3 строки (с номерами 0 – 2) и 4 столбца (с номерами 0-3) */
При объявлении массивов а[10] и В[3][4] определяется не только объем выделяемой памяти для 10 элементов массива a (с №№ 0 – 9) и 12 элементов массива B (3 строки и 4 столбца), но и для указателей-констант с именами a и В, значение которых равно адресу первых по счету (нулевых) элементов массивов.
Под массивы системой выделяется непрерывный участок памяти (его объем определяется системой при объявлении массива), где элементы массивов хранятся последовательно, элементы матрицы располагаются в последовательных ячейках памяти построчно.
Элементы массива нумеруются с нуля. Так же, с нуля, начинается нумерация строк и столбцов матрицы.
2) Инициализация массивов (заполнение массивов значениями) может производиться различными способами. Наиболее распространенные из них:
- явная инициализация (значения записываются в фигурных скобках при описании массива);
- с помощью генератора случайных чисел (см. функции rand(), random(), randomize());
- ввод значений с клавиатуры или интерактивный ввод (используются операторы cin>> и scanf()).
Явная инициализация вектора производится путем задания набора начальных значений элементов, разделенных запятыми и заключенных в фигурные скобки, по следующей форме:
int b[2] ={1,2}; // b[0]=1, b[1]=2
Аналогично одномерным векторам многомерные массивы могут быть так же инициализированы при их описании, например:
float BV[3][3][3]={1.1,1.2,1.3,
2.1,2.2,2.3,
3.1,3.2,3.3}
В многомерном массиве можно не указывать первую левую размерность, в этом случае элементы строк заключаются в фигурные скобки, например:
int V[ ][3]={{1,2},{4,5,6},{7}}; // элементы первой строки получат
// значения 1,2 и 0, второй - 4,5,6, а третей - 7,0,0, т.к. при
// недостатке значений система дописывает необходимое число нулей
Интерактивный ввод данных вектора выполняется с помощью операторов цикла. Например, для ввода значений вектора может быть предложена следующая последовательность операторов:
for (int i=0; i<n;i++){
cout<<"введите a[ "<<i<<" ] эл-т"<<'\n';
cin>>a[i]; }
Рассмотрим на примере формирование матрицы с помощью генератора случайных чисел:
...
int x[5][4]; // описание массива х с 20 элементами (5х4)
...
randomize();
...
for (int i=0; i<5; i++) // перебор строк от нулевой по пятую
for (int j=0; j<4; j++) // перебор элементов каждой строки
{ x[i][j] = random(10) – 5;
...
}
3) Обработка массива.
Обработка массива осуществляется по разработанному для задачи алгоритму, обращаясь к каждому конкретному элементу массива.
В C++ разрешены два способа доступа к элементам массива:
- классический - с помощью индексов;
- с использованием механизма указателей.
При классическом способе после имени указывается:
- для вектора – номер его элемента (нумерация начинается с нуля);
- для массива – номер строки и номер столбца, в котором находится элемент (нумерация строк и столбцов также начинается с нуля).
Например:
V[1][2]=3; /* использование индексов, элемент находится в столбце c номером 2 строки с номером 1 */
f = a[14]; // переменной f присваивается значение элемента
// вектора с номером 14 (по счету он 15-тый
Обращение через указатели-константы и указатели-переменные отличается. Указатель-константа, как нам известно, не меняет своего значения, поэтому для доступа к элементам массива используются величины, называемые смещениями. Если необходимый нам элемент расположен выше или левее элемента, на который указывает указатель-константа, то смещение имеет знак “-“ (“минус”), если ниже или правее – то знак “+” (“плюс”). Если положение указателя-константы совпадает с началом массива, то значения смещений совпадают с номерами строки и столбца, в которых находится необходимый нам элемент. Это очень удобно при произвольном обращении к элементам массива. Порядок указания смещений относительно указателя совпадает с порядком соответствующих им размерностей, так для матрицы первое смещение соответствует смещению по строкам, а второе – по столбцам. Каждый раз после смещения в нужном направлении необходимо разыменовать полученную величину. Следовательно, разыменований будет столько, сколь раз мы смещались относительно указателя.
*(*(V+1)+2)=3; // *(V[1]+2)=3; или V[1][2]=3.
В данном случае сначала следует обратиться к первой строке массива, т.е. к одномерному массиву V[1]. Для этого надо прибавить к адресу начала массива (V) смещение, равное номеру строки, и выполнить разыменование: (V+1). При сложении указателя с константой учитывается длина адресуемого элемента, т.е. 1 * (5 * sizeof(int)), поскольку элементом является строка, состоящая из 5 элементов типа int.
Далее требуется обратиться ко второму элементу полученного массива. Для получения его адреса опять применяется сложение указателя с константой (т.е. 2 * sizeof(int)), а затем применяется операция разыменования для получения значения элемента: *(*(V+1)+2).
*(V[1]+2)=3; // или V[1][2]=3,где V[1]- указатель на 1-ю строку,
// 2 - смещение
*(arr1+5)+=3; // или arr1[5]+=3, где arr1 –указатель на вектор,
// 5 – смещение
Применение механизма указателей основано на использовании факта, что имя вектора является указателем - константой, равной адресу начала вектора - первого байта первого элемента вектора (arr1==&arr1[0]). В результате этого, используя операцию разыменования “*” можно обеспечить доступ к любому элементу вектора. Так, эквивалентны будут обращения к i-му элементу вектора с использованием индекса - arr1[i] и ссылки *(arr1+i), поскольку (arr1+i)==&arr1[i].
*(*(V+1)+2)=3; /* V - указатель-const на начало массива с
именем «V» */
Обращение посредством указателя-переменной производится иначе. Чтобы обратиться к другому элементу массива значение указателя должно быть изменено, что соответствует поэлементному “перемещению” указателя по массиву, например: