Руководство полного идиота по программированию (на языке Си) |
Написал microsin | |
29.06.2008 | |
Copyright Андрей Богатырев abs@opentech.olvit.ru.
--------------------------------------------------------------------------- ПЕРЕМЕННЫЕ
Переменная - это такой "ящичек" с именем, Например, пусть у нас есть переменная с именем "x".
-----
Переменную можно изменять при помощи операции ПРИСВАИВАНИЯ. x = 12 ;
Это читается не как "икс равно 12",
Такая строка является простейшим ОПЕРАТОРОМ, Рассмотрим оператор x = x + 3;
Это не уравнение. Если рассматривать эту строку как математическое
1) "взять значение переменной ИКС" У опреатора присваивания есть две части: ЛЕВАЯ и ПРАВАЯ. ЛЕВАЯ_ЧАСТЬ = ПРАВАЯ_ЧАСТЬ ;
В левой части обычно стоит просто имя переменной В КОТОРУЮ
Если имя переменной встречается в ПРАВОЙ части, то это
При этом текущее значение самой переменной НЕ изменяется, Итак: x = x + 3 ; Пусть сейчас значение x есть 12 Сначала вычисляется ПРАВАЯ часть оператора присваивания. x + 3
-----
Сложение дает 15. Теперь вычисляется само присваивание: x = 15 ;
|
Стало:
В переменной с тем же именем теперь находится новое значение,
В некоторых языках программирования, например в Pascal или Modula, В правой части значение переменной может использоваться несколько раз: z = x * x + 2 * x; Тут есть две переменные:
z - для результата.
x * x означает "умножить икс на икс" (при этом само значение, x * 2 означает "взять два значения икс" + означает сложение.
Переменные надо ОБЪЯВЛЯТЬ. иднекс = 1; вместо индекс = 1;
то у нас появилась бы "лишняя" переменная "иднекс", а ожидаемое действие не
Переменные, которые будут хранить целые числа ( ..., -2, -1, 0, 1, 2, 3, ...),
int переменная1; Или сразу несколько в одной строке: int переменная1, переменная2; int означает сокращение от слова integer - "целый". ПРОГРАММА
Программа состоит из ОПЕРАТОРОВ, то есть действий.
/* ОБЪЯВЛЯЕМ ДВЕ ПЕРЕМЕННЫЕ */
/* Это еще не операторы, хотя при этом создаются 2 ящика для
/* А ТЕПЕРЬ - ОПЕРАТОРЫ. */
x = 3; /* 1 */ Значения переменных (то, что лежит в ящиках) меняются таким образом:
x y
/* после 1 */ 3 мусор
Как вы видите, переменные, которые не участвуют в левой части оператора
Последняя операция x = y; НЕ делает имена x и y синонимами.
----- -----
1) Из ящика y берется КОПИЯ числа 3 (безымянное значение). Значение целой переменной можно вывести на экран оператором печати: printf("%d\n", x); Пока будем рассматривать его как "магический". Над целыми числами можно производить такие арифметические операции:
x + y сложение
5 / 2 даст 2 В операторах присваивания используются такие сокращения:
ДЛИННАЯ ЗАПИСЬ СМЫСЛ СОКРАЩАЕТСЯ ДО В том числе x++; можно записать как x += 1; * СТРУКТУРЫ УПРАВЛЕНИЯ *
Обычно операторы выполняются последовательно,
оператор1; | УСЛОВНЫЙ ОПЕРАТОР if(условие) оператор; ...продолжение... Работает так: Вычисляется условие.
Если оно истинно, то выполняется оператор,
Если оно ложно, то сразу выполняется продолжение,
Если нам надо выполнить при истинности условия несколько операторов,
if(условие) { После } точка с запятой НЕ СТАВИТСЯ (можно и поставить, но не нужно). Условный оператор изображают на схемах так:
| Имеется вторая форма, с частью "иначе":
if(условие) оператор_если_истинно; "или то, или другое" (но не оба сразу)
| Пример1:
if(x > 10) Пример2: int x, y, z;
if(x < y) z = 1; Условия:
В качестве условий могут использоваться операторы СРАВНЕНИЯ
x < y меньше
Все эти операторы в качестве результата операции сравнения выдают Таким образом, на самом деле условный оператор работает так: if(условие) ....
Если условие есть НОЛЬ - то условие считается ложным. Это определение. Из него в частности вытекает, что сравнение с целым нулем можно опускать:
if(x != 0) ... ; сокращается до if(x) ... ; Пример: int x, y, z;
if(x == 1){ y = 2; z = x + y; } --------------------------------------------------------------------------- Пример со вложенными условными операторами:
if(x == 1){
Часто применяется последовательность условных операторов,
if(x == 1)
Самое сложное - привыкнуть к тому, что сравнение обозначается знаком ==, ЦИКЛ while ("до тех пор, пока истинно")
while(условие) или
while(условие){
|
Пример:
x = 10; "Цикл" он потому, что его тело повторяется несколько раз.
Чтобы цикл окончился, оператор-тело цикла должен менять ОПЕРАТОРЫ "И, ИЛИ, НЕ" Условия могут быть сложными.
ЕСЛИ красный И вес < 10 ТО ...; На языке Си такие условия записываются так:
if(условие1 && условие2) ...; /* "И" */ Например: if(4 < x && x <= 12) ...; Было бы неправильно записать if(4 < x <= 12) ...; ибо язык программирования Си НЕ ПОНИМАЕТ двойное сравнение! Еще примеры: if(x < 3 || y > 4) ...; if( ! (x < 3 || y > 4)) ...; ЦИКЛ for ("для каждого")
Этот цикл является просто иной записью одного из вариантов цикла while. У такого цикла есть "переменная цикла" или "счетчик повторений". int i; i = a; /* начальная инициализация */ while(i < b){ тело_цикла;
i += c; /* увеличение счетчика */ переписывается в виде int i;
for(i=a; i < b; i += c)
тело_цикла будет выполнено для значений i пока i < b В простейшем случае
for(i=1; i <= N; i++) i означает "номер повторения".
Такой цикл служит для повторения СХОЖИХ действий НЕСКОЛЬКО раз ОПЕРАТОР break ("вывалиться из цикла")
Оператор break заставляет прервать выполнение тела цикла
while(условие1){
if(условие2) и
for(i=0; условие1; i++){
if(условие2)
Этот оператор позволяет организовывать дополнительные Пример:
for(i=0; i < 20; i++){ В частности, с его помощью можно организовывать бесконечный цикл:
for(;;){ /* заголовок бесконечного цикла */
if(условие2)
Здесь в самом заголовке цикла НЕ ПРОВЕРЯЕТСЯ НИКАКИХ УСЛОВИЙ,
Единственный способ выйти из него -
Бесконечный цикл можно также организовать при помощи ОПЕРАТОР ВЫВОДА (ПЕЧАТИ) printf("текст"); Печатает на экран текст. printf("текст\n"); Печатает на экран текст и переходит к новой строке.
printf("слово1 слово2 "); печатает
слово1 слово2 слово3
Если переход на новую строку не задан явно, символом \n, printf("%d", x);
Печатает в текстовом виде ЗНАЧЕНИЕ переменной x. printf("икс равен %d - ого-го\n", x); Печатает сначала текст икс равен
затем значение переменной x как целое число, и переходит на новую строку (поскольку указан символ \n). Этот оператор может печатать и несколько значений переменных: int x, y;
x = 12; y = 15;
Данный оператор работает так. икс есть _
Далее он берет ПЕРВУЮ переменную из списка ~~~~ и икс есть 12_ далее он снова печатает текст пока не встретит %d икс есть 12, игрек есть _ Теперь он берет ВТОРУЮ переменную из списка и печатает ее: икс есть 12, игрек есть 15_
Снова печатает текст, включая перевод строки \n.
икс есть 12, игрек есть 15, все.
Печатать можно не только значения переменных, но и значения арифметических printf("равно: %d\n", 12 + 3 * 5); Контрольный вопрос, что печатается: int x, y, z;
x = 13; printf("x=%d xx=%d\nzzz=%d\n", x, y - 1, z * 2 + 1);
Тут в формате есть ДВА перевода строки,
x=13 xx=22
Заметьте, что перед тем как быть напечатанными, Что напечатает printf("x=%d\n y=%d\n", x, y);
x=13
Пробел перед y возник потому, что он СОДЕРЖИТСЯ ФУНКЦИИ
Функцией называется фрагмент программы,
Прелесть функции в том, что ее можно выполнить много раз Функция состоит из
ОБЪЯВЛЕНИЯ - описания того, как она что-то вычисляет
ВЫЗОВОВ - с конкретными значениями параметров, Объявление простейшей функции выглядит так: int func(int x){
/* Один или несколько операторов,
return x+1; --------------------------------------------------------------------------- int func(...
задает функцию с именем func
int означает, что функция возвращает целое значение. ...(int x)...
задает список аргументов (или параметров) функции.
...){
задает тело функции - некую последовательность объявлений return выражение;
задает оператор выхода из функции в точку ее вызова с возвратом значения Покажем простой пример ВЫЗОВА этой функции:
int y; Этот фрагмент работает следующим образом: y = func(5);
В этой точке мы 2) Смотрим на ОПРЕДЕЛЕНИЕ функции func. int func(int x){...
Мы вызвали функцию как func(5). То есть ДЛЯ ДАННОГО ВЫЗОВА наша функция (ее тело) превращается в int x;
x = 5; 3) x+1 есть 6. Далее должен выполниться оператор return. Он выполняется так:
Мы "читаем с бумажки" - откуда была вызвана функция func, y = func(5);
Вычеркиваем func(5) и заменяем его ЗНАЧЕНИЕМ выражения, y = 6; 4) Выполняем этот оператор и переходим к продолжению. --------------------------------------------------------------------------- int y, z, w;
y = func(5); Превратится в
y = 6;
При этом мы четыре раза "прыгнем" на определение функции func(), ПРОГРАММА В ЦЕЛОМ
Программа в целом состоит из функций. С ФУНКЦИИ main НАЧИНАЕТСЯ ВЫПОЛНЕНИЕ ПРОГРАММЫ.
(на самом деле этому предшествует отведение и инициализация Часто main() - единственная функция в программе. --------------------------------------------------------------------------- Структура программы такова: #include /* магическая строка */
/* ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ (о них позже) */
/* ФУНКЦИИ */
/* НАЧАЛЬНАЯ (ГЛАВНАЯ) ФУНКЦИЯ */ Пример программы: #include
int f1(int x, int y){
int f2(int x){
z = x+7;
void main(){
/* Операторы */
c = f1(a, b+3);
printf("A есть %d B есть %d C есть %d\n", a, b, c); Она печатает: A есть 60 B есть 5 C есть 23 КАК НЕ НАДО ПРОГРАММИРОВАТЬ ЦИКЛЫ int i;
for(i=0; i < 4; i++){
В данном примере цикл АБСОЛЮТНО НЕ НУЖЕН.
func0();
Цикл имеет смысл лишь тогда, когда много раз вызывается Аналогично, рассмотрим такой пример: int i;
for(i=0; i < 10; i++){
Тут funcN(i) берет на себя роль "а в остальных случаях". int i;
func0(); Заметьте, что цикл теперь начинается с индекса 3. А теперь - случай, где смесь цикла и условного оператора оправдана: int i;
for(i=0; i < 100; i++){
Тут в цикле проверяется четность индекса i. /* Треугольник из звездочек */ #include
/* putchar('c') - печатает одинокий символ c */
/* Функция рисования одной строки треугольника */
for(i=0; i < nstars; i++) /* Рисуем nstars звездочек подряд */
void main(){
/* ВЫПОЛНЯЕМЫЕ ОПЕРАТОРЫ (ДЕЙСТВИЯ) */
/* Треугольник из звездочек */ #include
void main(){
/* ВЫПОЛНЯЕМЫЕ ОПЕРАТОРЫ (ДЕЙСТВИЯ) */
/* Треугольник из звездочек */ #include
/* nstars - сколько звездочек напечатать */
void drawOneLine(int nspaces, int nstars){
for(i=0; i < nspaces; i++)
/*
Всего строк: LINES Все эти числа подсчитываются с картинки...
Их мы будем передавать в функцию drawOneLine в точке _вызова_,
В качестве параметра в точке вызова можно передавать не
*/
/* ВЫПОЛНЯЕМЫЕ ОПЕРАТОРЫ (ДЕЙСТВИЯ) */
/* Треугольник из звездочек */ #include
void drawOneLine(int nspaces, int nstars){
for(i=0; i < nspaces; i++)
void main(){
/* Для человека естественно считать с 1.
Он тоже выполнится 25 раз, но значение переменной-счетчика
n (номер строки)
Всего строк: LINES */
/* ВЫПОЛНЯЕМЫЕ ОПЕРАТОРЫ (ДЕЙСТВИЯ) */
/* char (от слова character). Буквы изображаются в одиночных кавычках 'a' 'b' '+'.
Пример:
letter = 'a';
Символ '\n' обозначает "невидимую букву" -
Зато сразу сделаем оговорку.
putchar('\'); или Надо: putchar('\\'); printf("\\");
Дело в том, что символ \ начинает последовательность из ДВУХ букв,
/* (x % n) == 0
В частности, так можно проверять числа на четность/нечетность,
Остатки от деления числа x на n */
/* Задача:
Решение: используем прежнюю программу,
Далее в основном цикле используем условный оператор и */ #include
void drawOneLine(int nspaces, int nsymbols, char symbol){
for(i=0; i < nspaces; i++)
/* Мы вынесем объявление этой переменной из функции,
void main(){
/* ВЫПОЛНЯЕМЫЕ ОПЕРАТОРЫ (ДЕЙСТВИЯ) */
if((nline % 2) == 0) /* четное ? */
/* То же самое, но теперь нужно еще и печатать номер строки. #include
/* Вообще-то глобальные переменные int LINES = 25; /* всего строк. */
/* Добавим к функции еще один аргумент, указатель - печатать ли
Оператор if(x) .....;
Второй добавленный аргумент - собственно номер строки.
if(drawLineNumber) /* На самом деле это условие более полно надо записывать как if(drawLineNumber != 0)
но в языке Си это то же самое.
/* Тут мы снова видим новый специальный символ \t - ТАБУЛЯЦИЯ.
| | | | | | | | | ...
*/
void main(){
/* ВЫПОЛНЯЕМЫЕ ОПЕРАТОРЫ (ДЕЙСТВИЯ) */
if((nline % 2) == 0) /* четное ? */
/* А почему именно 1 или именно 9 ?
/* Следующая задача будет касаться того,
Тут нам уже придется модифицировать функцию рисования строки. #include int LINES = 25; /* всего строк. */
void drawOneLine(int nspaces, int nsymbols){
for(i=0; i < nspaces; i++)
/* в цикле мы будем проверять на четность НОМЕР
void main(){
for(nline=0; nline < LINES; nline++) {
/* Задача нарисовать РОМБ: #include int LINES = 10; /* всего строк в половине ромба. */
void drawOneLine(int nspaces, int nsymbols){
for(i=0; i < nspaces; i++)
for(i=0; i < nsymbols; i++)
void main(){
for(nline=0; nline < LINES; nline++)
/* Мы нарисовали треугольник.
for(nline=LINES-2; nline >= 0; nline--) /* А теперь рисуем ромб, используя математические формулы. */ #include
void draw(int nspaces, int nstars, char symbol){
for(i=0; i < nspaces; i++)
void main(){
for(nline=0; nline < MIDDLELINE; nline++)
/* У следующего цикла for() нет инициализации
for( ; nline < LINES; nline++) МАССИВЫ
Массив - это несколько пронумерованных переменных,
Рассмотрим ПОЛКУ с N ящиками, Нумерация идет с НУЛЯ.
-------- Массив объявляется так: int var[N]; здесь N - его размер, число ячеек.
Это описание как бы объявляет N переменных типа int с именами
В операторах для обращения к n-ому ящичку (где 0 <= n < N) var[n]
где n - целое значение (или значение целой переменной, Пример: int var[5]; /* 1 */
var[0] = 2; /* 2 */ printf("var третье есть %d\n", var[3]); В ходе этой программы элементы массива меняются таким образом:
var[0] var[1] var[2] var[3] var[4] Как видим, каждый оператор изменяет лишь ОДНУ ячейку массива за раз.
Массив - набор переменных, которые не ИМЕНОВАНЫ разными именами, Индекс - часть ИМЕНИ ПЕРЕМЕННОЙ.
На самом деле индексация - это
Если в переменную не было занесено значение, printf("var4 есть %d\n", var[4]); напечатает все что угодно.
Поэтому переменные надо всегда инициализировать
Глобальные переменные автоматически инициализируются нулем,
Локальные переменные не инициализируются автоматически, и содержат МУСОР. Массивы НЕЛЬЗЯ присваивать целиком, язык Си этого не умеет.
int a[5]; a = b; /* ошибка */ Также нельзя присвоить значение сразу всем элементам (ячейкам) массива: a = 0; /* ошибка */
не делает того, что нами ожидалось, а является ошибкой. int i;
for(i=0; i < 5; i++) /* для каждого i присвоить a[i] = 0; */ ---------------------------------------------------------------------------
СВЯЗЬ МАССИВОВ И ЦИКЛОВ int i;
for(i=0; i < 5; i++) В данном случае индекс цикла служит также и индексом в массиве. Индексы в массиве идут с НУЛЯ. Пример инициализации: int index, array[5];
for(index=0; index < 5; index++) или int index, array[5];
index = 0; /* В массиве будет: { 1, 3, 5, 7, 9 } */
ИНДЕКС
для циклов -
Обычно массивы и циклы совмещаются так: int a[N], i;
for(i=0; i < N; i++) Примеры: int a[5];
a[0] = 17;
Пример: числа Фибоначчи.
f[1] = 1
Вот программа:
#include /* магическая строка */
void main(){
fibs[0] = 1; /* индексы отсчитываются с нуля!!! */ /* Тут показано, что индекс элемента массива может вычисляться */
for(index=2; index < N; index++)
/* Распечатка в обратном порядке */
Здесь мы видим новый для нас оператор #define const int N = 20;
К несчастью размер массива не может быть задан при помощи переменной, СТРОКИ
Строки есть массивы БУКВ - типа char, char string[20];
string[0] = 'П'; printf("%s\n", string);
%s - формат для печати СТРОК. char string[20];
string[0] = 'П'; printf("%s", string); или даже просто printf(string); Такие массивы можно записать в виде строки букв в "" char string[20] = "Привет\n";
Оставшиеся неиспользованными символы массива от string[8] до string[19]
ПОЧЕМУ ДЛЯ СТРОК ИЗОБРЕЛИ СИМВОЛ "ПРИЗНАК КОНЦА"?
char str[32]; /* массив для строки */
Этот подход работоспособен, но строка разбивается на два
(2) Хранить текущую длину в элементе str[0],
(3) Не хранить длину НИГДЕ, а ввести символ-признак конца строки. func(str); /* ОДИН аргумент - сам массив */
передается только сам массив, а его текущая длина может быть
int strlen(char s[]){ /* функция от массива букв */
while(s[counter] != '\0') /* пока не встретился признак конца текста */
Тут никаких ограничений нет. Именно этот подход и был избран
ИНИЦИАЛИЗАЦИЯ ГЛОБАЛЬНОГО МАССИВА int array[5] = { 12, 23, 34, 45, 56 }; char string[7] = { 'П', 'р', 'и', 'в', 'е', 'т', '\0' };
Если размер массива указан БОЛЬШЕ, чем мы перечислим элементов, int array[5] = { 12, 23, 34 };
Если мы перечислим больше элементов, чем позволяет размер массива - int a[5] = { 177, 255, 133 }; Операция индексации массива a[] дает:
при n значение выражения a[n] есть
-1 не определено (ошибка: "индекс за границей массива")
КАК ПРОИСХОДИТ ВЫЗОВ ФУНКЦИИ Пусть у нас описана функция, возвращающая целое значение.
/* ОПРЕДЕЛЕНИЕ ФУНКЦИИ func(). */
int func(int a, int b, int c){
...
return(некое_значение);
Здесь
Точка вызова - находится внутри какой-то другой main(){
int zz, var; Когда выполнение программы доходит до строки zz = func(33, 77, var + 3) + 44; 1) Происходит ВЫЗОВ ФУНКЦИИ func() (a) Этот пункт мы увидим ниже. (b) Создаются переменные с именами a, b, c, x, y;
(c) Переменным-аргументам присваиваются начальные значения, func(выражение1, выражение2, выражение3)
Вычисленные значения этих выражений соответственно будут присвоены int func(a, b, c){ /* a = номер 1, b = 2, c = 3 */ Первый параметр: a = 33; Второй параметр: b = 77; Третий параметр: c = var + 3; то есть, вычисляя, c = 20;
Локальные переменные x и y содержат неопределенные значения,
2) Выполняется ТЕЛО функции, то есть вычисления, записанные внутри { ... } x = a + 7;
И параметры, и локальные переменные - это ПЕРЕМЕННЫЕ, b = b + 4;
При этом никакие переменные ВНЕ этой функции не изменяются. 3) Производится ВОЗВРАТ из функции.
... Например, это может быть
... Рассмотрим, что при этом происходит в точке вызова: zz = func(33, 77, var + 3) + 44; (1) Вычеркиваем func(.....) zz = XXXXXXX + 44;
(2) Вычисляем значение "некое_значение" в операторе return,
(3) Подставляем это значение на место вычеркнутого func(.....) zz = 128 + 44; (4) АВТОМАТИЧЕСКИ УНИЧТОЖАЮТСЯ локальные переменные и аргументы функции:
a - убито Таких переменных (и их значений) больше нет в природе. (5) Пункт, который мы обсудим позже. (6) Продолжаем вычисление: zz = 128 + 44; Вычисляется в zz = 172; /* оператор присваивания */ ---------------------------------------------------------------------------
int func1(int x){
void main(){
var = 111;
printf("main: var=%d\n", var); /* 3 */
В данном случае в точке @ мы передаем в функцию func1() x = 111; Поэтому первый оператор printf() напечатает 111.
Затем мы изменяем значение переменной x на 77.
Поэтому второй оператор printf() напечатает 77. ---------------------------------------------------------------------------
ВРЕМЕННОЕ СОКРЫТИЕ ПЕРЕМЕННЫХ
int func1(int x){ /* f.1 */
void main(){
x = 111; /* 2 */
printf("main: x=%d y=%d\n", x, y); /* 4 */
А теперь мы и переменную внутри main(), и аргумент функции Будет то же самое, что в предыдущем примере.
В момент вызова функции func1() будет создана НОВАЯ переменная
Можно было бы уточнить эти переменные именами функций, main::x и func1::x (но это уже конструкции из языка Си++, а не Си). Выполним программу по операторам:
|/* 1 */ Отводятся переменные main::x и main::y для целых чисел;
+-------+
Этот механизм сокрытия имен позволяет писать функции main() и func1() ---------------------------------------------------------------------------
То же самое происходит с локальными переменными,
int func1(int arg){ /* локальная переменная-параметр func1::arg */
x = arg;
void main(){
x = 111;
printf("main: x=%d y=%d\n", x, y);
Действует тот же самый механизм временного сокрытия имени x.
имя_функции(..., ..., ....) в месте вызова функции. То есть ОПИСАНИЕ ФУНКЦИИ:
int f(int арг1, int арг2, int арг3){ ВЫЗОВ: .... f(выражение1, выражение2, выражение3) ... ТО В ТЕЛЕ ФУНКЦИИ ВЫПОЛНИТСЯ (в момент ее вызова):
арг1 = выражение1;
перем1 = МУСОР;
... ---------------------------------------------------------------------------
ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
Наконец, существуют переменные, которые объявляются ВНЕ ВСЕХ ФУНКЦИЙ,
Локальные переменные и аргументы УНИЧТОЖАЮТСЯ при выходе
int x = 12; /* ::x - ей можно заранее присвоить константу */
int f1(){
x = 77;
int f2(){
void main(){
x = 111; /* 1 */
y = f1();
В данном примере мы видим: Как выполняется программа?
/* 1 */ main::x = 111;
/* 2 */ Напечатает значение переменной main::x, то есть 111. К переменной же globvar у нас доступ есть.
/* 3 */ Печатает ::globvar. Мы обнаруживаем, что ее значение 0. В рамочку, подчеркнуть.
/* 4 */ При вызове f1()
В данном случае напечатается 77,
/* 5 */ При вызове f2() история интереснее.
Ответ: ::x
Переменные названы локальными еще и потому,
Это ОПРЕДЕЛЕНИЕ локальных переменных. То есть, если мы имеем
funca(){ то из функции funcb() мы НЕ ИМЕЕМ ДОСТУПА К ПЕРЕМЕННОЙ vara.
funcb(){
z = vara + 1; /* ошибка,
Если, в свою очередь, funcb() вызывает funcc(),
Остановитесь и осознайте.
Вернемся к параграфу КАК ПРОИСХОДИТ ВЫЗОВ ФУНКЦИИ
(a) Локальные переменные и аргументы вызывающей функции делаются невидимыми. (5) Локальные переменные и аргументы вызывающей функции снова делаются видимыми.
ОДНАКО глобальные переменные видимы из ЛЮБОЙ функции, ---------------------------------------------------------------------------
ПРОЦЕДУРЫ
void func(){
Такие функции вызываются ради "побочных эффектов", int glob;
void func(int a){
Оператор return тут необязателен, он автоматически выполняется
Вызов таких функций не может быть использован
main(){
z = func(7); /* ошибка, а что мы присваиваем ??? */ Корректный вызов таков:
main(){ Просто вызов и все. ЗАЧЕМ ФУНКЦИИ? Чтобы вызывать их с разными аргументами!
int res1, res2;
res1 = func(12 * x * x + 177, 865, 'x');
Кстати, вы заметили, что список фактических параметров
Функция описывает ПОСЛЕДОВАТЕЬНОСТЬ ДЕЙСТВИЙ,
В том то и состоит ее прелесть:
Функция - абстракция АЛГОРИТМА, то есть последовательности действий.
Оператор return может находиться не только в конце функции,
int f(int x){
y = x + 4; РЕКУРСИВНЫЕ ФУНКЦИИ. СТЕК Рекурсивной называется функция, вызывающая сама себя.
int factorial(int arg){ Эта функция при вызове factorial(n) вычислит произведение n * (n-1) * ... * 3 * 2 * 1
называемое "факториал числа n". Так что переменная factorial::arg должна получить еще и НОМЕР вызова функции: factorial::arg[уровень_вызова]
И на каждом новом уровне новая переменная скрывает все предыдущие.
+----------------------------------------+ Затем пойдут возвраты из функций:
+----------------------------------------+ Такая конструкция называется СТЕК (stack).
--------+ +------------
Положим в стек значение a Положим в стек значение b
--------+ +------------ Положим в стек значение c
--------+ +------------ Аналогично, значения "снимаются со стека" в обратном порядке: c, b, a.
В каждый момент времени у стека доступно для чтения (копирования) или
Так и в нашей рекурсивной функции переменная factorial::arg
Стек - это часто встречающаяся в программировании конструкция.
int stack[10];
/* Занесение значения в стек */
/* Снять значение со стека */
Обратите в нимание, что нет нужды СТИРАТЬ (например обнулять)
void main(){
while(in_stack > 0){ СТЕК И ФУНКЦИИ
Будем рассматривать каждый ВЫЗОВ функции как помещение в специальный стек Оператор return из вызванной функции выталкивает со стека ВЕСЬ такой блок. В качестве примера рассмотрим такую программу:
int x = 7; /* глобальная */
int factorial(int n){ w = n;
if(n == 1) return 1; /* #a */
x = 777; /* #c */
/* A */
func(); /* C */
Выполнение программы начнется с вызова функции main().
| в з г л я д |
--------+ +--------
В каждый данный момент видимы переменные, которые находятся main::z, main::y, ::x, ::v --------------------------------------------------------------------------- В точке /* B */ мы вызываем factorial(3).
--------+ +--------
При новом взгляде видимы:
Стали невидимы:
Строка "текущий оператор ..." указывает место, с которого надо возобновить
Когда выполнение программы в функции factorial(3) дойдет до точки
--------+ +-------- ---------------------------------------------------------------------------
Когда выполнение программы в функции factorial(2) дойдет до точки
--------+ +-------- ---------------------------------------------------------------------------
Затем в factorial(1) выполнение программы дойдет до точки /* #a */
При return вычеркивается ОДИН блок информации со стека вызовов функций,
--------+ +-------- --------------------------------------------------------------------------- Начинается выталкивание функций со стека и выполнение операторов return;
--------+ +--------
--------+ +--------
--------+ +--------
--------+ +-------- ---------------------------------------------------------------------------
Наконец, в точке /* C */ будет вызвана функция func().
--------+ +--------
В данном месте нас интересует - какие переменные видимы?
И все.
Многие функции более естественно выражаются через рекурсию.
int fibonacci(int n){ /* else */
return fibonacci(n-1) + fibonacci(n-2);
Поскольку тут отсутствует массив для запоминания промежуточных int called = 0;
int fibonacci(int n){
} else if(n==2)
return fibonacci(n-1) + fibonacci(n-2);
Она была вызвана... 2584 раза!
/* Рисуем хитрую геометрическую фигуру */ const int LINES = 15;
void draw(int nspaces, int nstars, char symbol){
for(i=0; i < nspaces; i++)
void main(){
symbols[0] = '\\';
for(nline=0; nline < LINES; nline++){
/* Переход на новую строку вынесен
/* Задача:
кот кот кот кошка кошка кот
Где идет последовательность #include /* магическая строка */
/* Объявление глобальных переменных.
int TOMCATS = 3; /* три кота */
/* и нам понадобится еще одна переменная - общее число зверей.
int nth_in_line = 0; /* номер зверя в текущей строке */
/* Функция, которая считает число зверей в одной строке
if(nth_in_line == ANIMALS_PER_LINE){
void main(){
ANIMALS = ANIMALS_PER_LINE * LINES; while(nanimal < ANIMALS){
for(i=0; i < TOMCATS; i++){
/* и проверить - не надо ли перейти на новую строку ? */
/* и проверить - не надо ли перейти на новую строку ? */
/* #include /* магическая строка */
int TOMCATS = 3; /* три кота */ int nth_in_line = 0; /* номер зверя в текущей строке */
void checkIfWeHaveToBreakLine(){
if(nth_in_line == ANIMALS_PER_LINE){
/* Одинокий оператор может обойтись без {...} вокруг него */
void main(){
ANIMALS = ANIMALS_PER_LINE * LINES; while(nanimal < ANIMALS){
for(i=0; i < TOMCATS; i++){
Давайте выводить по ДВЕ табуляции --
Еще раз внимание - один символ мы выводим как
Одиночные кавычки - для одной буквы.
printf("кот%d", nanimal);
checkIfWeHaveToBreakLine();
checkIfWeHaveToBreakLine(); /* Задача: напечатать корни из чисел от 1 до 100.
Новая информация: Для вычисления корня используется итерационный алгоритм Герона. q = корень из x;
q[0] := x;
Главное тут не впасть в ошибку, не клюнуть на q[n] и не #include
/* Еще одно новое ключевое слово - const. Обозначает константы.
/* Функция вычисления модуля числа */
/* Функция вычисления квадратного корня */ double sq = x;
/* Такая конструкция есть просто склейка двух строк:
while(doubleabs(sq*sq - x) >= epsilon){
void main() {
for(n=1; n <= 100; n++) }
/*
По формату %d печатаются значения типа int.
Что значит "напечатать значение выражения sqrt(xxx)" ?
Заметьте, что тут возвращаемое значение НЕ присваивается
Точно так же, как в операторе x = 12 + 34; printf("%d\n", 12); печатает ЧИСЛО 12, а не переменную. Точно так же, как можно писать double z; z = sqrt(12) + sqrt(23);
где значение, вычисленное каждой функцией, НЕ хранится z = sqrt( sqrt(81)); (корень из корня из 81 --> даст 3)
Далее, что означает конструкция (double) n ? int n;
Целые и действительные числа представлены в памяти 12 и 12.0 хранятся в памяти ПО-РАЗНОМУ.
Машина умеет преобразовывать целые числа в действительные
Заметим, что часто преобразование типа
Так, например, при сложении int и double
int var1;
var1 = 2; что означает на самом деле var3 = (double) var1 + var2; var3 станет равно 4.0
Более того, к примеру тип char - это тоже ЦЕЛЫЕ ЧИСЛА из интервала
*/
УКАЗАТЕЛИ
main(){
В аргументе x передаTтся КОПИЯ значения y, Отбросим два способа:
- объявление y как глобальной
- y=f(y);
Используется новая для нас конструкция: УКАЗАТЕЛЬ. Пример (@)
void f(int *ptr){ /* #2 */
main (){
f(&y); /* #1 */
Ну как, нашли три отличия от исходного текста? Мы вводим две новые конструкции:
&y "указатель на переменную y" или
*ptr означает "разыменование указателя ptr"
int *ptr; означает объявление переменной ptr, Для начала определим, что такое указатель.
int var1, var2, z; /* целочисленные переменные */
var1 = 12;
Мы будем изображать указатель в виде СТРЕЛКИ;
Таким образом, УКАЗАТЕЛЬ - это "стрелка, указывающая на некий ящик-переменную".
При этом, если стрелка указывает на переменную типа int, Если типа char, то тип - char *
АДРЕС (указатель на) можно взять только от переменной или элемента массива,
int x;
Законно &x стрелка на ящик "x"
Незаконно &(2+2) тут нет именованного "ящика", ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ
Указатели несколько различно ведут себя СЛЕВА и СПРАВА *pointer ---------------------------------------------------------------------------
СПРАВА от присваиваний и в формулах В нашем примере - это число 12. То есть *pointer означает "пройти по стрелке и взять указываемое ею ЗНАЧЕНИЕ". printf("%d\n", *pointer); Печатает 12;
z = *pointer; /* равноценно z = 12; */
Заставим теперь указатель указывать на другую переменную pointer = &var2;
________
После этого z = *pointer; --------------------------------------------------------------------------- Таким образом, конструкция z = *pointer; означает z = *(&var2); означает z = var2; То есть * и & взаимно СТИРАЮТСЯ. СЛЕВА от присваивания... *pointer = 123;
Означает "положить значение правой части (т.е. 123) Пройти по стрелке и положить значение в указываемую переменную.
В данном случае *pointer обозначает
________
pointer = &var2; означает *(&var2) = 123; означает var2 = 123; То есть снова * и & взаимно СТИРАЮТ друг друга. --------------------------------------------------------------------------- ЕщT пример: *pointer = *pointer + 66; или *pointer += 66; --------------------------------------------------------------------------- Вернемся к примеру с функцией (@). Как он работает?
В строке /* #1 */
В строке /* #2 */
В строке /* #3 */ что следует рассматривать как *(&y) = 7; точнее *(&main::y)=7; то есть как y = 7; точнее main::y=7; Что и хотелось.
При этом отметим, что само имя "y" этой переменной --------------------------------------------------------------------------- ПРИМЕР: обмен значений двух переменных.
void main(){ x=1; y=2;
temporary=x; x=y; y=temporary; Теперь то же с использованием адресов и указателей:
void swap(int *a, int *b){
tmp = *a; *a = *b; *b = tmp;
void main(){
x = 1; y = 2; ЕщT пример:
int x;
ptr1 = &x; ptr2 = &x;
То есть на одну переменную МОГУТ указывать несколько указателей.
ЕщT пример: x = *ptr1;
В ptr1 нет указателя ни на что, там есть мусор. Мораль: ВСЕГДА инициализируй переменные, указатели в том числе. МАССИВЫ
Язык Си работает с именами массивов специальным образом. int a[5]; является на самом деле указателем на его нулевой элемент.
То есть у нас есть переменные (ящики) a[0] a[1] ... a[4]. При этом само имя a при его использовании в программе означает &a[0]
a Поэтому int a[5]; /* ПередаTтся не КОПИЯ самого массива, а копия УКАЗАТЕЛЯ на его начало */
void f(int *a){ /* или f(int a[]), что есть равноценная запись */
main (){
Вызов f(a); сделает именно ожидаемые вещи.
ПРАВИЛО_1:
ПРАВИЛО_2: Второе правило влечет за собой ряд следствий.
int a[5]; /* массив */ ptr = a; /* законно, означает ptr = &a[0]; */ Теперь
ptr[0] = 3; /* означает a[0] = 3; */ Более того. Возьмем теперь ptr = &a[2];
a[0] a[1] a[2] a[3] a[4] Мы как бы "приложили" к массиву a[] массив ptr[]. В котором
ptr[0] есть a[2] Более того, допустимы отрицательные индексы!
ptr[-1] есть a[1] Итак: индексировать можно И массивы И указатели.
Кстати, для имени массива a[]
Это обратное следствие из схожести массивов и указателей.
/* Задача: написать функцию инвертирования порядка символов
/* Мы приведем рекурсивное и нерекурсивное решения (два варианта) */ /* Сначала - несколько служебных функций. */
/* ФУНКЦИЯ ПОДСЧЕТА ДЛИНЫ СТРОКИ.
while(s[counter] != '\0') /* пока не встретился признак конца текста */
/* ФУНКЦИЯ ПЕЧАТИ СТРОКИ.
В конце эта функция переводит строку.
while(s[i] != '\0'){
/* ТЕПЕРЬ МЫ ЗАНИМАЕМСЯ ФУНКЦИЕЙ ИНВЕРТИРОВАНИЯ.
Исходный массив: A B C D E F
n - размер массива.
Исходный массив: A B C D E F n=6
*/
for(i=1; i < n; i++)
/* Функция инвертирования.
b) Сдвинуть массив влево
c) В последний элемент массива поместить спасенный нулевой элемент.
d) Инвертировать начало массива длиной n-1.
Получится: Что и требовалось.
s[] - массив,
if(n <= 1) /* нечего инвертировать */
tmp = s[0]; /* спасти */
reverse(s, n-1); /* инвертировать начало */
/* ВТОРАЯ ВЕРСИЯ нерекурсивна. Рекурсия заменена циклом.
for(length=n; length > 1; --length){ char testString[] = "abcdefghijklmnopqrstuvwxyz";
/* Если не задать размер массива, он будет вычислен компилятором автоматически.
void main(){
len = strlen(testString);
printf("Строка для теста: \"%s\", ее длина %d\n",
А чтобы в putchar напечатать символ ' надо писать putchar('\'');
/* Первая инверсия */
/* Вторая инверсия - возвращает в исходное состояние */
/* Еще более простой вариант решения:
A B C D E F G H I J
J I H D E F G C B A
стоп. #include
/* Обмен значений двух переменных типа char */
c = *s1; *s1 = *s2; *s2 = c;
void reverse(char s[], int n){
first = 0; /* индекс первого элемента массива */
while(first < last){ /* пока first левее last */ char testString[] = "abcdefghijklmnopqrstuvwxyz.";
void main(){
len = strlen(testString); /* Есть такая стандартная функция */
/* Еще один вариант решения: #include char testString[] = "abcdefghijklmnopqrstuvwxyz.";
/* Конструкция sizeof(массив)/sizeof(массив[0]) char tempString[ sizeof(testString) / sizeof(testString[0]) ];
void reverse(char s[], int n){
/* вывернуть, результат в tempString[] */ tempString[n] = '\0'; /* признак конца строки */
/* скопировать на старое место */
s[n] = '\0'; /* признак конца строки */
void main(){
len = strlen(testString); /* Есть такая стандартная функция */ /* Задача инвертирования массива целых чисел */ #include
int arr[] = {1, 5, 10, 15, 20, 25, 30};
/* Распечатка массива в строку */
for(i=0; i < n; i++){
if(i == n-1) putchar('\n');
for(i=1; i < n; i++)
printShift(n); /* трассировка */
if(n <= 1){
pocket = row[0];
printShift(n); /* трассировка */ reverse(row, n-1);
printShift(n); /* трассировка */ 20.c
/* Задача: написать функцию для распечатки массива целых чисел
0 4 8
/* Пусть в массиве n элементов. 0 1 пусто пусто Поэтому if(n < columns) columns = n;
Далее, прямоугольная таблица с columns столбцами и lines строками columns*lines >= n
Вычислим число строк. lines >= n/columns Такое число вычисляется по формуле lines = (n + (columns - 1)) / columns; где деление целочисленное.
Далее надо только вывести формулу для индекса элемента в массиве
index(x, y) = (x * lines + y); #include int array[100];
void printArray(int a[], int n, int columns){
int x, y; /* номер колонки, номер строки - с нуля */
if(n < columns) columns = n;
/* Используем вложенные циклы: по строкам, а внутри - по столбцам */
/* break выводит только
/* сделать отступ в следующую колонку */
printf("%02d|%d", index, a[index]);
/* Инициализация значений элементов массива */
for(cols=4; cols <= 13; cols++){ #include
main(){ int value;
/* цикл по строкам */
/* цикл по столбцам */
/* что напечатать */
/* если это не нулевой столбец, то перейти if(x > 0) putchar('\t');
/* ... и в ней напечатать значение */
}
/* тогда
elem(0, y+1) - elem(COLUMNS-1, y) = 1 + LINES - COLUMNS*LINES; #include
int A = 150; /* Количество элементов */
int OFFSET_NEXT_COLUMN;
/* Рисуем строку таблицы */
for(col=0; col < COLUMNS; col++){
/* Увеличение при переходе в соседнюю колонку */
/* Увеличение при переходе из конца одной строки к началу следующей.
int main(){ LINES = (A + (COLUMNS - 1)) / COLUMNS;
OFFSET_NEXT_COLUMN = LINES;
for(nline=0; nline < LINES; nline++)
/* возврат 0 из main() означает "программа завершена успешно" */ /* ДВУМЕРНЫЕ МАССИВЫ */
/* Он объявляется так: int array[LINES][COLUMNS]; А индексируется так: array[y][x]
где 0 <= y <= LINES - 1
+-------------+-------------+-------------+------> ось x
Пока, на данной стадии знания Си,
/* Приведем пример, который заводит двумерный массив букв,
Здесь мы приводим алгоритм Брезенхема для рисования прямых,
#define LINES 31 /* число строк */ char field[LINES][COLUMNS];
/* В данной программе массив НЕ является параметром,
dy = y2 - y1;
/* Выбор тактовой оси */ if(dy < 0) { dy = -dy; ky = -1; }
if(dx < dy){ flag = 0; d = dx; dx = dy; dy = d; }
i1 = dy + dy; d = i1 - dx; i2 = d - dx;
for(i=0; i < dx; i++){
if(flag) x += kx; /* шаг по такт. оси */
if( d < 0 ) /* горизонтальный шаг */
int main(){
/* Заполнить поле пробелами */
/* Нарисовать картинку */
line(0,0, COLUMNS-1, LINES-1, '\\');
/* Распечатать массив */ |
|
Последнее обновление ( 30.06.2008 ) |