Указатели и масиви в С/C++. Видове, инициализация. Структура на паметта. Управление на динамичната памет. Връзка между масиви и указатели.

Указатели и масиви в С/C++. Видове, инициализация. Структура на паметта. Управление на динамичната памет. Връзка между масиви и указатели. Аритметични операции с указатели. Предаване на данни към функция чрез стойност, указател и адрес. Предаване на масиви.

 

1. Масиви - деклариране и използване

Масивите представляват поредици от наредени последователно в паметта елементи от един и същ тип. За достъп до всеки един елемент на масива се използва индекс, който указва поредния номер на елемента.

Дефиниране на масив:
тип  име_на_масив [брой_елементи];

Пример: Нека в една програма на С++ са необходими 5 променливи от целочислен тип. Вместо да декларираме 5 отделни променливи ще декларираме масив от 5 целочислени елемента:  
int A [5];   -  името на масива е А

На схемата се вижда масивът А с последователно разположените елементи, всеки един от които е от тип int.

Важно! Индексът на всеки масив трябва да бъде цяло число, като първият елемент на масива е с индекс 0.

Инициализиране на масив (първи начин):

int A [5] = { 2, 5, 8, 10, 22 };       - елементите на масива получават конкретни стойности. В тоя случай броят на тези стойности трябва да съответства на броя на елементите в масива.

Инициализиране на масив (втори начин):

int A [] = { 2, 5, 8, 10, 22 };       - елементите на масива получават конкретни стойности. В тоя случай не се указва броя на елементите в масива.

Достъп до елементите на масива: Елементът на масива се използва както променлива, като в квадратни скоби се посочва индексът на този елемент в масива:

име_на_масив [индекс];

Пример: Нека вторият елемент на масива А получи стойност 10 (Забележка: вторият елемент има индекс 1):

A [1] = 10;

Пример: Да се изведе на екрана стойността на втория елемент на масива А:

cout << A [1];

Пример: Променливата х да получи удвоената стойност на втория елемент на масива А:

x = A [1] * 2 ;

Важно! При достъп до елемент на масив индексът не трябва да излиза извън границите на масива.

Пример: A [5] - ще върне неопределена стойност, тъй като индекс 5 надхвърля границата на масива (4).

 

Пример: Да се направи програма на С++, която въвежда масив М с 10 елемента и намира тяхната сума.

#include "stdafx.h"
#include "iostream.h"

main()
{
    int M [10];
    int sum=0;
    for (int i=0; i<10; i++ )
    {
        cout<<"Element M[" << i << " ] = ";
        cin>>M[i];
        sum=sum+M[i];
    }
    cout << "Sumata e "<< sum << "\n";
}
 

2. Многомерни масиви - представят се като масиви от масиви

а) двумерен масив (таблица)

Пример: Да се дефинира матрица М (3х5):

int M [3] [5] ;

  0 1 2 3 4
0          
1          
2          

В случая M е двумерен масив, който може да се представи във вид на таблица. За достъп до елемент от този масив се използват два индекса - един, който указва номера на реда и друг, който указва номера на стълба. Например M [1] [2] е елементът на втория ред и на третия стълб.

Важно! Броят на елементите на един многомерен масив е произведение от броя на елементите за всяко измерение. Например масивът М [3] [5]  има общо 15 елемента.
 

б) триизмерен масив

Пример: Да се дефинира масивът M (3x5x2)

int M [3] [5] [2] ;

в) многомерен масив

int M [3] [5] [2] [10] [12] [4];  - шестмерен масив

 

Пример: Да се въведе матрица М (2х3) и се намери сумата от всичките й елементи.

#include "stdafx.h"
#include "iostream.h"

main()
{
    int M [2][3];
    int sum=0;
    for (int i=0; i<2; i++ )
        for (int j=0; j<3; j++ )
            {
                cout<<"Element M[" << i << "][" << j << "]=";
                cin>>M[i][j];
                sum=sum+M[i][j];
            }
    cout << "Sumata e "<< sum << "\n";
}

 

3. Указатели

а) определение и дефиниране

Всеки обект на езика С++ (променлива, константа, елемент на масив и т.н.) се съхранява в определени клетки от паметта на компютъра. А всяка клетка от паметта има определен номер, който се нарича адрес. Променлива, чиято стойност е адрес от паметта на компютъра, се нарича указател. Стойността на указателя посочва (указва) местоположението на дадена променлива и позволява косвен достъп до стойността на тази променлива, за разлика от директния достъп с помощта на нейното име.

Дефиниране на указател:

тип  * име_на_указател;

Примери:

int  * p1;        - указател към променлива от тип int
float * p2;      - указател към променлива от тип float
double * z;     - указател към променлива от тип double
char * s;         - указател към променлива от тип char

 

Адреси в паметта 100 101 102 103 104
Стойности на клетките 500 123 0 23 2

При p1=100  - указателят сочи към клетка от паметта с адрес 100. Стойността на тази клетка съгласно схемата е 500.

 

б) извличане на адрес - операторът &

При деклариране на променливи адресите им се определят от операционната система. Достъп до адреса на една променлива може да се осъществи с оператора "&".
Операторът за извличане на адрес се прилага пред променливата и връща адреса на тази променлива.
Например: &x означава адреса на променливата х.
Валидно е следното присвояване:
p = &x;  - в случая променливата р (указател) получава стойността на х.

в) извличане на стойност - операторът * (да не се бърка с оператора за умножение!)

Използвайки указател може да се извлече стойността, към която този указател сочи. Това става с оператора "*".
Пример:
a = *p;   - а получава стойността, указана от р

Пример:   // използване на указатели

#include "stdafx.h"
#include "iostream.h"

main()
{
    int value1 = 5, value2 = 15;
    int *p;
    p = &value1;      // p получава адреса на value1
    *p = 10;             // value1 получава стойност 10
    p = &value2;      // p получава адреса на value2
    *p = 20;             // value2 получава стойност 10
    cout << "value1= "<< value1 << "\n"<< "value2="<<value2;
}

г) връзка между указатели и масиви

При дефиниране на един масив името му всъщност представлява указател към първия елемент на масива.
Например:
    int A [20];
    int *p;
    p = A;       - това присвояване е валидно, тъй като масивът А е всъщност указател

Получавайки указателя на първия елемент на масива А, можем да извършваме следните операции:

int main( )
{
    int A [5];
    int *p;
    p = A;
    *p = 10;
    p++;
    *p = 20;
    p = &A[2];
    *p = 30;
    p = A + 3;
    *p = 40;
    p = A;
    *(p+4) = 50;
    for (int n=0; n<5; n++)
        cout << A [n] << ", ";
}

Следните два израза са еквивалентни:
  
A [5] = 0;
  * (A+5) = 0;

д) аритметични операции с указатели

Нека имаме следните 3 указателя:

char *mychar;           - типът char заема 1 байт
short *myshort;         - типът short заема 2 байтa
long *mylong;            - типът long заема 4 байтa

и нека те да сочат към адреси от паметта съответно 1000, 2000, 3000

Следните изрази:
    mychar++;
   
myshort++;
    mylong++;
задават следните стойности за указателите:
   
mychar = 1001
   
myshort = 2002
    mylong = 3004
Това е така, защото увеличавайки стойността на един указател с 1 означава той да сочи към следващия елемент от типа, с който е бил дефиниран. С други думи към указателя се добавя размерът в байтове на указания тип.

 

За определяне броя байтове, които един тип може да приема, се използва операторът sizeof ( тип )
Пример:
       
sizeof (char)  -  връща стойност 1
       
sizeof (short)  -  връща стойност 2
       
sizeof (long)  -  връща стойност 4
 

4. Управление на динамичната памет

Статична памет - памет, която се заделя за променливи, масиви и други преди стратиране на изпълнение на програмата.
Динамична памет - памет, която се заделя за променливи, масиви и други по време на изпълнение на програмата.

a) оператор new  - заделя динамична памет с нужното количество байтове

указател = new тип [ брой_елементи ]

Пример:
   
int * A;
    A = new int [5];    -  заделя се памет от 5 елемента от тип int.

б) оператор delete  - освобождаване на дамична памет

delete  указател

Пример:
   
delete A;     - освобождава паметта, заделена преди това с оператора new

#include "stdafx.h"
#include "iostream.h"

main()
{
        int * A;
        int n,i;
        cout << "Колко числа ще въвеждате? ";
        cin >> n;
        A = new int [n];        // заделя памет
        for (i=0; i<n; i++)
            cin >> A [i];
        for (i=0; i<n; i++)
            cout << A [i] << ", ";
        delete A;            // освобождава памет
}

 

5. Предаване на масиви като параметри към функция

В С++ не е възможно да се предаде по стойност цял масив, но може да се предаде неговия адрес. За тази цел при дефиниране на функцията се определя типа на масива по следния начин:

тип  име_на_функция (тип име_на_масив [ ] );

Пример:

#include "stdafx.h"
#include "iostream.h"
 

void print_masiv ( int A[ ] , int L)
{
    for (int i=0; i<=L; i++)
        cout << A[i] << ", ";
}

main ( )
{
      int Masiv1 [ ] = { 2, 4, 2, 5, 6 };
      int Masiv2 [ ] = { 1, 2, 3, 4, 5, 10, 34, 45 };
      print_masiv ( Masiv1, 5);
      print_masiv ( Masiv2, 8);
}