Вернуться к списку Дальше >>

2. Основы С и С++. Процессы изменения памяти при работе программы. План занятия. 1. Функции, их аргументы. 2. Точка входа в программу. 3. Типы данных и объявление переменных. 4. Стек памяти. 5. Арифметические операции. 6. Оператор условного перехода. 7. Оператор выбора. 8. Операторы циклов. 9. Массивы. 10. Использование написанного кода. 11. Функции ввода-вывода. Сегодня мы быстренько изучим С++. На прошлом занятии я заметил, что вы не все знаете, что это такое. Поэтому решил рассказать вам некоторые основные сведения об этом языке. С++ - язык обширный, многогранный и, в основном, невостребованный, и для его изучения одного часа явно недостаточно. Но вы скоро увидите, что и за два часа изучить его нельзя, и даже за 2004 часа. Его можно изучать сколь угодно долго, и постоянно открывать для себя что-то новое. Но с чего-то надо начать, и прежде чем начать писать какую-то серьезную программу (а именно такие мы собираемся писать) надо знать хоть что-то. Приступим. Из чего, по минимуму, должна состоять программа на С++? Из одной функции main. Вот она: void main() { // something } Что такое функция и для чего она нужна объяснять, надеюсь, не надо. Скажу только как она оформляется в С++. main - это название функции. Перед названием пишется тип переменной, которую эта функция будет возвращать. Слово void означает, что функция main ничего не возвращает. В круглых скобках (()) записываются входные параметры функции, а в фигурных ({}) - тело функции. Здесь они означают начало и конец функции. Двойная наклонная черта (//) означает начало комментария, который прдолжается до конца строки. Как же записывается список параметров? напишем нашу программу по-другому: void main(int argc, char *argv[]) { // something } Теперь она делает примерно то же самое, что и предыдущая, т.е. ничего, но уже более изощренно. Функция main теперь получает два параметра: argc и argv. Что они означают, пока неважно. Рассмотрим, каким образом они записываются. Список параметров функции, расположенный в круглых скобках формируется так: параметры записываются через запятую, причем сначала пишется тип параметра, а потом, через запятую, его название. В данном примере первым параметром является argc типа int - т.е. целого типа. Вторым параметром является argv, а тип его - двумерный массив символов, что тоже самое, что и массив строк. Не стоит пугаться такому написанию этого параметра - когда-нибудь вы все поймете. Теперь, когда мы узнали, как это пишется, стало интересно, что же все-таки мы написали. При таком написании функции main в параметрах argc и argv хранится информация о параметрах командной строки. Например при запуске программы мы написали: main.exe 1 param2 Тогда argc будет равно 3, а в argv будет 3 строки: argv[0] = "main.exe", argv[1] = "1" и argv[2] = "param2". Что делать с этими строками - решать вам. Применяется это, например, в архиваторах для записи флагов, команд, имен файлов и т.д.. Внимание! Вот мы написали последний пример, а что при этом произошло? Запустился exe-шник, но что это значит? И вообще, что это такое - exe-файл? Посмотреть его текст можно, открыв его в любом текстовом редакторе. Но что там такое - понять практически невозможно. Попытаюсь объяснить. Вначале идет всякая служебная информация: сколько места он занимает, где у него точка входа, начало списка указателей на функции и т.д.. Например, в windows-овских приложениях в первой же строке можно увидеть сообщение "This program cannot be run in DOS mode". А после этого идет сами коды функций, коротые используются программой. Примерно так. При запуске программы весь файл копируется в оперативную память, а затем создается некоторый курсор, который ходит по этой области памяти и выполняет команды, описанные в ней. В самом начале он становится на упомянутую точку входа в программу. Эта точка входа соответствует началу функции main. (Поэтому в программе на С++ обязательно должна быть такая функция.) Далее выполняются команды, расположенные в следующих байтах по порядку. так продолжается до тех пор, пока не происходит вызов какой-нибудь функции. Тогда в списке функций ищется ее адрес, и управление передается туда. После завершения работы функции управление возвращается в точку вызова. После завершения функции main завершается программа. Аминь! Какие типы есть в С++? На самом деле типов великое множество, т.к. программисты имеют право сами определять новые типы на основе уже существующих. Чем им не нравятся уже существующие зачастую сложно сказать, но иногда без этого просто не обойтись. Примеров этому у нас будет множество уже занятия через два. А пока опишу основные. int - целое число, занимает места столько, сколькоразрядная операционная система сейчас загружена. Т.е. в Windows занимает 32 бита, или 4 байта. Мы будем писать программы только для Windows, поэтому нам этого достаточно, но при написании переносимых программ не стоит забывать про это. Может принимать значения в соответствии с объемом памяти. Т.е. в Windows один бит уходит на знак, а 31 на число по модулю = 2^31. Соответственно от -2^31 до 2^31. bool - логическая переменная, может принимать значения true и false. Занимает столько же, сколько и int (непонятно почему). char - символ, например: 'a', '1', или целое число от 0 до 255. Занимает 1 байт. double (float) - вещественное число с плавающей запятой. 4 (2) байта. Для исползования переменных их сначала надо объявить. Это делается так: в любом (практически) месте программы надо написать: тип переменной пробел имя переменной: int i; После этого ее можно использовать в вычислениях и других операторах. Но что же при этом происходит? При выполнении программы она использует оперативную память для хранения переменных. Для этого в памяти есть два места - стек и куча. Это не взятые от фонаря названия, а вполне общеупотребительные понятия. Если куча используется для динамического управления памятью, и нам пока не нужна, то стек мы рассмотрим уже сейчас. Вообще стек - это метод хранения некоторых данных. Он представляет собой стакан, в который что-то кладут, а потом достают. Понятно, что достать что-то со дна стакана, не вынув все остальное невозможно, поэтому то, что было положено в него в последнюю очередь - будет вынуто первым. Это и есть стек. Теперь о том, как он применяется в работе с памятью. Рассмотрим функщию: void func() { int i; int j; char c; double d; bool b; // something } Стек в памяти представлен массивом если не бесконечным, то достаточно большим. При выполнении первой строчки (int i;) в вершине стека выделяется место для целой переменной (4 байта) и это место обзывается i. Теперь, при обращении к переменной i, программа найдет в памяти это место и прочитает записанное в нем значение. Выполняя следующую строчку, программа запишет в новый конец стека переменную j. И т.д... Т.е. после объявления переменных стек будет выглядеть так: i j c d b | ... | 4 байта | 4 байта | 1 байт | 4 байта | 4 байта | В конце работы функции выделенная в ней память в стеке освобождается. Первой освобождается память для b. Потом d... Последней i. Теперь пусть эта функция вызывается из main: void main() { int x; func(); int y; } Тогда сначала в стеке выделится место для x, потом в том же стеке в функции func выделится место для ее переменных, потом удалится, а только потом выделится место для y. Короче, схема ясна. Теперь рассмотрим операции, которые можно производить с переменными. a = 1; // операция присваивания, в память, ассоциированную с a записывается 1. a = a + 1; // создается временная переменная для 1, создается временная переменная для результата сложения, в нее записывается a + 1, затем из нее копируется в a, временные переменные удаляются. - // вычитание * // умножение / // деление % // вызтие остатка Более сложные вычисления: a = (3 + a * 5) - b; В стеке переменные будут появляться следующим образом: | a | b | 5 | a * 5 | 3 | 3 + a * 5 | (3 + a * 5) + b | уже были временные переменные затем при прмравнивании последняя временная переменная копируется в a, и все времееные переменные удаляются. Логические операции: == // равно != // неравно > // больше < // меньше >= // больше или равно <= // меньше или равно При всех операциях производятся осмысленные преобразования типов. При преобразовании переменной одного типа к более сложному потери данных не происходит. Наооборот - да. Сложность типов по убыванию: double, int, char, bool. Например: c = d; Если с - int, а d - double, то d округляется до его целой части. Если с - char, d - int, то при d < 256 - все нормально, иначе - ерунда. Если с - bool, d - int, то при d = 0, с становится false, иначе - true. Все операторы в функции выполняются последовательно, новый оператор не начинает свое выполнение, пока не завершился предыдущий. Это важно, т.к. можно быть уверенным в том, что переменные будут изменяться в том порядке, в котором мы им укажем. В языке также определены некоторые стандартные виды операторов, которые облегчают программисту написание некоторых стандартных процедур. Это известные вам операторы условного перехода, оператор выбора и операторы циклов. Первый, оператор условного перехода, тот оператор, без которого действительно не мог бы существовать никакой язык программирования. И действительно, в первых языках программирования есть только оператор проверки и goto. В С++ он оформляется так: if (условие) { // если условие выполнено } else { // в противном случае } Блок else может отвутствовать. Если в каком-то из наборов операторов только один оператор, то {} можно не писать. Условие должно содержать какое-то логическое выражение, которое при проверке сравнивается с true. Если там написано не логическое выражение, то произойдет приведение типов, а в С++ false соответствует только 0, остальное - true. Делайте выводы. Второй стандартный оператор - это оператор выбора или, иначе, switch. Необходим он для выбора из нескольких возможных вариантов и оформляется так: switch (переменная) { case значение1: // список операторов break; case значение2: // список операторов break; ... default: // список операторов break; } Оператор switch сравнивает значение переменной с набором констант и передает управление в соответствующее место. Если не подходит ни одна из констант, то выполняется блок default. В конце каждого блока надо ставить break. Это инструкция для выхода из оператора switch. Иначе будут выполняться операторы из следующего блока. Далее рассмотрим операоры циклов. Наиболее простой оператор цикла - это while. Он записывается так: while (условие) { // набор операторов } Перед началом выполнения операторов цикла, проверяется условие. Если оно равно true, то выполняется набор операторов. После этого условие проверяется снова, и т.д.. Когда условие оказалось равно false, выполнение цикла прекращается. Для всех операторов цикла существует две инструкции: continue и break. Инструкция continue говорит, что необходимо закончить текущее выполнение тела цикла и начать следующее. Инструкция break говорит, что необходимо завершить выполнение оператора цикла. Второй оператор цикла: do { // набор операторов } while (условие); Отличается от предыдущего оператора тем, что условие проверяется после первого выполнения тела цикла, и если оно равно true, то цикл продолжается. Третий оператор цикла: for (начальные действия; условие; действия после каждой итерации) { // тело цикла } Перед выполнением цикла выполняются начальные действия. Потом проверяется условие, и, если оно равно true, выполненяется тело цикла. После его выполнения перед проверкой условия выполняются действия из третьей группы. И т.д.. Таким образом данный цикл можно написать как цикл while: // начальные действия while (условие) { // тело цикла // действия после каждой итерации } Отличие заключается лишь в том, что после инструкции continue переход будет выполняться не на начало цикла, а на блок действий после каждой итерации. Пример: int i, a, b; for (i = 0, a = 0, b = 1; i < 10; i++) { a = a + i; b = b * i; } int i, a, b; i = 0; a = 0; b = 0; while (i < 10) { a = a + i; b = b * i; i++; } На этом описание встроенных видов операторов языка С++ завершено. Перейдем к другим немаловажным особенностям. К таким относится использование массивов. Массивы - множество переменных одного типа, связанных одним именем и структурой расположения в памяти. Объявление массивов делается так: int arr[4]; // массив из 4 переменных типа int. Нумерация переменных в массиве начинается с 0 и Доступ к ним осуществляется так: arr[0] = 0; // ... arr[3] = 3; При этом в стеке памяти переменные располагаются последовательно: | arr[0] | arr[1] | arr[2] | arr[3] | ... Двумерный массив объявляется так: int matr[3][3]; А в стеке его элементы имеют следующее расположение: | matr[0][0] | matr[0][1] | matr[0][2] | matr[1][0] | matr[1][1] | .. За многие годы существования языка С, а затем С++ тясячи программистов написали миллионы программ. Поэтому было бы странным предполагать, что каждый раз они переписывали функцию по выводу строки на экран. Естественней было бы предположить, что они написали ее один раз, а потом всегда ее использовали. На самом же деле, в большинстве случаев даже это не делается. Обычно програмист (я имею в виду неглупого программиста) старается делать как можно меньше, а следовательно, прежде чем начать что-то делать, смотрит, а не делал ли кто-то это до него. Это помогает, во-первых, избежать долгой и иногда мучительной работы, а во-вторых, ошибок, возникающих в ходе этой долгой и мучительной работы. Но и это иногда лень делать. В таких случаях следует обратиться к стандартным библиотекам. В них хранятся функции для всех самых необходимых случаев и поставляются они зачастую вместе с компиляторами. Для использования стандартных библиотек нужно включить их заголовочный файл в файл с нашей программой. Делается это так: #include <stdio.h> // функции для ввода-вывода Теперь разберем, что делается с этой строкой. Вообще, перед компилящией .cpp файла запускается препроцессор, который обрабатывает команды, начинающиеся с #. В частности, при выполнении команды #include <stdio.h> он заменяет эту строчку на файл stdio.h. Файл, получившийся после работы препроцессора, отправляется компилятору. Поэтому даже маленький файл: #include <stdio.h> void main() {} отправится компилятору, состоящим из нескольких сотен строк. Стандартные функции для ввода-вывода, находящиеся в библиотеке stdio.h это: scanf() и printf(). scanf("%d", &a); // считывает с клавиатуры целое число и записывает его в переменную a. Если вместо %d написать %с, то считывается символ, %lf - вещественное число. printf("%d %d", a, b); // выводит на экран числа a и b через пробел. Соответственно если записать %с, то будет выведен символ. И т.д..

Вернуться к списку Дальше >>

Хостинг от uCoz