Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Е) присваиванием указателю пустого значения.

Поиск

int *uk2 = NULL;

int *uk3 = 0;

 

Присваивание указателю предопределенной константы NULL или просто 0 приводит к “сбросу” указателя, после чего он не ссылается на какой-либо адрес. Объявленный, но не инициализированный указатель будет содержать произвольное значение. Непреднамеренное использование указателя до присвоения ему конкретного значения может разрушить не только собственную программу, но даже и операционную систему. Если указатель содержит нулевое значение, считается, что он ни на что не ссылается. Так можно предотвратить случайное использование неинициализированного указателя.

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

 

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

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

Обе приведенные ниже программы присвоят переменной y значение переменной x = 5. В варианте а) слева это делается традиционно, без применения указателей; в варианте б) справа то же самое реализовано с помощью переменной-указателя uk и косвенного присвоения значения переменной x.

 

а) без указателей б) с указателями

 

int main() int main()

{ {

int x = 5, y; int x = 5, y, *uk;

y = x; uk = &x; y = *uk;

cout << y; cout << y;

} }

 

 

Здесь унарная операция * в операторе y=*uk; означает «извлечь значение, находящееся по адресу, на который ссылается (показывает) указатель uk». А так как переменная uk содержит адрес переменной x, то выражение *uk равно значению 5. Этот опосредованный путь получения содержимого переменной называется косвенной адресацией. Для простоты конструкцию вида *uk можно считать именем переменой, на которую ссылается указатель. Её можно использовать во всех операциях, допустимых для величин соответствующего типа.

 

 

Присваивание значений с помощью указателей. Унарная операция косвенной адресации может быть использована в левой части оператора присваивания для того, чтобы разместить новые данные в той области памяти, на которую ссылается указатель. Продолжая вариант б) программы, приведенный выше, можно указанным способом изменить содержимое переменной x:

 

*uk = 10; // переменная x получит значение 10

 

Это присваивание означает буквально следующее: «переслать 10 по адресу, содержащемуся в переменной uk.

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

 

Проанализируйте приведенную ниже программу. Результаты её работы приведены в виде комментариев к каждому из операторов вывода.

 

// UKA_ADR.cpp Программа отлажена в Visual Studio 2008

#include "stdafx.h"

#include<conio.h>

#include <iostream>

using namespace std;

int main ()

{ // Устанавливаем локализацию для выходного потока

wcout.imbue(locale("rus_rus.866"));

 

int x=25;

int *ukx = &x;

 

wcout<<L"Адрес x = "; cout << &x <<endl; // 0012FF54

wcout<<L"Значение x = "; cout << x <<endl; // 25

 

wcout<<L"Указатель на x = "; cout << ukx <<endl; // 0012FF54

wcout<<L"Значение *ukx = "; cout << *ukx <<endl; // 25

 

*ukx = 1001;

 

wcout<<L"Новое значение x = "; cout << x <<endl; // 1001

wcout<<L"Размер ukx = "; cout <<sizeof ukx<<endl; // 4

wcout<<L"Размер *ukx = "; cout <<sizeof *ukx<<endl; // 4

 

getch(); return 0;

}

 

Использование указателей позволяет функциям изменять содержимое своих аргументов. Именно благодаря указателям функция может возвращать в вызывающую программу не одно, а несколько значений. Следующая программа, используя функцию swap(), с помощью указателей обменивает местами значения двух заданных аргументов a и b. Обратите внимание: формальные параметры функции объявлены как указатели на тип int, а при вызове функции ей передаются не значения, а адреса аргументов (см. также задачу NOSWAP, раздел ”Программирование алгоритмов с использованием функций”).

 

#include<conio.h>

#include<stdio.h>

 

void swap(int *x, int *y)

{

int p; p = *x; *x = *y; *y=p;

printf("%d %d\n",*x, *y);

}

 

int main()

{

int a = 5, b = 13;

swap(&a, &b);

printf("%d %d\n", a, b);

getch();return 0;

}

 

Протокол работы программы:

13 5

13 5

 

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

 

Массивы и указатели тесно связаны друг с другом. Рассмотрим следующий фрагмент программы.

 

int mas[5]; int *uk;

uk = mas;

 

Здесь mas представляет собой имя массива, содержащего 5 элементов целого типа, a uk — указатель на тип int. Особый интерес представляет последняя строка, при выполнении которой переменной uk присваивается адрес первого элемента массива mas. Как уже упоминалось, в языке C/C++ использование имени массива без индекса генерирует указатель на первый элемент этого массива. Другими словами, это присваивание могло бы выглядеть и так: uk = &mas[0]; поэтому uk будет указывать на элемент mas[0]. Это и есть ключевой момент, который необходимо четко понимать: неиндексированное имя массива, использованное в выражении, означает указатель на начало этого массива. Поскольку после рассмотренного выше присваивания uk будет указывать на начало массива mas, указатель uk можно использовать для доступа к элементам этого массива.

 

Операция инкремент (++) перемещает указатель к следующему элементу массива, а декремент (– –) – к предыдущему. Реально при инкрементировании или декрементировании значение переменной-указателя будет увеличиваться или уменьшаться на sizeof (тип), т.е. на величину, равную размеру его базового типа.

 

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

 

// uka_bgn Программа отлажена в Visual Studio 2008

#include<conio.h>

#include <iostream>

using namespace std;

 

int main ()

{

int x[]={5, 55, 555}, *z=x;

 

*z=7; cout<<x[0]<<'\n'; // x[0]=7

*z*=5; cout<<x[0]<<'\n'; // x[0]=7*5=35

(*z)++; cout<<x[0]<<'\n'; // x[0]=35+1=36

*z++; cout<<x[0]<<'\n'; // x[0]=36, но z++

cout<<*z<<'\n'; // *z=x[1]=55

cout<<*(z++)<<endl; // 55 то же, что *z++:

// сперва стaрое *z, потом z++

cout<<*z<<'\n'; // 555 убедились?

//но

z--; cout<<*++z<<'\n'; // 555 СПЕРВА ++z потом выборка

 

cout<<" новые установки \n";

z=x; cout<<++*z<<endl; // 37

cout<<*++z<<endl; // 55

cout<<(*z)++<<endl; // 55

cout<<++(*z)<<endl; // 57

getch(); return 0;

}

 

Арифметические операции над указателями не ограничиваются использованием операторов инкремента и декремента. Созначениями указателей можно выполнить операции сложения и вычитания, используя в качестве второго операнда целочисленные значения. Если указатель на определенный тип увеличивается или уменьшается на некоторую константу, его значение фактически изменяется на величину, равную произведению этой константы на размер его базового типа, т.е. константа *sizeof (тип), например:

 

short int mas1[5];

short int *uk1= mas1; // указатель на начало массива mas1

uk1++; // значение uk1 увеличивается на 2

uk1 = uk1+3; // значение uk1 увеличивается на 6

 

double mas2[10], *uk2 = &mas2[0]; // указатель на начало массива mas2

uk2 ++; // значение uk2 увеличивается на 8

 

uk2 = uk2+3; // значение uk2 увеличивается на 24

 

 

Например, чтобы получить доступ к четвертому элементу массива mas1, можно использовать одно из следующих выражений: mas1[3] или *(mas1+3) или *(ukl+3), либо даже ukl[3] и 3[uk1]. (Последние конструкции означают, что в С/C++ указатель, который ссылается на массив, можно индексировать так, как если бы это было имя массива). Во всех случаях будет выполнено обращение к четвертому элементу. (Помните, что индексирование массива начинается с нуля). Необходимость использования круглых скобок, в которые заключено выражение *(ukl+3), обусловлена тем, что операция "*" имеет более высокий приоритет, чем операция "+". Без этих круглых скобок выражение бы свелось к получению значения, адресуемого указателем ukl, т.е. значения первого элемента массива, которое затем было бы увеличено на 3.

 

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

 

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

double mas2[10];

for (int k=0; k<10; k++)

{ *mas2 = k; // Здесь все в порядке.

mas2++; //ОШИБКА! Указатель-константу mas модифицировать нельзя.

}

 

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

 

Следующая программа иллюстрирует использование указателей для обработки массива: функция max_arr() осуществляет поиск максимального элемента массива. Обратите внимание, как в заголовке функции объявлен указатель на массив.

 

// Программа отлажена в Visual Studio 2008

#include "stdafx.h"

#include<conio.h>

#include<stdio.h>

#include <iostream>

using namespace std;

 

 

// поиск max элемента массива

int max_arr (int *uk, int n)

{ int i, max = *uk;

for(i=0; i<n; i++)

{ if(*uk > max)

max = *uk;

uk++;

}

return max;

}

 

int main()

{

int a[ ]={1,-2,13,4,25};

int SIZE = sizeof(a)/sizeof(int);

printf("max = %d\n", max_arr(a, SIZE));

getch(); return 0;

}

 

Обычно указатели позволяют написать более компактный код по сравнению с использованием индексирования массивов. И хотя в последнем примере такое преимущество особо не заметно (оно явственно проявится при обработке строк), следует знать, что арифметические операции над указателями, как правило, выполняются быстрее, чем индексирование массивов (как правило, при использовании индексирования массивов генерируется и более длинный машинный код!). Поскольку быстродействие часто является определяющим фактором при выборе тех или иных средств языка программировании, то использование указателей для доступа к элементам массива — характерная особенность многих С/С++ - программ.

 

В предыдущей программе при вызове функции max_arr() мы использовали указатель, определяющий начало массива, и целое значение n, указывающее количество элементов массива, подлежащих обработке. Далее в функции использован классический заголовок цикла for(i=0; i<n; i++), характерный для обработки фиксированного числа элементов массива. Используем теперь несколько иную технологию обработки массива. Воспользуемся тем, что до вызова функции нам известен размер массива SIZE. Вычислим с его помощью указатель на конец массива и передадим функции два указателя: start определяет начало массива, а end = start + SIZE указывает на элемент, следующий за конечным элементом. Этот подход демонстрирует следующая программа.

 

// Программа отлажена в Visual Studio 2008

#include<conio.h>

#include<stdio.h>

#include <iostream>

using namespace std;

 

 

// поиск max элемента массива

int max_arr (int *start, int *end)

{

int max = *start;

while (start < end)

{

if(*start > max)

max = *start;

start++;

}

return max;

}

 

int main()

{

int a[ ]={1,-2,13,4,25};

int SIZE = sizeof(a)/sizeof(int);

printf("max = %d\n", max_arr(a, a + SIZE));

getch(); return 0;

}

 

Указатели можно использовать в большинстве допустимых в C/C++ выражений. Но при этом следует применять некоторые специфические правила и не забывать о приоритетах операций, связанных с указателями. Имейте в виду, что операции "*" и "&" имеют более высокий приоритет, чем любой из арифметических операторов, за исключением унарного минуса, приоритет которого такой же, как у операторов, применяемых для работы с указателями. Чтобы гарантированно получать желаемый результат, используйте правильно расставленные скобки. Так, чтобы инкрементировать или декрементировать значение, расположенное в области памяти, адресуемой указателем uk2, можно использовать оператор следующего вида:

 

(*uk2)++;

 

Круглые скобки здесь обязательны, поскольку без них операция * должна выполняться позже, чем операция "++". Чтобы лучше понять, что происходит при выполнении арифметических действий с указателями, рассмотрим последовательность действий, заданную в операторе

 

*uk2++ = 25;

 

Унарные операции разадресации и инкремента имеют одинаковый приоритет и выполняются справа налево; но т.к. инкремент постфиксный, он выполняется после операции присваивания. Таким образом, сначала по адресу, на который ссылается указатель uk2, будет записано число 25, и только потом указатель будет увеличен на количество байтов, соответствующее его типу (для типа double на 8). То же самое можно записать более подробно:

*uk2 = 25; uk2++;

Сложение двух указателей вообще недопустимо, а вот разность может быть весьма полезна. Один указатель можно вычесть из другого, если они оба имеют один и тот же базовый тип. Это имеет смысл делать, если оба указателя ссылаются на два участка одной и той же структуры данных (например, массива). Тогда разность покажет количество элементов базового типа, которые разделяют эти два участка. В качестве иллюстрации к сказанному рассмотрим функцию length(), вычисляющую длину заданной строки символов как разность указателей на конец строки (uk1) и на начало её (str). В указателе str хранится копия адреса строки-аргумента; поэтому он тоже может при необходимости быть подвержен изменению.

 

#include<stdio.h>

#include<conio.h>

// ДЛИНА СТРОКИ

int length (char *str)

{

char *uk1; uk1 = str; // два указателя на начало реальной строки

while(*uk1!= '\0') // выход на конец строки

uk1++;

return(uk1-str); // реальная длина строки

}

int main()

{

char S[80];

puts("Введи строку"); gets(S);

printf("Длина строки: %d", length(S));

getch(); return 0;

}

 

Сравнение указателей. Указатели можно сравнивать, используя операции отношения = =,!=, <, <=, > и >=. Однако сравнение указателей имеет определенный смысл, если сравниваемые указатели ссылаются на один и тот же объект, например массив. При этом можно говорить о еще одном типе сравнения: любой указатель можно сравнить с нулевым указателем, который, по сути, равен нулю.

Следующая программа пересылает символы строки S1 в строку S2, изменив порядок их следования на противоположный. Для реализации вышесказанного программа использует две переменные типа указатель uk1 и uk2. Первая сначала указывает на начало строки S1(её первый элемент), а вторая — на начало строки S2.

Первый цикл в программе продолжается до тех пор, пока указатель uk1 не достигнет конца строки S1. Затем во втором цикле программы указатель uk1 движется от конца строки к её началу. Обратите внимание на то, что указатель uk1 уже при первом обороте цикла ссылается на последний символ строки, а не на признак ее завершения (нулевой символ). Цикл продолжается до тех пор, пока указатель uk1 остается больше или равным адресу–указателю S1 на начало строки. На каждой итерации цикла символы, извлеченные с конца строки S1, перемещаются в начало строки S2. Заполнение строки S2 осуществляется с помощью указателя uk2. Каждая итерация второго цикла сопровождается инкрементированием указателя uk2 и декрементированием указателя uk1.

 

// revers.cpp запись строки в обратном порядке

#include <stdio.h>

#include <conio.h>

 

int main()

{

char s1[80], s2[80];

char *uk1=s1; char *uk2=s2;

printf("введи cтроку\n");

gets(s1);

//выход на конец строки

while(*uk1!= ‘\0’)

uk1++;

//переворачивание строки

while (--uk1 >= s1)

*uk2++ = *uk1;

 

*uk2=0; puts(s2);

getch();return 0;

}

 

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

char *uk1;

// Указателю uk1 присваивается адрес строковой константы:

uk1 = “ Умом Россию не понять”;

puts(uk1);

 

Дело в том, что компилятор, обнаружив в таком контексте строковую константу, сохраняет её в теле программного кода и генерирует указатель на эту строку. Поэтому приведенный выше фрагмент программы совершенно корректен и при выполнении выводит на экран фразу “ Умом Россию не понять”.

Следующая короткая программа выводит на экран символы заданной строки–литерала, двигаясь от конца строки к её началу и прибавляя при каждом обороте цикла по одному символу слева (напомним, что функция puts() выводит строку, адрес начала которой передан ей в качестве аргумента и при этом вывод всякий раз начинается с новой строки экрана).

 

#include<conio.h>

#include<stdio.h>

#include <iostream>

using namespace std;

 

 

int main()

{

char *uk2, *uk1 = "КОЛЯ";

uk2 = uk1 + strlen (uk1); //конец строки

 

while (--uk2 >= uk1)

puts(uk2);

 

getch(); return 0;

}

 

Вот результат работы программы:

Я

ЛЯ

ОЛЯ

КОЛЯ

 



Поделиться:


Последнее изменение этой страницы: 2016-12-16; просмотров: 413; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.217.224.165 (0.008 с.)