- •Экзамен по информатике
- •Цели программирования.
- •Области языков программирования. Научные приложения.
- •Области языков программирования. Коммерческие приложения.
- •Области языков программирования. Искусственный интеллект.
- •Области языков программирования. Системное программирование.
- •Области языков программирования. Языки сценариев.
- •Области языков программирования. Web-программирование.
- •Уровни языков программирования. Машинные языки (история возникновения, отличительные особенности, пример языков).
- •Уровни языков программирования. Языки ассемблера (история возникновения, отличительные особенности, пример языков).
- •Уровни языков программирования. Языки высокого уровня (история возникновения, отличительные особенности, пример языков).
- •Методы реализации программ. Компиляция (схема получения результата из исходного кода, преимущества и недостатки, сравнительная таблица).
- •Методы реализации программ. Интерпретация (схема получения результата из исходного кода, преимущества и недостатки, сравнительная таблица).
- •Методы реализации программ. Смешанная реализация (схема получения результата из исходного кода, преимущества и недостатки, сравнительная таблица).
- •Методы реализации программ. Трансляция (схема получения результата из исходного кода, преимущества и недостатки, сравнительная таблица).
- •Жизненный цикл программного продукта.
- •Этапы решения задач на эвм.
- •Цикл с параметром
- •Пример 1
- •Пример 2
- •Способы записи алгоритма. Псевдокод.
- •Пример 1
- •Пример 2
- •Рекомендации
- •Сравнение примеров
- •Способы записи алгоритма. Сравнение различных подходов. Выгоды использования блок-схем и псевдокода
- •Только псевдокод
- •Концепция памяти.
- •Принципы типизации данных.
- •Тип данных bool
- •Тип данных char
- •Целочисленные типы данных
- •Типы данных с плавающей точкой
- •Иерархия простых типов данных.
- •Стандартные типы данных. Таблица характеристик. Особенности выбора типа.
- •Правила приведения типов.
- •Пример, использующий преобразование типов
- •Оператор sizeof.
- •Переменные (объявление, инициализация, присвоение).
- •Константы. Специальные символы. Квалификатор const.
- •Область видимости переменных.
- •Операторы управления областью видимости.
- •Группы операций (особенности записи, таблица приоритетов)
- •Понятие ассоциативности, приоритета, размерности. Таблица приоритета операций.
- •Арифметические операции.
- •Логические операции (краткая схема вычислений).
- •Побитовые операции (таблицы истинности). Побитовые логические операции
- •Побитовое отрицание (not)
- •Побитовое и (and)
- •Побитовое или (or)
- •Сложение по модулю два (xor)
- •Другие побитовые логические операции
- •Битовые сдвиги
- •Принципы структурного программирования.
- •Структурное программирование: три базовые конструкции.
- •Основные операторы.
- •Параметры по-умолчанию.
- •Перегрузка функций.
- •Файлы (понятие, текстовые и двоичные файлы, структурированные и неструктурированные, операции, основные библиотеки для работы с файлами).
- •Режимы открытия файлов
- •Потоковый ввод-вывод. Библиотека потокового ввода-вывода. Вступление.
- •Консольный ввод/вывод.
- •Форматирование.
- •Потоковый ввод/вывод пользовательских типов.
- •Файловый ввод/вывод.
- •Файловый ввод-вывод. Стандартная библиотека ввода-вывода.
Иерархия простых типов данных.
Итак, прежде всего, давайте разберемся с тем, как типы данных взаимодействуют друг с другом. Существует так называемая иерархия типов, где все типы размещены по старшинству. Для того, что бы разбираться в преобразовании типов, необходимо всегда помнить порядок типов этой иерархии.
bool,char,short-int-unsigned int-long-unsigned long-float-double-long double
Несмотря на то, что некоторые типы имеют одинаковый размер, в них помещается разный диапазон значений, например, unsigned int в отличие от int может поместить в себя в два раза больше положительных значений, и потому является старше по иерархии, при этом оба типа имеют размер 4 байта. Кроме того, следует отметить, очень важную особенность, отраженную здесь, если в преобразовании типов участвуют такие типы, как bool,char,short, они автоматически преобразовываются к типу int.
Теперь, давайте рассмотрим, различные классификации преобразований.
Классификация по диапазону содержащихся значений. Все преобразования можно разделить на две группы относительно местоположения в иерархии типов участвующих в преобразовании.
Стандартные типы данных. Таблица характеристик. Особенности выбора типа.
Правила приведения типов.
И, вот, мы наконец-то подошли к тому, о чем говорили в самом начале данного раздела урока, как выяснить какого типа будет результат какого-то выражения. Давайте попробуем это вычислить, пользуясь полученными знаниями. Предположим у нас есть следующие переменные:
int I=27;
short S=2;
float F=22.3;
bool B=false;
Пользуясь этими переменными, мы собираемся составить такое выражение:
I-F+S*B
В переменную какого типа данных нам следует записать результат? Решить это просто, если представить выражение в виде типов данных:
int-float+short*bool
Напоминаем, что short и bool сразу же примут тип int, так что выражение будет выглядеть так:
int-float+int*int, при этом false станет 0
Умножение int на int даст, несомненно, результат типа int. А вот сложение float с int, даст на выходе float, так как, здесь вступает в игру новое правило: Если в каком-либо выражении используются разные типы данных, то результат, приводится к большему из этих типов. Ну и, наконец – вычитание из int типа float, согласно только что упомянутому правилу снова даст float. Таким образом, результат выражения будет иметь тип float.
float res= I-F+S*B; // 27-22.3+2*0
cout<<res; // на экране число 4.7
Теперь, когда вы знакомы с правилом, вам нет необходимости, детально разбирать выражение, достаточно просто найти наибольший тип, именно он и будет результирующим.
Примечание: Будьте очень внимательны также и при сочетании переменных с одинаковыми типами данных. Например, помните, если целое делится на целое, то и получиться целое. То есть, int A=3; int B=2; cout«A/B; на экране 1, так как результат - int и дробная часть утеряна. cout«(float)A/B; на экране 1.5, так как результат - float.
Пример, использующий преобразование типов
Теперь давайте закрепим знания на практике. Создадим проект и напишем нижеследующий код.
# include <iostream>
using namespace std;
void main(){
// объявление переменных и запрос на ввод данных
float digit;
cout<<"Enter digit:";
cin>>digit;
/* Даже, если пользователь ввел число с вещественной частью,
результат выражения запишется в int и вещественная часть будет утеряна,
разделив число на 100 мы получим количество сотен в нем. */
int res=digit/100;
cout<<res<<" hundred in you digit!!!\n\n";
}
Теперь, когда вы рассмотрели пример, вы, конечно же, убедились, что c помощью преобразования типов можно не просто организовать временный переход из одного типа в другой, но и решить простую логическую задачу. Следовательно, вы должны отнестись к этой теме с должным вниманием. Понимание преобразования в дальнейшем поможет вам не только решать интересные задачи, но избежать ненужных ошибок.
Преобразование типов в стиле С++
Для начала напомним, что оператор приведения типов позволяет компилятору преобразовывать один тип данных в другой. И язык программирования С, и язык программирования С++ поддерживают такую форму записи приведения типа:
(тип) выражение
Например,
double d;
d=(double) 10/3;
Операторы приведения типа в языке С++
Кроме стандартной формы в стиле С, язык С++ поддерживает дополнительные операторы приведения типа. Вполне очевидны положительные стороны этих новых способов. Во-первых данные преобразования позволяют сделать то, чего стандарт языка С даже не предполагал: снять модификатор const, или радикально поменять тип данных. Во-вторых, стиль С++ увеличивает контроль над преобразованиями и позволяет выявить ошибки с ними связанные на ранних этапах.
const_cast<тип> (объект)
dynamic_cast<тип> (объект) проверяет законность выполнения заданной операции приведения типа
reinterpret_cast<тип>(объект) следует использовать для перевода типов указателей, которые несовместимы по своей природе
static_cast<тип> (объект) Фактически, static_cast - это аналог стандартной операции преобразования в стиле С.
const_cast используется для явного переопределения модификаторов const и/или volatile. Новый тип должен совпадать с исходным типом, за исключением изменения его атрибутов const или volatile. Чаще всего оператор const_cast используется для снятия атрибута const. Например:
Следует помнить, что только оператор const_cast может освободить от "обета постоянства", т.е. ни один из остальных операторов этой группы не может "снять" с объекта атрибут const.
#include <iostream>
using namespace std;
// указатель на объект является константным,
// следовательно, через него изменить значение
// объекта нельзя
void test_pow(const int* v){
int*temp;
// снимаем модификатор const
// и теперь можем изменять объект
temp=const_cast<int*>(v);
// изменение объекта
*temp= *v * *v;
}
void main(){
int x=10;
// на экране - 10
cout<<"Before - "<<x<<"\n\n";
test_pow(&x);
// на экране - 100
cout<<"After - "<<x<<"\n\n";
}
Примечание: Кстати, стоит упомянуть о том, что такое volatile!!! Данный модификатор даёт компилятору понять, что значение переменной, может быть изменено неявным образом. Например, есть некая глобальная переменная, её адрес передаётся встроенному таймеру операционной системы. В дальнейшем эта конструкция может использоваться для отсчёта реального времени. Естественно, что значение этой переменной изменяется автоматически без участия операторов присваивания. Так вот такая переменная должна быть объявлена с использованием ключевого слова volatile. Это связано с тем, что многие компиляторы не обнаружив ни одного изменения переменной с помощью какого-либо присваивания, считают, что она не изменяется и оптимизируют выражения с ней, подставляя на её место конкретное значение. При этом компилятор переменную не перепроверяет. Действительно, зачем, если переменная для него имеет характер "неизменяемой"?! volatile - будет блокировать подобные действия компиляторов.
dynamic_cast проверяет законность выполнения заданной операции приведения типа. Если такую операцию выполнить нельзя, то выражение устанавливается равным нулю. Этот оператор в основном используется для полиморфных типов. Например, если даны два полиморфных класса, B и D, причем класс D выведен из класса B, то оператор dynamic_cast всегда может преобразовать указатель D* в указатель B*. Оператор dynamic_cast может преобразовать указатель B* в указатель D* только в том случае, если адресуемым объектом является объект D. И вообще, оператор dynamic_cast будет успешно выполнен только при условии, что разрешено полиморфное приведение типов (т.е. если новый тип можно законно применять к типу объекта, который подвергается этой операции). Рассмотрим пример, демонстрирующий всё вышесказанное:
#include <iostream>
using namespace std;
// базовый класс
class B{
public:
// виртуальная функция для
// последующего переопределения в потомке
virtual void Test(){
cout<<"Test B\n\n";
}
};
// класс-потомок
class D:public B{
public:
// переопределение виртуальной функции
void Test(){
cout<<"Test D\n\n";
}
};
void main(){
// указатель на класс-родитель
// и объект класса-родителя
B *ptr_b, obj_b;
// указатель на класс-потомок
// и объект класса-потомка
D *ptr_d, obj_d;
// приводим адрес объекта (D*) к указателю типа D*
ptr_d= dynamic_cast<D*> (&obj_d);
// если все прошло успешно - вернулся !0
// произошло приведение
if(ptr_d){
cout<<"Good work - ";
// здесь вызов функции класса-потомка
// на экране - Test D
ptr_d->Test();
}
// если произошла ошибка - вернулся 0
else cout<<"Error work!!!\n\n";
// приводим адрес объекта (D*) к указателю типа B*
ptr_b= dynamic_cast<B*> (&obj_d);
// если все прошло успешно - вернулся !0
// произошло приведение
if(ptr_b){
cout<<"Good work - ";
// здесь вызов функции класса-потомка
// на экране - Test D
ptr_b->Test();
}
// если произошла ошибка - вернулся 0
else cout<<"Error work!!!\n\n";
// приводим адрес объекта (B*) к указателю типа B*
ptr_b= dynamic_cast<B*>(&obj_b);
// если все прошло успешно - вернулся !0
// произошло приведение
if(ptr_b){
cout<<"Good work - ";
// здесь вызов функции класса-потомка
// на экране - Test B
ptr_b->Test();
}
// если произошла ошибка - вернулся 0
else cout<<"Error work!!!\n\n";
// ВНИМАНИЕ!!! ЭТО НЕВОЗМОЖНО
// попытка привести адрес объекта (B*) к указателю типа D*
ptr_d= dynamic_cast<D*> (&obj_b);
// если все прошло успешно - вернулся !0
// произошло приведение
if(ptr_d)
cout<<"Good work - ";
// если произошла ошибка - вернулся 0
else cout<<"Error work!!!\n\n";
}
Результат работы программы:
Good work - Test D
Good work - Test D
Good work - Test B
Error work!!!
static_cast выполняет неполиморфное приведение типов. Его можно использовать для любого стандартного преобразования. При этом никакие проверки во время работы программы не выполняются. Фактически, static_cast - это аналог стандартной операции преобразования в стиле С. Например, так:
#include <iostream>
using namespace std;
void main(){
int i;
for(i=0;i<10;i++)
// приведение переменной i к типу double
// результаты деления на экране, естественно
// вещественные
cout<<static_cast<double>(i)/3<<"\t";
}
reinterpret_cast переводит один тип в совершенно другой. Например, его можно использовать для перевода указателя в целый тип и наоборот. Оператор reinterpret_cast следует использовать для перевода типов указателей, которые несовместимы по своей природе. Рассмотрим пример:
#include <iostream>
using namespace std;
void main(){
// целочисленная переменная
int x;
// строка (указатель типа char)
char*str="This is string!!!";
// демонстрируем строку на экран
cout<<str<<"\n\n"; // на экране - This is string!!!
// преобразуем указатель типа char в число
x=reinterpret_cast<int>(str);
// демонстрируем результат
cout<<x<<"\n\n"; // на экране - 4286208
}