Указатели и функции
Шаг за шагом мы движемся в постижении указателей. Сегодняшняя тема - указатели и функции. Функции не могут принимать обычные массивы в качестве параметров, а также не могут возвращать обычные массивы.
Однако механизм указателей позволяет обойти это ограничение. Но сначала познакомимся с еще одним полезным свойством указателей, которое я почти не затронул.
Указатель может указывать на любую переменную или обычный массив.
В случае, когда мы хотим сделать указатель на обычную переменную, одинарную, не массив, мы должны использовать операцию взятия адреса - она обозначается значком амперсанд: &
Пример 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;
Вот мы подобрались к тексту и строкам. Но об этом уже в следующий раз…
PayPal: peter.semiletov@gmail.com
биткоин: 1PCo2zznEGMFJey4qFKGQ8CoFK2nzNnJJf