}
В рассматриваемом коде «n» элементов массива x1 копируется в массив x2. Если ввести значение «n», равное MSIZE, то весь массив x1 , будет скопировани в массив x2. Заметим, что инстукция x1 = x2; в связи с тем, x1 – константный указатель.
Пример 2. Обработка двумерных массивов.
Постановка задачи. В двумерном массиве, хранятся данные типа double. Массив состоит из nr строк и nc столбцов. Требуется вычислить две суммы summa_row и summa_col. Здесь summa_row - сумма элементов в каждой строке, а summa_col – сумма элементов для каждого столбца.
Решение
При обращении к элементам двумерного массива следует использовать два индекса, причем каждый из индексов должен записываться в своих индексных скобках. Отметим, левый индекс определяет номер строки, в которй находится элемент массива, а правый – номер столбца. Например, если m – матрицы, то m[1][2] – элемент этой матрицы, находящийся в строки с индексом 1 и столбце с индексом 2.
Каждая из сумм summa_row и summa_col должна вычисляться отдельно с использованием вложенных циклов. Дело в том, что при вычислении суммы summa_row матрица должна просматриваться по строкам, а при вычислении summa_col матрица должна просматриваться по стобцам.
Алгоритм просмотра по строкам реализуется с помощью вложенных циклов. Причем:
∙внешний цикл должен фиксировать индекс строки,
∙внутренний цикл - изменять индекс столбца.
Валгоритме просмотра по столбцам наоборот:
∙внешний цикл должен фиксировать индекс столбца,
∙внутренний цикл – изменять индекс строки.
#define MROW 5 #define MCOL 10
int main(void)
{
double summa_row[MROW]; int nr;
printf(“nr = ”); scanf(“%d”, &nr);
int nc; printf(“nc = ”); scanf(“%d”, &nc);
double m[MROW][MCOL];
// Ввод матрицы по строкам for(int r = 0; r < nr; r++)
{
for(c = 0; c < nc; c++)
91
{
printf(“m[%d][%d]”, c, r); scanf(“%lf”, &m[r][c]);
}
}
// Вычисление суммы для каждой строки
double summa_row[MROW];// массив для хранения суммы по // строкам
for(int r = 0; r < nr; r++)
{
double s = 0;
for(c = 0; c < nc; c++)
{
s += m[r][c];
}
summa_row[r] = s;
}
// Вычисление суммы для каждого столбца
double summa_col[MROW];// массив для хранения суммы по // столбцам
for(int c = 0; c < nc; c++)
{
double s = 0;
for(r = 0; r < nr; r++)
{
s += m[r][c];
}
summa_col[c] = s;
}
// вывод результатов вычислений
}
return 0;
Массивы занимают в памяти компьютера непрерывный участок памяти. Структура одномерного массива, как структура данных, хорошо соответствует одномерной природе структуры оперативной памяти компьютера. Иначе дело обстоит с двумерными массивами. Двумерность совместить с одномерностью оперативной памяти можно разными способами. В некоторых языках двумерные массивы хранятся по столбцам. Так двумерные массивы хранятся в языке Фортран. В языке Си двумерные массивы хранятся по строкам. Вначале первая строка, затем вторая и так далее.
92
Указатель – это программный элемент (переменная или выражение), значением которого является адрес переменной (указатель на объект) или функции (указатель на функцию). Вначале рассмотрим указатели на объект.
Целесообразно различать типизированные и нетипизированные указатели. Значением типизированного указателя может быть адрес объекта только определенного типа. Значением нетипизированного указателя может быть объект любого типа.
При работе с указателями следует использовать модель, состоящую из двух компонентов: собственно указателя и объекта, на который он установлен. Обозначим через T – тип объекта, на который должен быть установлен указатель. Тогда модель указателя может быть изображена в следующих двух вариантах (см. рис.).
А) Для типизированных указателей
T* |
T |
|
|
|
|
|
|
|
|
|
|
Указатель Объект
Б) Для нетипизированных указателей
void* |
|
T |
|
|
|
|
|
|
Указатель Объект
Из приведенной модели следует, что при работе с указателями недостаточно написать определение указателя (тем самым, выделив для него память), но необходимо обеспечить его инициализацию, состоящую в том, что в указатель записывается адрес того объекта, на который он должен быть установлен.
Для дальнейшего необходимо рассмотреть два оператора: разыменования и взятия адреса.
Это два основных оператора при работе с указателями. В результате выполнения оператора разыменования возвращается тот объект, на который установлен указатель. Основным оператором разыменования является префиксный унарный оператор “*”. Пусть в программе объявлен типизированный указатель p. Тогда, образовав выражение *p, получим доступ к тому объекту, адрес которого содержится в указателе p. Выражение *p относится к категории lvalue. Такое выражение может использоваться как в левой, так и в правой части оператора присваивания. Если T - тип объекта,
93
на который может быть установлен указателя p, относится к категории арифметических типов, то допустима инструкция присваивания следующего вида:
*p = 10;
Оператор взятия адреса является унарным префиксным оператором, позволяющим получить адрес своего операнда. Использование оператора взятия адреса приводит к образованию указателя-выражения. Приведем пример. Пусть в программе объявлены переменная n типа int и типизированный указатель ptr, значением которого могут быть адреса объектов типа int. Тогда с помощью следующей инструкции можно инициализировать указатель ptr:
ptr = &n;
Приведенная выше инструкция присваивания записывает в указательпеременную ptr значение указателя-выражения &n.
Можно выделить следующие основные области применения указателей:
1.Использование указателей в качестве параметров функций для возврата результатов вычислений.
2.Работа с объектами, размещенными в динамической памяти.
3.Замена манипуляций со сложными объектами данных манипуляцией с адресами этих объектов.
Нулевой указатель – это такое значение указателя, которое не может быть адресом переменной или функции. Наиболее часто используемыми представлениями нулевого указателя как константы являются 0, 0L и NULL. Считается хорошим стилем программирования всем указателям, которые не работают с реальными объектами (переменными) или функциями, было присвоено значение NULL
Вопределении указателя используется оператор разыменования “*”. Упрощенный формат определения указателя имеет следующий вид:
type * name;
Здесь type – спецификация типа, определяющая тип объекта, адрес которого будет храниться в указателе; name – имя (идентификатор) указателя. Заметим, что иногда тип объект, на который установлен указатель, называют базовым типом. Как уже отмечалось, в записи определений используется синтаксис выражений. Продемонстрируем это положение на примере. Пусть имеется следующее определение указателя:
int * ptr;
Здесь type – это int, а ptr – name. Приведенное выше определение можно “прочитать”, используя синтаксис выражений, следующим образом.
94
Применение оператора разыменования к имени ptr позволяет получить объект типа int. Отсюда следует тот факт, что ptr является именем указателя
Во время определения указатель может быть инициализирован. В качестве инициализаторов указателя при его объявлении могут использоваться:
∙Нулевой указатель.
∙Указатель-переменная
∙Указатель-выражение.
Приведем примеры инициализации указателей во время их определения.
/* Примеры инициализации указателей при их определении */
int* p = NULL; /* |
Инициализация |
указателя |
p нулевым указателем*/ |
|||
int* p2 |
= p; |
/* Инициализация |
указателя |
p2 значением |
||
|
|
|
|
существующего |
указателя |
p*/ |
int n = 5; |
/* Инициализация |
указателя |
значением |
|||
int* p3 |
= &n; |
|||||
/* |
|
|
|
указателя-выражения &n */ |
||
. . . . . . . . . . . . . . . . . . . . . . . . . . . */ |
||||||
Недействительный указатель (invalid pointer) – это ненулевой указатель, содержимое которого не является адресом переменной или функции. Любое использование недействительного указателя (разыменование такого указателя, сравнение его со значением NULL, передача его в качестве параметра в функцию) в стандарте языка Си приводит к неопределенному поведению программы. Рассмотрим возможные причины появления недействительных указателей.
Имеется ряд причин появления недействительных указателей:
∙Отсутствие инициализации указателей,
∙Ошибки, допущенные при преобразовании целочисленных значений к указательному типу.
∙Освобождение динамической памяти.
∙Ошибки, допущенные при использовании адресной арифметики. Здесь ограничимся рассмотрением только первой из перечисленных
выше причин. Одна из наиболее распространенных причин появление недействительных указателей связана с отсутствием инициализации указателей. Ниже приводится типичный пример программного кода, в котором используется неинициализированный указатель.
/* |
Ошибка в программном коде*/ |
|
double x; |
|
double* pn; |
|
x = modf(10.2, pn); // Использование неинициализированного |
95