указывает указатель, хранящийся в переменной pointer". Пройти по стрелке и положить значение в указываемую переменную. В данном случае *pointer обозначает не ЗНАЧЕНИЕ указываемой переменной, а САМУ указываемую переменную. ________ /pointer/ _/_______/_ | | | &var2 | | | |_______|_| | |Положить туда 123 | V ________ / var2 / _/_______/_ | | | 123 | | | |_________| pointer = &var2; *pointer = 123; означает *(&var2) = 123; означает var2 = 123; То есть снова * и & взаимно СТИРАЮТ друг друга. -------------------------------------------------------------------- Ещё пример: *pointer = *pointer + 66; или *pointer += 66; -------------------------------------------------------------------- Вернемся к примеру с функцией (@). Как он работает? В строке /* #1 */ Мы вызываем функцию f(), передавая в нее УКАЗАТЕЛЬ на переменную y ("адрес переменной y"). В строке /* #2 */ Отводится локальная переменная с именем ptr, которая в качестве начального значения получает значение первого аргумента функции в точке вызова - то есть УКАЗАТЕЛЬ на y. В строке /* #3 */ Мы видим *ptr = 7; что следует рассматривать как *(&y) = 7; точнее *(&main::y)=7; то есть как y = 7; точнее main::y=7; Что и хотелось. При этом отметим, что само имя "y" этой переменной внутри функции f() НЕВИДИМО и НЕИЗВЕСТНО! -------------------------------------------------------------------- ПРИМЕР: обмен значений двух переменных. void main(){ int x, y; int temporary; /* вспомогательная переменная */ x=1; y=2; temporary=x; x=y; y=temporary; printf("x=%d y=%d\n", x, y); /* Печатает x=2 y=1 */ } ----------------------------------------------------------------------- Теперь то же с использованием адресов и указателей: void swap(int *a, int *b){ int tmp; tmp = *a; *a = *b; *b = tmp; } void main(){ int x, y; x = 1; y = 2; swap(&x, &y); printf("x=%d y=%d\n", x, y); } ------------------------------------------------------------------------- Ещё пример: int x; int *ptr1, *ptr2; ptr1 = &x; ptr2 = &x; *ptr1 = 77; printf("%d\n", *ptr2); /* Печатает 77 */ То есть на одну переменную МОГУТ указывать несколько указателей. ------------------------------------------------------------------------- Ещё пример: int x; int *ptr1; /* Не инициализирована */ x = *ptr1; В ptr1 нет указателя ни на что, там есть мусор. Указатель указывает "в никуда" (пальцем в небо). Скорее всего произойдёт сбой в работе программы. Мораль: ВСЕГДА инициализируй переменные, указатели в том числе. МАССИВЫ Язык Си работает с именами массивов специальным образом. Имя массива "a" для int a[5]; является на самом деле указателем на его нулевой элемент. То есть у нас есть переменные (ящики) с именами a[0] a[1] ... a[4]. При этом само имя a при его использовании в программе означает &a[0] a | | | V a[0] a[1] a[2] a[3] a[4] _________________________________________ | | | | | | | | | | | | | | | | | | ----------------------------------------- Поэтому int a[5]; /* Передаётся не КОПИЯ самого массива, а копия УКАЗАТЕЛЯ на его начало */ void f(int *a){ /* или f(int a[]), что есть равноценная запись */ printf("%d\n", a[1]); a[2] = 7; } main (){ a[1] = 777; f(a); /* аргумент - массив */ printf("%d\n", a[2]); } Вызов f(a); сделает именно ожидаемые вещи. В этом примере мы видим два правила: ПРАВИЛО_1: При передаче в функцию имени массива в аргумент функции копируется не весь массив (жирновато будет), а указатель на его 0-ой элемент. ПРАВИЛО_2: Указатель на начало массива МОЖНО индексировать как сам массив. Это вторая операция, помимо *pointer, применимая к указателям: pointer[n]. Второе правило влечет за собой ряд следствий. int a[5]; /* массив */ int *ptr; /* указательная переменная */ ptr = a; /* законно, означает ptr = &a[0]; */ Теперь ptr[0] = 3; /* означает a[0] = 3; */ ptr[1] = 5; /* означает a[1] = 5; */ Более того. Возьмем теперь ptr = &a[2]; a[0] a[1] a[2] a[3] a[4] _________________________________________ | | | | | | a: | | | | | | | | | | | | ---------------------------------------------- | | | | ... ptr: | | | | ----------------------------- -2 -1 ptr[0] ptr[1] ptr[2] Мы как бы "приложили" к массиву a[] массив ptr[]. В котором ptr[0] есть a[2] ptr[1] есть a[3] ptr[2] есть a[4] ptr[3] находится за концом массива a[], МУСОР Более того, допустимы отрицательные индексы! ptr[-1] есть a[1] ptr[-2] есть a[0] ptr[-3] находится перед началом массива a[], МУСОР Итак: индексировать можно И массивы И указатели. Кстати, для имени массива a[] *a означает то же самое, что и a[0]. Это обратное следствие из схожести массивов и указателей. 19.c /* Задача: написать функцию инвертирования порядка символов в массиве char. A B C D ---> D C B A В решении можно использовать рекурсию. */ /* Мы приведем рекурсивное и нерекурсивное решения (два варианта) */ #include <stdio.h> /* Сначала - несколько служебных функций. */ /* ФУНКЦИЯ ПОДСЧЕТА ДЛИНЫ СТРОКИ. Как уже объяснялось, строка текста - это массив char, в конце которого помещен символ '\0'. Сам символ \0 не считается. */ int strlen(char s[]){ /* функция от массива букв */ int counter = 0; /* счетчик и одновременно индекс */ while(s[counter] != '\0') /* пока не встретился признак конца текста */ counter++; /* посчитать символ */ return counter; /* сколько символов, отличных от '\0' */ } /* ФУНКЦИЯ ПЕЧАТИ СТРОКИ. Печатаем каждый элемент массива как символ при помощи putchar(c). Как только встречаем элемент массива, равный '\0' - останавливаемся. Заметьте, что при наличии завершающего символа нам НЕ НАДО передавать в функцию размер массива, он нам неинтересен. В конце эта функция переводит строку. */ int putstr(char s[]){ int i = 0; /* индекс */ while(s[i] != '\0'){ putchar(s[i]); i++; } putchar('\n'); return i; } /* ТЕПЕРЬ МЫ ЗАНИМАЕМСЯ ФУНКЦИЕЙ ИНВЕРТИРОВАНИЯ. Для этого нам нужна вспомогательная функция: сдвиг элементов массива на 1 влево. Исходный массив: A B C D E F <---------- Результат: B C D E F F - Последний элемент удваивается. n - размер массива. Функция работает так: Исходный массив: A B C D E F n=6 После i=1 B B C D E F После i=2 B C C D E F После i=3 B C D D E F После i=4 B C D E E F После i=5 B C D E F F i=6 ==> остановка. */ void shiftLeft(char s[], int n){ int i; for(i=1; i < n; i++) s[i-1] = s[i]; } /* Функция инвертирования. Идея такова: - если длина массива меньше или равна 1, то инвертировать нечего, ибо массив состоит из 1 или 0 элементов. - если длина массива > 1, то a) Спасти нулевой элемент массива. A B C D E F | | V tmp b) Сдвинуть массив влево B C D E F F c) В последний элемент массива поместить спасенный нулевой элемент. tmp | V B C D E F A d) Инвертировать начало массива длиной n-1. {B C D E F}A Получится: F E D C B A Что и требовалось. s[] - массив, n - его длина. */ void reverse(char s[], int n){ char tmp; if(n <= 1) /* нечего инвертировать */ return; tmp = s[0]; /* спасти */ shiftLeft(s, n); /* сдвинуть */ s[n-1] = tmp; /* переместить */ reverse(s, n-1); /* инвертировать начало */ } /* ВТОРАЯ ВЕРСИЯ нерекурсивна. Рекурсия заменена циклом. Длина начала массива, которую надо инвертировать, вынесена на переменную length. */ void reverse1(char s[], int n){ char tmp; int length; for(length=n; length > 1; --length){ tmp = s[0]; shiftLeft(s, length); s[length-1] = tmp; } } char testString[] = "abcdefghijklmnopqrstuvwxyz"; /* Если не задать размер массива, он будет вычислен компилятором автоматически. Он будет равен числу букв внутри "..." ПЛЮС одна ячейка для невидимого символа '\0' на конце. В данном случае это 27. */ void main(){ int len; len = strlen(testString); /* вычислить длину строки: 26 ('\0' на конце не считается) */ printf("Строка для теста: \"%s\", ее длина %d\n", testString, len); /* Обратите внимание на два момента: - строку (массив char) следует печатать по формату %s - чтобы внутри "..." напечатать символ " надо изобразить его как \" А чтобы в putchar напечатать символ ' надо писать putchar('\''); */ /* Первая инверсия */ reverse(testString, len); putstr("Инвертированная строка:"); putstr(testString); /* Вторая инверсия - возвращает в исходное состояние */ reverse1(testString, len); putstr("Инвертированная в исходное состояние строка:"); putstr(testString); } 19_1.c /* Еще более простой вариант решения: просто обменивать элементы местами. A B C D E F G H I J J B C D E F G H I A | | эти J B C D E F G H I A J I C D E F G H B A | | потом эти J I C D E F G H B A J I H D E F G C B A | | потом эти ----> <----- J I H D E F G C B A J I H G E F D C B A | | J I H G E F D C B A | | J I H G F E D C B A стоп. */ #include <stdio.h> /* Обмен значений двух переменных типа char */ void swap(char *s1, char *s2){ char c; c = *s1; *s1 = *s2; *s2 = c; } void reverse(char s[], int n){ int first, last; first = 0; /* индекс первого элемента массива */ last = n-1; /* индекс последнего элемента массива */ while(first < last){ /* пока first левее last */ swap(&s[first], &s[last]); first++; /* сдвинуть вправо */ last--; /* сдвинуть влево */ } } char testString[] = "abcdefghijklmnopqrstuvwxyz."; void main(){ int len; len = strlen(testString); /* Есть такая стандартная функция */ reverse(testString, len); printf("Инвертированная строка: %s\n", testString); } 19_2.c /* Еще один вариант решения: сформировать ответ в дополнительном массиве, а потом скопировать его на прежнее место. */ #include <stdio.h> char testString[] = "abcdefghijklmnopqrstuvwxyz."; /* Конструкция sizeof(массив)/sizeof(массив[0]) выдает размер массива, даже если он не был явно объявлен. Эта конструкция применяется (чаще всего) для задания массива с размером, равным размеру уже объявленного массива. */ char tempString[ sizeof(testString) / sizeof(testString[0]) ]; void reverse(char s[], int n){ int i; /* вывернуть, результат в tempString[] */ for(i=0; i < n; i++) tempString[n-1-i] = s[i]; tempString[n] = '\0'; /* признак конца строки */ /* скопировать на старое место */ for(i=0; i < n; i++) s[i] = tempString[i]; s[n] = '\0'; /* признак конца строки */ } void main(){ int len; len = strlen(testString); /* Есть такая стандартная функция */ reverse(testString, len); printf("Инвертированная строка: %s\n", testString); } 19_3.c /* Задача инвертирования массива целых чисел */ #include <stdio.h> int arr[] = {1, 5, 10, 15, 20, 25, 30}; int arrLen = sizeof(arr) / sizeof(arr[0]); /* размер массива */ /* Распечатка массива в строку */ void printit(int row[], int n){ int i; for(i=0; i < n; i++){ printf("%d", row[i]); if(i == n-1) putchar('\n'); else putchar(' '); } } /* Печать отступа. Отладочная функция */ void printShift(int n){ n = arrLen - n; while(n > 0){ printf(" "); n--; } } /* Сдвиг массива */ void shiftleft(int row[], int n){ int i; for(i=1; i < n; i++) row[i-1] = row[i]; } /* Инвертирование */ void reverse(int row[], int n){ int pocket; printShift(n); /* трассировка */ printf("CALLED reverse(row, %d)\n", n); /* трассировка */ if(n <= 1){ printShift(n); /* трассировка */ printf("return from reverse(row, %d);\n", n); /* трассировка */ return; } pocket = row[0]; shiftleft(row, n); row[n-1] = pocket; printShift(n); /* трассировка */ printit(arr, arrLen); /* трассировка */ reverse(row, n-1); printShift(n); /* трассировка */ printf("all done; return from reverse(row, %d);\n", n); /* трассировка */ } void main(){ reverse(arr, arrLen); printit(arr, arrLen); } 20.c /* Задача: написать функцию для распечатки массива целых чисел в виде таблицы в columns столбцов. При этом порядок элементов должен быть таков: 0 4 8 1 5 9 2 6 10 3 7 */ /* Пусть в массиве n элементов. Если n < columns, то мы получаем такую ситуацию (n=2, columns=4) 0 1 пусто пусто Поэтому if(n < columns) columns = n; Далее, прямоугольная таблица с columns столбцами и lines строками может содержать максимум columns*lines элементов. Поэтому: columns*lines >= n Вычислим число строк. Нам нужно минимальное целое число строк, такое что lines >= n/columns Такое число вычисляется по формуле lines = (n + (columns - 1)) / columns; где деление целочисленное. Далее надо только вывести формулу для индекса элемента в массиве в зависимости от номера строки (y) и столбца (x). index(x, y) = (x * lines + y); причем если index >= n, то ничего не выводить */ #include <stdio.h> int array[100]; void printArray(int a[], int n, int columns){ int lines; /* число строк */ int x, y; /* номер колонки, номер строки - с нуля */ int index; /* индекс элемента в массиве */ if(n < columns) columns = n; lines = (n + (columns-1)) / columns; /* Используем вложенные циклы: по строкам, а внутри - по столбцам */ for(y=0; y < lines; y++){ for(x=0; x < columns; x++){ index = x * lines + y; if(index >= n) /* элемент за концом массива */ break; /* прервать строку */ /* break выводит только из внутреннего цикла (по столбцам) */ /* сделать отступ в следующую колонку */ if(x != 0) putchar('\t'); printf("%02d|%d", index, a[index]); /* Формат %02d заставляет печатать целое число с использованием ДВУХ цифр, причем если число состоит из одной цифры, то спереди приписывается нуль. */ } putchar('\n'); /* перейти к следующей строке */ } } void main(){ int i, cols; /* Инициализация значений элементов массива */ for(i=0; i < 100; i++) array[i] = i + 1; for(cols=4; cols <= 13; cols++){ printf("\t\t* * * ТАБЛИЦА В %d СТОЛБЦОВ * * *\n", cols); printArray(array, 77, cols); putchar('\n'); } } 20_1.c #include <stdio.h> main(){ int x, y; int COLUMNS = 11; int LINES = 10; int value; /* цикл по строкам */ for(y=0; y < LINES; y++){ /* цикл по столбцам */ for(x=0; x < COLUMNS; x++){ /* что напечатать */ value = LINES * x + y; /* если это не нулевой столбец, то перейти в следующую колонку */ if(x > 0) putchar('\t'); /* ... и в ней напечатать значение */ printf("%d", value); } putchar('\n'); /* новая строка */ } } 20_2.c /* elem(x, y) = LINES * x + y; тогда elem(0, y+1) - elem(COLUMNS-1, y) = 1 + LINES - COLUMNS*LINES; elem(x+1, y) - elem(x, y) = LINES; */ #include <stdio.h> int A = 150; /* Количество элементов */ int COLUMNS = 7; /* Количество столбцов */ int LINES; /* Количество строк */ int value; /* Значение в текущей клетке */ int OFFSET_NEXT_COLUMN; int OFFSET_NEXT_LINE; /* Рисуем строку таблицы */ void line(){ int col; /* номер колонки */ for(col=0; col < COLUMNS; col++){ if(value >= A) /* отсутствующий элемент */ printf("* "); else printf("%03d ", value); /* Увеличение при переходе в соседнюю колонку */ value += OFFSET_NEXT_COLUMN; /* 1 */ } /* Перейти к следующей строке */ putchar('\n'); /* Увеличение при переходе из конца одной строки к началу следующей. Заметим, что к value уже прибавлено OFFSET_NEXT_COLUMN из точки 1, поэтому при переходе в начало следующей строки в сумме прибавляется OFFSET_NEXT_COLUMN + OFFSET_NEXT_LINE равное 1 - LINES*COLUMNS + LINES, что соответствует формуле. */ value += OFFSET_NEXT_LINE; /* 2 */ } int main(){ int nline; /* Номер строки */ LINES = (A + (COLUMNS - 1)) / COLUMNS; OFFSET_NEXT_COLUMN = LINES; OFFSET_NEXT_LINE = 1 - LINES*COLUMNS; for(nline=0; nline < LINES; nline++) line(); /* возврат 0 из main() означает "программа завершена успешно" */ return 0; } 21.c /* ДВУМЕРНЫЕ МАССИВЫ */ /* Двумерный массив представляет собой двумерную прямоугольную таблицу из нумерованных переменных. Он объявляется так: int array[LINES][COLUMNS]; А индексируется так: array[y][x] где 0 <= y <= LINES - 1 0 <= x <= COLUMNS - 1 +-------------+-------------+-------------+------> ось x | array[0][0] | array[0][1] | array[0][2] | ... +-------------+-------------+-------------+ | array[1][0] | array[1][1] | array[1][2] | ... +-------------+-------------+-------------+ | array[2][0] | array[2][1] | array[2][2] | ... +-------------+-------------+-------------+ | ... ... ... V ось y Пока, на данной стадии знания Си, я рекомендую вам объявлять двумерные массивы как глобальные и не пытаться передавать их имена в функции как аргументы. */ /* Приведем пример, который заводит двумерный массив букв, рисует в нем некую геометрическую фигуру, и печатает этот массив. Здесь мы приводим алгоритм Брезенхема для рисования прямых, объяснения КАК он это делает мы опустим. Пардон. */ #define LINES 31 /* число строк */ #define COLUMNS 79 /* число столбцов */ char field[LINES][COLUMNS]; /* В данной программе массив НЕ является параметром, мы работаем с ним как с глобальной переменной. Функция рисования прямой линии, алгоритм Брезенхема. */ void line(int x1, int y1, int x2, int y2, char sym){ int dx, dy, i1, i2, i, kx, ky; int d; /* "отклонение" */ int x, y; int flag; dy = y2 - y1; dx = x2 - x1; if (dx == 0 && dy == 0){ field[y1][x1] = sym; /* единственная точка */ return; } kx = 1; /* шаг по x */ ky = 1; /* шаг по y */ /* Выбор тактовой оси */ if( dx < 0 ){ dx = -dx; kx = -1; } /* Y */ else if(dx == 0) kx = 0; /* X */ if(dy < 0) { dy = -dy; ky = -1; } if(dx < dy){ flag = 0; d = dx; dx = dy; dy = d; } else flag = 1; i1 = dy + dy; d = i1 - dx; i2 = d - dx; x = x1; y = y1; for(i=0; i < dx; i++){ field[y][x] = sym; /* нарисовать точку */ if(flag) x += kx; /* шаг по такт. оси */ else y += ky; if( d < 0 ) /* горизонтальный шаг */ d += i1; else{ /* диагональный шаг */ d += i2; if(flag) y += ky; /* прирост высоты */ else x += kx; } } field[y][x] = sym; /* последняя точка */ } int main(){ int x, y; /* Заполнить поле пробелами */ for(y=0; y < LINES; y++) for(x=0; x < COLUMNS; x++) field[y][x] = ' '; /* Нарисовать картинку */ line(0,0, 0, LINES-1, '*'); line(0,0, COLUMNS-1, 0, '*'); line(COLUMNS-1, 0, COLUMNS-1, LINES-1, '*'); line(0, LINES-1, COLUMNS-1, LINES-1, '*'); line(0,0, COLUMNS-1, LINES-1, '\\'); line(COLUMNS-1,0, 0,LINES-1, '/'); /* Распечатать массив */ for(y=0; y < LINES; y++){ for(x=0; x < COLUMNS; x++) putchar(field[y][x]); putchar('\n'); } return 0; }