Будь умным!


У вас вопросы?
У нас ответы:) 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. тема менеджмента качества 4
2. Лабораторная работа 3 по дисциплине Локальные системы управления Проектирование цифровых регулятор
3. Статья 48 Ответственность за нарушение законодательства Республики Беларусь о средствах массовой информаци
4. 60 годам Из документов следует что Дуччо был очень независимым и темпераментным человеком а за изысканнос
5. шерстит всю прессу договаривается сам смотрит квартиры
6. 60х годов 20века Со второй половины 1953 г
7. Движение искусств и ремесел
8. всё что не делается всё к лучшему и тд И как есть эти фразы не внушают внутреннего успокоения по крайней м
9. Партии Узбекистана
10. а. В ткани лёгкого располагается очаг белесоватосерого цвета округлой формы диаметром до 1 см каменистой п
11. Журналістська етика та українське телебачення
12. Кинетическая энергия ~ это энергия определяемая скоростью движения тела и зависящая от его массы
13. тематичної моделі Dмоделі та обчислення її параметрів за допомогою пограмного продукту MtLb
14. На тему- Разложение функции в ряд Фурье Выполнил- Макаричев
15. Методические рекомендации для практических занятий Тема- Коллоквиум 3
16. МолокозаводИволга 2
17. методигеским объединением по специальностям педаюгигеского образования в кагестве угебника для студен
18.  Теоретические и правовые основы банковского кредитования 1
19. Стратегический контроль в деятельности организации
20. Вакцинопрофілактика є специфічною профілактикою спрямованою проти певних інфекційних захворювань