อาร์เรย์ของชื่อเป็นตัวชี้ใน C หรือไม่? ถ้าไม่ความแตกต่างระหว่างชื่อของอาร์เรย์และตัวแปรตัวชี้คืออะไร?
&array[0]
อัตราผลตอบแทนที่เป็นตัวชี้ไม่อาร์เรย์;)
อาร์เรย์ของชื่อเป็นตัวชี้ใน C หรือไม่? ถ้าไม่ความแตกต่างระหว่างชื่อของอาร์เรย์และตัวแปรตัวชี้คืออะไร?
&array[0]
อัตราผลตอบแทนที่เป็นตัวชี้ไม่อาร์เรย์;)
คำตอบ:
อาเรย์เป็นอาเรย์และตัวชี้เป็นพอยน์เตอร์ แต่ในกรณีส่วนใหญ่ชื่ออาเรย์จะถูกแปลงเป็นพอยน์เตอร์ คำที่ใช้บ่อยคือพวกมันสลายตัวเป็นพอยน์เตอร์
นี่คืออาร์เรย์:
int a[7];
a
มีช่องว่างสำหรับเจ็ดจำนวนเต็มและคุณสามารถใส่ค่าลงในหนึ่งในนั้นด้วยการกำหนดดังนี้:
a[3] = 9;
นี่คือตัวชี้:
int *p;
p
ไม่มีช่องว่างสำหรับจำนวนเต็ม แต่สามารถชี้ไปที่ช่องว่างสำหรับจำนวนเต็มได้ ตัวอย่างเช่นเราสามารถกำหนดให้ชี้ไปที่หนึ่งในสถานที่ในอาร์เรย์a
เช่นสถานที่แรก:
p = &a[0];
สิ่งที่อาจทำให้สับสนคือคุณสามารถเขียนสิ่งนี้ได้:
p = a;
สิ่งนี้ไม่ได้คัดลอกเนื้อหาของอาร์เรย์a
ไปยังตัวชี้p
(สิ่งใดก็ตามที่จะหมายถึง) แต่ชื่ออาร์เรย์a
จะถูกแปลงเป็นตัวชี้ไปยังองค์ประกอบแรก เพื่อให้การมอบหมายนั้นเหมือนกันกับหน้าที่ก่อนหน้านี้
ตอนนี้คุณสามารถใช้p
วิธีการคล้ายกับอาเรย์:
p[3] = 17;
เหตุผลที่ใช้งานได้คือตัวดำเนินการ dereferencing ใน C [ ]
ถูกกำหนดเป็นพอยน์เตอร์ x[y]
หมายถึง: เริ่มต้นด้วยตัวชี้องค์ประกอบx
ขั้นตอนy
ไปข้างหน้าหลังจากสิ่งที่ตัวชี้ชี้ไปแล้วนำสิ่งที่มี ใช้ตัวชี้ไวยากรณ์เลขคณิตยังสามารถเขียนเป็นx[y]
*(x+y)
เพื่อให้การทำงานกับอาร์เรย์ปกติเช่นของเราa
ชื่อa
ในa[3]
ต้องถูกแปลงเป็นตัวชี้ก่อน (เป็นองค์ประกอบแรกในa
) จากนั้นเราก้าวไปข้างหน้า 3 องค์ประกอบและนำสิ่งที่มี ในคำอื่น ๆ : ใช้องค์ประกอบที่ตำแหน่ง 3 ในอาร์เรย์ (ซึ่งเป็นองค์ประกอบที่สี่ในอาร์เรย์ตั้งแต่องค์ประกอบแรกมีหมายเลข 0)
ดังนั้นโดยสรุปชื่ออาเรย์ในโปรแกรม C จะถูกแปลงเป็นพอยน์เตอร์ (ในกรณีส่วนใหญ่) ข้อยกเว้นหนึ่งคือเมื่อเราใช้sizeof
โอเปอเรเตอร์กับอาเรย์ หากa
ถูกแปลงเป็นตัวชี้ในบริบทนี้sizeof a
จะให้ขนาดของตัวชี้และไม่ใช่อาร์เรย์จริงซึ่งจะค่อนข้างไร้ประโยชน์ดังนั้นในกรณีนั้นa
หมายถึงอาร์เรย์เอง
functionpointer()
และ(*functionpointer)()
หมายถึงสิ่งเดียวกันแปลกพอสมควร
sizeof()
บริบทอื่น ๆ ที่ไม่มี array-> ชี้ผุเป็นผู้ดำเนินการ&
- ในตัวอย่างของคุณข้างต้น&a
จะเป็นตัวชี้ไปยังอาร์เรย์ 7 int
ไม่ได้เป็นตัวชี้ไปยังเดียวint
; นั่นคือประเภทของมันจะเป็นซึ่งไม่ได้โดยปริยายแปลงสภาพให้แก่int(*)[7]
int*
ด้วยวิธีนี้ฟังก์ชั่นสามารถนำพอยน์เตอร์ไปใช้กับอาร์เรย์ที่มีขนาดเฉพาะและบังคับใช้ข้อ จำกัด ผ่านระบบประเภท
เมื่อใช้อาร์เรย์เป็นค่าชื่อของมันจะแทนที่อยู่ขององค์ประกอบแรก
เมื่อไม่ได้ใช้อาร์เรย์เป็นค่าชื่อของมันจะแทนอาร์เรย์ทั้งหมด
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
หากการแสดงออกของประเภทอาร์เรย์ (เช่นชื่ออาร์เรย์) ปรากฏขึ้นในการแสดงออกที่ใหญ่กว่าและมันไม่ได้เป็นตัวถูกดำเนินการอย่างใดอย่างหนึ่ง&
หรือsizeof
ผู้ประกอบการแล้วประเภทของการแสดงออกของอาร์เรย์จะถูกแปลงจาก "N องค์ประกอบองค์ประกอบของ T" "ตัวชี้ถึง T" และค่าของนิพจน์คือที่อยู่ขององค์ประกอบแรกในอาร์เรย์
กล่าวโดยย่อชื่ออาเรย์ไม่ได้เป็นตัวชี้ แต่ในบริบทส่วนใหญ่จะถือว่าเป็นตัวชี้
แก้ไข
ตอบคำถามในความคิดเห็น:
หากฉันใช้ sizeof ฉันจะนับขนาดเฉพาะองค์ประกอบของอาร์เรย์หรือไม่ จากนั้นอาร์เรย์“ หัว” จะใช้พื้นที่ด้วยข้อมูลเกี่ยวกับความยาวและตัวชี้ (และนี่หมายความว่ามันใช้พื้นที่มากขึ้นกว่าตัวชี้ปกติ)
เมื่อคุณสร้างอาร์เรย์พื้นที่เดียวที่จัดสรรคือพื้นที่สำหรับองค์ประกอบเอง ไม่มีที่จัดเก็บข้อมูลสำหรับตัวชี้แยกต่างหากหรือข้อมูลเมตาใด ๆ ป.ร. ให้ไว้
char a[10];
สิ่งที่คุณได้รับในความทรงจำคือ
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
แสดงออก a
หมายถึงอาร์เรย์ทั้งหมด แต่ไม่มีวัตถุ a
ที่แยกต่างหากจากองค์ประกอบอาร์เรย์ตัวเอง ดังนั้นsizeof a
จะช่วยให้คุณมีขนาด (เป็นไบต์) ของอาร์เรย์ทั้งหมด การแสดงออกที่&a
จะช่วยให้คุณอยู่ของอาร์เรย์ซึ่งเป็นเช่นเดียวกับที่อยู่ขององค์ประกอบแรก ความแตกต่างระหว่าง&a
และ&a[0]
เป็นประเภทของผลลัพธ์1 - char (*)[10]
ในกรณีแรกและchar *
ในกรณีที่สอง
จุดที่สิ่งแปลก ๆ คือเมื่อคุณต้องการเข้าถึงแต่ละองค์ประกอบ - นิพจน์a[i]
ถูกกำหนดเป็นผลลัพธ์ของ*(a + i)
- กำหนดค่าที่อยู่a
, i
องค์ประกอบออฟเซ็ต( ไม่ใช่ไบต์ ) จากที่อยู่นั้นและยกเลิกผลของผลลัพธ์
ปัญหาคือa
ไม่ได้เป็นตัวชี้หรือที่อยู่ - มันเป็นวัตถุอาร์เรย์ทั้งหมด ดังนั้นกฎใน C ที่เมื่อใดก็ตามที่คอมไพเลอร์เห็นการแสดงออกของประเภทอาเรย์ (เช่นa
ซึ่งมีประเภทchar [10]
) และการแสดงออกนั้นไม่ได้เป็นตัวถูกดำเนินการของผู้ประกอบการsizeof
หรือ unary &
ประเภทของการแสดงออกนั้นจะถูกแปลง ("decays") ประเภทตัวชี้ ( char *
) และค่าของการแสดงออกเป็นที่อยู่ขององค์ประกอบแรกของอาร์เรย์ ดังนั้นการแสดงออก a
มีประเภทและค่าเช่นเดียวกับการแสดงออก&a[0]
(และโดยการขยายการแสดงออก*a
มีประเภทและค่าเช่นเดียวกับการแสดงออกa[0]
)
C ได้มาจากภาษาก่อนหน้านี้เรียกว่า B และ B ในa
เป็นวัตถุที่แยกต่างหากจากตัวชี้องค์ประกอบอาร์เรย์a[0]
, a[1]
ฯลฯ Ritchie อยากให้ความหมายของขอาร์เรย์ แต่เขาไม่อยากจะยุ่งกับการจัดเก็บวัตถุชี้แยกต่างหาก ดังนั้นเขาจึงกำจัดมัน คอมไพเลอร์จะแปลงนิพจน์อาร์เรย์เป็นนิพจน์พอยน์เตอร์แทนในระหว่างการแปลตามความจำเป็น
โปรดจำไว้ว่าฉันพูดว่าอาร์เรย์ไม่เก็บข้อมูลเมตาใด ๆ เกี่ยวกับขนาดของมัน ทันทีที่นิพจน์อาร์เรย์นั้น "สลาย" ไปยังตัวชี้ทั้งหมดที่คุณมีคือตัวชี้ไปยังองค์ประกอบเดียว องค์ประกอบนั้นอาจเป็นลำดับแรกขององค์ประกอบหรืออาจเป็นวัตถุเดียว ไม่มีวิธีที่จะรู้ได้จากตัวชี้
เมื่อคุณส่งนิพจน์อาเรย์ไปยังฟังก์ชั่นฟังก์ชั่นทั้งหมดที่ได้รับจะเป็นตัวชี้ไปยังองค์ประกอบแรก - มันไม่ทราบว่าอาร์เรย์มีขนาดใหญ่เพียงใด (นี่คือสาเหตุที่gets
ฟังก์ชั่นดังกล่าวเป็นภัยคุกคาม สำหรับฟังก์ชั่นที่จะรู้ว่ามีกี่องค์ประกอบในอาร์เรย์คุณต้องใช้ค่า Sentinel (เช่น 0 terminator ในสตริง C) หรือคุณจะต้องผ่านจำนวนองค์ประกอบเป็นพารามิเตอร์แยกต่างหาก
sizeof
เป็นตัวดำเนินการและจะประเมินเป็นจำนวนไบต์ในตัวถูกดำเนินการ (นิพจน์ที่แสดงถึงวัตถุหรือชื่อประเภทในวงเล็บ) ดังนั้นสำหรับอาร์เรย์ให้sizeof
ประเมินจำนวนองค์ประกอบที่คูณด้วยจำนวนไบต์ในองค์ประกอบเดียว หากความint
กว้าง 4 ไบต์จะมีอาร์เรย์ 5 องค์ประกอบที่มีขนาดint
สูงสุด 20 ไบต์
[ ]
พิเศษด้วยหรือไม่ ตัวอย่างเช่นint a[2][3];
จากนั้นx = a[1][2];
ถึงแม้ว่าจะสามารถเขียนใหม่เป็นx = *( *(a+1) + 2 );
ได้ที่นี่a
จะไม่ถูกแปลงเป็นประเภทตัวชี้int*
(แม้ว่าa
จะเป็นอาร์กิวเมนต์ของฟังก์ชันที่ควรแปลงเป็นint*
)
a
มีประเภทint [2][3]
ซึ่ง "สูญสลาย" int (*)[3]
ประเภท การแสดงออกที่*(a + 1)
มีประเภทint [3]
ซึ่ง "สูญสลาย" int *
เพื่อ ดังนั้นจะมีประเภท *(*(a + 1) + 2)
ชี้ไปที่อาร์เรย์ 3 องค์ประกอบแรกของ, ชี้ไปที่อาร์เรย์ 3 องค์ประกอบที่สองของ, เป็นอาร์เรย์ 3 องค์ประกอบที่สองของ, ชี้ไปที่องค์ประกอบที่สามของอาร์เรย์ที่สองของ, ดังนั้นจึงเป็นองค์ประกอบที่สามของอาร์เรย์ที่สองของ. วิธีที่ได้รับการแมปกับรหัสเครื่องนั้นขึ้นอยู่กับคอมไพเลอร์ int
a
int
a + 1
int
*(a + 1)
int
*(a + 1) + 2
int
*(*(a + 1) + 2)
int
อาร์เรย์ประกาศแบบนี้
int a[10];
จัดสรรหน่วยความจำเป็นเวลา 10 int
วินาที คุณไม่สามารถแก้ไขแต่คุณสามารถทำเลขคณิตชี้กับa
a
ตัวชี้แบบนี้จัดสรรหน่วยความจำเพียงตัวชี้p
:
int *p;
มันไม่ได้จัดสรรอะไรint
เลย คุณสามารถแก้ไขได้:
p = a;
และใช้ตัวห้อยอาร์เรย์ตามที่คุณสามารถทำได้ด้วย:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
วินาทีด้วยระยะเวลาเก็บข้อมูลอัตโนมัติ"
ชื่ออาร์เรย์จะให้ตำแหน่งหน่วยความจำด้วยตัวเองดังนั้นคุณสามารถใช้ชื่ออาร์เรย์เหมือนตัวชี้ได้:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
และสิ่งที่ดีอื่น ๆ ที่คุณสามารถทำได้เพื่อชี้ (เช่นการเพิ่ม / ลบล้างการชดเชย) คุณยังสามารถทำอาร์เรย์ได้อีกด้วย:
printf("value at memory location %p is %d", a + 1, *(a + 1));
ภาษาฉลาดถ้า C ไม่ได้เปิดเผยอาร์เรย์เป็นเพียง"ตัวชี้" บางชนิด (เชิงเป็นเพียงที่ตั้งหน่วยความจำมันไม่สามารถชี้ไปที่ตำแหน่งโดยพลการในหน่วยความจำและไม่สามารถควบคุมได้โดยโปรแกรมเมอร์) เราจำเป็นต้องใช้รหัสนี้เสมอ:
printf("value at memory location %p is %d", &a[1], a[1]);
ฉันคิดว่าตัวอย่างนี้ให้ความกระจ่างเกี่ยวกับปัญหา:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
มันรวบรวมความละเอียด (มี 2 คำเตือน) ใน gcc 4.9.2 และพิมพ์ต่อไปนี้:
a == &a: 1
อ๊ะ :-)
ดังนั้นข้อสรุปคือไม่อาร์เรย์ไม่ได้เป็นตัวชี้มันไม่ได้เก็บไว้ในหน่วยความจำ (ไม่ใช่แม้แต่แบบอ่านอย่างเดียว) เป็นตัวชี้ถึงแม้ว่ามันจะดูเหมือนว่ามันเป็นเพราะคุณสามารถรับที่อยู่ของมันด้วย & ผู้ประกอบการ . แต่ - โอ๊ะโอ - โอเปอเรเตอร์นั้นไม่ทำงาน :-)) ไม่ว่าด้วยวิธีใดคุณได้รับคำเตือนแล้ว:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++ ปฏิเสธความพยายามดังกล่าวพร้อมข้อผิดพลาดในเวลารวบรวม
แก้ไข:
นี่คือสิ่งที่ฉันต้องการแสดง:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
แม้ว่าc
และa
"ชี้" ไปยังหน่วยความจำเดียวกันคุณสามารถรับที่อยู่ของc
ตัวชี้ได้ แต่คุณไม่สามารถรับที่อยู่ของa
ตัวชี้ได้
-std=c11 -pedantic-errors
คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์สำหรับการเขียนรหัส C ที่ไม่ถูกต้อง สาเหตุที่เป็นเพราะคุณพยายามกำหนด a int (*)[3]
ให้กับตัวแปรint**
ซึ่งมีสองประเภทที่ไม่มีอะไรเกี่ยวข้องกันเลย ดังนั้นสิ่งที่ตัวอย่างนี้ควรจะพิสูจน์ฉันไม่มีความคิด
int **
ประเภทไม่ได้จุดที่มีคนหนึ่งที่ดีกว่าควรใช้void *
สำหรับการนี้
ชื่ออาร์เรย์ทำงานเหมือนตัวชี้และชี้ไปที่องค์ประกอบแรกของอาร์เรย์ ตัวอย่าง:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
ทั้งคำสั่งการพิมพ์จะให้ผลลัพธ์เดียวกันสำหรับเครื่อง ในระบบของฉันมันให้:
0x7fff6fe40bc0
อาร์เรย์คือชุดขององค์ประกอบที่มีความปลอดภัยและต่อเนื่องในหน่วยความจำ ใน C ชื่อของอาร์เรย์คือดัชนีขององค์ประกอบแรกและการใช้ออฟเซ็ตคุณสามารถเข้าถึงองค์ประกอบที่เหลือ "ดัชนีองค์ประกอบแรก" แน่นอนตัวชี้ไปยังทิศทางหน่วยความจำ
ความแตกต่างกับตัวแปรพอยน์เตอร์คือคุณไม่สามารถเปลี่ยนตำแหน่งที่ชื่ออาเรย์ชี้ไปได้ดังนั้นจึงคล้ายกับตัวชี้ const (คล้ายกันไม่เหมือนกันดูที่ความคิดเห็นของ Mark) แต่คุณไม่จำเป็นต้องตรวจสอบชื่ออาเรย์อีกครั้งเพื่อรับค่าถ้าคุณใช้ aritmetic ของตัวชี้:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
ดังนั้นคำตอบก็คือ 'ใช่'
ชื่ออาร์เรย์คือที่อยู่ขององค์ประกอบที่ 1 ของอาร์เรย์ ใช่ชื่ออาร์เรย์คือตัวชี้ const