Слайд 30Операции "увеличить на", "домножить на" и т.п.
double a[100];
double s;
int i;
.
. .
s = 0.0;
i = 0;
while (i < 100) {
s = s + a[i];
++i;
}
Здесь сумма элементов массива накапливается в переменной s. В строке
s = s + a[i];
к сумме s прибавляется очередной элемент массива a[i], т.е. значение s увеличивается на a[i]. В Си существует сокращенная запись операции увеличения:
s += a[i];
Оператор += читается как "увеличить на". Строка
x += y; // Увеличить значение x на y
эквивалентна в Си строке
x = x + y; // x присвоить значение x + y,
Слайд 31Логические операции
|| логическое "или" (логическое сложение)
&& логическое "и" (логическое умножение)
! логическое "не" (логическое отрицание)
Логические константы "истина" и "ложь" обозначаются
через true и false (это ключевые слова языка). Примеры логических выражений:
bool a, b, c, d;
int x, y;
a = b || c; // логическое "или"
d = b && c; // логическое "и"
a = !b; // логическое "не"
a = (x == y); // сравнение в правой части
a = false; // ложь
b = true; // истина
c = (x > 0 && y != 1); // c истинно, когда
// оба сравнения истинны
Слайд 32Самый высокий приоритет у операции логического отрицания, затем следует логическое
умножение, самый низкий приоритет у логического сложения.
Чрезвычайно важной особенностью операций
логического сложения и умножения является так называемое "сокращенное вычисление" результата. А именно, при вычислении результата операции логического сложения или умножения всегда сначала вычисляется значение первого аргумента. Если оно истинно в случае логического сложения или ложно в случае логического умножения, то второйаргумент операции не вычисляется вовсе!
Слайд 33Операции сравнения
Операция сравнения сравнивает два выражения. В результате вырабатывается логическое
значение - true или false (истина или ложь) в зависимости от значений выражений. Примеры:
bool
res;
int x, y;
res = (x == y); // true, если x равно y, иначе false
res = (x == x); // всегда true
res = (2 < 1); // всегда false
Операции сравнения в Си обозначаются следующим образом:
== равно, != не равно,
> больше, >= больше или равно,
< меньше, <= меньше или равно.
Слайд 34Побитовые логические операции
& побитовое логическое умножение ("и")
| побитовое логическое сложение
("или")
~ побитовое логическое отрицание ("не")
^ побитовое сложение по модулю 2
(исключающее "или")
Операции сдвига
int x, y;
. . .
x = (y >> 3); // Сдвиг на 3 позиции вправо
y = (y << 2); // Сдвиг на 2 позиции влево
При сдвиге влево на k позиций младшие k разрядов результата устанавливаются в ноль. Сдвиг влево на k позиций эквивалентен умножению на число 2k. Сдвиг вправо более сложен, он по-разному определяется для беззнаковых и знаковых чисел. При сдвиге вправо беззнакового числа на k позиций освободившиеся k старших разрядов устанавливаются в ноль. Сдвиг вправо на k позиций соответствует целочисленному делению на число 2k
Слайд 35Арифметика указателей
С указателями можно выполнять следующие операции:
сложение указателя и целого
числа, результат - указатель;
увеличение или уменьшение переменной типа указатель, что
эквивалентно прибавлению или вычитанию единицы;
вычитание двух указателей, результат - целое число.
Прибавление к указателю p целого числа n означает увеличение адреса, который содержится в переменной p, на суммарный размер n элементов того типа, на который ссылается указатель. Указатель как бы сдвигается на n элементов вправо, если считать, что индексы элементов массива возрастают слева направо. Аналогично вычитание целого числа n из указателя означает сдвиг указателя влево на n элементов
Слайд 36Связь между указателями и массивами
В языке Си имя массива a
является указателем на его первый элемент, т.е. выражения a и &(a[0]) эквивалентны. Учитывая арифметику
указателей, получаем эквивалентность следующих выражений:
a[i] ~ *(a+i)
Действительно, при прибавлении к a целого числа i происходит сдвиг на i элементов вправо. Поскольку имя массива является адресом его начального элемента, получается адрес i -го элемента массива a. Применяя операцию звездочка *, получаем сам элемент a[i]. Точно так же эквивалентны выражения
&(a[i]) ~ a+i (адрес эл-та a[i]).
Слайд 37Операция приведения типа
Операция приведения типа используется, когда значение одного типа
преобразуется к другому типу, в том случае, если существует некоторый
разумный способ такого преобразования. Операция обозначается именем типа, заключенным в круглые скобки; она записывается перед ее единственным аргументом. Рассмотрим два примера. Пусть требуется преобразовать целое число к вещественному типу. Как известно, целые и вещественные числа по-разному представляются в компьютере, см. раздел 3.3.1. Тем не менее, существует однозначный способ преобразования целого числа типа int к вещественному типу double. В первом примере значение целой переменной n приводится к вещественному типу и присваивается вещественной переменной x:
double x;
int n;
. . .
x = (double) n; // Операция приведения к типу double
Слайд 38Управляющие конструкции
Управляющие конструкции позволяют организовывать циклы и ветвления в программах. В Си всего
несколько конструкций, причем половину из них можно не использовать (они
реализуются через остальные).
Фигурные скобки
Оператор if
Выбор из нескольких возможностей: if...else if...
Цикл while
Выход из цикла break, переход на конец цикла continue
Оператор перехода на метку goto
Цикл for
Операция "запятая" и цикл for
Цикл do...while
Оператор switch (вычисляемый goto)
Слайд 39Представление программы в виде функций
Перед использованием или реализацией функции необходимо
описать ее прототип. Прототип функции сообщает информацию об имени функции, типе
возвращаемого значения, количестве и типах ее аргументов.
int gcd(int x, int y);
Описан прототип функции gcd, возвращающей целое значение, с двумя целыми аргументами. Имена аргументов x и y здесь являются лишь комментариями, не несущими никакой информации для компилятора. Их можно опускать, например, описание
int gcd(int, int);
Слайд 40Передача параметров функциям
В языке Си функциям передаются значения фактических параметров. При вызове
функции значения параметров копируются в аппаратный стек. Следует четко понимать, что изменение
формальных параметров в теле функции не приводит к изменению переменных вызывающей программы, передаваемых функции при ее вызове, - ведь функция работает не с самими этими переменными, а с копиями их значений! Рассмотрим, например, следующий фрагмент программы:
void f(int x); // Описание прототипа функции
int main() {
. . .
int x = 5;
f(x);
// Значение x по-прежнему равно 5
. . .
}
void f(int x) {
. . .
x = 0; // Изменение формального параметра
. . . // не приводит к изменению фактического
// параметра в вызывающей программе
}
Слайд 41Работа с памятью
В традиционных языках программирования, таких как Си, Фортран, Паскаль, существуют
три вида памяти: статическая, стековая идинамическая. Конечно, с физической точки
зрения никаких различных видов памяти нет: оперативная память - это массив байтов, каждый байт имеет адрес, начиная с нуля. Когда говорится о видах памяти, имеются в виду способы организации работы с ней, включая выделение и освобождение памяти, а также методы доступа.
Слайд 42Статическая память
Статическая память выделяется еще до начала работы программы, на стадии
компиляции и сборки. Статические переменные имеют фиксированный адрес, известный до
запуска программы и не изменяющийся в процессе ее работы. Статические переменные создаются и инициализируются до входа в функцию main, с которой начинается выполнение программы.
Существует два типа статических переменных:
глобальные переменные - это переменные, определенные вне функций, в описании которых отсутствует слово static. Обычно описания глобальных переменных, включающие слово extern, выносятся в заголовочные файлы (h-файлы). Слово extern означает, что переменная описывается, но не создается в данной точке программы. Определения глобальных переменных, т.е. описания без слова extern, помещаются в файлы реализации (c-файлы или cpp-файлы).
статические переменные - это переменные, в описании которых присутствует слово static. Как правило, статические переменные описываются вне функций. Такие статические переменные во всем подобны глобальным, с одним исключением: область видимости статической переменной ограничена одним файлом, внутри которого она определена, - и, более того, ее можно использовать только после ее описания, т.е. ниже по тексту. По этой причине описания статических переменных обычно выносятся в начало файла. В отличие от глобальных переменных, статические переменные никогда не описываются в h-файлах (модификаторы extern и static конфликтуют между собой). Совет: используйте статические переменные, если нужно, чтобы они были доступны только для функций, описанных внутри одного и того же файла. По возможности не применяйте в таких ситуациях глобальные переменные, это позволит избежать конфликтов имен при реализации больших проектов, состоящих из сотен файлов.
Слово static может присутствовать и в заголовке функции. При этом оно используется только для того, чтобы ограничить область видимости имени функции рамками одного файла. Пример:
static int gcd(int x, int y); // Прототип ф-ции
. . .
static int gcd(int x, int y) { // Реализация
. . .
}
Совет: используйте модификатор static в заголовке функции, если известно, что функция будет вызываться лишь внутри одного файла. Слово static должно присутствовать как в описании прототипа функции, так и в заголовке функции при ее реализации
Слайд 43Стековая, или локальная, память
Локальные, или стековые, переменные - это переменные,
описанные внутри функции. Память для таких переменных выделяется в аппаратном стеке.
Память выделяется в момент входа в функцию или блок и освобождается в момент выхода из функции или блока. При этом захват и освобождение памяти происходят практически мгновенно, т.к. компьютер только изменяет регистр, содержащий адрес вершины стека.
Слайд 44Динамическая память, или куча
Помимо статической и стековой памяти, существует еще
практически неограниченный ресурс памяти, которая называетсядинамическая, или куча ( heap ). Программа может захватывать
участки динамической памяти нужного размера. После использования ранее захваченный участок динамической памяти следует освободить.
В языке Си для захвата и освобождения динамической памяти применяются стандартные функции malloc и free, описания их прототипов содержатся в стандартном заголовочном файле "stdlib.h". (Имя malloc является сокращением от memory allocate- "захват памяти".) Прототипы этих функций выглядят следующим образом:
void *malloc(size_t n); // Захватить участок памяти
// размером в n байт
void free(void *p); // Освободить участок
// памяти с адресом p
Слайд 45Операторы new и delete языка C++
В языке C++ для захвата и освобождения
динамической памяти используются операторы new и delete. Они являются частью языка C++, в отличие
от функций malloc и free, входящих в библиотеку стандартных функций Си.
Пусть T - некоторый тип языка Си или C++, p - указатель на объект типа T. Тогда для захвата памяти размером в один элемент типа T используется оператор new:
T *p;
p = new T;
Например, для захвата восьми байтов под вещественное число типа double используется фрагмент
double *p;
p = new double;
При использовании new, в отличие от malloc, не нужно приводить указатель от типа void* к нужному типу: оператор newвозвращает указатель на тип, записанный после слова new.
Слайд 46С помощью оператора new можно захватывать память под массив элементов заданного типа. Для этого
в квадратных скобках указывается длина захватываемого массива, которая может представляться
любым целочисленным выражением. Например, в следующем фрагменте в динамической памяти захватывается область для хранения вещественной матрицы размера m*n:
double *a;
int m = 100, n = 101;
a = new double[m * n];
Такую форму оператора new иногда называют векторной
Если память под массив была захвачена с помощью векторной формы оператора new, то для ее освобождения следует использоватьвекторную форму оператора delete, в которой после слова delete записываются пустые квадратные скобки:
double *a = new double[100]; // Захватываем массив
. . .
delete[] a; // Освобождаем массив
Слайд 47Структуры
Структура — это конструкция, которая позволяет объединить несколько переменных с
разными типами и именами в один составнойобъект. Она позволяет строить
новые типы данных языка Си. В других языках программирования структуры называют записями или кортежами.
Описание структуры выглядит следующим образом:
struct R3Vector {
double x;
double y;
double z;
};
Слайд 48struct R3Vector u, v;
. . .
u = v; // Копируем
вектор как единое целое
В этом примере вектор v копируется в вектор u ; копирование структур сводится к переписыванию
области памяти. Сравнивать структуры нельзя:
struct R3Vector u, v;
. . .
if (u == v) { // Ошибка! Сравнивать структуры нельзя
. . .
}
Имеется также возможность работать с полями структуры. Для этого используется операция точка " .": пусть s — объект типа структура, f — имя поля структуры. Тогда выражение
s.f //является полем f структуры s, с ним можно работать как с обычной переменной
Слайд 49Структуры и указатели
struct S { . . . }; //
Определение структуры S
struct S *p; // Описание указателя на структуру
S
Указатели на структуры используются довольно часто. Указатель на структуру S описывается обычным образом, в качестве имени типа фигурирует struct S*
Списки и последовательности – частое применение структур
struct TreeNode { // Вершина дерева
struct TreeNode *parent; // Указатель на отца,
struct TreeNode *left; // на левого сына,
struct TreeNode *right; // на правого сына
void *value; // Значение в вершине
};
Слайд 50Для доступа к полям структуры через указатель на структуру служит
операция стрелочка, которая обозначается двумя символами ->(минус и знак больше), их
нужно рассматривать как одну неразрывную лексему (т.е. единый знак, единое слово). Пусть S — имя структуры, f — некоторое поле структуры S, p — указатель на структуру S. Тогда выражение
p->f
обозначает поле f структуры S (само поле, а не указатель не него!). Это выражение можно записать, используя операцию звездочка (доступ к объекту через указатель),
p->f ~ (*p).f
но, конечно, первый способ гораздо нагляднее. (Во втором случае круглые скобки вокруг выражения *p обязательны, поскольку приоритет операции точка выше, чем операции звездочка.)
Слайд 51Структуры и оператор определения типа typedef
Возможно анонимное определение структуры, когда имя
структуры после ключевого слова struct опускается; в этом случае список описываемых переменных
должен быть непустым (иначе такое описание совершенно бессмысленно).
struct { double x; double y; } t, *p;
чаще анонимное определение структуры комбинируют с оператором определения имени типа typedef
typedef struct {
double x;
double y;
} R2Point, *R2PointPtr;
Такая технология довольно популярна среди программистов и применяется в большинстве системных h-файлов. Преимущество ее состоит в том, что в дальшейшем при описании переменных структурного типа не нужно использовать ключевое слово struct, например,
R2Point a, b, c; // Описываем три точки a, b, c
R2PointPtr p; // Описываем указатель на точку
R2Point *q; // Эквивалентно R2PointPtr q;
Слайд 52Терминология объектно-ориентированного программирования
Объектно-ориентированное программирование позволяет оперировать в терминах классов: определять
классы, конструировать производные классы, создавать объекты, принадлежащие классу, - экземпляры
класса.
Сначала в некоторых языках программирования появился тип struct, расширением которого стал тип class.
Класс определяет данные (переменные) и поведение (методы). Данные и методы класса также называют членами класса. Класс рассматривается как определяемый пользователем тип данных.
Объектом называется экземпляр некоторого класса. Объект создается как переменная типа класса, которая используется для доступа к данным - членам класса и для вызова методов - членов класса.
Слайд 53Наследованием называется механизм, позволяющий производному классу наследовать структуру данных и
поведение другого класса, а также наследовать поведение, объявленное в интерфейсах
и абстрактных классах.
Наследование позволяет определять новые классы в терминах существующих классов.
В объектно-ориентированном программировании наследование может быть:
множественным, позволяющим производному классу наследоваться одновременно от нескольких классов (например, так реализован механизм наследования в С++);
простым, когда производный класс имеет только один наследуемый класс (например, так реализованы языки Java и Object Pascal).
Наследуемый класс принято называть базовым классом, или родительским классом (классом - предком, суперклассом).
Слайд 54Производный класс, наследующий структуру данных и поведение своего базового класса,
иногда также называется дочерним классом (классом - потомком, подклассом).
В производном
классе можно переопределять методы базового класса и добавлять новые методы. Непосредственным базовым классом называется класс, от которого порожден производный класс следующего уровня иерархии:
Слайд 55Полиморфизмом называется способность различных объектов по-разному обрабатывать одинаковые сообщения.
Инкапсуляция позволяет
работать в терминах объектов и скрывать их переменные и методы.
Использование инкапсуляции дает возможность модифицировать внутреннюю реализацию объекта без влияния на программу в целом до тех пор, пока не изменяется интерфейс с объектом.
В языках программирования инкапсуляция поддерживается реализацией модификаторов доступа, таких как protected - для защищенных членов класса на уровне класса, и private - для полностью защищенных членов класса.
Слайд 56Объявление и реализация класса в языке С++
Создаваемый класс должен быть
объявлен и реализован.
Объявление класса в языке С++ может иметь следующее
формальное описание:
class имя_класса : список_базовых_классов
{
// Модификатор доступа относится ко всем перечисленным
// после него членам до следующего модификатора доступа
public: // Объявление общедоступных членов класса
protected: // Объявление членов класса, доступных только для производных
// классов
private: // Объявление защищенных членов класса
};
Слайд 57Список базовых классов указывается после имени класса через символ двоеточия
( :), разделяется запятыми и может иметь модификаторы доступа.
class MyClass
: public ClassA, public ClassB, private ClassC {};
В языке С++ считается, что если модификатор доступа для класса или члена класса не указан, то по умолчанию предполагается модификатор доступа private (защищенный доступ). Для членов структур, объявляемых ключевым словом struct, по умолчанию модификатор доступа предполагается равным public.
Слайд 58Модификатор доступа базового класса позволяет определить, какие переменные и методы
базового класса будут доступны из производного класса. Модификатор доступа, указываемый
перед именем базового класса, определяет следующие правила доступа к переменным и методам базового класса из производного класса:
public - в производном классе доступны все переменные и методы базового класса с модификаторами доступа public и protected, и эти члены класса имеют те же права доступа;
protected - члены базового класса с модификаторами доступа public и protected доступны как protected, а с модификатором доступа private - недоступны.
private - члены базового класса с модификаторами доступа public и protected доступны как private, а с модификатором доступа private - недоступны.
Слайд 59В теле объявления класса указываются модификаторы доступа, описывающие права доступа
для переменных и методов класса:
модификатор доступа относится ко всем перечисленным
после него членам до следующего модификатора доступа;
один и тот же модификатор доступа может указываться несколько раз;
после модификатора доступа ставится символ двоеточие;
если модификатор доступа не указан, то по умолчанию предполагается private.
Для доступа к членам класса используется операция принадлежности ::, указываемая после идентификатора класса. Для доступа к членам экземпляра класса используются операции . и ->.
Для доступа к объекту самого класса внутри метода члена класса используется ключевое слово this.
Слайд 60class A
{
public:
int i;
Func1();
}
A::Func1() { return
this->i; } // this - указатель класса A
Слайд 61Конструкторы класса
Конструктором называется метод, вызываемый при создании объекта данного класса.
Класс может иметь несколько конструкторов, отличающихся списком параметров.
Деструктором называется метод,
вызываемый при разрушении объекта данного класса. Имена конструктора и деструктора совпадают с именем класса, но перед именем деструктора указывается символ ~.
При создании объектов последовательно вызываются конструкторы всех его базовых классов. Вызов деструкторов при уничтожении объекта происходит в обратном порядке.
Если конструктор базового класса имеет список параметров, то для его использования в производном классе следует создать конструктор производного класса с таким же списком параметров.
Слайд 62Создание объекта
Для создания объекта (экземпляра данного класса) следует объявить переменную
типа указатель на класс, а затем создать объект, выполнив оператор
new с указанием используемого конструктора.
A* ca;
ca= new A();
Эти же действия можно записать одним оператором.
A* ca= new A();
Для того чтобы можно было использовать конструктор с параметрами, значения параметров необходимо указать при создании объекта.
Слайд 63Вложенные классы
Язык С++ допускает использование вложенных классов - внутри тела
одного класса содержится объявление других классов.
class A
{
public:
A(void);
~A(void);
class B { // Вложенный класс
B(void) {};
~B(void) {};
char sStr2[3];
};
};
Слайд 64Объектные типы
Тип данных всегда определяет размер памяти, которая будет выделена
под переменную данного типа при ее создании.
При объявлении переменной объектного
типа (типа класса) создаются переменные члены класса и вызывается конструктор класса. Производные типы на основе классов позволяют получать доступ к членам класса.
Переменная, объявленная как указатель на класс, применяется для доступа к методам и переменным членам класса.
Слайд 65Преобразование объектных типов
Указатель на класс может быть преобразован к указателю
на базовый класс в двух случаях:
если базовый класс является доступным
и преобразование однозначно;
если указано явное преобразование типа указателя.
Указатели на члены класса или структуры не могут рассматриваться как обычные указатели и для них не выполняется стандартное преобразование типа.
Квалификация имен
Квалификация имен используется для однозначного понимания указываемого имени.
Для квалификации имени могут использоваться следующие операторы: :: (оператор принадлежности); . (оператор доступа к члену класса посредством имени); -> (оператор доступа к члену класса через указатель).