пользователей: 30398
предметов: 12406
вопросов: 234839
Конспект-online
РЕГИСТРАЦИЯ ЭКСКУРСИЯ

Указатели и адреса

Указатели

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

С указателями используются следующие операции:

  • & - взятие адреса переменной;
  • * - разъименование указателя, т.е. получение доступа к объекту;
  • -> - разъименование члена структуры;
  • [] - индексация, доступ к элементу массива, при этом считается, что указатель содержит адрес 0 элемента.

Операция индексации может быть реализована с помощью адресной арифметики, где применяются операции ++,--, и +, -.

Можно создавать указатели на любые типы за исключением ссылок и битовых полей. Указатель может указывать не только на данные, но и на область кода, т.е. на функцию.

//================================

// Name        : ptrtest.cpp

// Author      : your name

// Version     :

// Copyright   : Your copyright notice

// Description : base using of pointers

//=====================================

#include <iostream>

using namespace std;

 

struct Mystruct

{

int a,b;

int *ptr; // указатель как член структуры

int metod(int c){return c+a;}

int metod1(int c){return c+a;}

 

static int smetod(int c){return c+10;}

} mystruct,*sptr;

 

int func(int a){return a+5;}

 

int a=1,b=2,c=3,*iptr=&a;

int array[10]={100,101,102,103,104,105,106,107,108,109};

int * iptrarray[10];  // массив указателей типа int

int (*iarrayptr)[10]; // указатель на массив из 10 элементов типа int

 

 

int main() {

// пример применения указателей      

cout<<"a= "<<a<<", "<<"b="<<b<<", c="<<c<< endl;

cout<<"*iptr = "<<*iptr<<endl;

cout<<"iptr[0] = "<<iptr[0]<<endl;

cout<<"iptr[1] = "<<iptr[1]<<endl;

cout<<"*(iptr + 1) = "<<*(iptr+1)<<endl;

cout<<"1[iptr] = " <<1[iptr]<<endl;  // тоже возможный вариант

*iptr= 45; // меняем значение переменной a

cout<<"a = "<<a<<endl;

cout<<"----------------------------------------"<<endl;

 

// адресная арифметика

cout<<"iptr= "<<iptr<<endl;       // вывод адреса в памяти переменной a

cout<<"iptr+1= "<<iptr+1<<endl;   // вывод адреса в памяти переменной b

// разница между адресами переменных a и b в элементах

cout<<"delements=(iptr+1)-iptr = " << (iptr+1)-iptr <<endl;

// разница между адресами переменных a и b в байтах

cout<<"dbytes=(long)(iptr+1) - (long)iptr = " << (long)(iptr+1) - (long)iptr <<endl;

cout<<"dbytes=delements * sizeof(int) = "<< ((iptr+1)-iptr) * sizeof(int)<<endl;

cout<<"----------------------------------------"<<endl;

 

// указатель и массивы

// имя массива является указателем на первый элемент

iptr=array;

for(unsigned int i=0;i<10;i++) {

        cout<<"array["<<i<<"]="<<array[i]<<", *iptr="<<*iptr<<

        ", iptr[0]="<<iptr[0]<<endl;

        iptr++; // т.е. iptr=iptr+1;

        }

cout<<"----------------------------------------"<<endl;

 

// указатели и динамически выделяемая память

iptr=new int(100); // выделяем в памяти объект типа int со значением 100

cout<<"*iptr="<<*iptr<<endl;

delete iptr; // освобождаем выделенную память

cout<<"----------------------------------------"<<endl;

 

// пустой указатель

// обычно применяется когда заранее тип объекта неизвестен

void* vptr=new int(100);

cout<<"* ((int*)vptr)="<<* ((int*)vptr)<<endl;

delete (int*)vptr;

vptr=new double(3.14);

cout<<"* ((double*)vptr)="<<* ((double*)vptr)<<endl;

cout<<"----------------------------------------"<<endl;

 

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

// имя функции является и ее адресом

typedef int (*FPTR)(int);

int (*fptr)(int)=func; // или FPTR fptr=func;

cout<<"result of func(10): "<<func(10)<<endl;

cout<<"result of fptr(10): "<<fptr(10)<<endl;

// статические методы отличаются от обычных функций только

// областью видимости

fptr=Mystruct::smetod; // можно и так fptr=mystruct.smetod;

cout<<"result of Mystruct::smetod(30): "<<Mystruct::smetod(30)<<endl;

cout<<"result of fptr(30): "<<fptr(30)<<endl;

// конструкция fptr=mystruct.metod; будет ошибочной

// так как простым методам С++ не явно добавляет еще один аргумент

// указатель на сам объект mystruct

cout<<"----------------------------------------"<<endl;

 

// указатели и структуры

mystruct.a=10;

mystruct.b=20;

iptr=&mystruct.b;

cout<<"mystruct.b="<<mystruct.b<<", *iptr="<<*iptr<<endl;

sptr=&mystruct;

cout<<"(*sptr).a="<<(*sptr).a<<endl;

// вместо (*sptr).a удобней пользоваться операцией ->

cout<<"sptr->a="<<sptr->a<<", sptr->b="<<sptr->b<<endl;

sptr->ptr=iptr;

cout<<"*sptr->ptr="<<*sptr->ptr<<endl;

cout<<"----------------------------------------"<<endl;

 

 

// указатели на члены структуры, на практике редко встречается

int Mystruct::*isptr;         // указатель на член данных структуры

int (Mystruct::*fsptr) (int); // указатель на метод структуры

// при инициализации указателей на членов структуры

// используется имя структуры

isptr=&Mystruct::b;

fsptr= &Mystruct::metod;

cout<<"mystruct.*isptr="<<mystruct.*isptr<<endl;

cout<<"(mystruct.*fsptr)(40)="<<(mystruct.*fsptr)(40)<<endl;

fsptr=&Mystruct::metod1;

cout<<"now (mystruct.*fsptr)(40)="<<(mystruct.*fsptr)(40)<<endl;

cout<<"----------------------------------------"<<endl;

return 0;

}

Как видно из этого примера, работа с указателями требует большой внимательности. Легко затереть не те данные, или обратиться по указателю к уже удаленному объекту или забыть удалить объект. Также указатели позволяют обойти некоторые ограничения языка (например обратиться к защищенному члену класса) и даже повлиять на систему в целом (например, в DOS программе зная адрес можно обратиться к системным переменным DOS или BIOS). Поэтому в некоторых языках как java полностью откзались от указателей ради безопасности.

Для устранения проблемы удаления объекта существуют готовые классы указатели. Некоторые из них рассмотрены в пункте динамическая память.

 

Адреса

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

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

Лучшая иллюстрация страничной организации памяти компьютера - это страничная организация любой печатной книги. Для того чтобы найти нужную строчку, нет необходимости задавать ее номер, считая от начала текста. Вместо этого можно задать сначала номер страницы ( = сегмент ) и только затем номер строки, считая от начала этой страницы ( =смещение ).

Для обращения к статически заданной переменной можно использовать как ее имя, объявленное в разделе var, так и ее физический адрес.

Например, " адрес " одной и той же географической точки можно записать по-разному: "49°47' северной широты и 86°36' восточной долготы" или просто "вершина пика Белуха Восточная1) ".

Указатели

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

Описание указателей

При описании типизированного указателя необходимо сообщить компилятору, адреса переменных какого типа он может хранить:

var <имя_указателя>: ^<тип_адресуемой_переменной>;

Например:

var p: ^integer;
        q: ^real;
        s: ^array[1..10] of byte;

Кроме того, существуют универсальные нетипизированные указатели, которые могут хранить адрес переменной любого типа:

var <имя_указателя>: pointer;

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

Определение адреса

Физический адрес любой переменной можно узнать при помощи стандартной функции addr(<имя_переменной>):<указатель> или унарной операции@<имя_переменной>.

В зависимости от значения директивы компилятора {$T}, результатом операции @ будет либо типизированный указатель (если установлено {$T+} ), тип которого будет определен в соответствии с типом использованной переменной, либо нетипизированный указатель pointer (если установлено {$T-} ).

Результат функции addr() совместим с указателями любых типов:

p:= addr(x); {x: real; p: ^byte)

Разыменование

Для того чтобы воспользоваться значением, хранящимся по некоторому адресу, необходимо его оттуда "извлечь". Унарная операция ^ называетсяразыменованием и записывается по следующему шаблону:

<имя_указателя>^

Результатом операции ^ является значение, хранящееся по указанному адресу. Тип этого значения будет определяться типом ( типизированного ) указателя. Кнетипизированным указателям операцию разыменования применять нельзя.

Из-за вольностей, допускаемых процедурой addr(), при разыменовании порой могут возникнуть забавные ситуации. Например, в результате выполнения такой вот программы:

const a: array[1..3] of char ='ААА'; {код(А)=128 или 01000000}
var p: ^word;
begin p:= addr(a);
                       writeln(p^)
end

на экран будет выведено 32896, что в двоичной системе счисления выглядит как 01000000.01000000 (точкой помечена граница двух байтов). Иными словами, коды двух первых букв оказались слитыми в значение типа word.

Замечание: Операции @ и ^ являются взаимно обратными, то есть для любой переменной a и для любого типизированного указателя p верны следующие равенства:

@(p^)= p и (@a)^ = a

27.12.2014; 11:50
хиты: 134
рейтинг:0
Точные науки
информатика
Языки программирования
для добавления комментариев необходимо авторизироваться.
  Copyright © 2013-2024. All Rights Reserved. помощь