Небольшой обзор ранее пройденного материала:
Для любого типа возможен такой код (рассмотрим на примере
int):
int N;
//объявление переменной целого типа
int *p;
//объявление указателя на переменную целого типа
p=&N;
//присваивает указателю p адрес переменной N
N=*p;
//присваивает переменной N nо значение, которое находится по адресу,
сохраненному в p
Пример:
Функция, меняющая местами значения двух переменных -
swap(a,b):
void swap(double a, double b){
double tmp = a;
x = y;
y = tmp;
}
ожидаемого результата не будет, т.к. при вызове функции
swap(a, b);
сначала на стек положится значение переменной
a, затем переменной
b и адрес возврата, затем значения поменяются
на стеке местами и функция завершит свое выполнение.
Чтобы функция корректно работала, необходимо изменить её следующим
образом:
void swap(double *a, double *b){
double tmp = *a;
*x = *y;
*y = tmp;
}
Теперь функция действительно поменяет значения переданных в нее
переменных
Но! Вызов функции изменится на такой:
swap(&x, &y);
Т.е. мы передаем в функцию не значения переменных, а их адреса, и внутри
функции работаем именно с адресами.
Вывод: если нужно вернуть из функции несколько значений, то
необходимо передавать в нее адреса.
Пример 2:
Часто при написании программ используются функции
printf и
scanf. На их
примере можно рассмотреть использование указателей в стандартной
библиотеке.
int a = 1, b = 2;
printf("%d * %d = %d", a, b, a * b);
//выведет 1 * 2 = 2
Но если использовать функцию
scanf, то можно столкнуться с ошибкой:
запись scanf("%d", a); не запишет ничего
в переменную a. Происходит это из-за того,
что в функцию нужно передать именно адрес (если мы не передадим адрес, то
тогда функция не сможет изменить значение переданной в нее переменной).
Поэтому, изменив вызов этой функции на такой -
scanf("%d", &a); мы получим желанный
эффект - запись в переменную a значения,
введенного с клавиатуры пользователем.
Пояснение: использование адресов целесообразно в случае, когда
функция должна менять значение передаваемой в нее переменной.
Рассмотрим стандартную функцию
strlen на примере кода:
char str[] = "Hello";
int l = strlen(str);
Функция strlen возврещает длину переданной
в нее строки.
Но что же конкретно в нее передается?
Передавать в нее массив неэффективно, т.к. копирование всех элементов
последнего может занять длительное время.
Поэтому передается лишь указатель на массив.
Т.к. str в памяти компьютера записана в виде
|H|e|l|l|o|\0|, то сообщив функции
адрес символа 'H' мы даем ей всю необходимую информацию о строке.
Покажем, как можно писать эффективный код на примере реализации функции
strlen.
Вариант 1 реализации:
int strlen(char* ptr){
int len = 0;
while (ptr[len] != '\0'){
++len;
}
return len;
}
int strlen(char* ptr){
char* p = ptr;
while (*p != '\0'){
++p;
}
return p - ptr;
}
int strlen(char* ptr){
int len = 0;
while (ptr[len] != '\0'){
//1) ptr + len - сложение указателя и числа;
//2) *(ptr + len) - взятие по адресу;
//3) *(ptr + len) != '\0' - сравнение с 0;
++len;
/* инкрементирование длины */
}
return len;
}
int strlen(char* ptr){
char* p = ptr;
while (*p != '\0'){
//1) *p - взятие по адресу;
//2) *p != '\0' - сравнение с 0;
++p;
/* инкрементирование указателя */
}
return p - ptr;
}
Вспомним функцию swap. У нее был неудобный вызов -
swap(&a, &b). В языке С++ стало возможным
избавиться от этого неудобства. В нем можно писать просто
swap(a, b), нужно только правильно объявить и описать функцию:
void swap(double &a, double &b){
double tmp = a;
a = b;
b = tmp;
}
В этом примере использовался особый тип данных, которого нет в языке С - ссылка
(double & - ссылка на тип double).
При таком объявлении функции при вызове на стек положатся не значения переменных,
а их адреса.
Ссылка - это тот же указатель, который воспринимается и используется программистом как обычная переменная.
Отличия в том, что ссылка не может быть равна NULL или не
инициализирована.
Разберемся с условными обозначениями, используемыми в С++:
Для указателей используется *
для взятия адреса - &
для ссылок - &.
Но два последних случая не имеют ничего общего! Это не связанные между собой
обозначения разных действий, разные значения одного символа.
Размер определяется в момент компиляции | Размер определяется в момент запуска | Размер увеличивается в процессе работы программы |