1

Тема: Заміна індексів покажчиками

Вітаю форумчан!
Маю наступний код із книги КіР

#include <stdio.h>

int day_of_year(int year, int month, int day, int leap);
void month_day(int year, int yearday, int leap);

static char daytab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

int main(int argc, char *argv[])
{
    int c, leap, y, m, d, yearday; 
    // c - correct flag
    // leap - leap year flag
    // y - year, m - month, d - day
    input:
    scanf("%d %d %d", &y, &m, &d);
    c = 0;
    if(y < 0){
        printf("Year couldn't be negative\n");
        c = 1;
    }
    if(m <= 0 || m >= 12){
        printf("Month got to be 1-12\n");
        c = 1;
    }
    leap = y%4 == 0 && y%100 != 0 || y%400 == 0;
    if(d > daytab[leap][m] || d <= 0){
        printf("Day got to be more than 0 and not exceed days per month maximum\n");
        c = 1;
    }
    if(c == 1)
        goto input;
    yearday = day_of_year(y, m, d, leap);
    month_day(y, yearday, leap);
    return 0;
}

int day_of_year(int year, int month, int day, int leap){
    int i;
    printf("%d.", day);
    for(i = 1; i < month; i++)
        day += daytab[leap][i];
    printf("%d is the %d's day of %d year\n", month, day, year);
    return day;
}

void month_day(int year, int yearday, int leap){
    int i, m, d;
    
    for(i = 1; yearday > daytab[leap][i]; i++)
        yearday -= daytab[leap][i];
    m = i;
    d = yearday;
    printf("Full date: %d/%d/%d\n", m, d, year);
}

Завдання полягає в тому, щоб запис

daytab[leap][i]

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

#include <stdio.h>

int day_of_year(int year, int month, int day, int leap);
void month_day(int year, int yearday, int leap);

static char daytab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

int main(int argc, char *argv[])
{
    int c, leap, y, m, d, yearday; 
    // c - correct flag
    // leap - leap year flag
    // y - year, m - month, d - day
    input:
    scanf("%d %d %d", &y, &m, &d);
    c = 0;
    if(y < 0){
        printf("Year couldn't be negative\n");
        c = 1;
    }
    if(m <= 0 || m >= 12){
        printf("Month got to be 1-12\n");
        c = 1;
    }
    leap = y%4 == 0 && y%100 != 0 || y%400 == 0;
    if(d > (int) daytab[leap][m] || d <= 0){
        printf("Day got to be more than 0 and not exceed days per month maximum\n");
        c = 1;
    }
    if(c == 1)
        goto input;
    yearday = day_of_year(y, m, d, leap);
    month_day(y, yearday, leap);
    return 0;
}

int day_of_year(int year, int month, int day, int leap){
    int i;
    printf("%d.", day);
    for(i = 1; i < month; i++)
        day += *(*(daytab+leap)+i);
    printf("%d is the %d's day of %d year\n", month, day, year);
    return day;
}

void month_day(int year, int yearday, int leap){
    int i, m, d;
    
    for(i = 1; yearday > *(*(daytab+leap)+i); i++)
        yearday -= *(*(daytab+leap)+i);
    m = i;
    d = yearday;
    printf("Full date: %d/%d/%d\n", m, d, year);
}

Зрозуміло, що daytab+leap - це індекс елемента першого виміру daytab+leap+i - це індекс елемента в другому вимірі, але не зрозуміла ситуація із операторами розйменування, чому ми виставляємо зірочки саме в такому порядку, як це зазначено вище?

2

Re: Заміна індексів покажчиками

daytab[leap][i]
*(*(daytab+leap)+i)

Давайте розглянемо цей код покроково.

1. По факту, daytab - подвійний покажчик:

char **daytab.

А ще точніше сказати, що це - масив покажчиків на char:

(char*) * daytab;

2. Отримуємо доступ до leap-рядка у масиві рядків:

char *row = daytab[leap]; // char * --> to row
row = *(daytab+leap); // char * --> leap-й покажчик у масиві, зірочкою беремо адресу рядка, на який він вказує

3. Логіка елементу текстового рядка подібна:

row[i];
*(row+i);
*( *(daytab+leap) + i);

3 Востаннє редагувалося Ярослав (05.02.2013 21:52:56)

Re: Заміна індексів покажчиками

Не зрозуміло все одно.
Читаючи одну статтю наткнувся на пояснення що двовимірний масив

[0] 1 0 0 0 0 0 0 0 0 1
[1] 2 0 0 0 0 0 0 0 0 2

Вміщується в пам'ять таким чином

1 0 0 0 0 0 0 0 0 1 2 0 0 0 0 0 0 0 0 2

Отже для того щоб дістати елемент [1][2], потрібно написати щось виду **(12) (із твердження про *(*(0 + 1) + 2)?

4

Re: Заміна індексів покажчиками

Код нижче демонструє особливості розміщення об'єктів у пам'яті (і паралельно - доступ до елементів).

#include <iostream>
#include <malloc.h>

using namespace std;

int main()
{
    int staticArr[5][3]={0};
    
    cout<<"static array: "<<sizeof(staticArr)<<" bytes"<<endl;
    
    staticArr[2][1] = 100500;
    
    cout<<"static: [2][1] = "<<*(*staticArr+3*2+1)<<endl;
    // **(staticArr+3*2+1) // it would be moving through rows from staticArr[0] -> to staticArr[7], with next trying to get staticArr[7][0], probably...
    
    int **dynamicArr = new int*[5];
    
    for(int i = 0; i < 5; i++)
        dynamicArr[i] = new int[3];
    
    cout<<"dynamic array: "<<sizeof(dynamicArr)<<" bytes"<<endl;
    
    cout<<"Size of dynamic array: "<<_msize(dynamicArr)<<" bytes"<<endl;
    cout<<"subarrays in dynamic array: "<<_msize(dynamicArr)/sizeof(dynamicArr)<<endl;
    
    for(int i = 0; i < 5; i++)
        delete[] dynamicArr[i];
    
    delete[] dynamicArr;

    return 0;
}

Результат:
http://s12.postimage.org/4o300gtp9/untitled.jpg

Коли масив розміщується на стеку - елементи і правда ідуть компактно та'як ви вказали - лінійно. Однак коли мова йде про динамічну пам'ять - ви спочатку створюєте покажчик на масив покажчиків, які міститимуть адреси будь-яких областей пам'яті. Саме тому sizeof() так специфічно працює, адже він показує реальний обсяг пам'яті, виділеної під змінну. Статичний масив, по суті, це - суцільний об'єкт, незалежно від розмірності. А динамічний масив для нього - всього лише покажчик розміром 4 байти.

З.І: сподіваюся, я не заплутав вас ще більше? :)

5

Re: Заміна індексів покажчиками

Я все одно іще не отримав відповіді на своє запитання, хоча дізнався багато чого нового.
Мене цікавить суто як працює *(*(daytab+leap)+i)
Чому не можна написати просто **(daytab + leap + i)

6 Востаннє редагувалося User 298 (06.02.2013 10:33:57)

Re: Заміна індексів покажчиками

keithfay написав:

Я все одно іще не отримав відповіді на своє запитання, хоча дізнався багато чого нового.
Мене цікавить суто як працює *(*(daytab+leap)+i)
Чому не можна написати просто **(daytab + leap + i)

Не можна.
/* Гляньте ще раз код попереднього посту - там у коментарях також є примітка */

daytab - вказівник на int*, на початку - також вказівник на нульовий елемент масиву покажчиків на int.
По горизонталі - підмасиви int (так зручніше "малювати"), по вертикалі - елемент, до якого подвійним розіменуванням отримуємо доступ.

**(daytab)
         [0][1][2][3][4]
         [0]
          *

Тепер проілюструємо ваше питання. Нехай leap = 1, i=1.
Ви прагнули отримати елемент daytab[1][1].

**(daytab +leap)
         [0][1][2][3][4]
            [0]
          *->#

Поки все добре. Але...

**(daytab +leap +i)
         [0][1][2][3][4]
               [0]
          *--->#

daytab[2][0] проти daytab[1][1].

Ця ситуація справедлива для багатомірних масивів як динамічних, так і статичних.

Для статичних лишається ще один варіант: рух по усьому масиву як по лінійному одномірному. При цьому важливо пам'ятати один момент: доступ до елемента [a][d] при довжині рядків L досягається так:

*(*arr + a*L + d)

Це можливо завдяки тому, що статичний двовимірний масив завжди прямокутний, тоді як динамічний може бути "пошматованим".

7 Востаннє редагувалося Ярослав (06.02.2013 10:31:27)

Re: Заміна індексів покажчиками

Із усього що я прочитав - я опишу як я це зрозумів, а ви мене виправте
*(*(daytab+leap)+i)
Для того ж елементу [1][1]
1. Найпріоритетніша дія в дужках (daytab + leap) = 1
*(*1 + i)
2. Оператор розйменування має більший пріорітет перед +, тому наступним визначається [x][x] - перший індекс
*(*1+i)
3. Далі іде ройменування того що залишилось [1][x]
*(1)
Правильно?

8 Востаннє редагувалося User 298 (06.02.2013 10:42:50)

Re: Заміна індексів покажчиками

keithfay написав:

Із усього що я прочитав - я опишу як я це зрозумів, а ви мене виправте
*(*(daytab+leap)+i)
Для того ж елементу [1][1]
1. Найпріоритетніша дія в дужках (daytab + leap) = 1
*(*1 + i)
2. Оператор розйменування має більший пріорітет перед +, тому наступним визначається [x][x] - перший індекс
*(*1+i)

2.1. Знову дужки - тож виконується "+". А вже потім

3. Далі іде ройменування того що залишилось [1][x]
*(1)

А так - вірно. :)


З.І: вибачаюся за "многабукаф": тема смачна :)

9

Re: Заміна індексів покажчиками

І декілька останніх питань
Можна сказати що індекси будуть іти так
arr[j][k][l][m]
*(*(*(*(arr + j) + k) + l) + m),
де
* - [m]
* - [l]
* - [k]
* - [j]
Якщо так, то останнє питання, чому саме так?

10

Re: Заміна індексів покажчиками

keithfay написав:

І декілька останніх питань
Можна сказати що індекси будуть іти так
arr[j][k][l][m]
*(*(*(*(arr + j) + k) + l) + m),
де
* - [m]
* - [l]
* - [k]
* - [j]
Якщо так, то останнє питання, чому саме так?

Так. За принципом матрьошки: на кожному рівні - своя "одежина-масив" покажчиків певної вимірності. Ви покроково розіменовуєте вказівники, отримуючи доступ до "матрьошок" нижчих рівнів. :)

11

Re: Заміна індексів покажчиками

Корисно іноді почитати, що люди пишуть.

Наступний код має скомпілюватися без проблем:

int main(void)
{
    int x[2] = {0};
    x[0];// This is similar
    *(x+0);// to this
    0[x];//and this...

    return 0;
}