40гг первые цифровые компьютеры программирование путем коммутации проводов

Вид материалаДокументы

Содержание


Создание и использование конструктора
Пример деструктора
Пример перегрузки функций
Вызов различных функций
Пример неоднозначности при использовании аргументов по умолчанию
Перегруженный оператор “+”
Семантика средств описания действий
Частичная и полная корректность программы
Правила вывода
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   12

int month, day, year;

public:

void set(int, int, int);

void get(int*, int*, int*);

void next();

void print();

};


date.cpp

#include “date.h”

void date::print()

{

printf(“%i.%i.%i”,day,

month,year);

}




Конструкторы
  • Использование для обеспечения инициализации объекта класса функций вроде set_date() (установить дату) неэлегантно и чревато ошибками.
  • Поскольку нигде не утверждается, что объект должен быть инициализирован, то программист может забыть это сделать, или (что приводит, как правило, к столь же разрушительным последствиям) сделать это дважды.
  • Есть более хороший подход: дать возможность программисту описать функцию, явно предназначенную для инициализации объектов.
  • Поскольку такая функция конструирует значения данного типа, она называется конструктором.
  • Конструктор распознается по тому, что имеет то же имя, что и сам класс.
  • У конструктора отсутствует возвращаемое значение.


Создание и использование конструктора

class date{

private:

int month, day, year;

public:

date(int day, int month, int year);

date(char *str); // дата в строковом представлении

date(int day); // день, месяц и год сегодняшние



};

date d1; // ошибка

date d2(5,5,2005);

date *d3 = new date(6,6,2006);


Деструктор
  • Определяемый пользователем тип чаще имеет, чем не имеет, конструктор, который обеспечивает надлежащую инициализацию.
  • Для многих типов также требуется обратное действие, деструктор, чтобы обеспечить соответствующую очистку объектов этого типа.
  • Имя деструктора для класса X есть ~X() ("дополнение конструктора").
  • В частности, многие типы используют некоторый объем памяти из динамической памяти, который выделяется конструктором и освобождается деструктором.
  • Деструктор вызывается когда переменная выходит из области видимости (для класса памяти auto) или при использовании оператора delete.



Пример деструктора

class char_stack{

private:

int size;

char* top;

char* s;

public:

char_stack(int sz)

{ top=s=newchar[size=sz]; }

~char_stack(){ delete[] s; } // деструктор void push(char c) { *top++ = c; }

char pop() { return *--top;}

};


Inline
  • При программировании с использованием классов очень часто используется много маленьких функций.
  • Это может страшно понизить эффективность, потому что стоимость вызова функции (хотя и вовсе не высокая по сравнению с другими языками) все равно намного выше, чем пара ссылок по памяти, необходимая для тела функции.
  • Чтобы справиться с этой проблемой, был разработан аппарат inline- функций. Метод, определенный (а не просто описанная) в описании класса, считается inline.
  • Это значит, что при компиляции программы вызов функции будет заменен на подстановку ее тела в место вызова.

Ссылки на себя
  • В методе на атрибуты, для которого он был вызван, можно ссылаться непосредственно.
  • Указатель на объект, для которого вызван метод, является скрытым параметром метода. На этот неявный параметр можно ссылаться явно как на this.
  • При ссылке на атрибуты использование this излишне.
  • Главным образом this используется при написании методов, которые манипулируют непосредственно указателями.


Перегрузка операторов и функций


Перегрузка функций
  • Бывает удобно, чтобы функции, реализующие один и тот же алгоритм для различных типов данных, имели одно и то же имя.
  • Если это имя мнемонично, то есть несет нужную информацию, это делает программу более понятной, поскольку для каждого действия требуется помнить только одно имя.
  • Использование нескольких функций с одним и тем же именем, но с различными типами параметров, называется перегрузкой функций.
  • Компилятор определяет, какую именно функцию требуется вызвать, по типу передаваемых параметров.
  • Этот процесс называется разрешением перегрузки (перевод английского слова resolution в смысле «уточнение»). Тип возвращаемого функцией значения в разрешении не участвует.
  • Механизм разрешения основан на достаточно сложном наборе правил, смысл которых сводится к тому, чтобы использовать функцию с наиболее подходящими аргументами и выдать сообщение, если такой не найдется.


Пример перегрузки функций

Имеется четыре варианта функции, определяющей наибольшее значение:


// Возвращает наибольшее из двух целых:

int max(int, int);


// Возвращает подстроку наибольшей длины:

char* max(char*. char*);


// Возвращает наибольшее из первого параметра и длины второго:

int max (int, char*);


// Возвращает наибольшее из второго параметра и длины первого:

int max (char*, int);


Вызов различных функций
  • При вызове функции max компилятор выбирает соответствующий типу фактических параметров вариант функции (в приведенном примере будут последовательно вызваны все четыре варианта функции).


void f(int a, int b, char* C; char* d){

cout « max (a, b) « max(c, d) « max(a. c) « max(c, b);

}


Неоднозначности при вызове перегруженных функций
  • Неоднозначность может появиться при:
  • преобразовании типа;
  • использовании параметров-ссылок;
  • использовании аргументов по умолчанию.


Пример неоднозначности при преобразовании типа

#include

float f(float i){

cout « "function float f(float i)" « endl;

return i;

}

double f(double i){

cout « "function double f(double i)" « endl;

return i*2;

}

int main(){

float x = 10.09;

double у = 10.09;'

cout « f(x) « endl; // Вызывается f(float)

cout « f'(y) « endl; // Вызывается f(double)

/* cout « f(10) « endl; Неоднозначность - как преобразовать 10: во float или double? */

return 0;

}


Пример неоднозначности при использовании параметров-ссылок
  • Функции

int f(int a, int b);

int f(int a, int &b);
  • Вызов

int a = 5, b=10;

f(a,b);

  • Компилятор не сможет узнать, какая из этих функций вызывается, так как нет синтаксических различий между вызовом функции, которая получает параметр по значению, и вызовом функции, которая получает параметр по ссылке.


Пример неоднозначности при использовании аргументов по умолчанию

#include

int f(int a){return a;}

int f(int a, int b=1){return a * b;}

int main(){

cout « f(10, 2); // Вызывается f(int. int)

/* cout « f(10); Неоднозначность - что вызывается: f(int, int) или f(int) */

return 0;

}

Правила описания перегруженных функций
  • Перегруженные функции должны находиться в одной области видимости, иначе произойдет сокрытие аналогично одинаковым именам переменных во вложенных блоках.
  • Перегруженные функции могут иметь параметры по умолчанию, при этом значения одного и того же параметра в разных функциях должны совпадать.
  • В различных вариантах перегруженных функций может быть различное количество параметров по умолчанию.
  • Функции не могут быть перегружены, если описание их параметров отличается только модификатором const или использованием ссылки (например, int и const int или int и int&).


Перегрузка операторов
  • Третье поколение ЯП – языки, предоставляющие программисту средства определения абстрактных типов данных
  • С++, C#, Java
  • Типы понимаются как множества с операциями.
  • Хотелось бы, что бы одинаковые по смыслу операции обозначались бы одинаково не зависимо от того используется встроенный тип данных или тип данных определенный пользователем.
  • В этом случае операции, обозначаемые одним и тем же символом будут обозначать приводить к выполнению совершенно различных действий в зависимости от контекста.


Перегруженный оператор “+”

int a,b;

double c,d;

char *e,*f;

a = a + b;

с = с + d;

e = f + a;

string s1= “abc”, s2 = “def”, s3;

s3 = s1 + s2; // s3 = “abcdef”;


Правила перегрузки операций
  • Перегрузка операций осуществляется с помощью функций специального вида (функций-операций)
  • Функция-операция содержит ключевое слово operator, за которым следует знак переопределяемой операции:
  • тип operator операция ( список параметров) { тело функции }
  • при перегрузке операций сохраняются количество аргументов, приоритеты операций и правила ассоциации (справа налево или слева направо), используемые в стандартных тинах данных;
  • для стандартных типов данных переопределять операции нельзя;
  • функции-операции не могут иметь аргументов по умолчанию;
  • функции-операции наследуются (за исключением =);
  • функции-операции не могут определяться как static.
  • Функцию-операцию можно определить тремя способами:
  • как метод класса,
  • дружественная функция класса,
  • либо обычной функцией.


Переопределение операции с помощью метода класса


class Square{

private:

int x,y;

int size;

public:

Square(){x = 0; y = 0; size = 10;}

print(){printf(“ x=%i, y= %i, size=%i\n”,x,y,size);}

Square &operator++() {size++; return *this;}



};




Square s;

(++s).print();




Переопределение операции с помощью обычной функции


class Square{

private:

int x,y;

public:

int size;

Square(){x = 0; y = 0; size = 10;}

print(){printf(“ x=%i, y= %i, size=%i\n”,x,y,size);}



};


Square &operator++(Square &s) {s.size++; return s;}




Square s;

(++s).print();




Переопределение операции с помощью функции - друга

class Square{

private:

int x,y;

int size;

public:

Square(){x = 0; y = 0; size = 10;}

print(){printf(“ x=%i, y= %i, size=%i\n”,x,y,size);}

friend Square &operator++(Square &s);



};

Square &operator++(Square &s) {s.size++; return s;}


Square s;

(++s).print();




Унарная постфиксная операция
  • Операции постфиксного инкремента и декремента должны иметь первый параметр типа int.
  • Он используется только для того, чтобы отличить их от префиксной формы:

class Square{

private:

int x,y;

int size;

public:

Square(){x = 0; y = 0; size = 10;}

print(){printf(“ x=%i, y= %i, size=%i\n”,x,y,size);}

Square &operator++(int) {size++; return *this;}



};



Square s;

s++;

s.print();



Перегрузка бинарных операций
  • Бинарная функция-операция, определяемая внутри класса, должна быть представлена с помощью нестатического метода с параметрами, при этом вызвавший ее объект считается первым операндом.
  • Если функция определяется вне класса, она должна иметь два параметра типа класса.


class Square{

private:

int x,y;

int size;

public:

Square(){x = 0; y = 0; size = 10;}

print(){printf(“ x=%i, y= %i, size=%i\n”,x,y,size);}

bool operator>(const Square &s)

{

if(size >s.size)

return true;

else

return false;

}



};




Square s1,s2;

if(s1 > s2) printf(“s1 больше!”);




class Square{

private:

int x,y;

int size;

public:

Square(){x = 0; y = 0; size = 10;}

print(){printf(“ x=%i, y= %i, size=%i\n”,x,y,size);}

friend bool operator>(const Square &s1, const Square &s2);



};

bool operator>(const Square &s1, const Square &s2)

{

if(s1.size >s2.size)

return true;

else

return false;

}




Square s1,s2;

if(s1 > s2) printf(“s1 больше!”);




Перегрузка операции присваивания
  • Операция присваивания определена в любом классе по умолчанию как поэлементное копирование.
  • Она вызывается каждый раз, когда одному существующему объекту присваивается значение другого.
  • Если класс содержит поля, память под которые выделяется динамически, необходимо определить собственную операцию присваивания.
  • Чтобы сохранить семантику присваивания, операция-функция должна возвращать ссылку на объект, для которого она вызвана, и принимать в качестве параметра единственный аргумент — ссылку на присваиваемый объект.



Семантика средств описания действий


Разработка программ
  • Основная задача, стоящая перед каждым программистом, — получение правильной программы.
  • Гарантировать ее правильность может только тщательное пошаговое уточнение (разработка сверху вниз) и одновременное доказательство того, что все вычисления в программе дают корректные результаты за конечное время.
  • Этот способ реализуется в два этапа:
  • проведение декомпозиции общей задачи на точно определенные подзадачи и доказательство того, что если каждая подзадача решена корректно и полученные решения связаны друг с другом определенным образом, то задача будет решена корректно;
  • повторение декомпозиции и доказательство ее корректности до достижения подзадач настолько простых, что их решение выражается в нескольких строках языка программирования.
  • Чтобы доказать корректность программы, необходимо формально описать семантику операторов языка.


Основные понятия
  • Статическая структура программы представляется ее текстом на языке программирования, описывающем действия, которые должны быть выполнены при решении данной задачи.
  • Динамическая структура отражает процесс вычислений и состоит из последовательности состояний вычислений.
  • Состояние вычислений в любой момент времени включает значения всех переменных в этот момент и, таким образом, зависит от начальных значений переменных и предшествующих этапов вычислений программы.
  • Если статическая структура не зависит от исходных данных, то динамическая структура программы в значительной степени определяется выбором исходных данных, поскольку при выполнении программы будут сделаны различные переходы в зависимости от значений входных переменных.
  • Текущий оператор
  • изменяет состояние вычислений (значения некоторых переменных) и передает управление следующему оператору,
  • или производит проверку состояния вычислений (сравнивает значения определенных переменных) и на основе результата этой проверки передает управление другому оператору.
  • Язык программирования предоставляет как простые операторы — операторы действия, так и средства композиции — операторы управления, которые позволяют формировать составные операторы из других простых или составных.


Правила вывода
  • Для описания семантики языка необходимо иметь правила вывода, позволяющие определить
  • эффект воздействия простого оператора на состояние вычислений
  • свойства составного оператора, исходя из свойств его компонент.
  • Фундаментальное свойство всех описываемых здесь способов композиции заключается в том, что они позволяют объединить в один составной оператор с одним входом и одним выходом один или более операторов также с одним входом и одним выходом и не позволяют строить операторы с несколькими входами или выходами.
  • Именно это свойство дает возможность в достаточно простой форме описывать действия любого оператора или программы.


Спецификация программы
  • Пусть S — какой-нибудь оператор или программа, Р и Q — предикаты, описывающие состояние вычислений. Тогда семантика S описывается следующим образом:

{P} S {Q}
  • Это — спецификация программы, означающая: если предикат Р истинен перед выполнением S, то предикат Q будет истинен после завершения выполнения S.
  • Предикат Р называется предусловием S, а предикат Q — постусловием S.

Свойства спецификаций программ
  • Для любых S и Р всегда найдется некоторый предикат Q (например, true), который будет постусловием S, если Р — предусловие S
  • Для любого S и любого Q всегда найдется некоторый предикат Р (например, false), который будет предусловием S, если Q — постусловие S.
  • Для любых Р и Q всегда найдется программа S (например, while true do; end do;), для которой Р будет предусловием, a Q — постусловием.
  • Задача программиста — найти такую программу S, которая завершается и для которой Р является предусловием, a Q — постусловием.


Частичная и полная корректность программы
  • Если S — программа, корректность которой мы хотим установить, то соотношение {Р} S {Q} — это то, что мы должны доказать, где Р — условие, которому должны удовлетворять начальные значения переменных, a Q — условие, которому должны удовлетворять конечные значения переменных.
  • Соотношение {Р} S {Q} определяет только частичную корректность S.
  • Программа, в которой гарантируется получение требуемого результата при условии ее завершения, частично корректна.
  • Если дополнительно можно показать, что программа завершается при всех исходных данных, удовлетворяющих Р, то программа полностью корректна.
  • Чтобы доказать завершимость программы, необходим анализ циклов.


Процесс разработки программы
  • Нисходящий процесс разработки программ можно сформулировать следующим образом:
  • построение спецификации программы {Р} S {Q}, т.е. ясное и недвусмысленное определение того, как программа должна использоваться (предусловие Р) и какие результаты (постусловие Q) должны быть получены;
  • определение на каждом этапе декомпозиции спецификаций {Pj} Sj {Qj} для компонент Sj программы;
  • разработка программы и ее компонент одновременно с доказательством корректности указанных спецификаций.



Запись предикатов
  • При записи предикатов Р и Q могут быть использованы следующие средства:
  • Имена переменных, арифметические операции, операции сравнения.
  • Математические символы и функции.
  • Логические операции: конъюнкция,
    дизъюнкция, отрицание и т.д.
  • Кванторы всеобщности (), существования () и количества (N). Каждое вхождение переменной X в логическое выражение, которое не связано квантором, называется свободным, а X — свободной переменной.


Подстановки
  • Пусть Р — логическое выражение. Обозначение (1) используется для выражения, которое получается в результате подстановки выражения у вместо всех свободных вхождений переменной х в Р.
  • Аналогично (2) обозначает одновременную подстановку вместо всех свободных вхождений любой переменной хi в Р cоответствующего выражения уi Вхождения xi в уj не замещаются. Переменные х1,х2,… должны быть различными.



Правила вывода
  • Правила вывода, задающие семантику языка программирования, в общем случае это схемы рассуждений, позволяющие доказывать свойства программы, т.е. из некоторых предпосылок, входных условий получать утверждение о спецификации программы.
  • Если Н1,Н2,…,Нn — истинные утверждения, то Н — также истинное утверждение. Если входных условий нет (т.е. n=0), то правило вывода будем записывать просто в виде H



Простейшие правила вывода
  • Первое утверждает, что если выполнение программы S обеспечивает истинность утверждения R, то оно также обеспечивает истинность каждого утверждения, которое следует из R.
  • Второе правило утверждает, что если R — известное предусловие программы S, приводящей к результату Q после завершения своего выполнения, то это же относится к любому другому утверждению, из которого следует R.
  • Эти два правила называются правилами консеквенции.


Правила тавтологии
  • Правила тавтологии выводятся непосредственно из определения спецификации программы
  • Для любых P,Q, S