Матрицы и многомерные массивы

Матрицу можно рассматривать как массив элементов типа массив. Например, переменная a представляет матрицу размера 3×3 с вещественными элементами:

double a[3][3];

Для обращения к элементу матрицы надо записать его индексы в квадратных скобках, например, выражение a[i][j] представляет собой элемент матрицы a в строке с индексом i и столбце с индексом j. Элемент матрицы можно использовать в любом выражении как обычную переменную.

Элементы матрицы располагаются в памяти последовательно по строкам. Например, в рассматриваемой матрице сначала идут элементы строки с индексом 0, затем строки с индексом 1, в конце строки с индексом 2. Т.е. элементы матрицы располагаются следующим образом: a[0][0], a[0][1], a[0][2], a[1][0], a[1][1], a[2][2], a[2][0], a[2][1], a[2][2].

Имя матрицы, как и имя массива, это указатель на первый элемент. Например, запись a эквивалентна &a[0][0], запись a+1 эквивалентна &a[0][1] и т.п.

При описании матрицы можно выполнить инициализацию ее элементов:

double a[3][3]={ {10.3, 1.1, 2.3},

{5.2, 6.7, 3.1},

{0.9, 2.3, 9.7}};

Такая реализация матрицы удобна и эффективна. У данной реализации матриц, как и у подобной реализации массивов, есть один существенный недостаток: так можно реализовать только матрицу, размер которой известен заранее, т.е. до начала работы программы на стадии компиляции.

Реализация матриц, размер которых становится известным на стадии выполнения программы, будет рассмотрен в теме 5.

Все, что сказано о матрицах (двумерных массивах), можно обобщить и на трехмерные массивы и т.п.

Трехмерный массив описывается следующим образом:

int a3[10][20][30];

Можно представить данный трехмерный массив в виде массива из десяти элементов. Каждый элемент является двумерным массивом размера 20 на 30.

Пользовательские функции

Определение функций

В предыдущих разделах уже встречались функции. Пользовательская функция - это функция, определенная пользователем. Общий синтаксис функции следующий:

<тип возвращаемого значения> <имя функции> (<Список формальных параметров>)

Список формальных параметров позволяет описать параметры, передаваемые функции с указанием их типа. Синтаксис данного списка следующий:

<тип параметра 1><идентификатор параметра 1>,<тип параметра 2><идентификатор параметра 2> и т.д.

При вызове функции ей передаются значения фактических параметров. Фактические параметры должны точно соответствовать типу формальных параметров.

Изменение формальных параметров в теле функции не приводит к изменению переменных вызывающей программы, передаваемых функции в качестве фактических параметров. Это обусловлено тем, что при вызове функции значения фактических параметров копируются в стек, т.е. функция работает не с самими переданными переменными, а с копиями их значений. Для пояснения сказанного рассмотрим следующий фрагмент программы:

void f(int x) {

x = 0; // Изменение формального параметра не приводит к

// изменению фактического параметра в вызывающей программе

}

int main() {

int x = 5;

f(x);

// Значение x по-прежнему равно 5

. . .

}

Здесь в функции main вызывается функция f, которой передается значение переменной x, равное пяти. Несмотря на то, что в теле функции f формальному параметру x присваивается значение 0, значение переменной x в функции main не меняется.

Если необходимо, чтобы функция могла изменить значения переменных вызывающей программы, надо передавать ей указатели на эти переменные. Тогда функция может записать любую информацию по переданным адресам. Если в предыдущем примере мы хотим изменять переменную x в теле функции f(), то текст программы должен быть следующий

void f(int *x) {

*x = 0;

}

int main() {

int x = 5;

f(&x);

// Значение x равно 0

. . .

}

Напомним, что в функцию scanf() требуется передавать адрес переменной, значение которой вводится с помощью этой функции.

Если функция должна возвращать некоторое значение, то следует использовать оператор return <возвращаемое значение>. Рассмотрим пример функции, вычисляющей сумму элементов массива состоящего из n элементов.

#define N 5

int a[N]={1,2,3,4,5}

int sum_array(int *x, int n) {

int i,s=0;

for(i=0;i<n;i++) s+=x[i];

return s;

}

int main() {

printf("Сумма элементов массива a=%d",sum_array(a,N));

}

Напомним, что имя массива это указатель на этот массив (указатель на первый элемент массива). Поэтому если в качестве аргумента функции используется имя массива, то функции передается указатель на этот массив. Затем функция использует этот указатель для выполнения изменений в переданном массиве.

В рассмотренном примере заголовок функции производящей суммирование элементов массива был определен следующим образом:

int sum_array(int *x, int n)

Для того чтобы подчеркнуть тот факт, что параметром функции является массив, можно было использовать следующий заголовок:

int sum_array(int x[], int n).

Записи int *x и int x[] эквивалентны.

Прототипы функций

До сих пор все встречающиеся пользовательские функции определялись до главной функции main. Вызовы функций осуществлялись в функции main. Поэтому при трансляции строки функции main, содержащей вызов некоторой пользовательской функции, компилятору уже была известна вся информация об этой пользовательской функции, и ошибок трансляции не возникало.

Однако в некоторых случаях может возникнуть необходимость поместить строку вызова функции до описания самой функции. Одной из причин помещения пользовательских функций после функции main является повышение наглядности программы. Например, перепишем предыдущий пример следующим образом:

#define N 5

int a[N]={1,2,3,4,5}

int main() {

printf("Сумма элементов массива a=%d",sum_array(a,N));

}

int sum_array(int *x, int n) {

int i,s=0;

for(i=0;i<n;i++) s+=x[i];

return s;

}

Такая программа более наглядна, т.к. функция, выполняющая детальную реализацию, вынесена в конец программы, а главная функция - в начало.

Однако при трансляции строки

printf("Сумма элементов массива a=%d",sum_array(a,N));

компилятор выдаст ошибку, связанную с тем, что функция sum_array(a,N) еще не определена.

Для устранения подобных ошибок используют прототипы функций. Прототип функции сообщает информацию об имени функции, типе возвращаемого значения, количестве и типах ее аргументов. Пример:

int sum_array(int *x, int n);

Описан прототип функции sum_array. Имена аргументов x и n здесь являются лишь комментариями, не несущими никакой информации для компилятора. Их можно опускать, например, описание

int sum_array(int *, int);

является допустимым.

Описания прототипов функций обычно выносятся в заголовочные файлы. Для коротких программ, которые помещаются в одном файле, описания прототипов располагают в начале программы. Предыдущая программа должна быть переписана следующим образом:

#define N 5

int a[N]={1,2,3,4,5}

int sum_array(int *, int);

int main() {

printf("Сумма элементов массива a=%d",sum_array(a,N));

}

int sum_array(int *x, int n) {

int i,s=0;

for(i=0;i<n;i++) s+=x[i];

return s;

}

Отметим, что определение функций до вызывающей функции и не использование прототипов функций сделает программу на несколько строк короче, но такой подход считается плохим стилем. Лучше всегда описывать прототипы всех функций в начале текста. Это связано с тем, что функции могут вызывать друг друга, и правильно упорядочить их (чтобы вызываемая функция была реализована раньше вызывающей) во многих случаях невозможно. К тому же предпочтительнее, чтобы основная функция main, с которой начинается выполнение программы, была бы реализована раньше функций, которые из нее вызываются. Это соответствует принципам структурного программирования.

Аргументы командной строки

Аргументы командной строки являются параметрами функции main, с которой начинается выполнение Си-программы. До сих пор применялся вариант функции main без параметров, однако, при необходимости доступа к аргументам командной строки можно использовать следующий заголовок функции main:

int main(int argc, char *argv[]) { . . . }

Переменная argc равна числу аргументов, т.е. отдельных слов командной строки, а массив argv содержит указатели на строки, каждая из которых равна очередному слову командной строки. Нулевой элемент argv[0] равен имени программы. Таким образом, число аргументов argc всегда не меньше единицы.

Например, при запуске программы testprog с помощью командной строки

testprog -x abcd.txt efgh.txt

значение переменной argc будет равно 4, а массив argv будет содержать 4 строки "testprog", "-x", "abcd.txt" и "efgh.txt".

Пример программы, печатающей аргументы своей командной строки:

// Файл "comargs.cpp"

// Напечатать аргументы командной строки

#include <stdio.h>

int main(int argc, char *argv[]) {

int i;

printf("Число аргументов ком. строки = %d\n", argc);

printf("Аргументы командной строки:\n");

for (i = 0; i < argc; ++i) {

printf("%s\n", argv[i]);

}

return 0;

}

Вопросы для повторения

  1. Понятие константы
  2. Использование директивы препроцессора #define.
  3. Понятие переменной.
  4. Основные типы данных языка Си (int, long, short, unsigned, char, float, double)
  5. Понятие структуры данных.
  6. Механизмы агрегирования ячеек памяти.
  7. Графическое представление структуры данных.
  8. Оператор определения имени типа typedef
  9. Понятие массива.
  10. Понятие указателя.
  11. Связь между массивами и указателями в Си.
  12. Внешние и внутренние переменные.
  13. Функция printf().
  14. Функция scanf().
  15. Оператор присваивания
  16. Арифметические операции
  17. Операции увеличения и уменьшения
  18. Операции сравнения
  19. Логические операции
  20. Побитовые логические операции
  21. Операции сдвига
  22. Операции "увеличить на", "домножить на" и т.п.
  23. Операции с указателями.
  24. Операция приведения типа
  25. Назначение управляющих конструкций
  26. Фигурные скобки
  27. Оператор выбора if и операция условия
  28. Оператор множественного выбора switch
  29. Оператор цикла while
  30. Оператор цикла for
  31. Оператор цикла do...while
  32. Понятие структуры.
  33. Указатели и структуры.
  34. Структуры и оператор определения имени типа typedef
  35. Понятие строки.
  36. Представление строк в языке Си.
  37. Матрицы и многомерные массивы.
  38. Понятие пользовательской функции.
  39. Синтаксис определение пользовательских функций в Си.
  40. Прототипы функций.
  41. Аргументы командной строки.

Резюме по теме

В данной теме рассмотрены основы языка Си.

Тема 4.Работа с файлами

Цели и задачи изучения темы

В данной теме рассматриваются основные средства языка Си для работы с файлами.

Наши рекомендации