Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курс лекций по Программированию на С++.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
795.65 Кб
Скачать

Передача массивов в функции

Стилистически никаких отличий при использовании в функциях массивов практически нет, хотя и есть одна интересная деталь. Рассмотрим пример функции, возвращающей сумму элементов массива.

#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.

С указателями можно выполнять следующие операции: разадресация; присваивание; сложение с константой; вычитание; инкрементирование; декрементирование; сравнение; приведение типов. Например, выражение (*р)++ инкрементирует значение, на которое ссылается указатель. Суммирование двух указателей не допускается.