Будь умным!


У вас вопросы?
У нас ответы:) SamZan.net

Лекция 6 46 Указатели и массивы 4

Работа добавлена на сайт samzan.net:


PAGE  6

Лекция 6

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

4.6.1. Указатели

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

    Указатель на данные содержит адрес памяти, в которой хранятся данные определенного типа (основного или составного). Простейшее объявление такого указателя имеет вид:

тип  *имя;

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

int  *a, b, *с;

описываются два указателя на целое с именами а и с, а также целая переменная b.

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

тип  (*имя) (список_типов_аргументов);

Например, объявление:

int  (*fun)  (double, double);

определяет переменную fun  как указатель на функцию, возвращающую значение типа int и имеющую два аргумента типа double.

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

    Рассмотрим примеры:

int  i;                                         // целая переменная

const  int  ci = 1;                      // целая константа

int  *pi;                                     // указатель на целую переменную

const  int  *pci;                        // указатель на целую константу

int  *  const  cp = &i;               // указатель-константа на целую переменную

const  int  *  const  cpc = &ci; // указатель-константа на целую константу

void  f (int  a) { /*     */ }          // определение функции

void  (*pf) (int),                        // указатель на функцию

pf  =  f;                                      // присваивание адреса функции

    Указатели часто используются при работе с динамической памятью – кучей (от английского heap). Это свободная память, которую во время выполнения программы можно использовать в соответствии с ее потребностями. Доступ к выделенным участкам динамической памяти, называемым динамическими переменными, производится через указатели. Время жизни динамических переменных - от точки создания до конца программы или до явного освобождения памяти. В C++ используется два способа работы с динамической памятью. Первый использует функции mallос и free (достался в наследство от С), второй - операции new и delete.

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

  •  адрес существующего элемента;
  •  явный адрес области памяти;
  •  пустое значение (0);
  •  адрес выделенного участка динамической памяти;

    С указателями можно выполнять ряд операций: разадресация, или косвенное обращение к объекту (*), присваивание, сложение с константой, вычитание, инкремент (++), декремент (--), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&).

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

char  a;                            //  переменная типа char

char  *p  =  new char,    /*   объявление указателя и его инициализация адресом

                                             динамической переменной типа char */

*p  = ‘Ю’;  a  =  *p;        //   присваивание значений обеим переменным

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

#include  <stdio.h>

int  main()

  {

  unsigned  long  int  A  =  0xCC77FFAA;

  unsigned   short  int  *pInt  =  (unsigned  short  int*) &A;

  unsigned  char  *pChar  =  (unsigned  char  *) &A;

  printf  (“  |  %x | %x |  %x  |",  A,  *pInt,  *pChar);

  return  0;

  }

    Результаты:  |  CC77FFAA  |  FFAA  |  AA  |

    В этом примере при инициализации указателей были использованы операции приведения типов. Синтаксис операции явного приведения типа таков: перед именем переменной в скобках указывается тип, к которому ее требуется преобразовать. Поскольку при этом не гарантируется сохранение информации, то без необходимости не рекомендуется использовать явные преобразования типов.

    При смешивании в выражении указателей разных типов явное преобразование типов требуется для всех указателей, кроме void*. Указатель может неявно преобразовываться в значение типа bool (например, в выражении условного оператора), при этом ненулевой указатель преобразуется в true, а нулевой в false. Значение 0 (пустой указатель) неявно преобразуется к указателю любого типа.

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

short  *р  =  new  short[5];

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

p  +=  5;                                 // значение р увеличивается на 10

long  *q  =  new  long[5];

q++;                                       // значение q увеличивается на 4

Разность двух указателей - это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается.

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

    Наряду с указателем, в C++ имеется элемент данных ссылкаC его не было). Ссылку можно рассматривать как указатель, который всегда автоматически разыменовывается, и поэтому при работе с ней операция * не нужна. Формат объявления ссылки таков:

тип & имя;

где тип — это тип величины, на которую указывает ссылка, & - признак ссылки. Например:

int  kol;

int & pal  =  kol;                   // ссылка pal - альтернативное имя для kol

const  char &  CR = '\n';      // ссылка на константу

Имеют место следующие правила:

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

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

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

4.6.2. Массивы

    Конечная именованная последовательность однотипных величин называется массивом. Описание массива в программе отличается от описания простой переменной наличием после имени квадратных скобок, в которых задается количество элементов массива (размер). Например,

float  a[10];     // Вещественный массив из 10 элементов

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

int  b[5] = {3,  2,  1};   //  b[0]=3. b[l]=2,  b[2]=l. b[3]=0, b[4]=0

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

    Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма элементов массива.

#include  <iostream h>

int  main ()

  {

  const  int  n = 10;

  int  i,  sum;

  int  marks[n] = {3, 4, 5, 4, 4};

  for  (i = 0, sum = 0;  i < n;  i++)  sum  +=  marks[i];

  cout  <<  "Сумма элементов-  "  <<  sum;

  return  0;

  }

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

    Идентификатор массива является константным указателем на его нулевой элемент. Например, для массива из предыдущего имя marks  - это то же самое, что &marks[0], а к i-му элементу массива можно обратиться, используя выражение *(marks+i). Можно описать указатель, присвоить ему адрес начала массива и работать с массивом через указатель. Например:

int  a[100],  b[100];

………………………………………………

int  *pa = а;   // или inta = &а[0];

int  *pb = b;

for  (int  i = 0;  i < 100; i++)  *pb++ = *pa++;   // или pb[i] = pa[i];

    Динамические массивы создаются с помощью операции new, при этом необходимо указать тип и размер, например:

int  n = 100;

float  *p  =  new  float [n];

    Преимущество динамических массивов в том, что их размер может быть переменным, то есть нужный объем памяти определяется на этапе выполнения программы. Доступ к элементам динамического массива осуществляется так же, как и к статическим. Например, к 5-му элементу приведенного выше массива можно обратиться как р[5] или *(р+5).

    Альтернативный способ создания динамического массива — использование функции malloc библиотеки С:

int  n = 100;

float  *q  = (float *) malloc (n * sizeof (float));

Поскольку функция malloc возвращает значение указателя void*, то потребовалось явное преобразование типа.

    Память, зарезервированная с помощью new [], должна освобождаться операцией delete[], а память, выделенная функцией malloc - посредством функции free, например:

delete[]  p;  free (q);

    Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор

int  matr [6][8];

объявляет двумерный массив из 6 строк и 8 столбцов. В памяти такой массив располагается в последовательных ячейках построчно. Многомерные массивы размещаются так, что при переходе к следующему элементу быстрее изменяется последний индекс. Для доступа к элементу массива указываются все его индексы, например, matr[i][j], *(matr[i] + j) или *(*(matr + i ) + j).

    Количество индексов массива называется его размерностью.

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

int  mass1 [][] = { {1, 1},  {0, 2},  {1, 0} };

int  mass2 [3][2] = {1, 1, 0, 2, 1, 0};

Пример. Программа определяет в целочисленной матрице номер строки, которая содержит наибольшее количество нулевых элементов.

#include  <stdio.h>

int  main ()

  {

  const  int  nstr = 4, nstb = 5;  // размеры массива

  int  b[nstr][nstb]; // описание массива

  int  i, j:

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

  for (j = 0; j < nstb; j++)  scanf (“%d”,  &b[i][j]); // ввод массива

  int  istr = -1,  MaxKol  =  0;

  for  (i = 0;  i < nstr; i++)  // просмотр массива по строкам

    {                                  

     int  Kol  = 0;

     for  (j = 0; j < nstb; j++)  if  (b[i][j] == 0)  Kol++;   // Можно if  (!b[i][j]) …

     if  (Kol > MaxKol) {istr = i;  MaxKol = Kol;}

     }

  printf  (“Исходный массив\n”);

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

     {

      for  (j = 0;  j < nstb;  j++)  printf (“%d “,  b[i][j]);

      printf ("\n");

     }

  if  (istr  ==  -1)  printf  ("Нулевых элементов нет");

  else  printf (“Номер строки: %d”,  istr);

  return  0;

  }

4.6.3. Строки

    Строка представляет собой массив символов, заканчивающийся нулем (символом с кодом 0). Нуль-символ при необходимости записывается в виде ‘\0’. По расположению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковым литералом:

char  str[10]  =  “Vasia”;  // выделено 10 элементов с номерами от 0 до 9

                                         // первые элементы – ‘V’,  ‘a’,  ‘s’,  ‘i’,  'а',  '\0'

char  str[] = “Vasia”;   // выделено и заполнено б байтов

Оператор

char  *str = “Vasia”;

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

    Пример. Программа запрашивает пароль не более трех раз.

#include  <stdio.h>

#include <string.h>

int  main ()

  {

  char s[10], passw[]  =  “kuku”;  // passw - эталонный пароль,

                                                      // можно также написать *passw  =  “kuku”;

  int  i,  k  =  0;

  for  (i = 0; !k && i < 3;  i++)

     {

     printf (“\nвведите пароль:\n”);

     gets (s);                                  // Стандартная функция ввода строки с клавиатуры

     if  (strstr (s, passw) )  k = 1;  // “сравнение” строк (указатель вхождения passw в s)

     }

  if  (k)  printf (“\nпароль принят”);  else  printf (“\nпароль не принят”);

  return  0;

  }




1. на тему ОБГРУНТУВАННЯ ПРОЕКТУ ВЕРСТАТОБУДІВНОГО ПІДПРИЄМСТВА НА ОСНОВІ УКРУПНЕНИХ НОРМАТИВІВ
2. Общие сведения о гидратах4 2
3. Любовь и горчица 1968 Не спится няня Игорь Волгин Нет у меня Арины Родионовны И некому мне ск
4. ЛЕТНЕГО РЕБЕНКА Если ребенок окажется в школе в тот момент когда у него недостаточно развита речь мел
5. ТЕМАТИЧЕСКАЯ СТРУКТУРА БАНКА ТЕСТОВЫХ ЗАДАНИЙ СОДЕРЖАНИЕ БАНКА ТЕСТОВЫХ ЗАДАНИЙ Задание {{1}} Термин
6. Диалектика как философская концепция развития
7. Необходимость государственного регулирования общественного развития
8. Теория преподавания истории Украины в школе
9. Организация и управление на предприятиях для студентов технических специальностей заочного факультет
10. Аз ~йы~тау деген ма~ыналы~ ре~к бай~алатын т~ра~ты с~з тіркесі
11. Железорудная промышленность и черная металлургия
12. Компьютерная графика ФОЕНП 3 кредита 2й семестр 2011 ~ 2012 уч
13. Психология теории заговоров
14. е годы проходили под знаменем борьбы за качество продукции в 90е годы лозунгом десятилетия были принципы реи
15. ый год год вызвавший огромное количество споров и разговоров
16. лекция 338-5 История лекция Зайцева Елена Алексеевна 338-5 Подгруппа 1 Испанский язык Сал
17. Правила этикета
18. Великая Отечественная война1
19. МОЛОДЕЖЬ НЕ ТРАТИТ ВРЕМЯ ЗРЯ Городской фестиваль конкурс детского и молодёжного творчества Молодеж.html
20. Введение Сегодня в мире нет ни одной отрасли науки и техники ни одной учебной дисциплины которая разви