Яка різниця між наступними деклараціями:
int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);
Яке загальне правило для розуміння більш складних декларацій?
const
та volatile
класифікатори, які є важливими та складними, відсутні у цій статті.
Яка різниця між наступними деклараціями:
int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);
Яке загальне правило для розуміння більш складних декларацій?
const
та volatile
класифікатори, які є важливими та складними, відсутні у цій статті.
Відповіді:
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers
Третя така ж, як і перша.
Загальне правило - пріоритет оператора . Це може стати навіть набагато складнішим, оскільки вказівники функцій потрапляють у зображення.
( ) [ ]
асоціюються зліва направо та мають вищий пріоритет, ніж *
так, int* arr[8]
як читати як масив розміром 8, де кожен елемент вказує на int та int (*arr)[8]
як вказівник на масив розміром 8, який містить цілі числа
Використовуйте програму cdecl , як пропонує K&R.
$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>
Це працює і іншим способом.
cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
Я не знаю, чи має вона офіційне найменування, але я називаю це Праворуч Справедливий (TM).
Почніть з змінної, потім направо, і вліво, і вправо ... і так далі.
int* arr1[8];
arr1
це масив з 8 покажчиків на цілі числа.
int (*arr2)[8];
arr2
є вказівником (блок дужок справа-зліва) на масив з 8 цілих чисел.
int *(arr3[8]);
arr3
це масив з 8 покажчиків на цілі числа.
Це повинно допомогти вам скласти декларації.
int *a[][10]
коли останній досягає успіху.
( ) [ ]
ліворуч праворуч і праворуч ліворуч* &
int *a[4]; // Array of 4 pointers to int
int (*a)[4]; //a is a pointer to an integer array of size 4
int (*a[8])[5]; //a is an array of pointers to integer array of size 5
[5]
) являє собою внутрішній вимір. Це означає, що (*a[8])
це перший вимір, і, таким чином, зовнішнє зображення масиву. На що a
вказує кожен елемент , це інший цілий масив розміром 5.
Відповідь на останні два можна також вивести із золотого правила в С:
Декларація слід використовувати.
int (*arr2)[8];
Що станеться, якщо знешкодження arr2
? Ви отримуєте масив з 8 цілих чисел.
int *(arr3[8]);
Що станеться, якщо взяти елемент arr3
? Ви отримуєте вказівник на ціле число.
Це також допомагає при роботі з покажчиками на функції. Для прикладу sigjuice:
float *(*x)(void )
Що трапляється, коли ви зневажаєте x
? Ви отримуєте функцію, яку можна викликати без аргументів. Що відбувається, коли ви його називаєте? Він поверне вказівник на a float
.
Проте пріоритет оператора завжди складний. Однак використання дужок може насправді також заплутати, оскільки декларація слід використовувати. Принаймні, мені інтуїтивно arr2
виглядає як масив з 8 вказівників на ints, але насправді навпаки. Просто потрібно трохи звикнути. Причина достатня, щоб завжди додавати коментар до цих декларацій, якщо ви запитаєте мене :)
редагувати: приклад
До речі, я просто натрапив на таку ситуацію: функція, яка має статичну матрицю і яка використовує арифметику вказівника, щоб побачити, чи вказівник рядка не виходить за межі. Приклад:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))
int *
put_off(const int newrow[2])
{
static int mymatrix[3][2];
static int (*rowp)[2] = mymatrix;
int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);
memcpy(rowp, newrow, sizeof(*rowp));
rowp += 1;
if (rowp == border) {
rowp = mymatrix;
}
return *rowp;
}
int
main(int argc, char *argv[])
{
int i = 0;
int row[2] = {0, 1};
int *rout;
for (i = 0; i < 6; i++) {
row[0] = i;
row[1] += i;
rout = put_off(row);
printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
}
return 0;
}
Вихід:
0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]
Зауважте, що значення межі ніколи не змінюється, тому компілятор може оптимізувати це. Це відрізняється від того, що ви, можливо, спочатку хочете використовувати:: const int (*border)[3]
що оголошує кордон як вказівник на масив з 3 цілих чисел, які не змінять значення, поки існує змінна. Однак цей покажчик може бути вказаний на будь-який інший такий масив у будь-який час. Натомість ми хочемо такої поведінки для аргументу (оскільки ця функція не змінює жодного з цих цілих чисел). Декларація слід використовувати.
(ps: сміливо покращуйте цей зразок!)
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
Як правило, праві одинарних оператори (як []
, ()
, і т.д.) превалювати над лівими з них. Отже, int *(*ptr)()[];
був би вказівник, який вказує на функцію, яка повертає масив покажчиків на int (отримайте потрібних операторів, як тільки зможете, як ви вийдете з дужок)
error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];
під GCC 8 з$ gcc -std=c11 -pedantic-errors test.c
int *(*ptr)();
дозволяє вираз, як p()[3]
(або (*p)()[3]
), використовувати згодом.
int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }
і називати це так: foo(arr)[4];
що має містити arr[2][4]
, правда?
Я думаю, ми можемо використовувати просте правило ..
example int * (*ptr)()[];
start from ptr
" ptr
- вказівник на" іти вправо .. його ")" тепер перейти вліво "(" вийти вправо "()" так "до функції, яка не бере аргументів" йти вліво "і повертає вказівник" йти " праворуч "на масив" перейти ліворуч "на цілі числа"
)
, тепер ідіть вліво ... це *
"вказівник на" йти праворуч ... це )
, тепер ідіть вліво ... це (
вийти, йти прямо ()
так «на функцію , яка приймає ніяких аргументів» не так ... []
«і повертає масив» йти прямо ;
кінець, тому йдіть наліво ... *
«покажчики» наліво ... int
«цілих»
Ось цікавий веб-сайт, який пояснює, як читати складні типи на C: http://www.unixwiz.net/techtips/reading-cdecl.html
Ось як я трактую це:
int *something[n];
Зауважте про пріоритет: оператор індексів масиву (
[]
) має більш високий пріоритет, ніж оператор перенавантаження (*
).
Отже, тут ми застосуємо []
раніше *
, зробивши заяву рівнозначним:
int *(something[i]);
Зверніть увагу на те, як декларація має сенс:
int num
означає, щоnum
єint
,int *ptr
абоint (*ptr)
означає, що (значення atptr
) є anint
, що робитьptr
вказівник наint
.
Це можна прочитати як, (значення (значення в i-му індексі чогось)) є цілим числом. Отже, (значення в i-му індексі чогось) - це (ціле число вказівника), яке робить щось масивом цілочисельних покажчиків.
У другому
int (*something)[n];
Щоб мати сенс з цього твердження, ви повинні бути знайомі з цим фактом:
Примітка щодо представлення масиву вказівника:
somethingElse[i]
еквівалентно*(somethingElse + i)
Отже, замінюючи somethingElse
на (*something)
, ми отримуємо *(*something + i)
, що є цілим числом відповідно до декларації. Отже, (*something)
дано нам масив, який робить щось рівнозначне (вказівник на масив) .
Я думаю, друга декларація для багатьох бентежить. Ось простий спосіб зрозуміти це.
Дозволяє мати масив цілих чисел, тобто int B[8]
.
Маємо також змінну A, яка вказує на B. Тепер, значення A є B, тобто (*A) == B
. Звідси A вказує на масив цілих чисел. У вашому запитанні arr схожий на A.
Аналогічно, в int* (*C) [8]
, C - вказівник на масив покажчиків на ціле число.
int *arr1[5]
У цій декларації arr1
є масив з 5 покажчиків на цілі числа. Причина: квадратні дужки мають більшу перевагу над * (оператор відміни). І в цьому типі фіксується кількість рядків (5 тут), але кількість стовпців є змінною.
int (*arr2)[5]
У цій декларації arr2
є вказівник на цілий масив з 5 елементів. Причина: Тут дужки () мають більший пріоритет, ніж []. І в цьому типі кількість рядків є змінною, але кількість стовпців фіксована (5 тут).
У покажчику на ціле число, якщо покажчик збільшується, він переходить до наступного цілого числа.
у масиві вказівника, якщо покажчик збільшується, він переходить до наступного масиву