Небольшой обзор ранее пройденного материала:
  Для любого типа возможен такой код (рассмотрим на примере
   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 или не
инициализирована.
Разберемся с условными обозначениями, используемыми в С++:
Для указателей используется *
для взятия адреса - &
для ссылок - &. 
Но два последних случая не имеют ничего общего! Это не связанные между собой
обозначения разных действий, разные значения одного символа.
| Размер определяется в момент компиляции | Размер определяется в момент запуска | Размер увеличивается в процессе работы программы |