Яка різниця між наступними деклараціями:
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 тут).
У покажчику на ціле число, якщо покажчик збільшується, він переходить до наступного цілого числа.
у масиві вказівника, якщо покажчик збільшується, він переходить до наступного масиву