Над указателями можно производить следующие действия:
· складывать указатель и целое число;
· вычитать из указателя целое число;
· вычитать из указателя указатель – получается целое число.
При этом прибавляемое/вычитаемое или полученное целое число обозначает не количество байт, а количество элементов того типа, на который указывает указатель, т.е. это число умножается или делится на размер типа.
Вычитание указателей друг из друга определено только в том случае, если оба указателя указывают на элементы одного и того же массива (хотя язык не позволяет быстро проверить, так ли это). Результатом вычитания одного указателя из другого будет количество элементов массива (целое число) между этими указателями. В результате прибавления целого числа к указателю и вычитания целого числа из указателя получается новый указатель. Если полученный таким образом указатель не указывает на элемент того же массива (или на элемент, следующий за последним), что и исходный указатель, то результат его использования не определён.
Указатели можно использовать для обработки массивов.
int a[100], n, *end, *p;
end = a + n;
// n - количество элементов массива a. Имя массива является адресом его начала. // Таким образом, end - адрес элемента, находящегося после последнего элемента массива.
for (p = a; p < end; p++)
printf("%4d", *p);
Из того, что можно вычитать из указателя целое число, следует, что возможно использование отрицательных чисел в операции [ ].
int a[N]; int *endA = a + N - 1, i; for (i = 0; i < N; i++) printf("%4d", endA[-i]);
Ссылка представляет собой синоним имени, указанного при инициализации ссылки. Ссылку можно рассматривать как указатель, который всегда разыменовывается. Формат объявления ссылки:
тип &имя;
где тип — это тип величины, на которую указывает ссылка, & — оператор ссылки, означающий, что следующее за ним имя является именем переменной ссылочного типа, например:
int kol;int& pal = kol; // ссылка pal — альтернативное имя для kolconst char& CR = '\n'; // ссылка на константу
· Переменная-ссылка должна явно инициализироваться при ее описании, кроме случаев, когда она является параметром функции, описана как extern или ссылается на поле данных класса.
· После инициализации ссылке не может быть присвоена другая переменная.
· Тип ссылки должен совпадать с типом величины, на которую она ссылается.
· Не разрешается определять указатели на ссылки, создавать массивы ссылок и ссылки на ссылки.
Ссылки применяются чаще всего в качестве параметров функций и типов возвращаемых функциями значений. Ссылки позволяют использовать в функциях переменные, передаваемые по адресу, без операции разадресации, что улучшает читаемость программы.
Операция над ссылкой приводит к изменению величины, на которую она ссылается.
Вопросы:
1. Общие вопросы.
2. Указатели и адреса.
3. Указатели и аргументы функций.
4. Указатели и массивы.
5. Адресная арифметика или операции с указателями.
6. Многомерные массивы.
7. Указатели и многомерные массивы.
Изложение вопросов.
1. Язык С — небольшой, но мощный, удобный, гибкий, и главное, практичный язык программирования. Основу его практической мощи составляет институт указателей, без пользования которых С-программа — блеклая и неэффективная. Программист, в совершенстве овладевший использованием указателей, можно с уверенностью сказать, стал профессионалом высочайшего класса.
2. Рассмотрим упрощенную схему организации оперативной памяти машины. Память типичной машины представляет собой массив последовательно пронумерованных и проадресованных ячеек, с которыми с которыми можно работать по отдельности или связными кусками. Применительно к любой 16-разрядной и 32-разрядной машине верны следующие утверждения:
— один байт может хранить значение типа char; — двухбайтовая ячейка может рассматриваться как цело типа short; — четырехбайтовая — как целое типа long.
Указатель — это группа ячеек, в которых может храниться адрес. Например, ch имеет тип char, p — указатель, ссылающийся на ch, то графически ситуация выглядет следующим образом: В С имеется унарная операция & — операция получения адреса. Запись вида pch=&ch; присваивает адрес ячейки, где находится ch переменной pch, которая является переменной указателем. В этом случае принято говорить: pch указывает на ch, или, что одно и то же, pch ссылается на ch. Правило: операция получения адреса & применяется только к объектам, расположенным в памяти: к переменным и элементам массивов. Ее операндом не может быть ни выражение, ни константа, ни регистровая переменная. Приведем следующие записи:
int x=1, y=2, z[10]; /* описание переменных и массива */ int *pint; /* описание указателя на тип int */ pint=&x; /* теперь pint указывает на х*/ y=*pint; /* y теперь равен х, то есть 1 */ *pint=0; /* х теперь обнуляется */ pint=&z[0]; /* pint теперь указывает на z[0] */
Таким образом, описание переменной как указателя, аналогично описанию обычной переменной с добавлением символа (*) перед именем переменной. Приведем примеры правильного описания указателей:
int *pint; char *pchar; float *pfloat, *ptf; double *pdb.
После описания указателю разрешено ссылаться только на объекты заданного типа. Исключение составляет указатель на тип void, который может ссылаться на объекты любого типа. Но к такому указателю нельзя применять оператор разадресации. 3. Поскольку функции в С в качестве своих аргументов получают значения параметров, прямой возможности, находясь в вызванной функции, изменить переменную вызывающей функции нет. Чтобы получить желаемый эффект, надо вызываемой программе передать адреса тех переменных, которые должны быть изменены, а при описании функции описать формальные параметры как указатели на переменные. Рассмотрим следующий классический пример — программу, которая осуществляет обмен значениями двух переменных:
#include<stdio.h> void main (void); void change (int *, int *); void main (void) { int x=5, y=10; printf("Сейчас х=%d и y=%d. \n", x, y); change(&x, &y); /* передача адресов функции */ printf("Теперь х=%d и y=%d. \n", x, y); } void change (int *u, int *v) { int temp; temp=*u; *u=*v; *v=temp; }
Результат выполнения программы следующий: Сейчас x=5, y=10. Теперь x=10, y=5. 4. В языке С между массивами и указателями существует очень тесная связь, поэтому при изучении обычно их рассматривают вместе. Вначале рассмотрим основные моменты и положения, касающиеся массивов.
Массив — есть группа элементов одного типа. Массив должен быть описан перед его использованием в программе. Для создания массива компилятору необходимо знать: — тип данных; — класс памяти; — размерность. Приведем примеры описаний массивов:
#include<stdio.h> int temp[365]; /* внешний массив из 365 целых чисел*/ void main (void) { float rain[365]; /*автоматический массив из 365 вещественных чисел */ static char code[12]; /* статический массив из 12 символов */ extern temp[]; /* внешний массив, размер указан выше */ … }
Массивы могут быть одномерными и многомерными. Многомерные массивы рассмотрим позже. Массивы перед их использованием могут быть проинициализированы. Но такая возможность существует только для статической и внешней памяти, то есть внешние и статические массивы можно инициализировать, а автоматические и регистровые массивы инициализировать нельзя. Пример: static int days[]= {31,28,31,30,31,30,31,31,30,31,30,31}; Правила: 1. Если ничего не засылать в массив перед началом работы с ним, то внешние и статические массивы инициализируются 0, а автоматические массивы содержат какой-то “мусор”, оставшийся в этой части памяти. 2. Если используются пустые скобки для инициализации массива, то компилятор сам определяет количество элементов в списке и выделяет для него память нужного размера. Обозначение массива представляет собой просто скрытую форму использования указателей. Имя массива определяет адрес его первого элемента, то есть если mas[] — есть массив, то mas = = &mas[0]. Рассмотрим пример:
Результат: Указатели +0: 56014 86026 Указатели +1: 56016 86030 Указатели +2: 56018 86034 Указатели +3: 56020 86038 Первая строка содержит начальные адреса двух массивов. Следующая строка результат прибавления единицы к адресу и т.д. Вопрос: почему получается 56014+1=56016; 86026+1=86030. Обычно в вычислительной системе единицей адресации является байт; однако тип int использует два байта, тип float — четыре. При прибавлении единицы к указателю компилятор генерирует код: добавить единицу памяти. Для массивов это означает, что осуществляется переход к адресу следующего элемента, а не следующего байта. Отсюда вывод: при описании указателя обязательно указывается тип объекта, на который ссылается указатель, ибо машина должна знать, сколько байтов потребуется для запоминания объекта. Запишем следующие соотношения: dates+2 ó &dates[2]; — один и тот же адрес; *(dates+2) ó dates[2]; — одно и то же значение. Эти соотношения суммируют теснейшую связь между массивами и указателями. Они показывают, что можно использовать указатель для определения отдельного элемента массива, а также для получения его значения. Замечание: Компилятор с языка С построен таким образом, что превращает обозначение массива в указатели, поэтому метод указателей более предпочтителен. Напишем функцию, использующую массивы, а затем перепишем ее, применяя указатели. Рассмотрим простую функцию, которая находит среднее значение массива целых чисел. На входе в функцию имеем имя массива и количество элементов. На выходе получаем среднее значение, которое передаем при помощи оператора return.
int mean (int array[], int n) { int index; long sum; if (n>0) { for(index=0, sum=0; index<n; index++ ) sum+=array[index]; return ((int)(sum/n)); } else { printf("Нет массива. \n"); return (0); } }
Перепишем эту функцию, применяя указатели:
int mean (int *parray, int n)
{
int index;
long sum;
if (n>0) { for(index=0, sum=0; index<n; index++ )
sum+=*(array+index);
return ((int)(sum/n)); } else
{ printf("Нет массива. \n"); return (0); } }
Вызовы функции mean() будут выглядеть следующим образом: 1) mean(numbs, size) потому, что имя массива является указателем 2) mean(numbs, size) на его первый элемент Программа демонстрирует пять основных операций, которые можно выполнить над указателями: 1. Присваивание — указателю можно присвоить адрес. 2. Определение значения — операция * выдает значение, хранящееся в ячейке, на которую указывает указатель. 3. Получение адреса указателя — подобно любым переменным указатель имеет адрес и значение. 4. Увеличение указателя (уменьшение) — при увеличении указателя происходит его перемещение на единицу памяти (в данном случае на следующий элемент массива). При увеличении (уменьшении) указателя следует соблюдать осторожность. Компьютер не следит, ссылается ли еще указатель на объект или нет, и можно выйти за границы объекта и получить уже совершенно другую информацию. Замечание: Следует различать переменную-указатель и константу-указатель. Например: mas[12] — массив; то mas — указатель-константа на первый элемент, ибо в процессе работы программы не изменяется. Аналогично запись &mas[0] — также константа-указатель. Если имеем запись: int *ptmas; то ptmas указатель-переменная. Операции увеличения и уменьшения можно применять только к указателю-переменой, но не к указателю-константе. Приведем следующие примеры: Правильно Неправильно ptr1++ mas++ x++ 3++ ptr2=ptr1+2 ptr2=mas++ ptr2=mas+1 x=y+3++ 5. Разность между указателями — эта операция возможна. Обычно эта операция выполняется для указателей, ссылающихся на элементы одного и того же массива, чтобы определить на каком расстоянии друг от друга находятся элементы. 6. Массивы, размерность которых больше 1, называются многомерными. Например: mas[6] — одномерный массив; mas[2][3] — двумерный массив; mas[2][3][4] — трехмерный массив; Многомерные массивы удобно рассматривать как массив массивов. Например: static float rain[5][12]; есть массив, состоящий из 5 элементов, каждый из которых есть массив из 12 элементов. Можно трактовать данную запись в виде двумерного массива, состоящего из 5 строк и 12 столбцов. 7. Вопрос: как создать указатели для многомерных массивов? Для ответа на этот вопрос рассмотрим несколько примеров: int mas[4][2]; /* массив типа int из 4 строк и 2 столбцов */ int *ptmas; ptmas=mas; — на что указывает ptmas? ptmas указывает на первый столбец первой строки: ptmas ó mas ó &mas[0][0]; На что будет указывать ptmas+1? В связи с тем, что элементы двумерного массива располагаются в памяти построчно, то можно записать:
ptmas ó &mas[0][0]; ptmas +1 ó &mas[0][1]; ptmas +2 ó &mas[1][0]; ptmas +3 ó &mas[1][1]; ... ptmas +5 ó &mas[2][1]; ... ptmas +7 ó &mas[3][1].
Рассмотрим массив mas как массив массивов, то есть это массив из четырех строк, каждая из которых является массивом из двух элементов. Имя первой строки mas[0], четвертой — mas[3]. Мы знаем, что имя массива — есть указатель на его первый элемент, тогда можем записать следующие соотношения: mas[0] ó &mas[0][0] mas[1] ó &mas[1][0] mas[2] ó &mas[2][0] mas[3] ó &mas[3][0] Данные выводы позволяют использовать функцию, предназначенного для одномерного массива, для работы с двумерным массивом. В качестве примера рассмотрим нашу программу нахождения одного значения элементов массива, но в качестве массива будем использовать двумерный массив.
#include<stdio.h> void main (void); int mean (int, int); void main (void) { static int mas[3][4] = { {2,4,6,8}, {100,200,300,400}, {10,40,60,90} }; int row; for(row=0; row<3; row++) printf("Среднее строки %d равно %d \n", row, mean(mas[row],4)); } int mean (int array[], int n) { int index; long sum; if (n>0) { for(index=0, sum=0; index<n; index++) sum+=(long)array[index]; return ((int)(sum/n)); } else { printf("Нет массива.\n"); return (0); } }
Результаты работы программы: Среднее строки 0 равно 5. Среднее строки 1 равно 250. Среднее строки 2 равно 50. Предположим, программист хочет иметь функцию, работающую с двумерным массивом не по частям, как в предшествующем примере, а со всем целиком. Пусть функция main() выглядит следующим образом:
void main (void) { static int mas[3][4] = { {2,4,6,8}, {100,200,300,400}, {10,40,60,90} }; funk(mas); }
Здесь функция funk() использует в качестве аргумента mas, являющимся указателем на весь массив. Как написать правильно заголовок в объявлении функции funk(). Возможны следующие варианты: 1) funk (int mas[]) 2) funk (int mas[][])
3) funk (int mas[][4]) В первом случае рассматривается mas как одномерный массив, состоящий из 12 элементов. Информация о расчленении на строки отсутствует. Второй случай ошибочный. Указывается, что mas двумерный массив, но нигде не говорится из чего он состоит. Третий случай приемлем. Он сообщает компилятору, что массив следует разбить на строки по четыре столбца.
Тема № 8. Указатели и массивы в языке С.
Вопросы:
1. Общие вопросы.
2. Указатели и адреса.
3. Указатели и аргументы функций.
4. Указатели и массивы.
5. Адресная арифметика или операции с указателями.
6. Многомерные массивы.
7. Указатели и многомерные массивы.
Изложение вопросов.
1. Язык С — небольшой, но мощный, удобный, гибкий, и главное, практичный язык программирования. Основу его практической мощи составляет институт указателей, без пользования которых С-программа — блеклая и неэффективная. Программист, в совершенстве овладевший использованием указателей, можно с уверенностью сказать, стал профессионалом высочайшего класса.
2. Рассмотрим упрощенную схему организации оперативной памяти машины. Память типичной машины представляет собой массив последовательно пронумерованных и проадресованных ячеек, с которыми с которыми можно работать по отдельности или связными кусками. Применительно к любой 16-разрядной и 32-разрядной машине верны следующие утверждения:
— один байт может хранить значение типа char;
— двухбайтовая ячейка может рассматриваться как цело типа short;
— четырехбайтовая — как целое типа long.
Указатель — это группа ячеек, в которых может храниться адрес. Например, ch имеет тип char, p — указатель, ссылающийся на ch, то графически ситуация выглядет следующим образом:
В С имеется унарная операция & — операция получения адреса. Запись вида pch=&ch; присваивает адрес ячейки, где находится ch переменной pch, которая является переменной указателем. В этом случае принято говорить: pch указывает на ch, или, что одно и то же, pch ссылается на ch.
Правило: операция получения адреса & применяется только к объектам, расположенным в памяти: к переменным и элементам массивов. Ее операндом не может быть ни выражение, ни константа, ни регистровая переменная.
Приведем следующие записи:
int x=1, y=2, z[10]; /* описание переменных и массива */
int *pint; /* описание указателя на тип int */
pint=&x; /* теперь pint указывает на х*/
y=*pint; /* y теперь равен х, то есть 1 */
*pint=0; /* х теперь обнуляется */
pint=&z[0]; /* pint теперь указывает на z[0] */
Таким образом, описание переменной как указателя, аналогично описанию обычной переменной с добавлением символа (*) перед именем переменной. Приведем примеры правильного описания указателей:
int *pint;
char *pchar;
float *pfloat, *ptf;
double *pdb.
После описания указателю разрешено ссылаться только на объекты заданного типа. Исключение составляет указатель на тип void, который может ссылаться на объекты любого типа. Но к такому указателю нельзя применять оператор разадресации.
3. Поскольку функции в С в качестве своих аргументов получают значения параметров, прямой возможности, находясь в вызванной функции, изменить переменную вызывающей функции нет.
Чтобы получить желаемый эффект, надо вызываемой программе передать адреса тех переменных, которые должны быть изменены, а при описании функции описать формальные параметры как указатели на переменные.
Рассмотрим следующий классический пример — программу, которая осуществляет обмен значениями двух переменных:
#include<stdio.h>
void main (void);
void change (int *, int *);
void main (void)
{
int x=5, y=10;
printf("Сейчас х=%d и y=%d. \n", x, y);
change(&x, &y); /* передача адресов функции */
printf("Теперь х=%d и y=%d. \n", x, y);
}
void change (int *u, int *v)
{
int temp;
temp=*u;
*u=*v;
*v=temp;
}
Результат выполнения программы следующий:
Сейчас x=5, y=10.
Теперь x=10, y=5.
4. В языке С между массивами и указателями существует очень тесная связь, поэтому при изучении обычно их рассматривают вместе. Вначале рассмотрим основные моменты и положения, касающиеся массивов.
Массив — есть группа элементов одного типа. Массив должен быть описан перед его использованием в программе. Для создания массива компилятору необходимо знать:
— тип данных;
— класс памяти;
— размерность.
Приведем примеры описаний массивов:
#include<stdio.h>
int temp[365]; /* внешний массив из 365 целых чисел*/
void main (void)
{
float rain[365]; /*автоматический массив из 365 вещественных чисел */
static char code[12]; /* статический массив из 12 символов */
extern temp[]; /* внешний массив, размер указан выше */
…
}
Массивы могут быть одномерными и многомерными. Многомерные массивы рассмотрим позже.
Массивы перед их использованием могут быть проинициализированы. Но такая возможность существует только для статической и внешней памяти, то есть внешние и статические массивы можно инициализировать, а автоматические и регистровые массивы инициализировать нельзя. Пример:
static int days[]= {31,28,31,30,31,30,31,31,30,31,30,31};
Правила:
1. Если ничего не засылать в массив перед началом работы с ним, то внешние и статические массивы инициализируются 0, а автоматические массивы содержат какой-то “мусор”, оставшийся в этой части памяти.
2. Если используются пустые скобки для инициализации массива, то компилятор сам определяет количество элементов в списке и выделяет для него память нужного размера.
Обозначение массива представляет собой просто скрытую форму использования указателей. Имя массива определяет адрес его первого элемента, то есть если mas[] — есть массив, то mas = = &mas[0]. Рассмотрим пример:
void main (void)
{
int dates[4], *pti, index;
float bills[4], *ptf;
pti=dates; /* присвоить адрес массива указателю */
Первая строка содержит начальные адреса двух массивов. Следующая строка результат прибавления единицы к адресу и т.д. Вопрос: почему получается 56014+1=56016; 86026+1=86030.
Обычно в вычислительной системе единицей адресации является байт; однако тип int использует два байта, тип float — четыре. При прибавлении единицы к указателю компилятор генерирует код: добавить единицу памяти. Для массивов это означает, что осуществляется переход к адресу следующего элемента, а не следующего байта. Отсюда вывод: при описании указателя обязательно указывается тип объекта, на который ссылается указатель, ибо машина должна знать, сколько байтов потребуется для запоминания объекта. Запишем следующие соотношения:
dates+2 ó &dates[2]; — один и тот же адрес;
*(dates+2) ó dates[2]; — одно и то же значение.
Эти соотношения суммируют теснейшую связь между массивами и указателями. Они показывают, что можно использовать указатель для определения отдельного элемента массива, а также для получения его значения.
Замечание: Компилятор с языка С построен таким образом, что превращает обозначение массива в указатели, поэтому метод указателей более предпочтителен.
Напишем функцию, использующую массивы, а затем перепишем ее, применяя указатели. Рассмотрим простую функцию, которая находит среднее значение массива целых чисел. На входе в функцию имеем имя массива и количество элементов. На выходе получаем среднее значение, которое передаем при помощи оператора return.
int mean (int array[], int n)
{
int index;
long sum;
if (n>0)
{
for(index=0, sum=0; index<n; index++ )
sum+=array[index];
return ((int)(sum/n));
}
else
{
printf("Нет массива. \n");
return (0);
}
}
Перепишем эту функцию, применяя указатели:
int mean (int *parray, int n)
{
int index;
long sum;
if (n>0)
{
for(index=0, sum=0; index<n; index++ )
sum+=*(array+index);
return ((int)(sum/n));
}
else
{
printf("Нет массива. \n");
return (0);
}
}
Вызовы функции mean() будут выглядеть следующим образом:
1) mean(numbs, size) потому, что имя массива является указателем
2) mean(numbs, size) на его первый элемент
Программа демонстрирует пять основных операций, которые можно выполнить над указателями:
1. Присваивание — указателю можно присвоить адрес.
2. Определение значения — операция * выдает значение, хранящееся в ячейке, на которую указывает указатель.
3. Получение адреса указателя — подобно любым переменным указатель имеет адрес и значение.
4. Увеличение указателя (уменьшение) — при увеличении указателя происходит его перемещение на единицу памяти (в данном случае на следующий элемент массива). При увеличении (уменьшении) указателя следует соблюдать осторожность. Компьютер не следит, ссылается ли еще указатель на объект или нет, и можно выйти за границы объекта и получить уже совершенно другую информацию.
Замечание: Следует различать переменную-указатель и константу-указатель. Например: mas[12] — массив; то mas — указатель-константа на первый элемент, ибо в процессе работы программы не изменяется. Аналогично запись &mas[0] — также константа-указатель. Если имеем запись: int *ptmas; то ptmas указатель-переменная.
Операции увеличения и уменьшения можно применять только к указателю-переменой, но не к указателю-константе. Приведем следующие примеры:
Правильно Неправильно
ptr1++ mas++
x++ 3++
ptr2=ptr1+2 ptr2=mas++
ptr2=mas+1 x=y+3++
5. Разность между указателями — эта операция возможна. Обычно эта операция выполняется для указателей, ссылающихся на элементы одного и того же массива, чтобы определить на каком расстоянии друг от друга находятся элементы.
6. Массивы, размерность которых больше 1, называются многомерными. Например: mas[6] — одномерный массив;
mas[2][3] — двумерный массив;
mas[2][3][4] — трехмерный массив;
Многомерные массивы удобно рассматривать как массив массивов. Например: static float rain[5][12]; есть массив, состоящий из 5 элементов, каждый из которых есть массив из 12 элементов. Можно трактовать данную запись в виде двумерного массива, состоящего из 5 строк и 12 столбцов.
7. Вопрос: как создать указатели для многомерных массивов? Для ответа на этот вопрос рассмотрим несколько примеров:
int mas[4][2]; /* массив типа int из 4 строк и 2 столбцов */
int *ptmas;
ptmas=mas; — на что указывает ptmas?
ptmas указывает на первый столбец первой строки:
ptmas ó mas ó &mas[0][0];
На что будет указывать ptmas+1?
В связи с тем, что элементы двумерного массива располагаются в памяти построчно, то можно записать:
ptmas ó &mas[0][0];
ptmas +1 ó &mas[0][1];
ptmas +2 ó &mas[1][0];
ptmas +3 ó &mas[1][1];
...
ptmas +5 ó &mas[2][1];
...
ptmas +7 ó &mas[3][1].
Рассмотрим массив mas как массив массивов, то есть это массив из четырех строк, каждая из которых является массивом из двух элементов. Имя первой строки mas[0], четвертой — mas[3]. Мы знаем, что имя массива — есть указатель на его первый элемент, тогда можем записать следующие соотношения:
mas[0] ó &mas[0][0]
mas[1] ó &mas[1][0]
mas[2] ó &mas[2][0]
mas[3] ó &mas[3][0]
Данные выводы позволяют использовать функцию, предназначенного для одномерного массива, для работы с двумерным массивом. В качестве примера рассмотрим нашу программу нахождения одного значения элементов массива, но в качестве массива будем использовать двумерный массив.
#include<stdio.h>
void main (void);
int mean (int, int);
void main (void)
{
static int mas[3][4] = {
{2,4,6,8},
{100,200,300,400},
{10,40,60,90}
};
int row;
for(row=0; row<3; row++)
printf("Среднее строки %d равно %d \n", row, mean(mas[row],4));
}
int mean (int array[], int n)
{
int index;
long sum;
if (n>0)
{
for(index=0, sum=0; index<n; index++)
sum+=(long)array[index];
return ((int)(sum/n));
}
else
{
printf("Нет массива.\n");
return (0);
}
}
Результаты работы программы:
Среднее строки 0 равно 5.
Среднее строки 1 равно 250.
Среднее строки 2 равно 50.
Предположим, программист хочет иметь функцию, работающую с двумерным массивом не по частям, как в предшествующем примере, а со всем целиком. Пусть функция main() выглядит следующим образом:
void main (void)
{
static int mas[3][4] = {
{2,4,6,8},
{100,200,300,400},
{10,40,60,90}
};
funk(mas);
}
Здесь функция funk() использует в качестве аргумента mas, являющимся указателем на весь массив. Как написать правильно заголовок в объявлении функции funk(). Возможны следующие варианты:
1) funk (int mas[])
2) funk (int mas[][])
3) funk (int mas[][4])
В первом случае рассматривается mas как одномерный массив, состоящий из 12 элементов. Информация о расчленении на строки отсутствует. Второй случай ошибочный. Указывается, что mas двумерный массив, но нигде не говорится из чего он состоит. Третий случай приемлем. Он сообщает компилятору, что массив следует разбить на строки по четыре столбца.
Массивы
При использовании простых переменных каждой области памяти для хранения данных соответствует свое имя. Если с группой величин одинакового типа требуется выполнять однообразные действия, им дают одно имя, а различают по порядковому номеру.
Конечная именованная последовательность однотипных величин называется массивом. Описание массива в программе отличается от описания простой переменной наличием после имени квадратных скобок, в которых задается количество элементов массива (размерность):
float a [10]; //Пример описания массива из 10 вещественных чисел
Элементы массива нумеруются с нуля. Инициализирующие значения для массивов записываются в фигурных скобках:
Размерность массива может быть задана только целой положительной константой или константным выражением.
Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма элементов массива.
Пример 1
#include <iostream>using namespace std; int main() { const int n = 10; int marks[n] = {3, 4, 5, 4, 4}; for (int i = 0, sum = 0; i < n; i++) sum += marks[i]; cout << "Сумма элементов: " << sum; return 0;}
Размерность массивов предпочтительнее задавать с помощью типизированных констант.
Динамические массивы создают с помощью операции new, при этом необходимо указать тип и размерность, например:
float *p = new float [100];
В этой строке создается переменная-указатель на float, в динамической памяти отводится непрерывная область, достаточная для размещения 100 элементов вещественного типа, и адрес ее начала записывается в указатель p. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.
Преимущество динамических массивов состоит в том, что их размерность может не быть константой.
Память, зарезервированная под динамический массив с помощью new [], должна освобождаться оператором delete [], а память, выделенная функцией malloc — посредством функции free, например:
delete [] p;free (q);
Размерность массива не указывается, но квадратные скобки обязательны.
Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор
int matr [6][8];
задает описание двумерного массива из 6 строк и 8 столбцов. В памяти такой массив располагается в последовательных ячейках построчно. Многомерные массивы размещаются так, что при переходе к следующему элементу быстрее всего изменяется последний индекс.
Для доступа к элементу многомерного массива указываются все его индексы, например, matr[i][j].
Для создания многомерного массива в динамической памяти необходимо указать все его размерности (первая из них может быть переменной):
matr = new int[a][b];
Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции delete []. Указатель на константу удалить нельзя.
Пример 2
Программа определяет в целочисленной матрице номер строки, которая содержит наибольшее количество элементов, равных нулю.
#include <stdio.h> int main() { const int nstr = 4, nstb = 5; // размерности массива int b[nstr][nstb]; int i, j; for (i = 0; i < nstr; i++) // ввод массива for (j = 0; j < nstb; j++) scanf("%d", &b[i][j]); int istr = -1, MaxKol = 0; for (i = 0; i < nstr; i++) { // просмотр массива по строкам int Kol = 0; for (j = 0; j < nstb; j++) if (b[i][j] == 0) Kol++; if (Kol > MaxKol) { istr = i; MaxKol = Kol; } } printf(" Исходный массив:\n"); for (i = 0; i < nstr; i++) { for (j = 0; j < nstb; j++) printf("%d ", b[i][j]); printf("\n"); } if (-1 == istr) printf("Нулевых элементов нет"); else printf("Номер строки: %d", istr); return 0;}