4309

Переменная, хранящая адрес некоторого данного. Указатели

Контрольная

Информатика, кибернетика и программирование

Указатель – это переменная, хранящая адрес некоторого данного (объекта). Память компьютера делится на 8 -битовые байты. Каждый байт пронумерован, нумерация байт начинается с нуля. Номер байта называют адресом об адресе говорят, что он...

Русский

2012-11-16

121 KB

3 чел.

Указатель – это переменная, хранящая адрес некоторого данного (объекта).

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

Таким образом, указатель является просто адресом байта памяти компьютера.

Использование указателей в программах на С позволяет:

  1.  Упростить работу с массивами;
  2.  Распределять память под данные динамически, то есть в процессе исполнения программы;
  3.  Выполнить запись и чтение данных в любом месте памяти.

Значение указателя сообщает о том, где размещен объект.

Объявление указателей

Указатели, как и другие переменные,  должны  быть объявлены в программе. При объявлении указателя перед его именем ставится *. В общем случае объявление переменной – указателя имеет вид:

класс памяти  тип  * имя ;

  •  
  •  означает “указатель на “.

Например,

                         int  * iptr;

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

Другие примеры объявления указателей:

static   float  *f;

extern double *z;

extern char *ch;

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

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

Для получения адреса какого – либо объекта используется операция  &. Эта операция позволяет присваивать указателям адреса объектов. Например:

int a, *aptr;

char c, *cp;

aptr=&a;

cp=&c;

Доступ к объекту через указатель

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

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

Пример 1. Программа, выводящая значение указателя и значение объекта, на который он указывает.

 #include <stdio.h>


 void main (void)
 {
   int x, *px;

   px=&x;

   x=35;


   
printf("адрес x: %p\n",&x);
   printf("Значение указателя: %p\n",px);


   printf("Значение х: %d\n",x);
    printf("Значение, адресуемое указателем: %d\n",*px);
    }

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

адрес x:  FFF4

Значение указателя:     FFF4

Значение х:   35

Значение, адресуемое указателем:  35

Связь между переменной х и указателем px схематично показана на рисунке1.

Переменная-указатель px                   Переменная х

Унарная операция разадресации * может быть использована в левой части оператора присваивания, чтобы какие-то данные были запомнены в области памяти, на которую указывает указатель. Например:

float y,  *py =&y;

*py= -1.2;  

Здесь *py= -1.2 означает присвоение значения –1.2 по адресу, хранящемуся в указателе, то есть, то же, что и

                     y = -1.2;

Инициализация указателей

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

Примеры:

    Правильно                                                        Не правильно

Ошибка заключена в том, что указателю на данное целого типа присваивается адре переменной вещественного типа, а это не допустимо.

Указатель на неопределенный тип

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

 void * имя;

Например,  void *ptr;

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

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

  (тип *)   указатель

Пример 2. Программа, в которой используется указатель на неопределенный тип

#include <stdio.h>

 void main (void)

 {

   int a =123;

   double d= 3.45678;

// объвление указателя на неопределенный тип

   void *vp;

//указатель на неопределенный тип инициализирован адресом объекта целого типа

   vp=&a;

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

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

   printf("a=%d \n", *((int *)vp));

//указатель на неопределенный тип инициализирован адресом объекта  типа double

vp=&d;

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

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

      printf("d=%lf \n", *((double *)vp));

    }

Эта программа выведет следующее:

a=123

d=3.456780

Следует отметить, что в программе, показанной в примере 2, ни одна операция не может быть выполнена над указателем vp   до тех пор, пока явно не будет задан тип, на который он указывет!

Выражения с указателями

Можно присваивать один указатель другому, но эта операция имеет смысл только в том случае, если оба указателя адресуют один и тот же тип, например

{ int i=123 *pi1=&i,  *pi2;

// присваиваем указателю pi2 значение указателя pi1

 pi2=pi1;

//выведем значения  указателей:

printf(“ %p   %p”,  pi1, pi2);

}

В результате мы получим одно и то же значение адреса.

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

int  x=5, y, *px=&x;

y = *px + 5; // y получит значение 10

Приоритет у унарной  операции  разадресации * выше, чем у бинарных арифметических операций, поэтому при вычислении выражения  *px + 5 вначале будет извлечено содержимое по адресу, хранящемуся в р ( то есть получено значение 5), а затем будет выполнена бинарная операция сложения.

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

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

++   инкремента

- -    декремента

+,  -  сложения и вычитания

Если к указателю применяются операции ++ или --, то указатель увеличивается или уменьшается на размер объекта, который он адресует:

тип  *ptr;

ptr ++ = значение ptr + sizeof (тип)

ptr -- = значение ptr - sizeof (тип)

Например:

int i, *pi = &i;

float a, *pa =&a;

pi++;   //значение указателя pi увеличивается на 2 байта, так как он адресует объект

          //типа int,  указатель сдвигается вправо на 2 байта

pa++; // значение указателя pa увеличивается на 4 байта, так как он адресует объект

          //типа float,  указатель сдвигается вправо на 4 байта

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

тип  *ptr;

int n;

ptr + n = значение ptr + n*размер типа

Например,

float  a, *pa = &a;

pa = pa+3; // значение указателя pa будет увеличено на 12 байт (сдвиг указателя вправо)

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

тип  *ptr;

int n;

ptr - n = значение ptr - n*размер типа

Например:

int i, *pi = &i;

pi=pi-5; // уменьшение значения указателя на 10 байт (сдвиг указателя влево)

Пример 3.Использование указателей в выражениях

#include <stdio.h>

void main (void)

{

 int x=5,y,*px=&x;  //указатель px инициализирован адресом переменной x


 y=*px+5;  
 printf("y=%d значение указателя=%p\n",y,px);


 y=*px++;   //изменение значения указателя после его использования
  printf("y=%d значение указателя=%p\n",y,px);


  px=&x;
  y=(*px)++;   //изменение содержимого по адресу, хранящемуся в px


   printf("y=%d значение указателя=%p значение, адресуемое указателем *px= %d\n",y,px,*px);


  y=++*px;   //изменение содержимого по адресу px на единицу
  printf("y=%d значение указателя=%p\n",y,px);

  }

Результаты работы этой программы имеют вид:


y=10 значение указателя=1561:1000

y=5 значение указателя=1561:1002

y=5 значение указателя=1561:1000 значение, адресуемое     указателем*px= 6

y=7 значение указателя=1561:1000

УКАЗАТЕЛИ И МАССИВЫ

Между указателями и массивами существует прямая связь. Когда объявляется массив, например:

int arr [5];

то идентификатор массива arr определяется как константный указатель на первый (с индексом 0) элемент массива. Это означает, что имя массива содержит адрес элемента массива с индексом 0:

      arr  = &arr[0];

Так как идентификатор массива содержит адрес, то можно, например,  записать:

int   arr[ 5];    //объявление массива целых

int   *parr; //объявление указателя на данное целого типа

parr = arr;  // присваивавание указателю адреса первого элемента массива, то же, что

// и parr = &arr[0];

Связь между именем массива arr и указателем parr можно изобразить следующим образом:

arr[0]

arr[1]

arr[2]

arr[3]

arr[4]

Адрес элемента массива с индексом i может быть записан как

&arr[i],  что эквивалентно записи   parr+i

Значение элемента массива с индексом i может быть записано как

arr[i],  что эквивалентно записи     *(parr+i) или *(arr+i)

Все индексные выражения вида arr[i] компилятор преобразует к адресному представлению в соответствии с правилами выполнения операции сложения указателя с целой величиной:

arr[i]=*(arr+i) =* (arr  + i* sizeof(int))

ВАЖНО.

Указатель – это переменная, и ее значение можно изменять в программе, например, можно записать:

parr++;

Операция parr++ означает сдвиг указателя на следующий элемент массива arr++, то есть на 2 байта. Если, например, до выполнения операции parr++ указатель был установлен  на первый элемент массива, то в результате выполнения этой операции он окажется установленным на второй элемент:

arr[0]

arr[1]

arr[2]

arr[3]

arr[4]

Имя массива – константа. Значение адреса, которое хранит имя массива, изменять нельзя, поэтому ошибочной является следующая запись:

arr++;

Пример 4. Программа, в которой демонстрируется использование указателей на массив.

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <time.h>

#define N 100   //максимально допустимое количество элементов в массиве

int main(void)

{

 int a[N];  /* резервирование памяти под N элементов массива */

 int i; //переменная, управляющая циклом

 int *pa=a; //объявление указателя на данное целого типа

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

 clrscr();

 randomize();

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

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

    *(pa+i) = random(101)-50;  // присваивание элементу массива значения случайного

                          // числа из диапазона  от -50 до 50

//цикл вывода элементов  массива

  printf("\n\n      Массив выведен по 10 элементов в строке \n\n");

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

{   

printf("%6d ", *(pa+i)); // вывод массива

  if(i%10==9)

  printf("\n");

  }

  getch();

 return 0;

}

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

#include <stdio.h>

#include <conio.h>

void  main(void)

{

 int i;

int a[10];  //объявление массива целых из десяти элементов

 int   *pa  = a; //объявление указателя на данное целого типа и инициализация указателя

                      // адресом массива

 clrscr();

//цикл ввода элементов массива в диалоговом режиме

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

  {

    printf("\nВведите элемент массива с индексом %d  ",i);

    scanf("%d", pa+i);   //функция scanf получает адрес элемента массива

  }

  printf("\n\n   Вы ввели  массив  \n\n");

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

  printf("%4d ", *(pa+i)); // вывод массива

  getch();

}

Пример 5. Программа, в которой используются указатели на строки.

#include <stdio.h>

void main(void)

{

char s[]="строка как массив";

char *ps=s; //указатель ps инициализирован значением адреса строки s

char *ps1,*ps2,*ps3; //объявление указателей на строки

//присвоение указателю ps1 адреса строковой константы

ps1="В языке  си в одну\n"

 "строку можно записать\n"

 "целое стихотворение\n";

//присвоение указателю ps2 адреса строковой константы

ps2="предложение не будет\

перенесено на следующую строку";

//присвоение указателю ps3 адреса строковой константы

ps3="одна"

" строка";

//цикл вычисления длины строки s, адрес которой хранит указатель ps

//  выполнять, пока не найден конец строки

 while(*ps!='\0')

 ps++;

 printf("длина строки= %d\n",ps-s);

 puts(ps1);

 puts(ps2);

 puts(ps3);

 }

УКАЗАТЕЛИ И ДИНАМИЧЕСКИЕ ПЕРЕМЕННЫЕ

Память под переменные можно распределять в процессе исполнения программы.

Переменные, память под которые выделяется в процессе исполнения программы, называются динамическими. Они запоминаются в блоках памяти переменного размера, известных как “куча”. Принятое распределение памяти в показано на рисунке 1.

          Старшие адреса

         Младшие адреса

                                           Рис. 1.Распределение памяти в С

Динамические переменные удобно использовать, если в программе нужно работать с массивом, размер которого заранее не известен.

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

Чтобы воспользоваться функцией malloc(), необходимо объявить указатель, например:

float  *fp;

Затем вызовите функцию malloc(), задавая в круглых скобках число байтов для выделения из кучи, например:

fp  = (float *) malloc(16);  

или:

fp  = (float *) malloc( 4*sizeof (float));  

В этом примере выделено 16 байт под размещение данных типа float. Функция malloc() возвращает указатель на тип void. Выражение (float *) позволяет выполнить приведение  типа указателя к требуемому – float, так как перед применением указателя на тип void в программе требуется явное приведение его типа к определенному. Возвращаемое функцией malloc() значение адреса присваивается   укзателю fр.

Функция calloc() требует передачи ей двух аргументов – количества объектов, под которые запрашивается память и размер одного объекта, например:

long  *p;

p= (long *) calloc(10, sizeof (long)) ;

то же, что и:

p= (long *) calloc(10, 4)) ;

В приведенных примерах распределяется 40 байт памяти для размещения объектов типа long.

Кроме выделения памяти, функция calloc() устанавливает каждый байт выделенной памяти равным нулю.  Функцию calloc()  можно рекомендовать использовать для инициализации блоков памяти.

Нехватка памяти

Как функция malloc(), так и функция calloc() в случае ошибки (например, в куче не хватает объема запрашиваемой памяти)  возвращают NULL -  указатель, значение которого нельзя использовать в программе. Это обстоятельство можно использовать, чтобы обработать ошибку распределения памяти, например:

float  *fp;

fp  = (float *) malloc( 4*sizeof (float));  

          if (fp==NULL)

          printf(“Ошибка распределения памяти!”);

Удаление памяти в куче

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

free (p);

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

#include <stdio.h>

#include <conio.h>

#include <alloc.h>  // для функции malloc()

int main(void)

{

  int n;   //переменная, в которую будет введена размерность массива

 int i;      //управляющая переменная цикла

 int *pa;  //объявление указателя на данное целого типа

 clrscr();

  puts ("Введите количество элементов массива");

   scanf("%d",&n);

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

// инициализация указателя pa адресом, который возвращает функция malloc()

     pa=(int *)malloc(n*sizeof(int));

//ввод элементов массива в диалоговом режиме

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

  {

    printf("\nВведите элемент массива с индексом %d  ",i);

    scanf("%d",pa+i);   //функция scanf получает адрес элем. массива

  }

  printf("\n\n      Массив выведен по 10 элементов в строке \n\n");

//вывод элементов массива

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

{   printf("%6d ", *(pa+i)); // вывод массива

  if(i%10==9)

  printf("\n");

  }

  getch();

 return 0;    }

Пример 2. Обьединение двух строк к концу строки s дописывается строка t, вычисляется длина полученной строки.  Память под строки распределяется динамически

#include <alloc.h>

#include <stdio.h>

void main(void)

{ char *t,*s;   //объявление указателей на данные типа char

  int i=0;

// динамическое распределение памяти под строки

s=(char *)malloc(160);

t=(char *)malloc(80);

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

  gets(s);

  puts("первая строка");

  puts(s);

   puts("Bведите вторую строку ");

   gets(t);

   puts("вторая строка");

  puts(t);

// выполнять, пока не найден конец первой строки

 while(*s!='\0')

  {

  i++;

  s++;  //сдвигаем указатель в конец первой строки

  }

// выполнять, пока не найден конец второй строки

while(*t!='\0')

 {

   *s++=*t++;  //дописываем символы второй строки к первой
                    i++;

 }


          *s='\0';  //дописываем нулевой байт к концу результирующей строки

  printf("длина результирующей строки i=%d\n",i);

// сдвигаем указатель в начало результирующей строки

   while(--i>=0)

    {s--;

}

    puts("результат:");

puts(s);

  }

УКАЗАТЕЛИ И МНОГОМЕРНЫЕ МАССИВЫ

Многомерный массив в С – это массив массивов. Например:

int mas[ m ][ n ][ k ] ;

Можно сказать , что объявлен одномерный массив из  m элементов, каждый элемент которого является двумерным массивом, содержащим n*k  элементов.  В памяти это массив будет расположен так:

                                                              m массивов n*k

Двумерный массив n*k, в свою очередь, расположен в памяти по строкам:

                              n строк (одномерных массивов по k элементов в каждом)

  первая строка   вторая строка                                                                         n– ая строка

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

int a[ 5 ] [ 4 ];

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

Имя массива а – это указатель на одномерный  массив целых из пяти элементов, каждый из которых содержит адрес соответствующей строки массива. Значение   а  - адрес первой строки массива.

Так как имя двумерного массива  является указателем на массив, то его можно проиндексировать:

a[0] – это адрес строки с индексом 0,

a[1] – это адрес строки с индексом 1,

a[2] – это адрес строки с индексом 2,

a[3] – это адрес строки с индексом 3,

a[4] – это адрес строки с индексом 4.

Это означает, что каждый элемент массива а указывает на соответствующую строку, что схематично можно отобразить так:

                             

        a[0]

        a[1]

         a[2]

         a[3]

       

         a[4]

         

Так как a[0] – это адрес строки с индексом 0, а a[i] – это адрес строки с индексом i, то можно записать:

a [0] = & a [0] [0]; …. a[i] = & a[i] [0];

Но а[i] эквивалентно      *(a + i)  - это адрес строки с индексом i, он смещен от адреса начала на

          i  * 4*sizeof (int)  байт , ( 4 –это количество элементов в строке, или столбцов матрицы).

Для получения адреса элемента  двумерного массива, находящегося в строке с индексом i  и столбце с индексом j  следует к адресу строки матрицы добавить смещение элемента по строке:

                     *(a + i) + j  - это то же, что и & a [ i ] [ j ]

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

                      * (*(a + i) + j )  - это то же, что и  a [ i ] [ j ]

В общем случае вычисление смещения элемента двумерного массива, объявленного как

a [ m ] [ n ] следует выполнять  по фомуле:

 n * i + j, где

n – количество столбцов массива (или элементов в строке);

i – текущий индекс строки;

j– текущий индекс столбца.

Использование указателей вместо двумерного массива

Так как в памяти машины двумерный массив представлен как одномерный, то можно объвить указатель на массив и его размерности, например:

int *pa, int m=5,  n=4 ;

Пусть m – количество строк в массиве, а n – количество столбцов. Память под массив можно распределить динамически, например, так:

pa = (int ) malloc ( m *n *sizeof (int));

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

                            pa +i*m +j;

Значение элемента  массива, находящегося в строке с индексом i  и столбце с индексом j  можно вычислить так:

                            * (pa +i*m +j );

Пример 1. Вставка в матрицу столбца и заполнение его единицами

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <time.h>

void main (void)

{

int *a,i,j,n,m,num;//n- кол. строк, m- столбцов, num - номер вставляемого стол.

clrscr();

puts("Введите кол-во строк и столбцов\n" );

scanf("%d %d",&n,&m);

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

//распределяем память на один столбец больше, чем заданное количество столбцов

a=(int *)malloc(n*(m+1)*sizeof(int));

// обработка ошибки распределения памяти.

 if(a==NULL)

{ puts("Ошибка выделения памяти");

  exit(-1) ; //выход из программы,  если возникла ошибка

  }

  // заполнение матрицы случайными двузначными числами от 0 до 99

  randomize();

  m++;

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

    for(j=0;j<m-1;j++)

 *(a+i*m+j)=rand()%100;

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

   puts("ИСХОДНАЯ МАТРИЦА:\n");

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

  {  for(j=0;j<m-1;j++)

  printf("%4d", *(a+i*m+j));

     puts("");

 }

puts("Введите номер добавляемого в матрицу столбца \n");

scanf("%d",&num);

num--;

//Все столбцы матрицы, номер которых больше, чем номер добавляемого

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

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

 for(j=m-1;j>num;j--)

 {

   *(a+i*m+j)=*(a+i*m+(j-1));

 }

//заполняем добавляемый столбец единичными значениями

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

    *(a+i*m+num)=1 ;

puts("МАТРИЦА С ЕДИНИЧНЫМ СТОЛБЦОМ: \n");

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

  {

            for(j=0;j<m;j++)

  printf("%4d", *(a+i*m+j));

           puts("");

    }

 getch();

}

СВОБОДНЫЕ МАССИВЫ И УКАЗАТЕЛИ

Термин “свободный ”  относят к двумерным массивам. Свободный массив – это массив, в котором длины его строк (то есть количество элементов в строке)  могут быть различными. Свободные массивы могут быть любого типа. Для работы со свободными массивами используют массивы указателей, содержащие количество элементов, равное количеству строк свободного массива. Оперативная память для каждой строки может выделяться либо статически, либо динамически. Например:

int *a[100];

Объявлен массив указателей ( в нем 100 элементов) на данные типа int. Память выделяется для 100 указателей , по одному указателю на каждую из 100 строк свободного массива. Память для строк массива не выделена. Это можно сделать динамически. Например, если m -  количество элементов в строке с индексом i, то память для этой строки может быть выделена так:

a[i] = (int *) malloc(m*sizeof )int) );

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

char  *errors[]={“Невозможно открыть файл”,

                          “Ошибка распределения памяти”,

                          “Системная ошибка”

                          };

Таким объявлением память распределена:

  1.  Для одномерного массива указателей. Количество элементов этого массива определяется при его инициализации – по количеству символьных строк. Каждый указатель содержит адрес начала соответствующей символьной строки. Для нашего примера память выделена под массив указателей, в котором 3 элемента – по количеству строк свободного массива.
  2.  Для двумерного символьного массива со строками разной длины:

                                         

errors[ 0 ]

Н

е

в

о

з

м

о

ж

н

о

 

о

т

к

р

ы

т

ь

ф

а

й

л

\0

errors[ 1 ]

О

ш

и

б

к

а

р

а

с

п

р

е

д

е

л

е

н

и

я

п

а

м

я

т

и

\0

errors[ 2 ]

С

и

с

т

е

м

н

а

я

о

ш

и

б

к

а

\0

В памяти  строки этого массива будут располагаться друг за другом, каждая строка займет минимально возможную память:

Здесь нет неиспользуемых байт, которые появились бы, если бы был объявлен двумерный массив:

char  errors[3][28]={“Невозможно открыть файл”,

                               “Ошибка распределения памяти”,

                               “Системная ошибка”

                                };

При таком объявлении каждая строка занимает 28 байт – по размеру самой длинной строки- ‘Ошибка распределения памяти”,, поэтому при размещении строки, например ”Системная ошибка” будет занято всего 17 байт, а 11 байт окажутся не использованными.

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

  1.  каждая строка занимает минимально возможную память;
  2.  появляется возможность манипулировать не с самими строками, а только с их указателями, что дает значительный выигрыш в скорости работы программы при решении , например, задач, сортировки строк.

Пример. Сортировка строк в алфавитном порядке. Используется алгоритм пузырьковой сортировки.

#include <stdio.h>

#include <string.h>

#define SIZE 81  //предел длины строки, включая ‘\0’

#define LIM 20  //максимальное количество сортируемых строк

#define  STOP " " //признак конца ввода – ввод пустой строки

void main(void)

{

// массив для сохранения результатов ввода строк в памяти

unsigned char input[LIM][SIZE];

// массив указателей на строки

unsigned  char *ptstr[LIM];

// указатель на переменную, используемую в процессе перестановки

 unsigned  char *temp;

// управляющие переменные циклов

 int i,jk;

 int ct=0; //счетчик введенных строк

printf ("Введите не более %d строк, которые нужно отсортировать",LIM);

 puts("Окончание ввода - пустая строка");

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

  while ( ct < LIM && gets (input[ct]) !=NULL && input[ct][0]!='\0')

  {

//установка указателей на строки

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

  ptstr[ct]=input[ct];

  ct++;

  }

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

   for(i=0; i<ct-1; i++)

     for(j=i+1; j<ct; j++)

     {

      if(strcmp(ptstr[i],ptstr[j]) >0)

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

         temp= ptstr[i];

         ptstr[i]=ptstr[j];

         ptstr[j]=temp;

         }

         }

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

      puts("Отсортированный массив");

        for(k=0; k<ct; k++)

       puts(ptstr[k]);

       }  


 

А также другие работы, которые могут Вас заинтересовать

39031. Базовые технологии доступа к БД в Borland C++ Builder. Лабораторная работа 125.5 KB
  Указания к выполнению лабораторной работы Ранее уже говорилось что наборы данных представляют собой группы записей переданных из базы данных в приложения для просмотра и редактирования. Каждый набор данных инкапсулирован в специальном компоненте доступа к данным. Основные свойства и методы базового класса наборов данных TDtSet уже были рассмотрены нами ранее.
39032. Современные технологии доступа к БД 117 KB
  Технология ODBC все еще признается в качестве отраслевого стандарта доступа к базам данных однако также не развивается поэтому разработчики программного обеспечения все чаще обращают свое внимание на современные и возможно более эффективные технологии. С конца 90ых годов и до настоящего времени фирма Microsoft пытается создать универсальную платформу доступа к разнородным хранилищам данных для семейства своих операционных систем Windows. Источниками данных в данном случае могут являться не только SQLсервера Баз Данных но и иные...
39033. Технологии разработки распределенных информационных систем 121.5 KB
  Получить представление о компонентной объектной модели COM. Развитием трехуровневой архитектуры является так называемая многоуровневая nуровневая организация вычислений Multitier computing когда информационная система состоит из большого количества удаленных друг от друга объектов серверов каждый из которых может предоставлять другим объектам клиентам разнообразные информационные услуги. В настоящее время наиболее популярны следующие технологии организации распределенных информационных систем: Технология COM и ее развитие...
39034. Разработка технического задания на разработку ИС 77.5 KB
  Техническое задание ТЗ – это завершающий предпроектную стадию документ который содержит цели и обоснование проектирования а также определяет основные требования к ИС и исходные данные необходимые при разработке. В настоящее время при составлении технического задания обычно руководствуются требованиями следующих ГОСТов: 34.60289 Техническое задание на создание автоматизированной системы – описывает состав и содержание ТЗ которые распространяются на автоматизированную информационную систему в целом в том числе: общесистемные...
39035. Базовые технологии доступа к БД в Borland C++ Builder и их принципы 156 KB
  Указания к выполнению лабораторной работы Современные информационные системы не могут существовать без Баз Данных. По этой причине современные средства разработки приложений должны обеспечивать программиста средствами которые бы: Обеспечивали универсальный механизм доступа к базам данных построенных с использованием различных СУБД; Обеспечивали приемлемый уровень эффективности; Позволяли быстро разрабатывать полнофункциональные приложения для работы с БД любого размера. В Borlnd C Builder предлагается большое количество компонентов...
39036. Использование Borland C++ Builder в качестве средства быстрой разработки приложений 46 KB
  Компоненты для изучения: BitBtn StringGrid вкладка dditionl DteTimePicker вкладка Win32. Компоненты для изучения: StringGrid вкладка dditionl PgeControl вкладка Win32 Timer вкладка System. Компоненты для изучения: ColorBox вкладка dditionl RichEdit вкладка Win32 Timer вкладка System. Компоненты для изучения: MskEdit вкладка dditionl PgeControl вкладка Win32 Timer вкладка System.
39037. Разработка многооконных приложений с использованием Borland C++ Builder 88.5 KB
  Для добавления новой формы в проект приложения необходимо вызвать команду File New Form главного меню или нажать соответствующую кнопку на панели инструментов. Для каждой формы приложения вызывается метод CreteForm создать форму объекта приложения ppliction. Главная форма автоматически отображается на экране при запуске приложения в то время как остальные формы которые иногда называют вторичными будут созданы но останутся невидимыми для пользователя до тех пор пока не будут явно...
39038. Количество информации. Мера Хартли и мера Шеннона 80.5 KB
  Рассмотрение предложенных способов измерения количества информации удобно начать с примера. Тем не менее только на основе априорной информации мы не можем точно сказать какое именно число очков выпало в результате конкретного подбрасывания. С поступлением новой информации о результате подбрасывания эта неопределенность может уменьшаться.
39039. Понятие информационной системы 98.5 KB
  Сейчас пришло время дать этому понятию более точное определение: Информационная система ИС это взаимосвязанная совокупность средств методов и персонала используемых для хранения обработки и выдачи информации в интересах достижения поставленной цели. Как видно из определения информационные системы обеспечивают сбор хранение обработку поиск выдачу информации необходимой в процессе принятия решений задач из любой области. Основными элементами работы информационных систем являются: ввод новой информации и выдача текущей информации по...