Операции над указателями




Указатели

Указатель - это переменная, содержащая адрес другой переменной. Указатели очень широко используются в языке "C++".

Объявление указателя имеет следующее формальное описание:

тип_переменной *имя_переменной_адреса;

 

Инициализация указателя выполняется следующим образом:

тип_переменной имя_переменной_содержания;

имя_переменной_адреса = &имя_переменной_содержания;

 

Объявление указателя может быть выполнено с одновременной инициализацией:

тип_переменной *имя_переменной_адреса = &имя_переменной_содержания;

 

Доступ к значению переменной по указателю имеет следующее формальное описание:

имя_переменной_содержания1=*имя_переменной_адреса;

Указатели применяют:

1. Для возврата нескольких значений из функции. В качестве аргумента передаётся указатель на переменную, функция записывает туда значение.

2. Для создания различных структур данных: связанных списков, деревья и т. д.

3. Для передачи аргумента в функцию без копирования (и вызова конструктора для объектов), которое может оказаться долгим для сложных объектов. Правда, здесь лучше использовать константные ссылки, и т.д.

Положим, Вы передаете в функцию какую-то переменную. Там с ее значением что-то делается, и новое значение должно быть занесено в ту переменную, значение которой Вы в функцию передали. В том случае, если в функцию передается переменная таким образом:

void func (int a) {
a += 10;b=a; return b;
}
......
int i = 2; int c;
c=func(i);

значение переменной i не изменится, потому что в функцию передается копия этой переменной и с ней производятся операции. А если передать указатель или ссылку на эту переменную, то получите доступ к ней и можете изменять ее значение

void func (int* a) {
*a += 10;
}
......
int i = 2;
func(&i);

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

 

При работе с указателями действуют следующие правила:

  • при объявлении переменной-указателя перед именем переменной указывается операция *;
  • если одним оператором объявляется несколько переменных-указателей, то перед каждой такой переменной следует указывать операцию *;
  • после объявления указателя его следует инициализировать адресом значения того же типа, что и тип указателя;
  • для получения адреса переменной перед ее именем указывается операция взятия адреса &;
  • для получения значения переменной по указателю на нее перед указателем ставится операция разыменования * (называемая иногда операцией взятия значения);
  • указатель строки содержит адрес первого символа строки;
  • при увеличении указателя на единицу значение, содержащееся в переменной-указателе, увеличивается на число байт, которое отведено под переменную данного типа.

Операцию разыменования & нельзя использовать:

  • для регистровых переменных (register r1; pReg=&r1;);
  • с константами (pAdr1=&444;);
  • с арифметическими выражениями (int i=1234; pAdr1=&(i+3);).

Например:

int iVar;

int *pInt; // Указатель

pInt=&iVar; // Эквивалентно оператору

// int *pInt=&iVar;

*pInt=20; // Эквивалентно оператору

// iVar=20;

Указателю может быть присвоено значение другого указателя: в этом случае следует использовать операцию *.

Операции инкремента ++ и декремента -- указателя можно выполнять как после операции присвоения (*pV++=22;), так и до нее (*++pV=22;). В последнем случае сначала будет увеличено значение указателя, а затем выполнен оператор присваивания.

Выполнять вычитание можно только над указателями одного типа. Результатом вычитания указателей является целое число, указывающее разность адресов памяти.

Над указателями одного типа можно выполнять операцию сравнения, возвращающую логическое значение.

Константные указатели

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

Например:

char str1[]="123";

const char* pstr1= str1;

// pstr1 можно изменять,

// а *pstr1 - нельзя.

Задание ключевого слова const перед объявлением указателя создает этот указатель как указатель на константу (при этом само значение, доступное не через данный указатель, остается изменяемым).

Для того чтобы создать константный указатель, вместо оператора * используется *const.

Например:

const char *const pstr1= str1;

Указатели на массивы

И указатель, и массив содержат адрес. Поэтому указателю может быть присвоен адрес первого элемента массива или имя массива. Но адресу массива нельзя присвоить значение указателя.

Например:

float fArray[3]; // Массив

float* pArray;

pArray=fArray; // Эквивалентно оператору

// pArray=&fArray[0];

pArray++; // Указывает на второй

// элемент массива

float* pArray2;

pArray2=&fArray[1]; // Указывает на второй

//элемент массива

Категории указателей

В языках C, C++ различают три категории указателей. Первая категория указателей предназначена для хранения адресов данных определенного типа (по терминологии языка Паскаль – типизированные указатели). При их объявлении указывается тип данных, на которые эти указатели могут "смотреть". Ко второй категории относятся указатели, которые могут "смотреть" на данные любого типа (по терминологии языка Паскаль – нетипизированные указатели). При их объявлении используется служебное слово void. Наконец, третью группу составляют указатели, значениями которых могут быть только адреса точек входа в функции (по терминологии языка Паскаль – данные процедурного типа). Объявление и использование указателей разных категорий имеет свою специфику.

Для объявления одного указателя с именем p1 или нескольких указателей с именами p1, p2,..., которые должны будут "смотреть" на объекты типа type1, используется одна из следующих синтаксических конструкций:

type1 *p1;

type1 *p1,*p2,...;

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

int x=2,y;

int *p1=&x; //инициализация адресом переменной x

int *p2(&x); //инициализация адресом переменной x

int *p3=p1; //инициализация значением другого указателя

Если целочисленному указателю p1 присваивается имя массива a или его адрес, то это эквивалентно засылке в p1 адреса первого элемента массива a[0]:

int a[10];

int *p1=a; //p1 смотрит на начало массива a

int *p2=&a[0]; //p2 тоже смотрит на начало массивa a

Когда указатель p1 "смотрит" на переменную x, то по значению указателя можно извлечь значение переменной x или изменить его:

int x=5,y;

int *p1=&x; //значением p1 является адрес x

..........

y=*p1; //теперь значение переменной y равно 5

*p1=2; //теперь значение переменной x равно 2

Когда указатель p2 "смотрит" на начало массива q, то доступ к элементам этого массива можно организовать одним из следующих способов:

int q[20];

int *p2=q;

...........

y=*(p2+5); //теперь y=q[5]

x=p2[3]; //теперь x=q[3]

*(p2+1)=7; //теперь q[1]=7

Не типизированному указателю может быть присвоено значение указателя любого типа. Однако непосредственно извлечь или изменить значение по не типизированному указателю нельзя. Приходится прибегать к приведению типов:

#include <iostream.h>

#include <conio.h>

 

void main()

{ int x=5;

void *p=&x;

int *p1;

p1=(int*)p; //приведение указателя p к типу int*

cout<<"x="<<*p1<<endl;

getch();

}

Для объявления указателя pf на функцию типа double f(double x) имя указателя заключается в круглые скобки:

double (*pf)(double x);

Вместо имени функции здесь указано имя указателя в круглых скобках. А в качестве его конкретного значения можно задать адрес любой функции с аргументом типа double, которая возвращает значение типа double:

#include <iostream.h>

#include <conio.h>

#include <math.h>

 

void main()

{ double (*pf)(double x); //объявление указателя на функцию

double x=0.2;

pf=sin; //присвоение значения указателю

cout<<"sin(0.2)="<< pf(x) <<endl; //обращение по указателю

cout<<"sin(0.2)="<<(*pf)(x)<<endl; //обращение по указателю

getch();

}

Операции над указателями

Основные операции, чаще всего применяемые к указателям – сложение указателя с целым числом или вычитание из указателя целого числа. Обе они широко применяются в том случае, когда указатель связан с массивом. По сути дела, эти операции эквивалентны аналогичным процедурам над индексами элементов массива. И точно так же как прибавление к индексу означает переход к следующему элементу массива, прибавление 1 к указателю означает увеличение его текущего значения на количество байт, соответствующих типу указателя (точнее, типу данных, на которые указатель обязан смотреть):

int q[6]={1,2,3,4,5,6};

int *p = &q[2];

cout << *p++ <<endl; //сначала выводится 3, потом p=&q[3]

cout << (*p)++ <<endl; //сначала выводится 4, потом p=&q[4]

cout << *(p++) <<endl; //сначала выводится 5, потом p=&q[5]

Если при обработке некоторого массива используются два указателя p1 и p2, продвигаемые навстречу друг другу, то их разность (p2-p1) определяет количество элементов массива расположенных между этим двумя адресами.

Ссылки

Ссылки представляют особый вид данных, напоминающих указатели. Будучи объявлены в функции, они должны быть связаны с адресами конкретных объектов и после этого изменять свои значения не могут. В дальнейшем в рамках этой функции они выступают как синонимы своих объектов:

int x;

int &rx=x; //объявление и инициализация ссылки

Ссылка rx является эквивалентом идентификатору x, т.е. операторы x=5 и rx=5 абсолютно идентичны.

Главное преимущество ссылок проявляется при спецификации параметров функций. Если формальный параметр объявлен в заголовке функции как ссылка, то упрощается его использование в теле функции (в отличие от указателей к именам ссылок ничего добавлять не надо) и становится более естественным вызов функций (вместо формальных параметров-ссылок указываются имена переменных).

 

Функции Функции являются основными программными единицами в языках C, C++. Из них как из кирпичиков складывается программа. В отличие от алголоподобных языков в программах на C, C++ не допускается вложенность функций. Любая программа на C должна содержать главную функцию с именем main, с которой начинается выполнение программы. Вызов всех остальных функций прямо или косвенно инициируется главной функцией. Аргументы функций – один из основных способов обмена информацией между частями программы. Функция может возвращать значение, – результат своей работы, или выполнять некоторое другое действие, не связанное с возвратом результата. Если функция возвращает значение, то в ее заголовке перед именем функции должен быть указан тип возвращаемого значения: double mid(double x,double y) В теле функции, возвращающей значение, обязан присутствовать оператор return (от англ. – возврат), содержащий результат работы функции – ее значение: double mid(double x, double y) { return (x+y)/2.; } Если функция не возвращает значение, то в ее заголовке перед именем функции должен быть указан тип void. В этом случае в теле функции может встретиться оператор return без параметра. Но оператор return может и отсутствовать – выход из функции произойдет при достижении последней фигурной скобки: void print_v(int *a,int n) { int j; printf("\n"); for(j=0; j<n; j++) printf("%8d",a[j]); printf("\n"); } Если перед именем функции не указан ни один из стандартных типов и отсутствует спецификатор void, то считается, что функция возвращает значение типа int. Параметры-значения Формальный параметр в заголовке функции называют параметром-значением, если перед его именем указан только тип. Например, функция mid, вычисляющая среднее арифметическое двух величин, получает в качестве фактических аргументов два числовых значения определенного типа: double mid(double x, double y) { return (x+y)/2.; } В качестве фактических аргументов, соответствующих параметрам- значениям, могут быть заданы любые числовые выражения (формулы): w1 = mid(x*cos(fi)+y*sin(fi), x*sin(fi)-y*cos(fi)); Значения этих выражений вычисляются и записываются в стек, откуда их извлекает функция mid и помещает переданные значения в свои локальные переменные x и y (формальные параметры-значения можно рассматривать как локальные переменные функции). При необходимости, значение вычисленного выражения автоматически приводится к типу формального параметра. После работы функции возвращаемый результат возвращается в специально выделенном регистре. Почти все математические функции раздела math.h используют передачу аргумента по значению. Параметры-указатели Формальный параметр в заголовке функции называют явным параметром- указателем, если перед его именем находится символ *. Например, функция swap1, осуществляющая перестановку местами значений двух переменных, должна получить в качестве параметров адреса этих переменных, т.е. указатели на них: void swap1(int *x,int *y) //явные параметры-указатели { int tmp=*x; *x=*y; *y=tmp; } Таким образом, если мы хотим, чтобы результат работы вызываемой функции нашел отражение в передаваемых параметрах, мы должны сообщать не значения параметров, а их адреса. Зная адрес, вызванная функция сама может извлечь нужное значение и, при необходимости, отправить по этому адресу полученный результат. Для вызова функции, параметрами которой являются указатели, в качестве фактических аргументов надо задавать адреса переменных, т.е. их имена с предшествующим символом &: int x=2,y=3; swap1(&x,&y); Одним из наиболее распространенных способов использования указателей является передача в качестве адреса имени массива. Например, для суммирования компонент вектора можно воспользоваться следующей функцией: int sum_v(int *a,int n) { int j,s=0; for(j=0; j<n; j++) s += a[j]; return s; } Обращение к такой функции может выглядеть следующим образом: int q[20]; .......... k1=sum_v(q,20); //суммирование всех компонент вектора k2=sum_v(q,10); //суммирование первых 10 компонент вектора k3=sum_v(&q[5],3); //суммирование q[5]+q[6]+q[7] k4=sum_v(q+5,3); //суммирование q[5]+q[6]+q[7] Имя массива одновременно является и указателем на его первый элемент (т.е. q и &q[0] – это одно и то же). Параметры-ссылки Формальный параметр в заголовке функции называют явным параметром-ссылкой, если перед его именем находится символ &. Например, функция swap2, осуществляющая перестановку местами значений двух переменных, должна получить в качестве параметров ссылки на эти переменные, т.е. их адреса: void swap2(int &x,int &y) //явные параметры-ссылки { int tmp=x; x=y; y=tmp; } Точно так же, как и параметр-указатель, параметр-ссылка является адресом. Поэтому вызванная функция по ссылке может извлечь и, при необходимости, изменить нужное значение. В отличие от указателя доступ по ссылке не требует добавления к имени переменной какого-либо символа. Во-первых, упрощается организация тела функции – в нем можно использовать просто имена параметров-указателей. Во-вторых, упрощается вызов такой функции – на месте фактических аргументов тоже можно писать просто имена объектов (т.к. имена объектов и ссылки на них являются эквивалентами):
 

Параметры по умолчанию

В языке C++ допускается определение функций, у которых в заголовке указаны значения некоторых параметров:

double mid1(double x=0.5, double y=0.5)

{ return (x+y)/2.; }

К такой функции можно обратиться с одним (первым) аргументом или вообще без аргументов:

z=mid1(0.75); //результат равен 0.625=(0.75+0.5)/2.

p=mid1(); //результат равен 0.5=(0.5+0.5)/2.

Значения параметров по умолчанию могут быть заданы не выборочно, а только для параметров, идущих подряд и расположенных в конце списка. Это означает, что компилятор расценивает как ошибку следующий заголовок функции:

double mid2(double x=0.5,double y)

В отличие от этого следующий заголовок считается правильным:

double mid3(double x,double y=0.5)

Функция с параметрами по умолчанию работает правильно в двух случаях. Во-первых, если ее описание находится выше вызывающей функции и в заголовке функции содержится информация о параметрах по умолчанию. Во-вторых, если ее описание находится ниже и в заголовке функции отсутствуют сведения о параметрах по умолчанию, но они содержатся в прототипе. Одновременное упоминание значений по умолчанию и в заголовке функции, и в прототипе приводит к сообщению об ошибке как в системе BC 3.1, так и в ВСВ. Ниже приводится один из вариантов правильного оформления такой программы:

#include <iostream.h>

#include <conio.h>

 

double mid(double x=1.,double y=1.);

 

void main()

{ //double mid(double x,double y);

double x=0.4,y=0.2,z;

z=mid(x,y); cout<<"z="<<z<<endl;

z=mid(x); cout<<"z="<<z<<endl;

z=mid(); cout<<"z="<<z<<endl;

getch();

}

 

double mid(double x,double y)

{ return (x+y)/2.; }



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2017-12-07 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: