Поиск

Полнотекстовый поиск:
Где искать:
везде
только в названии
только в тексте
Выводить:
описание
слова в тексте
только заголовок

Рекомендуем ознакомиться

'Документ'
Биография Александр Дюма родился в 1802 году в семье генерала Тома-Александра Дюма и Марии-Луизы Лабурэ, проживавших в небольшом городе Вилле-Котре. С...полностью>>
'Автореферат диссертации'
Защита состоится «2 июня» 2008 г. в 11.00 час. на заседании диссертационного совета Д 210.010.04 по защите диссертаций на соискание ученой степени до...полностью>>
'Документ'
Приглашаем главных специалистов по клинической лабораторной диагностики, анестезии-реанимации, врачей анестезиологов-реаниматологов, клинической лабо...полностью>>
'Урок'
Цели урока: знакомство с терминами «экологическая безопасность», «цепь загрязнения», «эколог»; знакомство с правилами личной экологической безопаснос...полностью>>

8. Указатели. Указатель это переменная, содержащая адрес другой переменной

Главная > Библиографический указатель
Сохрани ссылку в одной из сетей:

8. Указатели.

Указатель - это переменная, содержащая адрес другой переменной.

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

int x;

int *px;

Из описания следует, что указатель может указывать только на определенный тип объектов.

Унарная операция * ("взятие значения") рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если y также имеет тип int, то операция

y = *рх;

присваивает y содержимое того объекта, на который указывает рх. Так последовательность операций

рх = &х;

y = *рх;

присваивает y то же самое значение, что и оператор

y = x;

Указателю можно присваивать адрес объекта и непосредственно при описании:

int x;

int *px=&x;

Унарная операция & ("взятие адреса") уже использовалась нами в стандартном операторе ввода scanf. Здесь указатель px содержит адрес переменной x, ему присвоен ее адрес.

Над указателями определены операции сложения и вычитания с числом:

px++;

В этом примере унарная операция ++ увеличивает px так, что он указывает на следующий элемент набора объектов того типа, что задан при определении указателя.

px-=2;

Операция px+=i увеличивает px так, чтобы он указывал на элемент, отстоящий на i элементов от текущего элемента.

Сравнение указателей в общем случае некорректно! Это связано с тем, что одним и тем же физическим адресам памяти могут соответствовать различные пары значений "сегмент смещение".

Указатели являются переменными, соответственно, их можно присваивать:

int *py=px;

или:

int *py;

py=px;

Теперь py указывает на то же, что px.

Указатели px и py адресуют одну и ту же переменную x, но сравнение px==py может быть некорректным, в отличие от сравнения значений *px==*py;.

Унарная операция & выдает адрес объекта, так что оператор

рх = &х;

присваивает адрес х переменной рх; говорят, что теперь рх указывает на х. Операция & применима только к переменным и элементам массива, конструкции вида &(х-1) и &3 являются незаконными. Нельзя также получить адрес регистровой переменной.

Указатели могут входить в выражения. Например, если px указывает на целое x, то *px может появляться в любом контексте, где может встретиться x. Так оператор

y = *px + 1;

присваивает y значение, на 1 большее значения x (получаем значение из указателя, затем прибавляем 1). Оператор

printf ("\n%d", *px);

печатает текущее значение x. Оператор

d = sqrt((double)*px);

получает в d квадратный корень из x, причем до передачи функции sqrt значение x преобразуется к типу double.

В выражениях вида

y = *px + 1;

унарные операции * и & связаны со своим операндом более крепко, чем арифметические операции (см. таблицу приоритетов из лекции 1), так что это выражение берет значение, на которое указывает px, прибавляет 1 и присваивает результат переменной y:

y = (*px) + 1;

Выражение

y = *(px + 1);

имеет совершенно иной смысл: записать в y значение, взятое из ячейки памяти, следующей за той, на которую указывает px. Адрес, на который указывает px, при этом не изменится.

Ссылки на указатели могут появляться в левой части операторов присваивания. Если px указывает на x, то

*px = 0;

записывает в x значение 0, а

*px += 1;

увеличивает значение x на единицу, как и выражение

(*px)++;

Круглые скобки в последнем примере необходимы; если их опустить, то поскольку унарные операции, подобные * и ++, выполняются справа налево, это выражение увеличит px, а не ту переменную, на которую указывает px.

Наконец, операция

*px++;

получает значение из указателя, затем сдвигает указатель на следующую ячейку памяти (поскольку использована постфиксная форма инкремента).

8.2 Указатели и аргументы функций.

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

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

Пример 1: указатели в качестве аргументов функции

void swap (int *a, int *b) {

int c=*a; *a=*b; *b=*c;

}

int a,b,c;

swap (&a,&b); //Передача параметров по адресу и прием по значению!

int *p=&c;

swap (&a, p);

Сравните с

void swap (int &a, int &b) {

int c=a; a=b; b=c;

}

int a,b;

swap (a,b); //Передача параметров по значению и прием по адресу!

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

Как правило, указатель используется для последовательного доступа к элементам статического или динамического массива. Так, конструкция

int a[]={1,2,3};

int *p=a;

for (int i=0;i<3;i++) printf ("\t%d",*p++);

последовательно распечатает элементы массива a, доступ к которым осуществлялся через указатель p.

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

int *p=&a[0];

или

int *p=&(*a+0);

или

int *p=&*a;

Пример: сканирование строки с помощью указателя

int strlen(char *s){

int n;

for (n = 0; *s != '\0'; s++) n++;

return(n);

}

char *s="Test";

int len=strlen (s);

Здесь с указателем на тип char сопоставляется статическая строка символов, подробнее это прием будет раскрыт ниже.

Тело функции strlen можно было записать и короче:

int n=0;

while (*s++) n++;

return n;

Существуют различия между массивами и указателями.

  1. Указатель занимает одну ячейку памяти, предназначенную для хранения машинного адреса (в частности, адреса нулевого элемента массива). Массив занимает столько ячеек памяти, сколько элементов определено в нем при его объявлении. Только в выражении массив представляется своим адресом, который эквивалентен указателю.

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

Для одномерного массива следующие 2 выражения эквивалентны, если а — массив или указатель, а b — целое:

а[b]

*+ b)

Аналогично, для матрицы a с целочисленными индексами i и j эквивалентны выражения

a[i][j]

*(*(a+i)+j)

Пример:

#include <stdio.h>

void main () {

int a[2][3]={

{1,2,3},

{4,5,6}

};

int i=1,j=2;

printf ("%d",*(*(a+i)+j)); //a[1][2]=6

}

Специальное применение имеют указатели на тип void. Указатель на void может указывать на значения любого типа. Однако для выполнения операций над указателем на void либо над указуемым объектом, необходимо явно привести тип указателя к типу, отличному от void. Например, если объявлена переменная i типа int и указатель р на тип void:

int i;

void *p;

то можно присвоить указателю р адрес переменной i:

p = &i;

но изменить значение указателя нельзя:

р++; /* недопустимо */

(int *)р++; /* допустимо */

В стандартном включаемом файле stdio.h определена константа с именем NULL. Она предназначена специально для инициализации указателей. Гарантируется, что никакой программный объект никогда не будет иметь адрес NULL.

8.4. Указатели и символьные данные.

Если описать указатель message в виде

char *message;

то в результате оператора

message = "Any string of text";

message будет указывать на фактический массив символов. Это не копирование строки, так как в операции участвуе только указатель. Также важно то, что в языке Си не предусмотрены какие-либо операции для обработки всей строки символов как целого. Как и в других контекстах, присваивание значения переменной можно объединить с ее определением:

char *message = "Any string of text";

Указатели в сочетании с операцией инкремента естественным образом используются для сканирования строк. С таком контексте динамически меняющийся указатель на строку часто называют просто строкой.

Пример. Функция копирования строки.

#include <stdio.h>

char *strcpy (char *s, char *t) {

char *n=s; //запомнили, куда показывал s

while (*t!='\0') {

*s++=*t++;

}

return n; //s сдвинулся, вернули его начальное значение

}

void main () {

char *s1="none",*s2="test";

printf ("\n%s",strcpy(s1,s2));

}

Функция получает 2 указателя на строки s (строка назначения) и t (строка-источник). Значением *t++ является символ, на который указывал t до увеличения; постфиксная операция ++ не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ помещается в старую позицию s, до того как s будет увеличено. Конечный

результат заключается в том, что все символы копируются из t в s (включая завершающий символ нуля!).

Более кратко процесс копирования можно было бы описать в виде

while ((*s++ = *t++) != '\0');

Здесь увеличение s и t вынесено в проверочную часть цикла. Или же, опуская сравнение с нулем,

while (*s++ = *t++);

Напишем функцию сравнения строк с указателями. Она вернет число меньше 0, если строка s лексикографически (по кодам символов) предшествует t, 0, если строки одинаковы и положительное значение, если s "больше" t по кодам символов.

int strcmp(char *s, char *t) {

for ( ; *s == *t; s++, t++)

if (*s == '\0') return(0);

return(*s-*t);

}

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

8.5. Указатели и динамическая память.

В стандартной бибилотеке stdlib.h имеются две функции:

  • функция void *malloc(n) с целочисленным беззнаковым аргументом n возвращает в качестве своего значения нетипизированный указатель p, который указывает на первый из n выделенных байт памяти. Эта память может быть использована программой для хранения данных; перед использованием указатель должен быть типизирован операцией приведения типа. Если выделить память не удалось, функция возвращает NULL.

  • функция void free(p) освобождает приобретенную таким образом память, так что ее в дальнейшем можно снова использовать. Обращения к free должны производиться в порядке, обратном тому, в котором производились обращения к malloc.

Примечание:

Операция приведения типа имеет вид sizeof(тип) и позволяет узнать размер переменной этого типа в байтах.

Пример 1. Указателю p сопоставляется динамическая память на n символов, значение n вводится пользователем.

#include <stdlib.h>

#include <stdio.h>

unsigned char *p;

unsigned n;

printf ("\nN="); fflush (stdin); scanf ("%u",&n);

p=(unsigned char *)malloc(n*sizeof(unsigned char));

if (p==NULL) {

//Здесь производится диагностика ошибки

}

Пример 2. Функция strsave копирует свою строку-аргумент в динамически выделенную область памяти.

#include <stdlib.h>

#include <stdio.h>

#include <string.h> /*бибилотека с прототипами строковых функций*/

char *strsave(char *s) {

char *p=NULL;

p=(char *) malloc(strlen(s)+1);

if (p != NULL) strcpy(p, s);

return(p);

}

void main () {

char *s1="hello", *s2;

s2=strsave(s1);

printf ("\n%s",s2);

}

9. Указатели и функции с переменным числом аргументов

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

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

В списке типов аргументов в качестве имени типа допускается также конструкция void *, которая специфицирует аргумент типа "указатель на любой тип". Для доступа к переменному списку параметров можно использовать указатели.

Пример. Запись кодов клавиш в собственный буфер клавиатуры

#define KEY unsigned int

#define MAX_BUF 9

static KEY Buf [MAX_BUF];

KEY Start = 0;

void Set_Key( KEY kol, ... ) {

//Параметр kol задает количество остальных параметров

KEY *ptr;

ptr = &kol; //Указываем на первый символ в строке параметров

for(; kol != 0; kol-- ) {

Buf [Start++] = *++ptr; //Последовательно записываем все символы

Start %= MAX_BUF; //во внутренний буфер с контролем его переполнения

}

return;

}

Примеры вызова этой функции:

#define ENTER 0x000D

#define END 0x4F00

#define RIGHT 0x4d00 /* полные двухбайтовые коды клавиш */

Set_Key (1,ENTER);

Set_Key (2,END,RIGHT);

В дальнейшем функция получения кодов символов может извлекать ранее записанные символы, например, так:

#define MODE unsigned char

KEY Get_Key (void) {

KEY sim;

MODE scan,ascii;

if (Start != End) {

sim = Buf [End++]; End %= MAX_BUF;

}

else {

asm MOV AH,0x00;

asm INT 16H;

sim=_AX; //эквивалентно вызову bioskey (0);

scan=(MODE)((sim&0xff00)>>8); //_AH

ascii=(MODE)(sim&0x00ff); //_AL

if (ascii) scan=0;

sim=(scan<<8)+ascii;

}

return (sim);

}

Существует также библиотека stdarg.h для работы с переменными списками аргументов.

9.2. Указатели и прямой доступ к памяти

Рассмотрим этот пункт на примере организации прямого доступа к памяти видеоадаптера в текстовом режиме с разрешением экрана 80*25 позиций. Как известно, видеопамять при этом начинается с адреса B800:0000 и состоит из пар байт "символ-атрибут", описывающих экранные позици слева направо и затем сверху вниз.

static unsigned char far *s = (unsigned char far *) 0xB8000000UL;

void putc (int x, int y, char c) {

*(s+y*160+x*2)=c;

}

void main () {

putc (0,0,'*'); putc (79,0,'*');

putc (0,24,'*'); putc (79,24,'*');

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

}

Модификатор far определяет "длинный" 4-байтовый указатель, подробнее мы познакомимся с типами указателей в дальнейшем.

Поскольку одна строка экрана консоли состоит из 80 символов и требует 160 байт памяти, конструкция *(s+y*160+x*2), где x – экранный стобец, а y – строка, адресует на экране позицию в y-строке и x-столбце.

Учитывая, что операции сдвига порождают более быстрый код, чем умножение, а 160=128+32=27+25, в функции putc лучше использовать присваивание вида

*(s+ (y<<7) + (y<<5) + (x<<1)) = c;



Скачать документ

Похожие документы:

  1. Ликбез по переменным и указателям в C++

    Документ
    Что такое переменная? Переменная – это определенное место в памяти, имеющее имя, по которому мы его используем. Переменная имеет значение (текущее значение), грубо говоря, это то, что записано сейчас по определенному месту в памяти.
  2. Пояснительная записка Курсовая работа по дисциплине «информатика» на тему: Ссылочные типы. Динамические переменные

    Пояснительная записка
    Федеральное агентство по образованиюгосударственное образовательное учреждение высшего профессионального образования Тюменский государственный нефтегазовый университетТобольский индустриальный институтКафедра математики и информатикиПояснительная
  3. Учебно-методический комплекс учебной дисциплины сдм. 02 «программирование» подготовки магистров по направлению 050200 «Физико-математическое образование» магистерская программа «Информатика в образовании»

    Учебно-методический комплекс
    Многоуровневая подготовка специалистов на современном этапе требует инновационного подхода к разработке образовательных программ и отбору содержания учебных курсов, ориентация их на мировой уровень подготовки по направлению бакалавр
  4. С++. Краткий курс лекций

    Курс лекций
    Этот оператор позволяет передать управление одному из нескольких помеченных метками операторов в зависимости от значения целочисленного выражения. Метки оператора switch имеют специальный вид:
  5. Это методическое пособие предназначено для желающих самостоятельно научиться решать задачи по основным разделам предмета на языках Бейсик и Паскаль. Предлагаемая методика включает

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

Другие похожие документы..