ความแตกต่างระหว่างการประกาศต่อไปนี้คืออะไร:
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 )
ฉันไม่รู้ว่ามันมีชื่ออย่างเป็นทางการหรือไม่ แต่ฉันเรียกมันว่า Right-Left Thingy (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
คำตอบสำหรับสองคนสุดท้ายนั้นสามารถหักออกจากกฎทองใน C:
การประกาศตามการใช้งาน
int (*arr2)[8];
จะเกิดอะไรขึ้นถ้าคุณ dereference arr2
? คุณได้อาร์เรย์จำนวนเต็ม 8 จำนวน
int *(arr3[8]);
จะเกิดอะไรขึ้นถ้าคุณนำองค์ประกอบมาจากarr3
อะไร คุณได้รับตัวชี้ไปยังจำนวนเต็ม
นอกจากนี้ยังช่วยเมื่อจัดการกับตัวชี้ไปยังฟังก์ชั่น ในการใช้ตัวอย่างของ sigjuice:
float *(*x)(void )
จะเกิดอะไรขึ้นเมื่อคุณ dereference x
? คุณได้รับฟังก์ชั่นที่คุณสามารถเรียกได้โดยไม่มีข้อโต้แย้ง จะเกิดอะไรขึ้นเมื่อคุณเรียกมันว่า 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
คือตัวชี้" ไปทางขวา .. ")" ตอนนี้ไปทางซ้าย "(" ออกมาด้านขวา "()" ดังนั้น "ไปยังฟังก์ชันที่ไม่มีอาร์กิวเมนต์" ไปทางซ้าย "และส่งกลับตัวชี้" ไป " ขวา "เป็นอาร์เรย์" go left "ของจำนวนเต็ม"
)
ตอนนี้ไปทางซ้าย ... มันคือ*
"ตัวชี้ไปที่" ไปทางขวา ... มัน)
ตอนนี้ไปทางซ้าย ... มันคือ(
ออกมาไปทางขวา()
เพื่อให้ "เพื่อฟังก์ชั่นซึ่งจะมีข้อโต้แย้งไม่" ไปขวา ... []
"และผลตอบแทนอาร์เรย์ของ" ไปทางขวา;
ท้ายเพื่อให้ไปทางซ้าย ... *
"ชี้" ไปเหลือ ... int
"จำนวนเต็ม"
นี่คือเว็บไซต์ที่น่าสนใจที่อธิบายวิธีการอ่านประเภทที่ซับซ้อนใน C: http://www.unixwiz.net/techtips/reading-cdecl.html
นี่คือวิธีที่ฉันตีความมัน:
int *something[n];
หมายเหตุสำคัญ: ตัวดำเนินการตัวห้อย (
[]
) มีลำดับความสำคัญสูงกว่าตัวดำเนินการอ้างอิง (*
)
ดังนั้นที่นี่เราจะใช้[]
ก่อนหน้า*
ทำให้คำสั่งที่เทียบเท่ากับ:
int *(something[i]);
หมายเหตุเกี่ยวกับวิธีการประกาศจะทำให้ความรู้สึก:
int num
หมายถึงการnum
เป็นint
,int *ptr
หรือint (*ptr)
หมายถึง (มูลค่าptr
) เป็นint
ซึ่งทำให้ตัวชี้ไปยังptr
int
สิ่งนี้สามารถอ่านได้ว่า (ค่าของ (ค่าที่ดัชนี ith ของบางอย่าง)) เป็นจำนวนเต็ม ดังนั้น (ค่าที่ดัชนี ith ของบางอย่าง) คือ (ตัวชี้จำนวนเต็ม) ซึ่งทำให้บางสิ่งนั้นเป็นอาร์เรย์ของตัวชี้จำนวนเต็ม
ในอันที่สอง
int (*something)[n];
เพื่อให้เข้าใจถึงคำแถลงนี้คุณต้องคุ้นเคยกับข้อเท็จจริงนี้:
หมายเหตุเกี่ยวกับการเป็นตัวแทนตัวชี้ของอาร์เรย์:
somethingElse[i]
เทียบเท่า*(somethingElse + i)
ดังนั้นแทนที่somethingElse
ด้วย(*something)
เราได้รับ*(*something + i)
ซึ่งเป็นจำนวนเต็มตามประกาศ ดังนั้น(*something)
ให้เราอาร์เรย์ซึ่งจะทำให้สิ่งที่เทียบเท่ากับ(ชี้ไปยังอาร์เรย์)
ฉันเดาว่าการประกาศครั้งที่สองนั้นทำให้หลายคนสับสน นี่เป็นวิธีง่ายๆในการทำความเข้าใจ
int B[8]
ช่วยให้มีอาร์เรย์ของจำนวนเต็มคือ
Let 's ยังมีตัวแปรซึ่งจุดไป B. ตอนนี้ค่าที่ A เป็น B (*A) == B
คือ ดังนั้น A ชี้ไปที่อาร์เรย์ของจำนวนเต็ม ในคำถามของคุณ arr คล้ายกับ A
ในทำนองเดียวกันในint* (*C) [8]
C เป็นตัวชี้ไปยังอาร์เรย์ของตัวชี้ไปยังจำนวนเต็ม
int *arr1[5]
ในการประกาศarr1
นี้เป็นอาร์เรย์ของ 5 พอยน์เตอร์ต่อจำนวนเต็ม เหตุผล: วงเล็บเหลี่ยมมีความสำคัญมากกว่า * (ตัวดำเนินการยกเลิกการลงทะเบียน) และในประเภทนี้จำนวนแถวได้รับการแก้ไข (5 ที่นี่) แต่จำนวนคอลัมน์เป็นตัวแปร
int (*arr2)[5]
ในการประกาศarr2
นี้เป็นตัวชี้ไปยังอาร์เรย์จำนวนเต็ม 5 องค์ประกอบ เหตุผล: ที่นี่ () วงเล็บเหลี่ยมมีความสำคัญมากกว่า [] และในประเภทนี้จำนวนแถวเป็นตัวแปร แต่จำนวนคอลัมน์ได้รับการแก้ไข (5 ที่นี่)
ในตัวชี้ถึงจำนวนเต็มถ้าตัวชี้เพิ่มขึ้นมันจะไปเป็นจำนวนเต็มถัดไป
ในอาร์เรย์ของตัวชี้ถ้าตัวชี้เพิ่มขึ้นมันจะกระโดดไปยังอาร์เรย์ถัดไป