Страница:
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
-------------------------------------------------------------------------
Затем в factorial(1) выполнение программы дойдет до точки /* #a */
и будет производиться return 1.
При return вычеркивается ОДИН блок информации со стека вызовов функций,
и возобновляется выполнение "текущего оператора" в функции,
ставшей НОВОЙ вершиной стека вызовов.
Заметьте, что в данной ситуации вызванные функции factorial(m) еще не завершились.
В них еще ЕСТЬ что сделать: вычислить выражение в return,
и собственно выполнить сам return. Вычисления будут продолжены.
--------+ +--------
|=======================|
| +-|---> текущий оператор return 1;
| w = 1 |
| n = 1 |
|factorial(1) |
|=======================|
| +-|---> текущий оператор return 2 * factorial(1);
| w = 2 |
| n = 2 |
|factorial(2) |
|=======================|
| +-|---> текущий оператор return 3 * factorial(2);
| w = 3 |
| n = 3 |
|factorial(3) |
|=======================|
| +-|---> текущий оператор z = factorial(3);
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
-------------------------------------------------------------------------
Начинается выталкивание функций со стека и выполнение операторов return;
--------+ +--------
|=======================|
| +-|---> текущий оператор return 2 * 1;
| w = 2 |
| n = 2 |
|factorial(2) |
|=======================|
| +-|---> текущий оператор return 3 * factorial(2);
| w = 3 |
| n = 3 |
|factorial(3) |
|=======================|
| +-|---> текущий оператор z = factorial(3);
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
--------+ +--------
|=======================|
| +-|---> текущий оператор return 3 * 2;
| w = 3 |
| n = 3 |
|factorial(3) |
|=======================|
| +-|---> текущий оператор z = factorial(3);
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
--------+ +--------
|=======================|
| +-|---> текущий оператор z = 6;
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
--------+ +--------
|=======================|
| z = 6 |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
-------------------------------------------------------------------------
Наконец, в точке /* C */ будет вызвана функция func().
Рассмотрим точку /* #c */ в ней.
--------+ +--------
|=======================|
| x = 777; |
|func(); |
|=======================|
| +-|---> текущий оператор func();
| z = 6 |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
В данном месте нас интересует - какие переменные видимы?
Видимы:
func::x = 777
::v = 333
И все.
::x заслонен локальной переменной.
main::y, main::z невидимы, так как находятся
не на вершине стека вызовов функций
-------------------------------------------------------------------------
Многие функции более естественно выражаются через рекурсию.
Хотя, часто это приводит к излишним вычислениям по сравнению с итерациями
(то есть циклами). Вот пример - числа Фибоначчи.
int fibonacci(int n){
if(n==1 || n==2) return 1;
/* else */
return fibonacci(n-1) + fibonacci(n-2);
}
void main(){
printf("20ое число Фибоначчи равно %d\n", fibonacci(20));
}
Поскольку тут отсутствует массив для запоминания промежуточных
результатов, то этот массив на самом деле неявно моделируется
в виде локальных переменных внутри стека вызовов функций.
Однако этот способ плох - в нем слишком много повторяющихся
действий. Добавим оператор печати - и посчитаем, сколько раз
была вызвана fibonacci(1) ?
int called = 0;
int fibonacci(int n){
if(n==1){
called++;
return 1;
} else if(n==2)
return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
void main(){
printf("20ое число Фибоначчи равно %d\n", fibonacci(20));
printf("fibonacci(1) была вызвана %d раз\n", called);
}
Она была вызвана... 2584 раза!
/* Рисуем хитрую геометрическую фигуру */
#include <stdio.h>
const int LINES = 15;
void draw(int nspaces, int nstars, char symbol){
int i;
for(i=0; i < nspaces; i++)
putchar(' ');
for(i=0; i < nstars; i++)
putchar(symbol);
}
void main(){
int nline, nsym;
char symbols[3]; /* Массив из трех букв */
symbols[0] = '\\';
symbols[1] = 'o';
symbols[2] = '*';
for(nline=0; nline < LINES; nline++){
for(nsym = 0; nsym < 3; nsym++)
draw(nline, nline, symbols[nsym]);
/* Переход на новую строку вынесен
из функции в главный цикл */
putchar('\n');
}
}
/* Задача:
нарисовать таблицу вида
кот кот кот кошка кошка кот
кот кот кошка кошка кот ...
Где идет последовательность
кот, кот, кот, кошка, кошка...
повторяющаяся много раз и располагаемая по 6 зверей в строку.
*/
#include <stdio.h> /* магическая строка */
/* Объявление глобальных переменных.
В данном случае - констант.
*/
int TOMCATS = 3; /* три кота */
int CATS = 2; /* две кошки */
int ANIMALS_PER_LINE = 6; /* шесть зверей в каждой строке */
int LINES = 25; /* число выводимых строк */
/* и нам понадобится еще одна переменная - общее число зверей.
Ее мы вычислим через уже заданные, поэтому тут мы ее объявим...
но вычислить что-либо можно только внутри функции.
В нашем случае - в функции main().
*/
int ANIMALS; /* общее число зверей */
int nth_in_line = 0; /* номер зверя в текущей строке */
/* Эта переменная не может быть локальной в функции, так как
* тогда она уничтожится при выходе из функции. Нам же необходимо,
* чтобы ее значение сохранялось. Поэтому переменная - глобальная.
*/
/* Функция, которая считает число зверей в одной строке
и либо переводит строку, либо переводит печать в
следующую колонку (табуляцией).
*/
void checkIfWeHaveToBreakLine(){
nth_in_line++; /* текущий номер зверя в строке (с единицы) */
if(nth_in_line == ANIMALS_PER_LINE){
/* Если строка заполнена зверьми... */
putchar('\n'); /* новая строка */
nth_in_line = 0; /* в новой строке нет зверей */
} else {
putchar('\t'); /* в следующую колонку */
}
}
void main(){
int nanimal; /* номер зверя */
int i; /* счетчик */
ANIMALS = ANIMALS_PER_LINE * LINES;
nanimal = 0;
while(nanimal < ANIMALS){
for(i=0; i < TOMCATS; i++){
/* Оператор printf() выводит СТРОКУ СИМВОЛОВ.
СТРОКА заключается в двойные кавычки
(не путать с одиночными для putchar().
*/
printf("кот");
nanimal++; /* посчитать еще одного зверя */
/* и проверить - не надо ли перейти на новую строку ? */
checkIfWeHaveToBreakLine();
}
for(i=0; i < CATS; i++){
printf("кошка");
nanimal++; /* посчитать еще одного зверя */
/* и проверить - не надо ли перейти на новую строку ? */
checkIfWeHaveToBreakLine();
}
}
/* putchar('\n'); */
}
/*
Та же задача, но еще надо печатать номер каждого зверя.
Ограничимся пятью строками.
*/
#include <stdio.h> /* магическая строка */
int TOMCATS = 3; /* три кота */
int CATS = 2; /* две кошки */
int ANIMALS_PER_LINE = 6; /* шесть зверей в каждой строке */
int LINES = 5; /* число выводимых строк */
int ANIMALS; /* общее число зверей */
int nth_in_line = 0; /* номер зверя в текущей строке */
void checkIfWeHaveToBreakLine(){
nth_in_line++;
if(nth_in_line == ANIMALS_PER_LINE){
putchar('\n');
nth_in_line = 0;
} else
printf("\t\t"); /* @ */
/* Одинокий оператор может обойтись без {...} вокруг него */
}
void main(){
int nanimal;
int i;
ANIMALS = ANIMALS_PER_LINE * LINES;
nanimal = 0;
while(nanimal < ANIMALS){
for(i=0; i < TOMCATS; i++){
/* Формат %d выводит значение переменной типа int
в виде текстовой строки.
Сама переменная должна быть в списке после формата
(список - это перечисление переменных через запятую).
Переменных ИЛИ выражений (формул).
Давайте выводить по ДВЕ табуляции --
это место отмечено в функции checkIfWeHaveToBreakLine()
как @.
Еще раз внимание - один символ мы выводим как
putchar('a');
Несколько символов - как
printf("abcdef");
Одиночные кавычки - для одной буквы.
Двойные кавычки - для нескольких.
*/
printf("кот%d", nanimal);
nanimal++;
checkIfWeHaveToBreakLine();
}
for(i=0; i < CATS; i++){
printf("кошка%d", nanimal);
nanimal++;
checkIfWeHaveToBreakLine();
}
}
}
/* Задача: напечатать корни из чисел от 1 до 100.
Новая информация:
Нам понадобится новый тип данных - ДЕЙСТВИТЕЛЬНЫЕ ЧИСЛА.
Это числа, имеющие дробную часть (после точки).
Как мы уже знаем, целые - это int.
буква - это char.
действительное число - это double.
(есть еще слово float, но им пользоваться не рекомендуется).
Для вычисления корня используется итерационный алгоритм Герона.
q = корень из x;
q[0] := x;
q[n+1] := 1/2 * ( q[n] + x/q[n] );
Главное тут не впасть в ошибку, не клюнуть на q[n] и не
завести массив. Нам не нужны результаты каждой итерации,
нам нужен только конечный ответ. Поэтому нам будет вполне
достаточно ОДНОЙ, но изменяющейся в цикле, ячейки q.
*/
#include <stdio.h>
/* Еще одно новое ключевое слово - const. Обозначает константы.
В отличие от переменных, такие имена нельзя изменять.
То есть, если где-то потом попробовать написать epsilon = ... ;
то это будет ошибкой.
*/
const double epsilon = 0.0000001; /* точность вычислений */
/* Функция вычисления модуля числа */
double doubleabs(double x){
if(x < 0) return -x;
else return x;
}
/* Функция вычисления квадратного корня */
double sqrt(double x){
double sq = x;
/* Такая конструкция есть просто склейка двух строк:
double sq;
sq = x;
Называется это "объявление переменной с инициализацией".
*/
while(doubleabs(sq*sq - x) >= epsilon){
sq = 0.5 * (sq + x/sq);
}
return sq;
}
void main() {
int n;
for(n=1; n <= 100; n++)
printf("sqrt(%d)=%lf\n",
n, sqrt((double) n)
);
}
/*
Здесь в операторе printf() мы печатаем ДВА выражения.
ФОРМАТ ЗНАЧЕНИЕ
------ --------
%d -- n
%lf -- sqrt((double) n)
По формату %d печатаются значения типа int.
По формату %c печатаются значения типа char.
По формату %lf (или %g) печатаются значения типа double.
Что значит "напечатать значение выражения sqrt(xxx)" ?
Это значит:
- вызвать функцию sqrt() с аргументом, равным xxx;
- вычислить ее;
- возвращенное ею значение напечатать по формату %lf,
то есть как действительное число.
Заметьте, что тут возвращаемое значение НЕ присваивается
никакой переменной, мы не собираемся его хранить.
Точно так же, как в операторе x = 12 + 34;
12 и 34 не хранятся ни в каких переменных,
а оператор
printf("%d\n", 12);
печатает ЧИСЛО 12, а не переменную.
Точно так же, как можно писать
double z;
z = sqrt(12) + sqrt(23);
где значение, вычисленное каждой функцией, НЕ хранится
в своей собственной переменной (такая переменная на самом
деле существует в компьютере, но программисту она не
нужна и недоступна).
Или
z = sqrt( sqrt(81));
(корень из корня из 81 --> даст 3)
Далее, что означает конструкция (double) n ?
Функция sqrt() требует аргумента типа double.
Мы же предлагаем ей целый аргумент
int n;
Целые и действительные числа представлены в памяти
машины ПО-РАЗНОМУ,
поэтому числа
12 и 12.0 хранятся в памяти ПО-РАЗНОМУ.
Машина умеет преобразовывать целые числа в действительные
и наоборот, надо только сказать ей об этом.
Оператор (double) x
называется "приведение типа к double".
Заметим, что часто преобразование типа
выполняется автоматически.
Так, например, при сложении int и double
int автоматически приводится к double, и результат
имеет тип double.
int var1;
double var2, var3;
var1 = 2;
var2 = 2.0;
var3 = var1 + var2;
что означает на самом деле
var3 = (double) var1 + var2;
var3 станет равно 4.0
Более того, к примеру тип char - это тоже ЦЕЛЫЕ ЧИСЛА из интервала
0...255. Каждая буква имеет код от 0 до 255.
*/
УКАЗАТЕЛИ
=========
void f(int x){
x = 7;
}
main(){
int y = 17;
f(y);
printf("y=%d\n", y); /* печатает: y=17 */
}
В аргументе x передаётся КОПИЯ значения y,
поэтому x=7; не изменяет значения у.
Как все же сделать, чтобы вызываемая функция
могла изменять значение переменной?
Отбросим два способа:
- объявление y как глобальной
(много глобальных переменных - плохой стиль),
- y=f(y);
(а что если надо изменить МНОГО переменных?
return, к несчастью, может вернуть лишь одно значение).
Используется новая для нас конструкция: УКАЗАТЕЛЬ.
--------------------------------------------------
Пример (@)
void f(int *ptr){ /* #2 */
*ptr = 7; /* #3 */
}
main (){
int y=17;
f(&y); /* #1 */
printf("y=%d\n", y); /* печатает: y=7 */
}
Ну как, нашли три отличия от исходного текста?
----------------------------------------------------------------------
Мы вводим две новые конструкции:
&y "указатель на переменную y" или
"адрес переменной y"
*ptr означает "разыменование указателя ptr"
(подробнее - позже)
int *ptr; означает объявление переменной ptr,
которая может содержать в себе
указатель на переменную,
хранящую int-число.
Для начала определим, что такое указатель.
int var1, var2, z; /* целочисленные переменные */
int *pointer; /* указатель на целочисленную переменную */
var1 = 12;
var2 = 43;
pointer = &var1;
Мы будем изображать указатель в виде СТРЕЛКИ;
это хороший прием и при практическом программировании.
________
/pointer/
_/_______/_
| значение| Переменная, хранящая указатель
| есть | (адрес другой переменной)
| |
| &var1 |
| |
|_______|_|
|
|&var1 - сам указатель на var1 -
| "стрелка, указывающая на переменную var1".
V Она обозначается &var1
________
/ var1 /
_/_______/_
| значение|
| есть |
| 12 |
|_________|
Таким образом, УКАЗАТЕЛЬ - это "стрелка, указывающая на некий ящик-переменную".
Начало этой стрелки можно (в свою очередь) хранить в какой-нибудь переменной.
При этом, если стрелка указывает на переменную типа int,
то тип переменной, хранящей начало стрелки, есть int *
Если типа char, то тип - char *
АДРЕС (указатель на) можно взять только от переменной или элемента массива,
но не от выражения.
int x;
int arr[5];
Законно &x стрелка на ящик "x"
& arr[3] стрелка на ящик "arr[3]"
Незаконно &(2+2) тут нет именованного "ящика",
на который указывает стрелка,
да и вообще ящика нет.
Указатели несколько различно ведут себя СЛЕВА и СПРАВА
от оператора присваивания.
Нас интересует новая операция, применяемая только к указателям:
*pointer
--------------------------------------------------------------------
СПРАВА от присваиваний и в формулах
===================================
*pointer означает
"взять значение переменной (лежащее в ящике),
на которую указывает указатель,
хранящийся в переменной pointer".
В нашем примере - это число 12.
То есть *pointer означает "пройти по стрелке и взять указываемое ею ЗНАЧЕНИЕ".
printf("%d\n", *pointer);
Печатает 12;
z = *pointer; /* равноценно z = 12; */
z = *pointer + 66; /* равноценно z = 12 + 66; */
Заставим теперь указатель указывать на другую переменную
(иначе говоря, "присвоим указателю адрес другой переменной")
pointer = &var2;
________
/pointer/
_/_______/_
| |
| &var2 |
| |
|_______|_|
|
|&var2
|
V
________
/ var2 /
_/_______/_
| |
| 43 |
| |
|_________|
После этого z = *pointer;
означает z = 43;
--------------------------------------------------------------------
Таким образом, конструкция
z = *pointer;
означает
z = *(&var2);
означает
z = var2;
То есть * и & взаимно СТИРАЮТСЯ.
*pointer = 123;
Означает "положить значение правой части (т.е. 123)
в переменную (ящик), на который указывает указатель,
хранящийся в переменной 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].
Это обратное следствие из схожести массивов и указателей.
/* Задача: написать функцию инвертирования порядка символов
в массиве 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);
}
/* Еще более простой вариант решения:
просто обменивать элементы местами.
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);
}
/* Еще один вариант решения:
сформировать ответ в дополнительном массиве,
а потом скопировать его на прежнее место.
*/
#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);
}
/* Задача инвертирования массива целых чисел */
#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);
}
/* Задача: написать функцию для распечатки массива целых чисел
в виде таблицы в 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');
}
}
#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'); /* новая строка */
}
}
/*
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;
}
/* ДВУМЕРНЫЕ МАССИВЫ */
/*
Двумерный массив представляет собой двумерную
прямоугольную таблицу из нумерованных переменных.
Он объявляется так:
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;
}
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
-------------------------------------------------------------------------
Затем в factorial(1) выполнение программы дойдет до точки /* #a */
и будет производиться return 1.
При return вычеркивается ОДИН блок информации со стека вызовов функций,
и возобновляется выполнение "текущего оператора" в функции,
ставшей НОВОЙ вершиной стека вызовов.
Заметьте, что в данной ситуации вызванные функции factorial(m) еще не завершились.
В них еще ЕСТЬ что сделать: вычислить выражение в return,
и собственно выполнить сам return. Вычисления будут продолжены.
--------+ +--------
|=======================|
| +-|---> текущий оператор return 1;
| w = 1 |
| n = 1 |
|factorial(1) |
|=======================|
| +-|---> текущий оператор return 2 * factorial(1);
| w = 2 |
| n = 2 |
|factorial(2) |
|=======================|
| +-|---> текущий оператор return 3 * factorial(2);
| w = 3 |
| n = 3 |
|factorial(3) |
|=======================|
| +-|---> текущий оператор z = factorial(3);
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
-------------------------------------------------------------------------
Начинается выталкивание функций со стека и выполнение операторов return;
--------+ +--------
|=======================|
| +-|---> текущий оператор return 2 * 1;
| w = 2 |
| n = 2 |
|factorial(2) |
|=======================|
| +-|---> текущий оператор return 3 * factorial(2);
| w = 3 |
| n = 3 |
|factorial(3) |
|=======================|
| +-|---> текущий оператор z = factorial(3);
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
--------+ +--------
|=======================|
| +-|---> текущий оператор return 3 * 2;
| w = 3 |
| n = 3 |
|factorial(3) |
|=======================|
| +-|---> текущий оператор z = factorial(3);
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
--------+ +--------
|=======================|
| +-|---> текущий оператор z = 6;
| z = мусор |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
--------+ +--------
|=======================|
| z = 6 |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
-------------------------------------------------------------------------
Наконец, в точке /* C */ будет вызвана функция func().
Рассмотрим точку /* #c */ в ней.
--------+ +--------
|=======================|
| x = 777; |
|func(); |
|=======================|
| +-|---> текущий оператор func();
| z = 6 |
| y = 12 | +------+---------+
|main() | |x = 7 | v = 333 |
+-----------------------+-----------+------+---------+-----
СТЕК ВЫЗОВОВ ФУНКЦИЙ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
В данном месте нас интересует - какие переменные видимы?
Видимы:
func::x = 777
::v = 333
И все.
::x заслонен локальной переменной.
main::y, main::z невидимы, так как находятся
не на вершине стека вызовов функций
-------------------------------------------------------------------------
Многие функции более естественно выражаются через рекурсию.
Хотя, часто это приводит к излишним вычислениям по сравнению с итерациями
(то есть циклами). Вот пример - числа Фибоначчи.
int fibonacci(int n){
if(n==1 || n==2) return 1;
/* else */
return fibonacci(n-1) + fibonacci(n-2);
}
void main(){
printf("20ое число Фибоначчи равно %d\n", fibonacci(20));
}
Поскольку тут отсутствует массив для запоминания промежуточных
результатов, то этот массив на самом деле неявно моделируется
в виде локальных переменных внутри стека вызовов функций.
Однако этот способ плох - в нем слишком много повторяющихся
действий. Добавим оператор печати - и посчитаем, сколько раз
была вызвана fibonacci(1) ?
int called = 0;
int fibonacci(int n){
if(n==1){
called++;
return 1;
} else if(n==2)
return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
void main(){
printf("20ое число Фибоначчи равно %d\n", fibonacci(20));
printf("fibonacci(1) была вызвана %d раз\n", called);
}
Она была вызвана... 2584 раза!
/* Рисуем хитрую геометрическую фигуру */
#include <stdio.h>
const int LINES = 15;
void draw(int nspaces, int nstars, char symbol){
int i;
for(i=0; i < nspaces; i++)
putchar(' ');
for(i=0; i < nstars; i++)
putchar(symbol);
}
void main(){
int nline, nsym;
char symbols[3]; /* Массив из трех букв */
symbols[0] = '\\';
symbols[1] = 'o';
symbols[2] = '*';
for(nline=0; nline < LINES; nline++){
for(nsym = 0; nsym < 3; nsym++)
draw(nline, nline, symbols[nsym]);
/* Переход на новую строку вынесен
из функции в главный цикл */
putchar('\n');
}
}
/* Задача:
нарисовать таблицу вида
кот кот кот кошка кошка кот
кот кот кошка кошка кот ...
Где идет последовательность
кот, кот, кот, кошка, кошка...
повторяющаяся много раз и располагаемая по 6 зверей в строку.
*/
#include <stdio.h> /* магическая строка */
/* Объявление глобальных переменных.
В данном случае - констант.
*/
int TOMCATS = 3; /* три кота */
int CATS = 2; /* две кошки */
int ANIMALS_PER_LINE = 6; /* шесть зверей в каждой строке */
int LINES = 25; /* число выводимых строк */
/* и нам понадобится еще одна переменная - общее число зверей.
Ее мы вычислим через уже заданные, поэтому тут мы ее объявим...
но вычислить что-либо можно только внутри функции.
В нашем случае - в функции main().
*/
int ANIMALS; /* общее число зверей */
int nth_in_line = 0; /* номер зверя в текущей строке */
/* Эта переменная не может быть локальной в функции, так как
* тогда она уничтожится при выходе из функции. Нам же необходимо,
* чтобы ее значение сохранялось. Поэтому переменная - глобальная.
*/
/* Функция, которая считает число зверей в одной строке
и либо переводит строку, либо переводит печать в
следующую колонку (табуляцией).
*/
void checkIfWeHaveToBreakLine(){
nth_in_line++; /* текущий номер зверя в строке (с единицы) */
if(nth_in_line == ANIMALS_PER_LINE){
/* Если строка заполнена зверьми... */
putchar('\n'); /* новая строка */
nth_in_line = 0; /* в новой строке нет зверей */
} else {
putchar('\t'); /* в следующую колонку */
}
}
void main(){
int nanimal; /* номер зверя */
int i; /* счетчик */
ANIMALS = ANIMALS_PER_LINE * LINES;
nanimal = 0;
while(nanimal < ANIMALS){
for(i=0; i < TOMCATS; i++){
/* Оператор printf() выводит СТРОКУ СИМВОЛОВ.
СТРОКА заключается в двойные кавычки
(не путать с одиночными для putchar().
*/
printf("кот");
nanimal++; /* посчитать еще одного зверя */
/* и проверить - не надо ли перейти на новую строку ? */
checkIfWeHaveToBreakLine();
}
for(i=0; i < CATS; i++){
printf("кошка");
nanimal++; /* посчитать еще одного зверя */
/* и проверить - не надо ли перейти на новую строку ? */
checkIfWeHaveToBreakLine();
}
}
/* putchar('\n'); */
}
/*
Та же задача, но еще надо печатать номер каждого зверя.
Ограничимся пятью строками.
*/
#include <stdio.h> /* магическая строка */
int TOMCATS = 3; /* три кота */
int CATS = 2; /* две кошки */
int ANIMALS_PER_LINE = 6; /* шесть зверей в каждой строке */
int LINES = 5; /* число выводимых строк */
int ANIMALS; /* общее число зверей */
int nth_in_line = 0; /* номер зверя в текущей строке */
void checkIfWeHaveToBreakLine(){
nth_in_line++;
if(nth_in_line == ANIMALS_PER_LINE){
putchar('\n');
nth_in_line = 0;
} else
printf("\t\t"); /* @ */
/* Одинокий оператор может обойтись без {...} вокруг него */
}
void main(){
int nanimal;
int i;
ANIMALS = ANIMALS_PER_LINE * LINES;
nanimal = 0;
while(nanimal < ANIMALS){
for(i=0; i < TOMCATS; i++){
/* Формат %d выводит значение переменной типа int
в виде текстовой строки.
Сама переменная должна быть в списке после формата
(список - это перечисление переменных через запятую).
Переменных ИЛИ выражений (формул).
Давайте выводить по ДВЕ табуляции --
это место отмечено в функции checkIfWeHaveToBreakLine()
как @.
Еще раз внимание - один символ мы выводим как
putchar('a');
Несколько символов - как
printf("abcdef");
Одиночные кавычки - для одной буквы.
Двойные кавычки - для нескольких.
*/
printf("кот%d", nanimal);
nanimal++;
checkIfWeHaveToBreakLine();
}
for(i=0; i < CATS; i++){
printf("кошка%d", nanimal);
nanimal++;
checkIfWeHaveToBreakLine();
}
}
}
/* Задача: напечатать корни из чисел от 1 до 100.
Новая информация:
Нам понадобится новый тип данных - ДЕЙСТВИТЕЛЬНЫЕ ЧИСЛА.
Это числа, имеющие дробную часть (после точки).
Как мы уже знаем, целые - это int.
буква - это char.
действительное число - это double.
(есть еще слово float, но им пользоваться не рекомендуется).
Для вычисления корня используется итерационный алгоритм Герона.
q = корень из x;
q[0] := x;
q[n+1] := 1/2 * ( q[n] + x/q[n] );
Главное тут не впасть в ошибку, не клюнуть на q[n] и не
завести массив. Нам не нужны результаты каждой итерации,
нам нужен только конечный ответ. Поэтому нам будет вполне
достаточно ОДНОЙ, но изменяющейся в цикле, ячейки q.
*/
#include <stdio.h>
/* Еще одно новое ключевое слово - const. Обозначает константы.
В отличие от переменных, такие имена нельзя изменять.
То есть, если где-то потом попробовать написать epsilon = ... ;
то это будет ошибкой.
*/
const double epsilon = 0.0000001; /* точность вычислений */
/* Функция вычисления модуля числа */
double doubleabs(double x){
if(x < 0) return -x;
else return x;
}
/* Функция вычисления квадратного корня */
double sqrt(double x){
double sq = x;
/* Такая конструкция есть просто склейка двух строк:
double sq;
sq = x;
Называется это "объявление переменной с инициализацией".
*/
while(doubleabs(sq*sq - x) >= epsilon){
sq = 0.5 * (sq + x/sq);
}
return sq;
}
void main() {
int n;
for(n=1; n <= 100; n++)
printf("sqrt(%d)=%lf\n",
n, sqrt((double) n)
);
}
/*
Здесь в операторе printf() мы печатаем ДВА выражения.
ФОРМАТ ЗНАЧЕНИЕ
------ --------
%d -- n
%lf -- sqrt((double) n)
По формату %d печатаются значения типа int.
По формату %c печатаются значения типа char.
По формату %lf (или %g) печатаются значения типа double.
Что значит "напечатать значение выражения sqrt(xxx)" ?
Это значит:
- вызвать функцию sqrt() с аргументом, равным xxx;
- вычислить ее;
- возвращенное ею значение напечатать по формату %lf,
то есть как действительное число.
Заметьте, что тут возвращаемое значение НЕ присваивается
никакой переменной, мы не собираемся его хранить.
Точно так же, как в операторе x = 12 + 34;
12 и 34 не хранятся ни в каких переменных,
а оператор
printf("%d\n", 12);
печатает ЧИСЛО 12, а не переменную.
Точно так же, как можно писать
double z;
z = sqrt(12) + sqrt(23);
где значение, вычисленное каждой функцией, НЕ хранится
в своей собственной переменной (такая переменная на самом
деле существует в компьютере, но программисту она не
нужна и недоступна).
Или
z = sqrt( sqrt(81));
(корень из корня из 81 --> даст 3)
Далее, что означает конструкция (double) n ?
Функция sqrt() требует аргумента типа double.
Мы же предлагаем ей целый аргумент
int n;
Целые и действительные числа представлены в памяти
машины ПО-РАЗНОМУ,
поэтому числа
12 и 12.0 хранятся в памяти ПО-РАЗНОМУ.
Машина умеет преобразовывать целые числа в действительные
и наоборот, надо только сказать ей об этом.
Оператор (double) x
называется "приведение типа к double".
Заметим, что часто преобразование типа
выполняется автоматически.
Так, например, при сложении int и double
int автоматически приводится к double, и результат
имеет тип double.
int var1;
double var2, var3;
var1 = 2;
var2 = 2.0;
var3 = var1 + var2;
что означает на самом деле
var3 = (double) var1 + var2;
var3 станет равно 4.0
Более того, к примеру тип char - это тоже ЦЕЛЫЕ ЧИСЛА из интервала
0...255. Каждая буква имеет код от 0 до 255.
*/
УКАЗАТЕЛИ
=========
void f(int x){
x = 7;
}
main(){
int y = 17;
f(y);
printf("y=%d\n", y); /* печатает: y=17 */
}
В аргументе x передаётся КОПИЯ значения y,
поэтому x=7; не изменяет значения у.
Как все же сделать, чтобы вызываемая функция
могла изменять значение переменной?
Отбросим два способа:
- объявление y как глобальной
(много глобальных переменных - плохой стиль),
- y=f(y);
(а что если надо изменить МНОГО переменных?
return, к несчастью, может вернуть лишь одно значение).
Используется новая для нас конструкция: УКАЗАТЕЛЬ.
--------------------------------------------------
Пример (@)
void f(int *ptr){ /* #2 */
*ptr = 7; /* #3 */
}
main (){
int y=17;
f(&y); /* #1 */
printf("y=%d\n", y); /* печатает: y=7 */
}
Ну как, нашли три отличия от исходного текста?
----------------------------------------------------------------------
Мы вводим две новые конструкции:
&y "указатель на переменную y" или
"адрес переменной y"
*ptr означает "разыменование указателя ptr"
(подробнее - позже)
int *ptr; означает объявление переменной ptr,
которая может содержать в себе
указатель на переменную,
хранящую int-число.
Для начала определим, что такое указатель.
int var1, var2, z; /* целочисленные переменные */
int *pointer; /* указатель на целочисленную переменную */
var1 = 12;
var2 = 43;
pointer = &var1;
Мы будем изображать указатель в виде СТРЕЛКИ;
это хороший прием и при практическом программировании.
________
/pointer/
_/_______/_
| значение| Переменная, хранящая указатель
| есть | (адрес другой переменной)
| |
| &var1 |
| |
|_______|_|
|
|&var1 - сам указатель на var1 -
| "стрелка, указывающая на переменную var1".
V Она обозначается &var1
________
/ var1 /
_/_______/_
| значение|
| есть |
| 12 |
|_________|
Таким образом, УКАЗАТЕЛЬ - это "стрелка, указывающая на некий ящик-переменную".
Начало этой стрелки можно (в свою очередь) хранить в какой-нибудь переменной.
При этом, если стрелка указывает на переменную типа int,
то тип переменной, хранящей начало стрелки, есть int *
Если типа char, то тип - char *
АДРЕС (указатель на) можно взять только от переменной или элемента массива,
но не от выражения.
int x;
int arr[5];
Законно &x стрелка на ящик "x"
& arr[3] стрелка на ящик "arr[3]"
Незаконно &(2+2) тут нет именованного "ящика",
на который указывает стрелка,
да и вообще ящика нет.
Указатели несколько различно ведут себя СЛЕВА и СПРАВА
от оператора присваивания.
Нас интересует новая операция, применяемая только к указателям:
*pointer
--------------------------------------------------------------------
СПРАВА от присваиваний и в формулах
===================================
*pointer означает
"взять значение переменной (лежащее в ящике),
на которую указывает указатель,
хранящийся в переменной pointer".
В нашем примере - это число 12.
То есть *pointer означает "пройти по стрелке и взять указываемое ею ЗНАЧЕНИЕ".
printf("%d\n", *pointer);
Печатает 12;
z = *pointer; /* равноценно z = 12; */
z = *pointer + 66; /* равноценно z = 12 + 66; */
Заставим теперь указатель указывать на другую переменную
(иначе говоря, "присвоим указателю адрес другой переменной")
pointer = &var2;
________
/pointer/
_/_______/_
| |
| &var2 |
| |
|_______|_|
|
|&var2
|
V
________
/ var2 /
_/_______/_
| |
| 43 |
| |
|_________|
После этого z = *pointer;
означает z = 43;
--------------------------------------------------------------------
Таким образом, конструкция
z = *pointer;
означает
z = *(&var2);
означает
z = var2;
То есть * и & взаимно СТИРАЮТСЯ.
*pointer = 123;
Означает "положить значение правой части (т.е. 123)
в переменную (ящик), на который указывает указатель,
хранящийся в переменной 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].
Это обратное следствие из схожести массивов и указателей.
/* Задача: написать функцию инвертирования порядка символов
в массиве 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);
}
/* Еще более простой вариант решения:
просто обменивать элементы местами.
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);
}
/* Еще один вариант решения:
сформировать ответ в дополнительном массиве,
а потом скопировать его на прежнее место.
*/
#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);
}
/* Задача инвертирования массива целых чисел */
#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);
}
/* Задача: написать функцию для распечатки массива целых чисел
в виде таблицы в 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');
}
}
#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'); /* новая строка */
}
}
/*
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;
}
/* ДВУМЕРНЫЕ МАССИВЫ */
/*
Двумерный массив представляет собой двумерную
прямоугольную таблицу из нумерованных переменных.
Он объявляется так:
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;
}