АЙТИШНЫЙ САЙТ ПЕТРА СЕМИЛЕТОВА

ПЕТР СЕМИЛЕТОВ

ПРОСТО С++: УРОК 12

Указатели и функции

Шаг за шагом мы движемся в постижении указателей. Сегодняшняя тема - указатели и функции. Функции не могут принимать обычные массивы в качестве параметров, а также не могут возвращать обычные массивы.

Однако механизм указателей позволяет обойти это ограничение. Но сначала познакомимся с еще одним полезным свойством указателей, которое я почти не затронул.

Указатель может указывать на любую переменную или обычный массив.

В случае, когда мы хотим сделать указатель на обычную переменную, одинарную, не массив, мы должны использовать операцию взятия адреса - она обозначается значком амперсанд: &

Пример 12-01.cpp:

#include <iostream>

using namespace std;

int main (int argc, char *argv[])
{
  int i = 13;
  int *p;

  p = &i;

  cout << *p << endl;
 
  return 0;
}

Мы объявили целочисленную переменную i со значением 13, потом указатель p, и присвоили p адрес i.

Затем выводим p, предваряя его звездочкой, что означает - использовать значение переменной, на которую указывает массив.

В этом примере кстати используются классические сокращения, i - от integer, целое, а p от pointer, указатель. Если вы где-то в чужом исходнике встречаете переменную с названием, предваренным маленькой “p”, например pMemory, это более чем явно указывает, что мы имеем дело с указателем. А если имя переменной предваряется буквой i или int, например iNumber, скорее всего речь идет о переменной типа int. Так программисты, чтобы тип переменной был очевиден, указывают на него начальной буковкой имени переменной.

Теперь кое-что более практически полезное, Пример 12-02.cpp:

#include <iostream>

using namespace std;

int main (int argc, char *argv[])
{
  int a = 13; //объявим переменную a
  int b = 8;  //объявим переменную b

  int *p; //объявим указатель p на любую переменную типа int

  p = &a; //присвоим указателю p адрес переменной a

//далее выведем значение переменной, на которую указывает p
  cout << *p << endl; //вывелось значени переменной a

  p = &b; //присвоим указателю p адрес переменной b

//теперь p указывает на b, и...
  cout << *p << endl; //посредством p выведется значение b
 
  return 0;
}

Объявим переменные a и b со значением 13 и 8.

Объявим указатель p. Присвоим p сначала адрес a. Выведем на консоль значение p.

Так, выводится значение из переменной a, то есть 13. Теперь присвоим указателю p адрес переменной b. Снова выведем на консоль указатель p. Теперь напишется число 8, ибо p указывает уже на переменную b.

Таким образом указателем мы можем как бы подключаться к разным переменным и получать их значения.

Итак, указатели используются для динамического создания массивов. Указатели используются для доступа к другим переменным, и мы познакомились с чудесным амперсандом. Вообще говоря, в С++ он используется по-разному.

Рассмотрим обычную функцию:

int sum (int a, int b)
{
   return a + b;
}

Функция sum складывает значения переданных параметров-переменных и возвращает нам результат сложения. Тонкость в том, что в параметрах передаются копии, а не сами объекты. Иначе говоря, в следующем коде:

int x = 2;
int z = 3;
int f = sum (x, z);

…когда мы вызовем функцию sum, ей передадутся не непосредственно переменные b и z в качестве параметров, но автоматически созданные копии этих переменных. Поэтому мы не сможем внутри функции sum изменять значения “внешних” x и z.

Рассмотрим пример 12-03.cpp:

#include

using namespace std;

int sum (int a, int b) { a = 10; return a + b; }

int main (int argc, char *argv[]) { int x = 2; int z = 3; int f = sum (x, z);

cout « x « endl;

return 0; }

В функции sum мы делаем финт ушами, мы переменной-параметру А присваиваем значение 10, оно перебивает значение 2 из переменной x, которое передается функции sum при ее вызове.

Повлияло ли это на значение самой переменной х? Стало ли оно равно 10? Нет.

В функции

int sum (int a, int b)
{
   a = 10;
   return a + b;
}

…переменные-параметры a и b это как бы переменные-заместители. Они получают внешние значения от внешних переменных и действуют локально, внутри тела функции.

Но что делать, если мы хотим повлиять на передаваемую в функцию переменную? Ее надо передать по ссылке, указать, что в параметре мы передаем адрес переменной.

Изменим заголовок функции sum, добавив перед а амперсанд.

Пример 12-04.cpp:

#include <iostream>

using namespace std;

int sum (int &a, int b)
{
   a = 10;
   return a + b;
}

int main (int argc, char *argv[])
{
  int x = 2;
  int z = 3;
  int f = sum (x, z);

  cout << "x: " << x << endl;
 
  return 0;
}

Запустим программу. Итак, теперь на экран выводится х равное 10.

Что произошло?

int sum (int &a, int b)
{
   a = 10;
   return a + b;
}

…при вызове функции sum в переменную n будет передан уже АДРЕС переменной x. И теперь, когда мы обращаемся внутри функции к переменной а, мы обращаемся как бы напрямую к x - той внешней, по отношению к функции, переменной, что была передана в качестве параметра.

Однако подобный трюк можно провернуть и с помощью указателя.

Пример 12-05.cpp:

#include <iostream>

using namespace std;

int sum (int *a, int b)
{
   *a = 10;
   return *a + b;
}

int main (int argc, char *argv[])
{
  int x = 2;
  int z = 3;
  int f = sum (&x, z);

  cout << "x: " << x << endl;
 
  return 0;
}

Здесь мы в функции sum объявляем переменную-параметр а как указатель, внутри функции обращаемся с ней как с указателем, а при вызове функции sum в main, передаем ей в качестве первого параметра адрес переменной x.

Итак, одна и та же задача решается двумя разными способами, первый кажется мне изящнее.

Чтобы завершить покамест тему указателей и функций, посмотрим, как добиться от функции возврата массива.

Мы знаем, что традиционным способом это невозможно, то есть нельзя написать функцию F с заголовком вроде

int[] f()

Зато можно так:

int* f()

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

Упрощенный, нерабочий пример такой функции, функции под названием read_file:

char* read_file()
{
   //создаем в куче массив:
   char *a = new int [размер_файла];
   //затем неким образом читаем содержимое файла в указатель-массив a, затем возвращаем из функции этот a

  return a;
}

Функция фактически вернет указатель, адрес в памяти, на первый элемент выделенной под переменную a области, и не важно, что a локальна для функции read_file - а это указатель, в указателе хранится адрес, и функция возвращает этот адрес.

Разумеется, такой массив-указатель необходимо будет потом удалить из памяти оператором delete. Условный код!

Читаем содержимое текстового файла в переменную-указатель text:

chat *text = read_file();

потом что-то делаем с этим текстом. И потом:

delete [] text;

Вот мы подобрались к тексту и строкам. Но об этом уже в следующий раз…

Поддержать курс:

Навигация:

Оглавление

Предыдущий урок

Следующий урок