---Лекция 7.2
Замечание о константных указателях
int x=10; int *p=&x; int &q=p;
const int * u;//-указатель на константу целого типа
int * const w=&x;// указатель-константа на целую переменную
const int n=200;
const int * const r=&n;//указатель-конст. на конс-ту цел. типа
10.Сложные типы.
Данные сложного типа строятся путём объединения данных простых и ранее построенных сложных типов в некоторую совокупность- её называют структура данных.
Смысл объединения – иметь возможность обращаться (по имени ) сразу ко всей структуре.
При построении совокупности данных обычно приходится решать 3 вопроса:
1)фиксируется или нет количество элементов в совокупности; если фиксируется, то говорят структура статическая, место под неё может быть выделено в указанном объеме и сохраняется в области видимости;
2)элементы в совокупности однотипные или необязательно однотипные, т.е. тип элементов можно указать один раз или указывать для каждого элемента.
3)элементы в совокупности упорядочены или нет. Упорядочивание бывает
= с помощью системы индексов, например: b0, b1, . . .b15
A1,1 A1,2 … A1,m
…………………………………………….
An,1 An,2 … An,m
= с помощью перечисления:
Тип имя;
……………….
Тип имя;
Если в совокупности элементы упорядочены, то в ней есть операция доступа к элементу.
Массив – совокупность фиксированного количества однотипных элементов, которые упорядочены с помощью индексов.
Описание массива (одномерного) :
Тип имя [размер];
Тип- тип каждого элемента массива; имя – имя всей совокупности; размер – целая константа или константное выражение с целым положительным значением, указывает количество злементов в массиве. Тип и размер определяют количество памяти , необходимое для размещения массива. Элементы массива занимают последовательные области памяти.
Пример: int a[5]; // в массиве 5 элементов типа int.
Возможна инициализация массива:
double b[4] = {3.5,-1.2,5.1}; // инициализируются первые 3 элемента, а те, которым не задано значение , обнуляются.
При инициализации размер массива может не задаваться, тогда место выделяется по количеству инициализирующих значений.
int x[ ]={3,2,1}; //здесь объявлен массив из 3-х элементов.
==!!! В С++ у массива нет типа, с этим связаны некоторые особенности использования массива.
Рекомендуется при работе с массивом:
==Описать именованную константу, задающую размер массива (м.б. глобальную):
const int n=100;
==затем описать массив;везде, где требуется его размер, использовать n.
int a [n ]; // в дальнейшем, если окажется , что n надо изменить, исправить можно только строку с описанием именованной константы и перетранслировать программу.
Доступ к элементу массива выполняется с помощью такой конструкции
Имя_массива [индексное_выражение] это переменная с индексом. её тип –тип элемента массива
Здесь 0<= индексное_выражение<=n-1; инд. выраж.-целого типа
Например: a[0] a[1] a[2] . . a[i] . . a[n-1]-элементы а;
Тип этих переменных с индексом есть int, использование a[i], (например, в цикле) позволяет перебирать элементы массива с изменением i от 0 до n-1.
Замечание!!! Контроль за выходом значения индекса за пределы [0,n-1] не выполняется, поэтому использование индекса за пределами может привести к какой-нибудь непредсказуемой ситуации.
==Где можно использовать массив и его элементы?
Элемент массива можно использовать везде, где можно использовать переменную типа этого элемента: для простого типа(только они были рассмотрены ранее к этому моменту) в выражениях, ему можно присвоить значение в операторе присваивания, ему можно ввести значение (сin>>a[i]; ), можно вывести на экран его значение (cout<<a[i];)
Массив может быть передан в функцию, но присвоить его элементам что-то, ввести его или вывести можно только поэлементно!
Замечание. На [] нужно смотреть как на операцию определения адреса элемента массива, а затем доступа к элементу по найденному адресу. В случае многократного использования одного и того же элемента лучше присвоить его значение переменной, а затем её использовать.
if (a[i]>max) max=a[i];//дважды выполняется a[i]
Лучше: r=a[i]; if(r>max) max=r;
Связь массива и указателя Одна из особенностей С++ состоит в том, что идентификатор массива является константным указателем на его нулевой элемент.
int b[10]; имя b- константный указатель на b[0],т.е. b=&b[0];
Учитывая, что b-это указатель на начало массива, а элементы его располагаются последовательно в памяти друг за другом в порядке возрастания индексов, то адрес i-го элемента есть b+i, а сам элемент *(b+i), т.е. *(b+i)эквивалентно b[i].
Пример: во все элементы массива b поставить 1.
double b[n];
for(int i=0;i<n;++i) {*(b+i)=1;cout<<b[i]<<" ";}//b[i]=1;
//1 1 1 1 1 1 1 1 1 1
Можно описать указатель p и ему присвоить значение константного указателя b и изменять p:
double *p=b; for (int i=0;i<n;++i){*p++=2;cout<<b[i]<<" ";}
//2 2 2 2 2 2 2 2 2 2
!!! нельзя b++; *p++=2;вып-ся так *p=2; p++;
Работа с массивом переменной длины.
1 способ. Описывается массив некоторой максимальной длины n, а используется его часть- к элементов: от 0-го до (к-1)-го, причем к должно быть не больше n.
Передача массива в функцию в качестве параметра.
Массивы могут быть параметрами функций, функции могут возвращать массив, используя в качестве типа результата функции, указатель на массив, но не сам массив!
При передаче массива в функцию(через параметр,например,double a[n]) передаётся указатель, т.е. массив всегда передаётся по адресу(не требуется копировать его элементы в память стека!),при этом информация о размере массива n теряется и должна передаваться дополнительно. Так как передаётся указатель, то фактический параметр- массив может изменяться в функции (не имя этого фактического массива, а его элементы! )
Пример программы для ввода массива нужной длины, причём ввод массива и его длины выполнит функция , а главная функция к ней обращается.
#include <iostream>
using namespace std;
void rdm(int& ,double []);
void wrm(int ,double []);
== const int n=100;
int main()
{
double a[n];
int ma;
rdm(ma,a); wrm(ma,a); //фактический параметр – имя массива a
return 0;
}
== //ввод массива нужной длины
void rdm(int& k, double a[])
{// чтение массива
cout<<"\n kol-vo el-ov <= "<<n<<" ";
cin>>k;
cout<<"el-ti cherez probel:\n";
for(int i=0; i<k; i++) cin>>a[i];
}
==//выдача массива
void wrm(int k,double a[])
{cout<<’\n’;
for (int i=0; i<k; i++)
{cout<<a[i]<<" ";
If((i+1)%10==0) cout<<’\n’;//по 10 чисел в строке
}
cout<<’\n’;
}
Пример 2.Найти скалярное произведение двух векторов.
#include <iostream>
using namespace std;
void rdm(int& ,double []);
double scalpr(int ,double *,double *);
void wrm(int ,double []);
const int n=10;
int main()
{
double a[n],b[n];
int ma,mb;
rdm(ma,a);wrm(ma,a);
rdm(mb,b);wrm(mb,b);
if (ma!=mb){cout<<"razmeri raznie"<<endl;return 1;}
cout<<"scalpr="<< scalpr(ma,a,b) <<'\n';
return 0;
}
. . . . . . . . . .
//Скалярное произведение
//двух векторов x и y
double scalpr(int k,double *x,double *y)
// double scalpr(int k,double x[],double y[])
{double a=0;
for (int i=0; i<k; i++)
a+=*(x+i)**(y+i);
//a+=x[i]*y[i];
return a;
}
2 способ работы с массивом переменной длины: массив с нужным количеством элементов создают как динамический, т.е. во время выполнения (в динамике )программы.
В С++ есть универсальная операция, которая получает память у операционной системы и возвращает указатель на начало выделенного блока. Если память не выделена, указатель получает значение 0.
Тип * имя_ указателя = new тип;
float * u=new float;//выделено место под значение типа float и на него указывает указатель u.
Обращение к выделенной памяти:
Переменная *u явл. переменной типа float.
Можно инициализировать память float * u=new float(1.5);
Можно выделить место под массив нужного размера:
int k; сin>>k;
double * p;
p=new double [k]; // double * p=new double [k];
В динамической памяти выделяется непрерывная область для размещения к переменных типа double.
!!! динамический массив не обнуляется;
Нельзя его инициализировать при создании.
Преимущества дин. массива очевидны:создаётся во время выполнения программы, нужного размера.
Обращение к элементам массива такое же, как к элементам статического массива, но только с использованием указателя: p[i] или *(p+i).
Память под динамический массив должна освобождаться. Для этого используется операция delete:
Для переменной: delete u;
Для массива: delete [] p;
Дин. память освобождается автоматически после окончания работы программы. Освобождение памяти , когда она больше не нужна, является хорошим тоном, а иногда это требуется выполнить обязательно. Однако , если в функции описана локальная переменная- указатель и ей присвоено значение по new , то по окончании работы функции эта переменная- указатель исчезнет, а выделенная дин. память останется недоступной.
Пример 3 пример функции, которая возвращает указатель на массив
//Слияние 2-ух массивов,
//упорядоченных по неубыванию
#include <iostream>
using namespace std;
void rdm(int& ,int []);
int * slmas(int ,int [],int ,int []);
void wrm(int ,int []);
const int n=100;
int main()
{
int a[n],b[n];
int ma,mb;
rdm(ma,a);wrm(ma,a);
rdm(mb,b);wrm(mb,b);
int *t=slmas(ma,a,mb,b);
wrm(ma+mb,t);
delete []t;
return 0;
}
//ввод массива нужной длины
void rdm(int& k, int a[])
{// чтение массива
cout<<"\n kol-vo el-ov <= "<<n<<" ";
cin>>k;
cout<<"el-ti cherez probel:\n";
for(int i=0;i<k;i++)
cin>>a[i];
}
//выдача массива
void wrm(int k,int a[])
{
cout<<"\n";
for(int i=0;i<k;i++)
cout<<a[i]<<" ";;
cout<<"\n";
}