
- •220300 - Системы автоматизированного проектирования
- •Состав языка и принцип работы компилятора языка высокого уровня
- •Идентификаторы
- •Альтернативный ввод-вывод в стиле с
- •Константы
- •Манипуляторы
- •Преобразования типов
- •Арифметические операции
- •Функции стандартной библиотеки
- •Операции отношения и логические операции
- •Базовые конструкции структурного программирования
- •Цикл с параметром (for)
- •Цикл с предусловием (while)
- •Цикл с постусловием (do while)
- •Рекомендации по программированию циклов
- •Ветвления
- •Условный оператор if
- •Вложенные конструкции
- •Проблемы соответствия if и else во вложенных ветвлениях
- •Условная операция
- •Оператор switch
- •Операторы передачи управления
- •Контрольная работа
- •Массивы
- •Сортировка массива методом выбора
- •Сортировка массива методом пузырька
- •Многомерные массивы
- •Операции со строками
- •Функции
- •Void starline(); // объявление функции (прототип)
- •Передача аргументов в функцию по значению
- •Void charline(char sim, int n); // объявление функции (прототип)
- •Возвращение функцией значения
- •Передача аргументов по ссылке
- •Возвращение значения по ссылке
- •Перегруженные функции
- •Переменные и функции
- •Передача массивов в функции
- •Указатели
- •Указатели и массивы
- •Указатели и функции
- •Указатели и строки
- •Динамический массив и динамическое выделение памяти
- •Ввод/вывод
- •Форматированный ввод/вывод
- •Конструкторы и методы
- •Двоичный ввод/вывод. Бинарные файлы
- •Двунаправленный ввод/вывод
- •Функции стандартной библиотеки для работы с файлами или ввод/вывод в стиле с
Передача массивов в функции
Стилистически никаких отличий при использовании в функциях массивов практически нет, хотя и есть одна интересная деталь. Рассмотрим пример функции, возвращающей сумму элементов массива.
#include <iostream>
#include <conio>
using namespace std;
const int N=5; // размер массива (глобальная константа)
int summ(int mas[N]); // Вар.II: int summ(int mas[], int N)
int main() {
int arr[N]={3, 4, 5, 4, 4}; // arr[0]=2, arr[1]=4, arr[2]=5, ...
cout << "Summa elementov massiva: " << summ(arr); // Вар.II: summ(arr, N)
getch(); return 0; }
//---------------------------------------------------------------------------
int summ(int mas[N]) { // Вар.II: int summ(int mas[], int N)
int s=0;
for(int j=0; j<N; j++) s = s + mas[j];
return s;
}
Из данного примера видно, что при вызове функции в качестве аргумента используется только имя массива summ(arr). Это имя (в нашем случае arr) в действительности представляет собой адрес массива в памяти. Использование адресов для массивов-аргументов похоже на использование аргумента ссылки, при котором значения элементов массива не копируются в функцию. Вместо этого функция работает с оригинальным массивом, хотя ссылается на него используя другое имя. Такой механизм используется для массивов, потому что они могут быть очень большими и дублирование массива в каждую функцию отнимало бы много времени и пространства памяти.
Таким образом, массив всегда передаётся по адресу. При передаче по адресу в стек заносятся копии адресов аргументов, а функция осуществляет доступ к ячейкам памяти по этим адресам и может изменить исходные значения аргументов. Однако, необходимо понимать, что адрес – это не то же самое, что ссылка.
Лекция 7 (9 стр.)
Указатели
Когда компилятор обрабатывает оператор определения переменной, например int i=10;, он выделяет память в соответствии с типом (int) и инициализирует её указанным значением (10). Все обращения к переменной по её имени (i) заменяются компилятором на адрес области памяти, в которой хранится значение переменной. Программист может определить собственные переменные для хранения адресов областей памяти. Такие переменные называются указателями.
Приведём примеры использования указателей:
доступ к элементам массива;
передача аргументов в функцию, от которой требуется изменить эти аргументы;
передача в функцию массивов и строковых переменных;
динамическое выделение памяти;
создание сложных структур данных (связные списки, бинарные деревья и т.д.).
Идея указателей заключается в следующем. Каждый байт компьютера имеет адрес. Таким образом, каждая переменная и каждая функция программы начинается с какого-либо конкретного адреса. Для получения адреса переменной можно использовать операцию получения адреса &. Необходимо понимать, что адреса переменных – это не то же самое, что их значение. Также необходимо помнить, что операция получения адреса переменной (&var1) и операция ссылки в определении функции (int& var1) – это две разные операции.
Переменная, содержащая в себе значение адреса, называется указателем. Рассмотрим следующий пример.
int main() {
int var1 = 11; // две переменные
int var2 = 22;
cout << &var1 << endl // покажем адреса переменных (увидим: 7208448 7208444)
<< &var2 << endl << endl;
int* ptr; // это переменная-указатель на целое
ptr = &var1; // присвоим ей значение адреса var1
cout << ptr << endl; // и покажем на экране (увидим: 7208448)
ptr = &var2; // теперь значение адреса var2
cout << ptr << endl; // и покажем на экране (увидим: 7208444)
getch(); return 0; }
В данной программе, в строке int* ptr; определена переменная ptr как указатель на int, т.е. эта переменная может содержать в себе адрес переменной типа int. Итак, указатели предназначены для хранения адресов областей памяти. Как видно из данного примера, указатель может хранить адрес переменной соответствующего типа.
Вместо вывода на дисплей адресов, хранящихся в переменной ptr, можно вывести значения, хранящиеся по адресу, на который указывает ptr. Для этого достаточно изменить строку, например, cout << ptr << endl; на cout << *ptr << endl; //увидим 11, если ptr указывает на var1. Выражение *ptr позволяет получить значения переменных var1 и var2 и называется операцией разыменовывания (разадресации), которая означает: взять значение переменной, на которую указывает указатель.
Указатели можно использовать не только для получения значения переменной, на которую он указывает, но и для выполнения действий с этими переменными. Рассмотрим следующий пример.
int main ( ) {
int var1, var2; // две переменные
int* ptr; // указатель на целое
ptr = &var1; // пусть ptr указывает на var1
*ptr = 37; // то же самое, что var1 = 37;
var2 = *ptr; // то же самое, что var2 = var1;
cout << var2 << endl; // убедимся, что var2 равно 37
getch(); return 0;
}
Необходимо понимать, что звёздочка, используемая в операции разыменовывания, - это не то же самое, что звёздочка, используемая при объявлении указателя. Доступ к значению переменной, хранящейся по адресу, с использованием операции разыменовывания называется непрямым доступом. Пока в приводимых примерах мы не обнаружили преимущества использования указателей для доступа к переменным, поскольку мы всегда можем применить прямой доступ. Важность указателей становится очевидной в том случае, когда мы не имеем прямого доступа к переменной.
Необходимо запомнить следующее правило: адрес, который помещается в указатель, должен быть одинакового с ним типа. Например, мы не можем присвоить указателю на int адрес переменной типа float.
С указателями можно выполнять следующие операции: разадресация; присваивание; сложение с константой; вычитание; инкрементирование; декрементирование; сравнение; приведение типов. Например, выражение (*р)++ инкрементирует значение, на которое ссылается указатель. Суммирование двух указателей не допускается.