Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Хлопин Сергей Владимирович Конспект лекций «Теория и технологии программирования. Процедурное программирование» Санкт-Петербург 2011 |
Оглавление.
Этапы создания программного продукта. 5
Структурирование программы. 6
Этапы получения программного кода 8
Функциональная декомпозиция 8
Алгоритм программы 9
Функция main 16
Блоки кода 17
Комментарии 17
Базовые понятия языка Си. 18
Специфика присвоения имён. 19
Ключевые слова Си 20
Типы данных. 22
Функции 23
Блоки кода программы. 24
Операторы языка C. 25
Базовые типы данных. 29
Файловый ввод - вывод. 30
Понятие, объявление и определение. 31
Ключевое слово typedef. 32
Виды компоновки программ: 32
Область видимости. 33
Пространство имён. 34
Оператор разрешения области видимости. 36
Пространство имён namespace. 36
Инструкции. 37
Инструкции условий. 38
Инструкции цикла. 39
Инструкции безусловного перехода. 40
Препроцессор. Заголовочные файлы. Директивы препроцессора. 41
Указатели. 45
Арифметика указателей. 46
Связь массивов и указателей. 50
Двумерные массивы. 51
Трёхмерные массивы. 52
Массивы указателей. 52
Динамическое распределение памяти. 53
Управление памятью. Оператор new и delete. 53
Оператор new и массивы. 54
Многомерные массивы. 55
Ссылки. 56
Функции. 58
Определение функции (реализация). 60
Вызов inline функции. 61
Указатели на массивы в качестве параметров функции. 65
Значения аргументов функций по умолчанию. 65
Переменное число параметров функции. 67
Функции стандартной библиотеки 68
Виды возвращаемых значений и механизмы их формирования. 69
Проблемы при возвращении ссылки или указателя. 70
Ключевое слово const. 71
“Перегрузка” имён функции. 71
Возможный конфликт при использовании параметров по умолчанию. 73
Рекурсивные функции. 73
Структуры Си. 74
Структуры и функции. 77
Любой выпущенный программный продукт за свой жизненный цикл проходит множество стадий от возникновения идеи и у заказчика к оформлению технического задания, производства программного комплекса и последующей его поддержки. Жизненный цикл программного продукта может быть представлен в виде хронологического ряда, который изображен на рисунке 1.
Постановка задачи |
Документирование |
|
Проектирование |
||
Оценка риска |
||
Верификация |
||
Кодирование |
||
Тестирование |
||
Уточнения |
||
Производство |
||
Сопровождение |
Рис. 1. Этапы создания программного продукта.
Чем хуже организованны этапы разработки, тем больше требуется время на программирование, и тем самым повышается стоимость программы.
Для увеличения скорости разработки программы, вся задача по созданию программы делится на подзадачи между разработчиками. В данном случае, применяется метод структурного программирования.
Структурное программирование метод программирования, обеспечивающий создание текста программы, структура которого: отражает структуру решаемой задачи и хорошо читается не только создателями, но и другими пользователями.
Для создания структурированного вида программного кода необходимо проводить деление поставленной задачи на несколько крупных подзадач, каждая из которых в свою очередь также может быть поделена на более мелкие подзадачи. Предложенный подход называемый декомпозицией задачи на подзадачи представлен на рисунке 2.
Помимо декомпозиции задачи на подзадачи, применяется также файловая декомпозиция различные подзадачи, полученные при декомпозиции задачи, представляются в виде отдельных функций, которые могут быть размещены в разных файлах исходного кода.
Задача
Подзадача 2
Подзадача 1
Подзадача 3
Подзадача 1.1
Подзадача 1.2
Рис. 2. Декомпозиция задачи на подзадачи.
Программа
Файл 1
Файл 2
Файл 3
Функция 1
Функция 3
Функция 2
Блок 3
Блок 2
Блок 1
Рис. 3. Файловая декомпозиция.
Разбиение на файлы позволяет:
Но между файлами возникают взаимодействия, поэтому программист обязан за ними следить.
Получение из исходного кода исполняемого модуля программы, проходит несколько этапов:
Функция первый уровень абстракции программирования (один и тот же код работает с разными наборами данных), при этом решаются две задачи:
Основной задачей программиста является «объяснение» компилятору, каким образом компилятор должен формировать вызов по отношению к данному модулю функции. То есть, программист должен правильно задать входные данные.
Пример функциональной декомпозиции изображен на следующей схеме:
main ( ) {… вызов f1 ( ) вызов f4 ( ) } |
f1 ( ) {… вызов f2 ( ) вызов f3 ( ) } f4 ( ) { } |
f2 ( ) {… } f3 ( ) {… } |
В C++ функция это фрагмент (блок) кода, оформленный определенным образом (в частности, ограничен фигурными скобками) и выполняющий некоторое законченное действие. В языках C функция принимает на вход множество значений, но возвращает всегда одно.
Основным в процессе программирования является разработка алгоритма. Это один из наиболее сложных этапов решения задачи с использованием ЭВМ. В начале обучения программированию, на наш взгляд, целесообразно не привязываться сразу к какому-либо языку, а, разрабатывать алгоритм решения задачи используя блок-схемы. После такой алгоритмизации проще перейти к записи того же алгоритма на определённом языке программирования. Основными алгоритмическими структурами (ОАС) являются следование, развилка и цикл. В более сложных случаях используются суперпозиции (вложения) ОАС.
Ниже приведены графические обозначения (обозначения на блок-схемах) ОАС.
|
|
|
|
|
|
На схемах СЕРИЯ обозначает один или несколько любых операторов; УСЛОВИЕ есть логическое выражение (ЛВ) (если его значение ИСТИНА, переход происходит по ветви ДА, иначе по НЕТ). На схеме цикла с параметром использованы обозначения: ПЦ параметр цикла, НЗ начальное значение параметра цикла, КЗ конечное значение параметра цикла, Ш шаг изменения параметра цикла.
Начало и конец алгоритма на блок-схемах обозначают овалом, вводимые и выводимые переменные записываются в параллелограмме.
В примерах мы будем использовать запись алгоритмов с помощью блок-схем и словесное описание.
Линейные алгоритмы
Простейшие задачи имеют линейный алгоритм решения. Это означает, что он не содержит проверок условий и повторений.
Пример 1. Пешеход шел по пересеченной местности. Его скорость движения по равнине v1 км/ч, в гору v2 км/ч и под гору v3 км/ч. Время движения соответственно t1, t2 и t3 ч. Какой путь прошел пешеход?
1. Ввести v1, v2, v3, t1, t2, t3. 2. S1 := v1 * t1. 3. S2 := v2 * t2. 4. S3 := v3 * t3. 5. S := S1 + S2 + S3. 6. Вывести значение S. 7. Конец. |
Для проверки работоспособности алгоритма необходимо задать значения входных переменных, вычислить конечный результат по алгоритму и сравнить с результатом ручного счета.
Пример 2. Дано натуральное трехзначное число n, в записи которого нет нулей. Составить алгоритм, который возвращает значение ИСТИНА, если верно утверждение: "число n кратно каждой своей цифре", и ЛОЖЬ в противном случае.
1. Ввести число n 2. A := n mod 10 {разряд единиц} 3. B := n div 100 {разряд сотен} 4. C := n div 10 mod 10 {десятки} 5. L := (n mod A=0) and (n mod B=0) and (n mod C=0) 6. Вывод L 7. Конец |
На приведенной выше схеме DIV и MOD соответственно операции деления нацело и получения остатка от целочисленного деления. В фигурных скобках записаны пояснения (комментарии) к операторам.
Развилка
Достаточно часто то или иное действие должно быть выполнено в зависимости от значения логического выражения, выступающего в качестве условия. В таких случаях используется развилка.
Пример 1. Вычислить значение функции
1. Ввести x. 2. Если x£12, то y:=x2 3. Если x<0, то y:=x4 4. y := x2 5. Вывести y 6. Конец |
При тестировании алгоритмов с развилкой необходимо подбирать такие исходные данные, чтобы можно было проверить все ветви. В приведенном выше примере должно быть по крайней мере три тестовых набора.
Пример 2. Дано натуральное число n. Если число нечётное и его удвоение не приведет к выходу за 32767 (двухбайтовое целое число со знаком), удвоить его, иначе оставить без изменения.
Чтобы удовлетворить условию удвоения, число n должно быть нечетным и меньше 16384.
1. Ввести число n 2. Если число n нечетное и меньше 16384, то n := n * 2 3. Вывод n 4. Конец |
Рассмотренный пример иллюстрирует неполную развилку. Также следует отметить, здесь логическое выражение, являющееся условием, содержит 2 операнда.
Циклы
Если какие-либо операторы необходимо выполнить несколько раз, то их не переписывают каждый раз заново, а организуют цикл.
Пример 1. Подсчитать количество нечетных цифр в записи натурального числа n.
Идея решения. Из заданного числа выбирать из младшего разряда цифру за цифрой до тех пор, пока оно не исчерпается, т.е. станет равным нулю. Каждую нечётную цифру учитывать.
1. Ввести число n 2. K := 0 {подготавливаем счётчик} 3. Если n = 0, переход к п. 7 4. Если n mod 10 mod 2 = 1, то K := K +1 5. n := n div 10 6. Переход к п. 3 7. Вывод K 8. Конец |
Задача решена двумя способами. Слева решение оформлено с использованием цикла с предусловием, справа с постусловием.
Пример 2. Дана последовательность, общий член которой определяется формулой
Вычислить при n>2 сумму тех ее членов, которые больше заданного числа e.
При решении задачи находится очередной член последовательно и, если он больше e, добавляется к сумме.
1. Ввести e 2. S := 0 3. A := 1/4 4. n := 3 5. Сравнить А с e. Если A>=e, переход к п. 10 6. S := S + A 7. A := (n-1)/(n*n) 8. n := n + 1 9. Переход к п. 5 10. Вывод S 11. Конец |
В рассмотренных выше примерах количество повторений заранее неизвестно. В первом оно зависит от количества цифр в записи натурального числа, во втором от числа e.
В тех же случая, когда количество шагов известно из условия задачи, проще и предпочтительней использовать цикл с параметром.
Пример 3. Найти произведение первых k натуральных чисел, кратных трём.
При составлении алгоритма учтем, что первое натуральное число, кратное 3, есть тройка, а все последующие больше предыдущего на 3.
1. Ввод k 2. P := 1 {здесь накапливаем произведение} 3. T := 0 {здесь будут числа, кратные 3} 4. I := 1 5. Если I > k, переход к п. 10 6. T := T + 3 7. P := P * T 8. I := I + 1 9. Перейти к п. 5 10. Вывод P 11. Конец |
Другие примеры будут записаны уже на ЯПВУ. В настоящей же публикации предпринята попытка продемонстрировать, что изучение программирования разумно начинать собственно с разработки алгоритмов, не акцентируя первоначально внимания на записи алгоритма на том или ином языке программирования. В то же время автор, являясь сторонником структурного подхода к программированию, предлагает придерживаться этого подхода и при программировании на уровне блок-схем.
main - основная функция языка си. Для языка си характерна следующуя специфика:
Для различных версий компиляторов языка си могут быть применены различные виды главной функции main:
В языке С имеет различие написание маленьких и заглавных букв
Завершение программы происходит в случаях:
при этом в случаях 1, 2 и 3 имеется корректный выход, 4 некорректный выход из программы.
Блок кода несколько строк, заключённые в фигурные скобки.
Если внутри блока кода содержится другой блок кода, то он называется составной инструкцией.
Внутри каждого блока кода могут быть объявлены данные, локальные для данного блока.
Существуют два возможных способа создания коментариев:
Рекомендации:
Ключевые слова Cи - слова, зарезервированные только для использования компилятором языка Си.
примеры:
#include
#define
if, for, while, int, float, char…
Идентификаторы используются для присвоения имён объектам.
примеры:
Примеры правильных имён |
Примеры неправильных имён |
hello Hello this_is_very_long_var _u_name |
a var1 my.var1 char $my _TYPE |
Ключевые слова- это предопределенные идентификаторы, которые имеют специальное значение для компилятора Си. Их можно использовать только так как они определены. Имена объектов программы не могут совпадать с названиями ключевых слов.
Список ключевых слов:
auto, double, int, struct, break, else, long, switch, case, enum, register, typedef, char, extern, return union, const, float, short, unsigned, continue, for, signed, void, default, goto, sizeof, while, do, if, static, volatile.
Ключевые слова не могут быть переопределены. Тем не менее, они могут быть названы другим текстом, но тогда перед компиляцией они должны быть заменены посредством препроцессора на соответствующие ключевые слова.
Понятие lvalue и rvalue.
Для того, чтобы отличать выражения, обозначающие объекты, от выражений, обозначающих только значения были введены понятия lvalue и rvalue. Определение lvalue использовается для обозначения выражений, которые могли стоять слева от знака присваивания (left-value); им противопоставлялись выражения, которые могли находиться только справа от знака присваивания (right-value).
Характерные случаи использования lvalue и rvalue:
rvalue:
lvalue:
Типы данных. В языке Си имеются два существенно различных типа данных: int- целый и float - вещественный(с плавающей точкой). Из них можно создавать еще два типа: char - символьный , double - вещественный с двойной точности.
Следует различать тип данных и модификатор типа.
Имеются следующие базовые типы:
К модификаторам относятся:
Тип данных и модификатор типа определяют:
Итак, к данным целого типа относятся следующие типы:
char, int, long int (2 байта), short int (4 байта). Модификаторы signed и unsigned могут предшествовать любому целому типу, но они не обязательны. Они указывают, как интерпретируется старший бит переменной. По умолчанию все переменные целого типа считаются signed, т.е. левый бит интерпретируется как знак.
К вещественным типам данных (с плавающей точкой) относятся:
float, double, long double (10 байт).
Функция - это самостоятельная единица программы, созданная для решения конкретной задачи. Функциями удобно пользоваться, например, если необходимо выполнить однотипные действия с одинаковым по типу набором данных.
Функции могут возвращать значение. Это значение может быть использовано далее в программе.
Использование функций позволяет:
Существует два типа функций:
Пример рекурсивной функции:
int Factorial (int a)
{
if(a==a)
{
return 1;
}
else
{
int fact a=a factorial(a-1);
return fact;
}
}
Блоки кода служат для логического разделения областей программы. Блок кода выделяется в тексте фигурными скобками:
{
Блок кода
}
Могут содержать в себе вложенные Блоки кода.
{
Блок кода
{
Блок кода 1
}
}
Можно объявить данные или переменные, и они будут локальные (нельзя использовать вне блока).
Использование блоков кода позволяет графическую структуру программы сделать более читабельной.
Арифметические операторы + - * …
1.1) % - остаток от целочисленного деления
int x=1, y=2, z;
z=x/y; //z=0
z=x%y; //z=1
z=3%5; //z=2
1.2)Инкремент увеличение числа на единицу.
Декремент уменьшение числа на единицу.
x=1;
y=x++; (y=x+1)
y++; (y=y+1)
префиксная запись y=x++
постфиксная запись y=++x
true (!=0)
false (0)
3.1) Операторы отношения
< , <= , >=, >, = =, !=
3.2) Логические операторы
!, && , //
( и ) ( или)
if(year>14&&year<18)
if(t>36.7|| t<36 || p>140)
Тернарный оператор «?:» является сокращенной формой конструкции if…else. Он получил такое имя потому, что включает в себя три операнда. Оператор вычисляет условие и возвращает одно значение в случае, если условие верно, и другое значение, если условие неверно. Синтаксис оператора:
условие ? значение_истина : значение_ложь
Здесь условие это выражение типа Boolean, значение_истина представляет собой значение, которое возвращается, если условие равно true, и значение_ложь возвращается в противном случае.
1) int a=1, b=2, c;
c=a,b; //с=1
c=(a,b); //c=2
c=a++,b++ ; //c=1, a=2, b=3
2) if(std::cin>>x, x>0) //вычисляет все, но формируется последняя
char symbol;
symbol=A;
printf(“%c”, symbol);
char symb 1[10];
symb 1[0]=s;
symb 1[1]=t;
symb 1[2]=u;
symb 1[3]=d;
………………
Данные
Данные
Константы
Переменные
Логические данные
Символы (строки)
Числа
Плавающая (.)
Целые
Данные могут быть:
int float double unsigned short long
3) символы и строки;
char char[]
4) логические;
bool
Символьные данные (литералы) символ, пара символов или три символа, заключенные в кавычки.
ASCII (7-и битная кодировка)
0-127 символов
KOI-8
UNICODE 65535 символов
Строковые литералы последовательность, заключенная в двойные кавычки символов.
const char[]
Перечисление.
enum
my_action =0 //плюс
=1 //минус
=2 //умножить
=3 //делить
1)enum Action
{
plus, //0
minus, //1
multiply, //2
division, //3
};
my_action = plus, //автоматически присваивает значение
2)enum Action
{
plus=5,
minus=3,
multiply=2,
division=1,
};
3)enum Action
{
plus=5, //5
minus, //6
multiply, //7
division, //8
};
int m=7;
if(m>minus)
{
true
}
Типы данных
базовые производные
1)Базовые
целые
bool
void
плавающие
char (1)
short(2)
long(4)
int (системно зависим)
_int n
(n=8,16,32,64)
long long (8)
Плавающая точка:
float (4)
double (8)
long double (8)
2)Производные
y=300*sin(x)
int double
Неявное приведение типов производит компилятор автоматически по правилу:
”Полученное в результате вычисления выражение приводится к типу слева от знака равенства.”.
Явное приведение типов.
y=300*(int)sin(x); //неверно!!!
y=(int)(300*sin(x));
float var1;
cin>>var1;
if(var1 != (int)var1) проверка на тип данных
{
// не целое
}
Булевые переменные (тип BOOL).
True =1 False=0
ex. :
bool b=(x<=y);
//b=5 ошибка
int n=b; (неявное приведение к типу int)
Старый BOOL 4 бита
Сейчас BOOL 1 бит
Программист даёт имена переменным или функциям, а потом использует переменные в выражении или вызове функции. Когда компилятор встречает в тексте программы любое имя, он должен знать, что имеется ввиду под этим именем, поэтому в C использованию любого имени должно предшествовать описание его свойств.
Declaration
Объявлений одного имени может быть сколько угодно, но они должны быть согласованы.
Определение может быть только одно.
extern int iNumber;
float var2; //объявление + определение
double var3 = 3.14159,
Один тип объявляется через , :
char var1, var2,var3, var4 = y ;
typedef тип синоним типа,
typedef unsigned char BYTE;
BYTE var8;
#if defined(_win32)
typedef int int32;
#else
typedef long int 32;
#endif
int32 i=1 - 32 разряда памяти
Нельзя комбинировать имена, созданные с помощью typedef.
//typedef long int 32 MY; - НЕЛЬЗЯ
for (int index…)
{
index
}
1) Внешняя
external
(все переменные определены вне {} без спецификатора static)
int var1,
int main
{}
2) Внутренняя
internal
(объявление вне {}, но с ключевым словом static )
3) Без компоновки
no linkage
(не подлежат компоновке)
{int var1;
}
file.cpp
int z;
static int y;
void f1()
{
int x;
static int;
}
void f2()
{
использование y
}
void f3()
{
использование y, z
}
file2.cpp
extern int z;
void f4();
{
использование z;
}
Автоматическая память все переменные, которые были определены внутри блока, или параметры функции.
{
…
int i;
}
//i=1
File scope область видимости для переменной не {} будет видна только в этом файле
#include…
int z;
void main()
{
z=10;
}
--- namespace;
---function scope;
---void f1(void)
{
label1:
}
void f2(void)
{
label1:
}
---class scope
---область видимости имён параметров прототипа функции.
Скрытие имени переменной.
int ix; //глобальная
void Func ()
{
int ix; //локальная
ix=2;
{
int ix; //локальная
ix=3;
}
}
К скрытому глобальному имени можно обратиться с помощью оператора разрешения области видимости “ :: ”
int ix; // глобальная
void Func ()
{
ix=1;
int ix;
:: ix=50;
обращение к глобальной переменной
ix=4;
}
Void F1 ()
{
ix=20;
}
namespace one { int version=1;}
namespace two { int version=2;}
int main ()
{
int n=one::version; //1
int n1=two:: version; //2
}
Инструкции идут по порядку, содержат имена, разделители.
инструкция
имя оператор разделители
(частей инструкции)
TAB пробел
Типы инстр. |
Для чего она |
Примечание |
составная инструкция |
группа интср. |
{} может не содержать ни |
|
( заключ. В {}) |
одной инструкции |
инструкция объявления |
имя переменной |
Вводит новое имя в новые |
|
(нового типа) |
области видимости |
Последова-тельность операторов, |
может содержать арифм. |
|
инструкция выражения |
операндов, действ. |
выражения, логич. выраж., |
|
над ними |
вызов ф-ии |
пустая инструкция |
; |
|
инструкция выбора |
if, else, switch |
|
инструкция цикла |
do, while, for |
while (усл.) инструкция |
инструкция безусловного |
continue, go to, return, |
|
перехода |
break |
|
If, if else
if (условие) {true инструкция}
[ else {false инструкция}] не обязательно
If (x!=0) … if (x)
Переключатель switch
switch (выражение)
{
case конст. _1:
…//
break;
case конст. _2:
…//
break;
default:
}
Существенно:
1) В скобках switch находится выражение, которое в итоге сводится к целому типу (switch переводит к целому типу).
2) Точки, куда передаётся управление, помечены ключевым словом case. Каждой такой метке сопоставляется константа (типа int) для сравнения со значением, вычисленным в скобках.
3) Обычно использование case предполагает, что будет выполняться вся ветвь программы, следующая за операндом. Для прекращения обработки case выражений, используется функция break, которая передаёт управление на закрывшуюся скобку.
switch (выражение)
{
case 5;
cout<<5;
case 4;
cout<<4;
}
Выражение default будет выполняться, если не совпало ни одно условие.
switch (выр-е)
{
case 5;
{
int iX;
}
break;
}
While.
Обычно используется для нерегулярных циклов (количество повторений заранее неизвестно).
while (условие продолжения цикла) {операторы}
Замечание: С помощью while очень легко создать бесконечный цикл.
while (int i=1) {}
break выход из бесконечных цикла
if(getch()=_) break;}
do… while
do тело цикла while (условие продолжения)
for
Используется для организации регулярных циклов (с известным количеством повторений).
for ([инициализирующая инструкция];[выражение 1, условие]; [выражение 2])
{ тело цикла;}
for (index=0, index <10, index ++)
Условие продолжения цикла с оператором for вычисляется на каждой итерации, поэтому не стоит компилировать вычисление сложения выражения.
Лучше выражение вычислять один раз до начала цикла.
for (… ; r<x*y*z; r++) => for (…,r<R; r++)
{}
В любой части цикла могут быть несколько выражений, они должны быть разделены “,”, а не “;”.
for (i=0, j=0; i<100; i++, j++)
Тело цикла может быть пустым.
break, continue, go to , return.
Break прерывает выполнение switch, while, for и передаёт управление на инструкцию, следующую за ними.
Если имеет место вложенность, то прерывается самая внутренняя по отношению к break инструкция.
while ()
{
continue
while ()
{
break;
}
}
continue прерывает выполнение while и for и переходит к следующей итерации.
return - прерывает выполнение текущей функции и возвращает управление вызывающей функции.
go to осуществляет безусловный переход, но только в пределах одной функции main function
Получение исполняемого кода из исходного текста происходит в несколько этапов: на первом этапе с исходным текстом программы работает специальная программа препроцессор.
Основная цель препроцессора - закончить форматирование исходного текста программы на С++.
Затем окончательный текст подвергается компиляции.
Замечание:
Список директив препроцессора (все начинаются с #).
# include включает в исходный текст программы текстовый файл (работает только с исходным кодом)
> - включение происходит из стандартной библиотеки текстовых файлов
“ ” файл сначала используется в исходном каталоге, а затем в стандартной библиотеке
# define задаёт макроподстановки и определяет имена для компилятора
# undef отменяет #define
# if # endif # ifdef # ifndef |
директивы условной трансляции - позволяют формировать различный код, который поступает к компилятору, что способствует переносимости программы. |
# pragma- позволяет настраивать компилятор с учётом специфических особенностей компьютера или ОС.
# error включается между #if и #endif для проверки какого-либо условия на этапе компиляции. При выполнении такого условия компилятор выдаёт сообщение, указанное в #error, и восстанавливается.
Лучше избегать использование директив препроцессора.
1. #define - задаёт макроподстановку.
-идентификатор_макро
-тело_макро
Препроцессор просматривает исходный текст, заменяет каждое вхождение идентификатор_макро на тело_макро.
#define First 1
{
main ()
If (n== First) // if (n==1)
}
#define False 0
#define True ! False
Можно также работать с функциями:
#define SQUARE(x) x*x
main()
int z=2;
y=SQUARE(z+1); //y примет значение 9
#define
Если тело_макро пустое, то идентификатор_макро будет заменён на пробел.
Предопределённые идентификаторы.
_ _cplusplus - исходный текст должен компилироваться как C++, а не C
_ _ DATE_ _ - во время компиляции преобразовывается в строку, заключая в кавычки, содержащую дату компиляции.
_ _FILE_ _ - вместо FILE подставляется строчка с именем файла, с учётом полного пути
_ _LINE_ _ - содержит в себе номер текущей строки
_ _TIME_ _ - время последней компиляции
cout << “Error in”<<_ _FILE_ _ #define
<<”str”<<_ _LINE_ _<<
<<”cur time”<<_TIME_;
Диагностический макрос (микрокоманда) assert.
Макрос assert имеет вид: assert (выражение)
Позволяет программисту отследить не предусмотренные задачей ситуации во время выполнения программы.
assert (x<0)
assertion failed: x<0; file…;
line…….;
2. #undef отмена использования #define
#define MESSAGE “hello”
cout<<MESSAGE;
#undef MESSAGE
cout<<MESSAGE; // выдаст ошибку
#ifdef MESSAGE
#else
#define MESSAGE “hello”
#endif
#if defined(_Win32)
typedef ind int 32;
#else
typedef long int 32;
#endif
int 32 m=1; // 32 разряда
3. #include получаем доступ к стандартной библиотеке
#include
std lib user files
general.h general.cpp func1.cpp func2.cpp
#include “general.h” #include “general.cpp” #include “func1.cpp” #include “func2.cpp”
Указатель переменная, содержащая адрес другого объекта си программы.
Если переменная содержит адрес некоторого другого объекта, то говорят, что переменная указывает на этот объект.
Виды указателей
на объект на функции
-указатели
-базовые типы
-на массивы
-struct, union, class…
int * pn; //объявим указатель на тип данных int
int *pn; то же
int* pn; самое
Замечание:
int a;
int b=10;
В зависимости от контекста объявления память под указатель компилятор может выделить: в стеке (локальная переменная), в статической области данных (глобальная или статическая), heap (динамически независима от того, где находится сам объект).
int *p; //глобальная
{
int * p1; //локальная
static int * p2; //статическая
}
Инициализация указателя и оператор получения адреса объекта.
& - амперсант
Чтобы провести явную инициализацию указателя:
int n=1;
int *pn=&n;
int a[100];
int *pa=&a;
char *pstr=”string”;
char *pstr=&”string”;
int * p2;
p2=pn;
pn++;
pn=pn+sizeof(int);
p2=pn+5;
p2=pn+5*sizeof(int)
if (pn==p2)
Существует вид указателя на объекты любого типа:
void *;
void * pvoid;
int n;
char c;
int * pn;
pvoid=&h; // OK
pvoid=&c; //OK
pvoid=pn; //OK
reinterpret_cast
double d=99.99;
int n=d; //d=99
int n=0x12345;
char c=n;
int *pn=&n;
char *pc=reinterpret_cast
<char*> (&n);
Массивы.
Свойства массивов:
Объявление встроенного массива.
Объявление совмещено с определением, поэтому программист должен предоставить компилятору информацию, сколько выделить памяти.
char cAr[10];
int iAr[15];
float fAr[5][3];
float f1Ar[3][3][8];
2. с extern
При объявлении массива производим описание свойств внешнего массива, определённого в другом файле.
При объявлении программист должен предоставить компилятору информацию о том, как вычислить адрес начала массива.
extern in tar[10][5];
const int N=10;
char cAr[N]; //OK!
int N1=5;
char cAr1[N1]; //ошибка
Инициализация массива.
int ar1[10][5];
main ()
{
static char ar2[100];
float ar3[3][4][5];
}
Явная инициализация.
int ar4[3]={1,2,3};
char cAr[2]={A;B};
char cAr1[2]={“AB”};
int nAr[2][2]={{1;1},{2;2}};
char cstring[2][80]={“Первая строка”,”Вторая строка”};
80
2 п |
е |
р |
в |
а |
я |
_ |
с |
т |
р |
о |
к |
а |
в |
т |
о |
р |
а |
я |
_ |
с |
т |
р |
о |
к |
а |
Можно объявить одномерный массив без указания числа элементов.
int nAr[][3]={{1,2,3},{2,3,4},…{7,5,8}} //не указано количество строк
Обращение к элементу массива. Оператор [].
Доступ к отдельным элементам массива может выполняться с помощью индексов (они нумеруются с 0, а не с 1). Индекс может быть любым целым выражением.
char Ar[10];
char c=Ar[0];
c=Ar[g];
c=Ar[i]; //i-int
c=Ar[i+g-k]; // c=Ar[3.48]; ERROR!!
float b;
c=Ar[(int)b]; //приводит к типу int (может быть потеря данных)
size of количество памяти, которое занимает массив
char ar[]=”abc”;
size_t n=sizeof(ar)/sizeof(char)
n=sizeof(ar)/sizeof(ar[0]);
int ar[][3]={1,2,3,4,5,6,7};
size_t n=sizeof(ar)/sizeof(int);
n=sizeof(ar[0])/sizeof(ar[0][0]) //подсчёт элементов в строке
n=sizeof(ar)/sizeof(ar[0]); //подсчёт числа строк в массиве
ar[]={2,4,5,8};
n=sizeof(ar)/sizeof(ar[0]);
Имя одномерного массива компилятор интерпретирует как константный указатель на нулевой элемент массива.
ar[0]=1 |
ar[1]=2 |
ar[2]=3 |
ar[3]=4 |
ar[4]=5 |
ar
p
В памяти:
int *p=ar;
int *int [];
int tmp=ar[i];
равнозначно
tmp=p[i];
tmp=*(p+i);
tmp=*(ar+i);
переместим указатель к следующему элементу массива, но не ar++(ошибка!)
p++; //ar++i
Двумерные массивы можно представить как одномерный, каждым элементом которого является строка (т.е. одномерный массив). Имя двумерного массива компилятор интерпретирует как константный указатель на нулевую строку.
int ar[2][3]={{1;2;3},{4;5;6}};
ar[0][0]=1 |
ar[0][1]=2 |
ar[0][2]=3 |
ar[1][0]=4 |
ar[1][1]=5 |
ar[1][2]=6 |
int ar[N][M];
int sum!=0;
for (int i=0; i<N; i++)
{
for (int j=0; j<M; j++)
sum+=ar[i][j];
int *p=&ar[0][0];
for (int i=0; i<sizeof(ar)/sizeof(int); i++)
{
sum+=*p;
p++;
}
}
Имя трёхмерного массива компилятор интерпретирует как константный указатель на нулевой слой (нулевой двумерный массив).
int ar[2][3][4]={{{1,2,3,4},{5,6,7,8},{9,10,11,12}},{{-1,-2,-3,-4},{-5,-6,-7,-8},{-9,-10, -11,-12 }}}
char *ar[20];
char *ar[]={ “One”,”Two”,”Three” };
char ar1[][6]={ “One”,”Two”,”Three” };
char ar1[3][6];
Т.к. в запрещённом режиме строковые литералы, расположенные в области памяти, защищены от записи, попытка изменить их вызовет ошибку времени выполнения.
ar1[1][1]= //ar[1][1]=”D”; - ошибка!
malloc
calloc
realloc
free
int Mbyte=…;
int *p=static_cast<int*>(malloc(Mbyte));
if (p)…
size_t n=_msize(p)
calloc - обнуляет содержимое памяти
realloc - изменяет размер ранее захваченного блока памяти
p=ststic_cast<int*> realloc(p,2000);
free - освобождается выделенная память
free(p)
Преимущество new и delete:
int *p=new int; //sizeof(int)
malloc(4)
int *p=operator new (sizeof(int));
При динамическом выделении памяти из-за большого количества информации, «накладные» расходы достаточно велики.
delete(p);
Создавать массивы следует только тогда, когда:
Не стоит забывать об удалении (освобождении) динамически занятой памяти.
int n=выражение;
//int ar[n]; // ERROR!!!
int *pn=new int[n]; //выделившийся в памяти блок размером n*sizeof(int)+служебная информация
pn[i]=…;
int *tmp=new int[n*z];
for (int i=0; i<n; i++){tmp[i]=pn[i];}
memcpy(tmp,pn,n*sizeof(int));
delete[]pn;
pn=tmp;
pn[i]=…;
delete[]pn;
delete[]tmp;
pn= Ø;
tmp= Ø;
pf=new float[n][2]; //создали массив векторов
delete []pf;
int M=выражение;
int N=выражение;
int *p=new int[M*N];
p[i*M+j]=…;
Операция new в памяти более сложна, чем выделение, т.к. программист явно или неявно передаёт информацию о количестве элементов, а оператор delete получает только титрированный указатель.
Проблема состоит в определении: является ли данный указатель указателем на одиночный объект или на массив объектов. Для этих целей в C++ существует две формы delete:
Косвенное обращение к объекту.
Указатель (pointer) переменная, Ссылка (reference)
переменная, которая явно содержит адрес объекта. |
которая тоже содержит адрес объекта, но синтаксически ею пользуется также, как и самим объектом. |
int *p=&x; int &r=x; // ссылка с именем
int *p; //OK //int &r; // ERROR!!!
int tmp=*p; int tmp=r;
(*p)++; r++;
Примечание. Ссылку можно интерпретировать как константный указатель, при каждом использовании которого, автоматически происходит разыменование.
p++; //OK r++;
int y; int y=5;
p=&y; //OK r=y; //r=y=x=5;
Комментарий: переменной x (адрес которой содержится в ссылке r) будет присвоено значение y.
if (p) if (r)
int **pp=&p; int *pr=&r; (указывает на объект, псевдоним
которого является
int y=**pp;
ссылка, то есть pr содержит адрес r
переменной x
int n; ссылки на ссылку не
существует
int *p=&n; int n;
int *&refp=p; int &r=n;
*ref p=2; //n=2; int &rr=r; // ERROR!!!
*p=4;
void *p; //OK void &r=x;
//ERROR!!!
не знаем какого типа или какая память выделится т.к. ссылка это всегда псевдоним совершенно определённого объекта
double *p; double d;
size_t n=sizeof(p); //4 байта double &rd=d;
size_t n=sizeof(rd); //8 байт
константная ссылка
int *p=0x10000000; //ERROR!//СИ int &r=1; //ERROR!!!
int *p=(int*)0x10000000; //OK!//СИ++ const int &r=1; //OK!
int *p=reinterpret_cast<int*>(0x10000000);
Функция часть программы, которая принимает параметры, выполняет инструкции, называемые телом функции, а затем возвращает управление вызывающей программе.
Функции позволяют:
Объявление функции: предварительное описание, которое извещает компилятор о типе возвращаемого значения, количестве и типах передаваемых аргументов.
Используя прототип, компилятор может выполнить контроль числа аргументов и проверить соответствие их типов при вызове функции.
При необходимости компилятор может произвести неявное преобразование типа.
[спецификатор][тип][соглашение по вызову] имя_функций ([список_аргументов] //[void])
или
[] - необязательно, ( ) обязательно.
Спецификатор: соглашение по функции.
[Тип]: задаёт тип возвращённое функцией значение.
Если поле отсутствует, то функция должна возвращать int.
Если void не возвращает значение
char MyFunc()
char MyFunc()
void MyFunc() //тип возвращённого значения int
Имя функции особый тип указателя, называется указателем на функцию. Его значение является адрес-точка входа в функцию.
Список аргументов. Определяет количество и тип аргументов (параметров), передаваемых в функцию.
Список_аргументов==тип_аргумента1[имя_аргумента1], тип_аргумента2[имя_аргумента2]…
Если в функцию не передавать аргументы, то поле пустое или содержит void.
Определение функции включает те же поля, что и прототип функции + тело функции код, выполняющийся при вызове функции (заключён в {}).
double summa (double a, double b)
{
double c;
c=a+b;
return c;
}
Вызов функции: для обеспечения эффективного и безопасного вызова функций требуется обеспечить:
2) функция должна производить одни и те же действия с разными наборами данных.
вызывающая часть |
стек |
функция |
a=5; b=8; |
|
1) Сохранение контекста |
sum(a;b) |
|
вызова. |
1) Формирование |
|
2) Создание текущего |
параметров в стеке. |
5 |
контекста. |
2) Сохранение адреса |
8 |
3) Тело функции. |
возврата и передача |
|
4) Формирование |
управления (вызова). |
|
return sum func |
3) Восстановление |
|
5) Восстановление контекста |
стека. |
|
вызова. |
4) Получение результата. |
|
6) Возврат. |
Ключевое слово inline указывает компилятору, что он должен пытаться, каждый раз встречая вызов функции, вместо последовательности действий подставлять тело функции.
При этом:
“-“ увеличивает объем памяти
Inline функция не применима:
inline
int sum(int x, int y)
{
return x+y;
}
main()
{
c=sum(3,5);
}
_ _cdecl, _fastcall, _stdcall - соглашение о вызове функции
Соглашения включают понятия:
Пример: пусть существует функция вида:
void calltype f(char c, short s, int i);
main()
{
f(A, 10,9999 , 1.23)
Адрес возврата
A
10
9999
1,23
//вызов
возврат организован командой ret()
сdecl - параметры формируются справа налево.
_stdcall (в стиле Паскаль) используется для уменьшения объёма исполняемого файла.
_f@20
//количество байт, занимаемое параметром в стеке
A
10
Адрес возврата
9999
1,23
Способы передачи параметров
по значению по адресу
по указателю по ссылке(C++)
Передача параметров по значению простая передача в стеке, не оставляющая возможности для изменения самих переменных, вызывающих функцию.
Передача параметров по адресу способ предполагает, что функция получает не копии объектов, а их адреса. При этом программист получает возможность изменить значение объекта по этому адресу. Вызовы подразделяются на вызовы по ссылке и по адресу.
Если при передаче адреса требуется запретить функции модифицировать значения по этому адресу, используется ключевое слово const.
main()
{
int a=2, b=1, r1, r2;
fp(a,b,&r1,&r2);
}
указатель:
void fp(int x, int y, int *sub,int sum)
{
sum=x+y;
sub=x-y;
}
main()
{
int a=2, b=1,r1,r2;
fr(a,b,r1,r2);
}
ссылка:
void fr ( int x, int y, int sum, int sub)
{
sum=x+y;
sub=x-y;
}
int My (const char *p)
{
int n=0;
while (*p)
{
p++;
n++;
}
return n;
}
main()
{
char ar[10]=”QWERTY”
int count = My(ar)
count=My(“ABC”);
}
void f(int*)
main()
{
int x=1;
int 8p=&x;
f(p);
f(&x);
}
int f1();
void f2(int, int)
main()
{
int x=1; y=5;
int z=f1();
int w=x+y;
f2(z,w); // f2(f1(), x+y);
}
Использование “ , ” , формирование параметров.
void f(int);
int main()
{
int x;
std: cin>>x;
f(x); // f (cin>>x, x);
}
Компилятор Си никогда не передаёт массивов по значению. Передаётся только указатель на массив.
int ar1[k];
int ar2[k][m];
F(ar1,ar2);
void F(int *p1, int (*p2)[M])
int **p2;
void F(int p1[], int p2[][M])
void f (char, int, float);
f(A, 3, 2.4);
void f1 (char, int, double=1.5);
f1(A, 3, 2.4); // по умолчанию можно использовать >=1 аргумента, но все они должны располагаться в конце списка аргументов
f1(A, 3);
Замечание: поставить запятую вместо пропущенного параметра по умолчанию НЕЛЬЗЯ!!
f1(A, , 3.2);
c=10;
a=f;
Блок схемы.
Вывод а;
вывод b;
Имя функции, передаваемые параметры
a<b
нет да
Имя функции
Возвращ.
параметр
Си допускает использование переменного числа параметров.
Специфика вызова:
int Func(int i; …);
int Func (int i, float z, …);
Признаком конца списка параметров является -1.
int Func(int i, …)
{
int count=0, sum=0;
int *p=&i;
while(*p!=-1)
{
count ++;
sum+=*p++;
}
return sum;
}
(printf, scanf).
int printf( const char *, …);
Функция printf предназначена для перевода указанных программистом в качестве необязательных параметров значений в строковое представление.
Функция определяет точное число необязательных параметров, подсчитывая количество символов % в единственном обязательном параметре.
%c char
%d int
%i int
%e exp // x=1,1e+0,02
%f float
%g float , но без лишних нулей
%o восьмеричное представление числа
%p значение адреса указателя в шестнадцатеричном виде
%s массив строк
%u преобразование аргумента к целому беззнаковому в десятичном виде
%x - преобразование аргумента к целому беззнаковому в шестнадцатеричном виде
int m = -1;
printf(“%x”,m);
%[+-n]s (строка символов)
%[+-n]f (дробное число в фиксированном формате)
%[+-n]e (количество цифр после десятичной точки)
%[+-n]i (d) (количество выводимых цифр)
%[+-n]g (максимальное количество цифр)
Пользовательский тип
Адрес
Базовый тип
Возвращаемое значение
указатель
Объединение
структура
ссылка
класс
Функция может возвращать:
-объект базового типа;
-объект пользовательского типа;
-указатель или ссылку;
bool func(); //базовый тип
main()
{
bool b= Func();
}
int &f()
{
static int n;
return n;
}
int *f1(int n)
{
int nN=n*5; //return &nN;
} //функция возвращает указатель на область памяти в стеке, которая после возвращения из функции может быть задействована компилятором для других целей
int &f2(int n)
{
int nN=n*5;
return nN;
} //функция возвращает указатель на локальную временную переменную
int main()
{
z=f2(1) +f2(2)+f2(3);
}
Нельзя возвращать адреса локальных объектов.
Возвращать можно:
void func (const int *pn); // позволяет объявлять функции с ключевым словом const
Такое объявление параметра обеспечивает неизменяемость объекта, на который указывает pn, то есть это позволяет компилятору блокировать нежелательные побочные эффекты вызова функции.
void func1 (const char *pc)
{
//*pc=A;
}
Замечание:
void func( const int n);
Перегрузка одна из особенностей C++, которая позволяет использовать одно и то же имя для разных функций.
Этот механизм возможен ввиду того, что функции с одним и тем же именем, но с разным количеством и разными типами параметров компилятор декорирует по-разному.
Ограничение в использовании перегрузки:
int MaxInt(int x; int y)
{
return (x>y);
}
double MaxDouble (double x, double y)
{
return (x>y);
}
//примеры без перегрузки
main()
{
int i=MaxInt (12,8);
double d= MaxDouble(1.1;2.2);
}
C перегрузкой:
main()
{
int i=Max(12,8);
double d= Max(1.1, 2.2);
double dd=Max(1.1, 2); //ERROR!!
}
void f(int x, int y=5);
void f(int x, int y = 0);
void f (int);
int main () {
f (2, 5) ; // ошибка
f (5); // ошибка, компилятор не знает, какую функцию вызывать.
}
Вызывают сами себя.
Происходит выполнение одного и того же кода с разными наборами данных.
Каждое выполнение тела функции имеет свою область стека для параметров и локальных переменных.
Достоинства:
Недостатки:
Специфика рекурсивных функций:
Факториал без рекурсии:
int n=5;
int res=1;
for(int i=n; i>1; i--)
res *=i;
int F(int n)
{
if (n<=1)
return 1;
else
{
int x=F(n-1);
return n*x;
}
}
В большом количестве случаев удобно обращаться к совокупности переменных Структура языка Си средство для укрупнения данных.
Объявление структуры описание компилятором шаблона, по которому он будет создавать объект данного типа.
struct имя
{
список полей структуры
}
struct student
{
char name[30]
unsigned int cource,
bool sex;
int age;
…
}
Создание объектов структуры.
Создание объектов пользовательского типа выглядит так же, как создание переменной базового типа.
student vasya;
Си: struct student vasya;
Присваивание значений полям структуры.
vasya. age=20;
vasya. sex=1;
vasya. cource=1;
strcpy(vasya. name, “Vasiliy”);
[struct] student g_182_4[20];
g_182.4[0]. age=18;
Создание структур типов данных.
Применяется при программировании на языке Си и используется для создания своих собственных типов данных.
typedef struct
{
char name[30]
...
int age;
}
student;
student pety;
Инициализация структур.
[struct] student Ira ={“Irina”, 2, 0, 19, …};
Замечание: инициализация указывается в том порядке, в котором соответствующие поля объявлены в структуре.
Инициализация массивов структур похожа на инициализацию многомерных массивов.
{
“Masha”, 1, 0, 18…
}
Действия со структурами.
Компилятор умеет создавать копии существующих объектов и присваивать одному, уже существующему объекту, значение другого.
[struct] student Balabanov1={“Anton”, 2, 0, 19, …};
[struct] student Balabanov2=Balabanov1;
strcpy(Balabanov2. Name, “Vladimir”);
Balabanov2=Balabanov1;
Указатели и структуры.
[struct] student *man1=new[struct] student;
Для обращения к полям структуры посредством указателя используется селектор
->(минус больше)
strcpy(man1->name, “Yan”);
man1->sex=1;
man1->age=25;
man1->course=1;
Оператор sizeof. Размерность объекта.
struct A
{int ma;;}
size_n n=sizeof(A); //4
struct AA
{int ma; char m_c;};
size_n n=sizeof(AA); //8
struct AAA {int;char;double;};
size_t n=sizeof (AAA); //16
Структуры значительно облегчают процесс обмена данными между функциями.
Если требуется передать в функцию большее количество данных (разных по типу, а не по смыслу), то длинный список аргументов:
Так как структура может передаваться и по значению, и по адресу, значительно эффективнее передать адрес одного объекта типа структура.