// Проверяем
значение work_size
|
if
(work_size<=0 || work_size>phys_size)
|
cout<<
"Ошибочный размер массива";
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Вводим
значения элементов массива
|
for
(inti=0; i<work_size; i++)
|
{
|
cout<<
"Введите значение элемента "
|
|
|
|
|
<< i+1
<< ": ";
|
}
|
cin>>
M[i];
|
|
|
|
|
|
|
|
// Определяем
сумму элементов массива
|
|
|
for
(inti=0; i<work_size; i++)
|
|
sum += M[i];
|
|
// Выводим
результат на экран
|
cout<<
"СУММА ЭЛЕМЕНТОВ МАССИВА: " <<sum;
|
}
|
|
|
|
|
|
getch(); //
Ожидаем нажатия на любую клавишу
|
}
|
|
|
|
|
|
1.4 Многомерные массивы
Массивы, рассмотренные нами выше, являются одномерными. В общем случае,
массивы могут иметь несколько измерений.
Многомерный массив - это массив, элементами которого являются массивы.
Двумерный массив - массив одномерных массивов - может быть представлен
как матрица, состоящая из строк и столбцов, на пересечении которых размещаются
значения (рис. 1.1).
Рисунок 1.1 - Двумерный массив
Трехмерный массив - массив двумерных массивов - может быть представлен
как множество одинаковых матриц или как куб (рис. 1.2).
При объявлении многомерного массива в отдельных квадратных скобках для
каждого измерения указывается количество элементов:
Рисунок 1.1 - Трехмерный массив массив
Для обращения к элементу многомерного массива необходимо определить его
порядковый номер по каждому измерению:
.5 Указатели, динамические массивы
Как видно из рассмотренного примера программы, приведенного в листинге
1.1 в разделе 1.3 курсового проекта, в Си++ массивы статические, и их размер
задается при описании. Это не всегда удобно, кроме того, при решении некоторых
задач заранее неизвестен размер формируемого массива.
В Си++ существуют динамические массивы - массивы переменной длины, они
определяются с помощью указателей.
Указатель - переменная,
значением которой является адрес памяти, по которому хранится объект
определенного типа. При объявлении указателей всегда указывается тип объекта,
который будет храниться по данному адресу.
Указатель описывается следующим образом:
* name;
Здесь name - переменная, объявляемая, как указатель . По этому адресу
(указателю) храниться значение типа type.
Например:
*i;
Объявляем указатель (адрес) i. По этому адресу будет храниться переменная
типа int.
Переменная i указывает на тип данных int.
*x,*z;
Объявляем указатели с именами x и z, которые указывают на переменные типа
float.
Операции * и & при работе с указателями
При работе с указателями в основном используются операции & и *.
Операция & возвращает адрес своего операнда.
Например, если объявлена переменная a следующим образом:
float a;
тооператор
_a=&a;
записывает в переменную adr_a адрес переменной a, переменная adr_aдолжна
быть указателем на тип float. Ее следует описать следующим образом:
*adr_a;
Операция * выполняет действие, обратное операции &. Она возвращает
значение переменной, хранящееся по заданному адресу.
Например, оператор
=*adr_a;
записывает в переменную a вещественное значение, хранящееся по адресу
adr_a.
Операция присваивания указателей.
Значение одного указателя можно присвоить другому. Если указатели одного
типа, то один можно присваивать другому с помощью обычной операции
присваивания.
Рассмотрим следующий пример
#include<stdio.h>
#include <math.h>main()
{PI=3.14159,*p1,*p2;=p2=&PI;
printf("По адресу p1=%p хранится
*p1=%g\n",p1,*p1);("По адресу p2=%p хранится *p2=%g\n",p2,*p2);
В этой программе определены: вещественная переменная PI=3.14159 и два
указателя на тип float p1 и p2. Затем в указатели p1 и p2 записывается адрес
переменной PI. Операторы printf выводят на экран адреса p1 и p2 и значения,
хранящиеся по этим адресам. Для вывода адреса используется спецификатор типа
%p. В результате работы этой программы в переменных p1и p2 будет храниться
значение одного и того же адреса, по которому хранится вещественная переменная
PI=3.14159.
По адресу p1=0012FF7C хранится *p1=3.14159
По адресу p2=0012FF7C хранится *p2=3.14159
Если указатели ссылаются на различные типы, то при присваивании значения
одного указателя другому, необходимо использовать преобразование типов. Без
преобразования можно присваивать любому указателю указатель void *. Рассмотрим
пример работы с указателями различных типов.
#include<stdio.h>
#include <math.h>main()
{PI=3.14159,*p1;
double *p2;
//В переменную p1 записываем адрес PI=&PI;
//указателю на double присваиваем значение, которое ссылается
на
//тип float.=(double *)p1;
printf("По адресу p1=%p хранится
*p1=%g\n",p1,*p1);("По адресу p2=%p хранится *p2=%e\n",p2,*p2);
}
По адресу p1=0012FF7C хранится *p1=3.14159
По адресу p2=0012FF7C хранится *p2=2.642140e-308
В указателях p1 и p2 хранится один и тот же адрес, но значения, на
которые они ссылаются, оказываются разными. Это связано с тем, указатель типа
*float адресует 4 байта, а указатель *double - 8 байт. После присваивания
p2=(double *)p1; при обращении к *p2 происходит следующее: к переменной,
хранящейся по адресу p1, дописывается еще 4 байта из памяти. В результате
значение *p2 не совпадает со значением *p1.
#include<stdio.h>
#include<math.h>main()
{PI=3.14159,*p1;*p2;=&PI;=(float *)p1;
printf("По адресу p1=%p хранится
*p1=%g\n",p1,*p1);("По адресу p2=%p хранится *p2=%e\n",p2,*p2);
После присваивания p2=(double *)p1; при обращении к *p2 происходит
следующее: из переменной, хранящейся по адресу p1, выделяется только 4 байта. В
результате и в этом случае значение *p2 не совпадает со значением *p1.
Отсюда вытекает следующий вывод: при преобразовании указателей разного
типа приведение типов разрешает только синтаксическую проблему присваивания.
Следует помнить, что операция * над указателями различного типа, ссылающимися
на один и тот же адрес, возвращает различные значения.
Если есть следующий оператор
*p;
то в переменной p хранится адрес, а с помощью конструкции *p можно
получить значение, хранящееся по адресу p.
В случае использования оператора
p;
то для вычисления адреса используется конструкция &p, а в переменной
p находится вещественное значение.
Арифметические операции над адресами.
Над адресами в языке Си определены следующие операции:
суммирование, можно добавлять к указателю целое значение;
вычитание, можно вычитать указатели или вычитать из указателя целое
число.
Однако при выполнении арифметических операций есть некоторые особенности.
Рассмотрим их на следующем примере.
double *p1;*p2;*i;++++;++;
}
Операция p1++ увеличивает значение адреса на 8, операция p2++ увеличивает
значение адреса на 4, а операция i++ на 2. Операции адресной арифметики
выполняются следующим образом:
операция увеличения приводит к тому, что указатель будет слаться на
следующий объект базового типа (для p1 - это double,д ля p2 - float, для i -
int);
операция уменьшения приводит к тому, что указатель, ссылается на
предыдущий объект базового типа;
после операции p1=p1+n, указатель будет передвинут на n объектов базового
типа;p1+n как бы адресует n-й элемент массива, если p1 - адрес начала массива.
1.6
Использование адресов и указателей при работе с динамическими массивами
С помощью указателей в Си можно выделить участок памяти (динамический
массив) заданного размера для хранения данных определенного типа. Для этого
необходимо выполнить следующие действия:
. Описать указатель (например, переменную p) определенного типа.
. Начиная с адреса, определенного указателем, с помощью функций calloc,
malloc или операции new выделить участок памяти определенного размера. После
этого p будет адресом первого элемента выделенного участка оперативной памяти
(0-й элемент массива), p+1 будет адресовать - следующий элемент в выделенном
участке памяти(1-й элемент динамического массива), & , p+i является адресом
i-го элемента.
Необходимо только следить, чтобы не выйти за границы выделенного участка
памяти.
К i-му элементу динамического массива p можно обратиться одним из двух
способов *(p+i) или p[i].
. Когда участок памяти будет не нужен, его можно освободить с помощью
функции free(), операции delete.
Перед подробным описанием работы с динамическими переменными, рассмотрим
функции calloc, malloc, realloc
и free и операции new и delete.
Единственным параметром функции malloc является целое беззнаковое
значение, определяющее размер выделяемого участка памяти в байтах. Функция
malloc возвращает безтиповый указатель (void *) на выделенный участок памяти.
Обращение к функции malloc имеет вид
*malloc(n);
здесь n определяет размер выделяемого участка памяти в байтах, функция
вернет значение NULL, если выделить память не удалось и указатель на выделенный
участок памяти, при успешном выделении.
.7 Формирование динамических массивов с использованием
библиотечных функций
Динамический массив - массив переменной длины, память
под который выделяется в процессе выполнения программы.
Обычно, объем памяти, необходимый для той или иной
переменной, задается еще до процесса компиляции посредством объявления этой
переменной. Если же возникает необходимость в создание переменной, размер
которой неизвестен заранее, то используют динамическую память. Резервирование и
освобождение памяти в программах на C++ может происходить в любой момент
времени. Доступ к элементам динамического массива такой же, как и к
статическим. Динамический массив нельзя при создании инициализировать и нельзя
обнулять.
Формирование массивов с переменными размерами можно организовать с
помощью указателей и средств динамического распределения памяти двумя
способами:
с использованием библиотечных функций, описанных в заголовочных файлах
alloc.h и stdlib.h (стандартный Си);
с использованием операций new и delete (Си++).
Для выделения и освобождения динамической памяти используются функции, приведенные
в табл. 1.1.
Таблица 1.1 - Библиотечные функции для формирования динамических
массивов
Функция
|
Прототип и краткое описание
|
malloc
|
void * malloc(unsigned s)
Возвращает указатель на начало области динамической памяти длиной в s байт,
при неудачном завершении возвращает NULL
|
calloc
|
void *
calloc(unsigned n, unsigned m) Возвращает
указатель на начало области динамической памяти для размещения n элементов
длиной по m байт каждый, при неудачном завершении возвращает NULL
|
realloc
|
void *
realloc(void * p, unsigned s) Изменяет
размер блока ранее выделенной динамической памяти до размера s байт, р- адрес
начала изменяемого блока, при неудачном завершении возвращает NULL
|
free
|
void *free(void p)
Освобождает ранее выделенный участок динамической памяти, р - адрес первого
байта
|
Пример:
Функция для формирования одномерного динамического массива
int * make_mas(int n)
{
int
*mas;=(int*)malloc(n*sizeof(int));(inti=0;i<n;i++)[i]=random(10);mas;
}
Для выделения памяти используется функция malloc, параметром которой
является размер выделяемого участка памяти равный n*sizeof(int). Так как
функция malloc возвращает нетипизированный указатель void*, то необходимо
выполнить преобразование полученного нетипизированного указателя в указатель
int*.Освободить выделенную память можно функцией free(mas).
.8 Формирование динамических массивов с использованием
операций new и delete
Для динамического распределения памяти используются операции new и
delete. Операция
имя_типа
или
newимя_типа инициализатор
позволяет выделить и сделать доступным свободный участок памяти, размеры
которого соответствуют типу данных, определяемому именем типа. В выделенный
участок заносится значение определяемое инициализатором, который не является
обязательным параметром. В случае успешного выделения памяти операция
возвращает адрес начала выделенного участка памяти, если участок не может быть
выделен, то возвращается NULL.
Примеры:
1) int *i;=new int(10);
) float *f;=new float;
1)
int
*mas=new[5];
В примерах 1, 2 показано как выделить память под скалярные переменные,
пример 3 показывает выделение памяти под массив переменных.
Операция delete указатель освобождает участок памяти ранее выделенный
операцией new.
Пример:
Функция для формирования двумерного динамического массива
int ** make_matr(int n)
{**matr;,j;=new int*[n];(i=0;i<n;i++)
{[i]=new int[n];(j=0;j<n;j++)[i][j]=random(10);
}matr;
При формировании матрицы сначала выделяется памяти для массива указателей
на одномерные массивы, а затем в цикле с параметром выделяется память под n
одномерных массивов.
Рисунок 1.2 - Выделение памяти под массив
Чтобы освободить память необходимо выполнить цикл для освобождения
одномерных массивов
for(inti=0;i<n;i++)
delete matr[i];
После этого освобождаем память, на которую указывает указатель matr
delete [] matr;
Выводы к главе 1
В данной главе нами было подробно описано с демонстрацией примеров такие
понятия, как: правила использования динамической памяти, указателя и его
назначение, формат определения указателя, способы инициализации указателя,
операции над указателями, связь массива с указателем, способы доступа к
элементам массива через указатель.
Из выше изложенного отметим возможности указателей и динамических
массивов в С++.
Преимущество указателей в том, что они могу указывать на различные
области памяти; в том, что сами указатели занимают в памяти гораздо меньше
места, чем массив/объект, на который они указывают (вместо отправки в функцию
полностью массива/объекта, т.е. его копии, можно передать только указатель на
него, а скопировать указатель гораздо проще).
Преимущество динамических массивов состоит в том, что размерность может
быть переменной, то есть объем памяти, выделяемой под массив, определяется на
этапе выполнения программы, причем размер можно памяти можно вычислять в
процессе выполнения программы, а не на этапе компиляции. Динамическое выделение
памяти к тому же производится из кучи, а не из стека, что потенциально
увеличивает потолок запроса на выделение, а динамическую память можно
освободить за ненадобностью.
ГЛАВА 2. РАЗРАБОТКА ПРИКЛАДНОЙ ПРОГРАММЫ. ПРЕДСТАВЛЕНИЕ
.1 Функции, требования к программе
Принципы программирования на языке C++ основаны на понятии функции.
Функция - самостоятельная единица программы, спроектированная для реализации конкретной
задачи. Функции в языке C++ играют ту же роль, какую играют функции,
подпрограммы и процедуры в других языках, хотя детали их структуры могут быть
разными. Вызов функции приводит к выполнению некоторых действий. Например, при
обращении к функции printf() осуществляется вывод данных на экран. Другие же
функции позволяют получать величины, используемые затем в других частях
программы.
В разработанной нами программе мы описали функцию под общим именем
main(), которая выполняет формирование и обработку двухмерного динамического
массива. Функции пользователя в данной программе мы не использовали.
Работа с динамическими массивами имеет ряд особенностей и сложностей. Они
усугубляются еще и тем, что в С++ в соответствии с требованиями
эффективности программного кода, функции библиотеки минимально защищены от
ошибок программирования:
если динамическая переменная создана, а указатель на нее «потерян»
программой, то такая переменная представляет собой «вещь в себе» -существует,
но недоступна для использования. Тем не менее, занимаемая ею память остается за
программой;
ошибки в процессе создания, уничтожения и работы с динамическими
переменными (повторная попытка уничтожения динамической переменной, попытка
уничтожения переменной, не являющейся динамической и т.д.), приводят к
непредсказуемым последствиям в работе программы. Причем программа «валится»
иногда не в том месте, где производятся ошибочные действия, при последующих
выводах функций работы с библиотекой.
.2 Анализ и исследование результата работы программы
Напишем программу, которая для целочисленной матрицы, определяет среднее
арифметическое ее элементов и количество положительных элементов в каждой
строке.
Алгоритм решения этой задачи очевиден. Для вычисления среднего
арифметического элементов массива требуется найти их общую сумму, после чего
разделить ее на количество элементов. Порядок просмотра массива роли не играет.
Определение положительных элементов каждой строки требует просмотра матрицы по
строкам. Обе величины вычисляются при одном просмотре матрицы. Здесь
размерности массива заданы именованными константами N row и M col, что
позволяет легко их изменять. Для упрощения отладки рекомендуется задать
небольшие значения этих констант. При вычислении количества положительных
элементов для каждой строки выполняются однотипные действия:
обнуление счетчика n, просмотр каждого элемента строки и сравнение его с
нулем, при необходимости увеличение счетчика на единицу, а после окончания
вычислений - вывод результирующего значения счетчика.
Рекомендуется после ввода матрицы выполнять её контрольный вывод на
экран.
#include <iostream>
#include<clocale>namespace std;()
{(LC_CTYPE, "");, ncol;<< "Введите N row: "
<<endl;>>nrow;<< "Введите M col:"
<<endl;>>ncol;** a = new int*[nrow];i, j;(i = 0; i <nrow; i++)
{[i] = new int[ncol];(j = 0; j <ncol; j++)
{[i][j] = rand() % 201 - 100; // -100 : 100<<
a[i][j] << " ";
}<<endl;
}n; // счетчик положительных элементов
float s = 0; // суммаэлементов(i = 0; i <nrow; i++)
{= 0;(j = 0; j<ncol; j++)
{+= a[i][j];(a[i][j]>0) n++;
}<< "B строке " << i << "
кол-во положительных элементов: "
<< n <<endl;
}/= nrow*ncol; // вычисление среднего значение<<
"Среднее арифметическое равно: " << s <<endl;
system("pause");(int i = 0; i <nrow;
i++)a[i];[]a;
return;
}
Рисунок 2.1 - Реакция ЭВМ
На рис. 2.1 приведен пример демонстрации работы двухмерного динамического
массива с использованием операции new.
В нашем примере для матрицы выделяется динамическая память, которая
задана именованными константами Nrow=5 и Mcol=4.
Затем осуществляется ввод элементов матрицы случайным образом, после чего
выполняется обработка массива, и вывод результата на экран, в нашем случае это
вывод количества положительных элементов в каждой строке и среднее
арифметическое ее элементов.
Программа была протестирована и проверен результат работы с помощью
математических вычислений вручную.
ВЫВОДЫ
При разработке данного курсового проекта были достигнуты все
запланированные цели и задачи: систематизация знаний по теме, получили практические
навыки выделения, перераспределения и освобождение памяти при работе с
динамическими массивами, а также разработан программный продукт, который
демонстрирует работу динамического массива.
Преимуществом разработки данной программы с использованием динамического
распределения памяти в отличие от статического является:
экономное использование памяти;
возможность изменять размер массива во время работы программы.
Недостатком данного программного продукта является то, что в программе не
обеспечивается автоматическая оптимизация кода, а также выдача транслятором
диагностических сообщений. Для полноценной реализации динамических массивов
требуется, чтобы их поддержка была включена в трансляторы языка C++.
Данный курсовой проект можно использовать как учебный пример по
дисциплине «Программирование» для наглядной демонстрации работы с динамическими
массивами. Размер программного продукта 9,03 МБ.
СПИСОК ИСПОЛЬЗУЕМЫХ ИСТОЧНИКОВ
1. Архангельский
А.Я. Программирование в С++ Builder 6/А.Я. Архангельский-М.:БИНОМ,2002.-1152с.
2. Вирт
Н. Алгоритмы и структуры данных.: Пер. С англ. - М.: Мир, 2001.
. Подбельский
В.В. Язык Си++. - М.: Финансы и статистика, 2004.
. Седжвик
Р. Фундаментальные алгоритмы на C++. Части 1-4. Анализ. Структуры данных.
Сортировка. Поиск. 2001.
. Подбельский
В.В., Программирование на языке Си++:/ Учеб. пособие для студентов вузов,
обучающихся по специальностям “Приклад. Математика “ и “Вычисл. машины,
комплексы, системы и сети”/В.Подбельский.-5 е изд.-М.: Финансы и статистика,
2003.-560с.
. Страуструп,
Бьерн. Язык программирования С++/Бьерн Страуструп; пер.с английского
С.Анисимова, М.Кононова под ред. Ф.Андреева, А.Ушакова.- Спец.
издание.-М.:Бином,2004.-1038с
. Керниган,
Бриан В., Ритчи, ДеннисМ. Язык программирования С/ Пер. с англ. изд., перераб.
И доп.-М.:Финансы и статистика, 1992.-271 с.
. Топп
У., Форд У. Структуры данных в С++. 1999.
. Хэзфилд
Р., Кирби Л. Искусство программирования на C. Фундаментальные алгоритмы,
структуры данных и примеры приложений. 2001.
. Фридман
А.Л. Язык программирования Си++. Курс лекций: [учеб. пособие для вузов]/А.Л.
Фридман - М.: Интернет-Университет Информационных технологий, 2004.