ฉันเพิ่งเรียนรู้ C และสงสัยว่าฉันควรใช้ตัวไหนในวิธีหลักของฉัน มีความแตกต่างกันหรือไม่? อันไหนธรรมดากว่ากัน?
char **argv
เป็นว่าเทียบเท่ากับการchar *argv[]
เป็นประกาศพารามิเตอร์ (และเพียงเป็นประกาศพารามิเตอร์) 2. อาร์เรย์ไม่ใช่ตัวชี้
ฉันเพิ่งเรียนรู้ C และสงสัยว่าฉันควรใช้ตัวไหนในวิธีหลักของฉัน มีความแตกต่างกันหรือไม่? อันไหนธรรมดากว่ากัน?
char **argv
เป็นว่าเทียบเท่ากับการchar *argv[]
เป็นประกาศพารามิเตอร์ (และเพียงเป็นประกาศพารามิเตอร์) 2. อาร์เรย์ไม่ใช่ตัวชี้
คำตอบ:
ในฐานะที่คุณเป็นเพียงการเรียนรู้ C, ผมขอแนะนำให้คุณไปจริงๆพยายามที่จะเข้าใจความแตกต่างระหว่างอาร์เรย์และตัวชี้แรกแทนที่จะเป็นเรื่องธรรมดาสิ่ง
ในส่วนของพารามิเตอร์และอาร์เรย์มีกฎที่น่าสับสนเล็กน้อยที่ควรชัดเจนก่อนดำเนินการต่อ ขั้นแรกสิ่งที่คุณประกาศในรายการพารามิเตอร์จะได้รับการปฏิบัติเป็นพิเศษ มีสถานการณ์เช่นนี้ที่สิ่งต่างๆไม่สมเหตุสมผลเมื่อเป็นพารามิเตอร์ฟังก์ชันใน C สิ่งเหล่านี้คือ
อย่างที่สองอาจจะไม่ชัดเจนในทันที แต่จะชัดเจนเมื่อคุณพิจารณาว่าขนาดของมิติอาร์เรย์เป็นส่วนหนึ่งของประเภทใน C (และอาร์เรย์ที่ไม่ได้กำหนดขนาดมิติข้อมูลจะมีประเภทที่ไม่สมบูรณ์) ดังนั้นหากคุณจะสร้างฟังก์ชั่นที่รับอาร์เรย์ทีละค่า (รับสำเนา) ก็สามารถทำได้สำหรับขนาดเดียวเท่านั้น! นอกจากนี้อาร์เรย์อาจมีขนาดใหญ่และ C จะพยายามให้เร็วที่สุด
ใน C สำหรับเหตุผลเหล่านี้อาร์เรย์ค่าไม่ได้มีอยู่จริง หากคุณต้องการรับค่าของอาร์เรย์สิ่งที่คุณจะได้รับคือตัวชี้ไปยังองค์ประกอบแรกของอาร์เรย์นั้น และในที่นี้มีทางออกอยู่แล้ว แทนที่จะวาดพารามิเตอร์อาร์เรย์ที่ไม่ถูกต้องขึ้นด้านหน้าคอมไพเลอร์ C จะเปลี่ยนประเภทของพารามิเตอร์ที่เกี่ยวข้องเป็นตัวชี้ จำไว้ว่าสิ่งนี้สำคัญมาก พารามิเตอร์จะไม่เป็นอาร์เรย์ แต่จะเป็นตัวชี้ไปยังประเภทองค์ประกอบที่เกี่ยวข้องแทน
ตอนนี้ถ้าคุณพยายามส่งอาร์เรย์สิ่งที่ส่งผ่านแทนจะเป็นตัวชี้ไปยังองค์ประกอบแรกของอาร์เรย์
เพื่อความสมบูรณ์และเพราะฉันคิดว่าสิ่งนี้จะช่วยให้คุณเข้าใจเรื่องนี้ได้ดีขึ้นมาดูกันว่าสถานะของสถานการณ์เป็นอย่างไรเมื่อคุณพยายามมีฟังก์ชันเป็นพารามิเตอร์ อันที่จริงประการแรกมันจะไม่สมเหตุสมผลเลย พารามิเตอร์เป็นฟังก์ชันได้อย่างไร? หึเราต้องการตัวแปร ณ ที่นั้นแน่นอน! ดังนั้นสิ่งที่คอมไพเลอร์ทำเมื่อสิ่งนั้นเกิดขึ้นคือการแปลงฟังก์ชันเป็นตัวชี้ฟังก์ชันอีกครั้ง การพยายามส่งผ่านฟังก์ชันจะส่งผ่านตัวชี้ไปยังฟังก์ชันนั้น ๆ แทน ดังนั้นสิ่งต่อไปนี้จึงเหมือนกัน (คล้ายกับตัวอย่างอาร์เรย์):
void f(void g(void));
void f(void (*g)(void));
โปรดทราบว่า*g
จำเป็นต้องมีวงเล็บรอบ ๆ มิฉะนั้นก็จะต้องระบุให้ฟังก์ชั่นที่กลับมาแทนการชี้ไปยังฟังก์ชั่นกลับมาvoid*
void
ตอนนี้ฉันได้กล่าวไปแล้วในตอนต้นว่าอาร์เรย์อาจมีประเภทที่ไม่สมบูรณ์ซึ่งจะเกิดขึ้นหากคุณยังไม่ได้ระบุขนาด เนื่องจากเราพบแล้วว่าไม่มีพารามิเตอร์อาร์เรย์ แต่พารามิเตอร์อาร์เรย์ใด ๆ เป็นตัวชี้ขนาดของอาร์เรย์จึงไม่สำคัญ นั่นหมายความว่าคอมไพเลอร์จะแปลสิ่งต่อไปนี้ทั้งหมดและทั้งหมดเป็นสิ่งเดียวกัน:
int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);
แน่นอนว่ามันไม่สมเหตุสมผลเลยที่จะใส่ขนาดใดก็ได้และมันก็ถูกโยนทิ้งไป ด้วยเหตุนี้ C99 จึงมีความหมายใหม่สำหรับตัวเลขเหล่านั้นและอนุญาตให้สิ่งอื่น ๆ ปรากฏขึ้นระหว่างวงเล็บ:
// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory.
int main(int c, char *argv[static 5]);
// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);
// says the same as the previous one
int main(int c, char ** const argv);
สองบรรทัดสุดท้ายบอกว่าคุณจะไม่สามารถเปลี่ยน "argv" ภายในฟังก์ชันได้ - มันกลายเป็นตัวชี้ const คอมไพเลอร์ C เพียงไม่กี่ตัวเท่านั้นที่รองรับคุณสมบัติ C99 เหล่านั้น แต่คุณลักษณะเหล่านี้ทำให้ชัดเจนว่า "อาร์เรย์" ไม่ใช่หนึ่งเดียว มันเป็นตัวชี้
โปรดทราบว่าทั้งหมดที่ฉันกล่าวข้างต้นเป็นจริงก็ต่อเมื่อคุณมีอาร์เรย์เป็นพารามิเตอร์ของฟังก์ชัน หากคุณทำงานกับอาร์เรย์ภายในอาร์เรย์จะไม่เป็นตัวชี้ มันจะทำงานเป็นตัวชี้เพราะตามที่อธิบายไว้ก่อนหน้านี้อาร์เรย์จะถูกแปลงเป็นตัวชี้เมื่ออ่านค่า แต่ไม่ควรสับสนกับพอยน์เตอร์
ตัวอย่างคลาสสิกตัวอย่างหนึ่งมีดังต่อไปนี้:
char c[10];
char **c = &c; // does not work.
typedef char array[10];
array *pc = &c; // *does* work.
// same without typedef. Parens needed, because [...] has
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;
คุณสามารถใช้อย่างใดอย่างหนึ่ง เทียบเท่ากันอย่างสมบูรณ์ ดูlitbความคิดเห็น 'และคำตอบของเขา
ขึ้นอยู่กับว่าคุณต้องการใช้งานอย่างไร (และคุณสามารถใช้ได้ไม่ว่าในกรณีใด ๆ ):
// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
while (--argc > 0)
{
printf("%s ", *++argv);
}
printf("\n");
return 0;
}
// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for (i=1; i<argc; i++)
{
printf("%s ", argv[i]);
}
printf("\n");
return 0;
}
ซึ่งเป็นเรื่องธรรมดามากขึ้น - มันไม่สำคัญ โปรแกรมเมอร์ C ที่มีประสบการณ์อ่านรหัสของคุณจะเห็นว่าทั้งสองอย่างใช้แทนกันได้ (ภายใต้เงื่อนไขที่เหมาะสม) เช่นเดียวกับผู้พูดภาษาอังกฤษที่มีประสบการณ์อ่านว่า "พวกเขา" และ "พวกเขา" ได้อย่างง่ายดาย
สิ่งที่สำคัญกว่าคือคุณต้องเรียนรู้ที่จะอ่านและรับรู้ว่าพวกเขามีความคล้ายคลึงกันเพียงใด คุณจะอ่านโค้ดมากกว่าที่คุณเขียนและคุณจะต้องรู้สึกสบายใจกับทั้งสองอย่างเท่า ๆ กัน
char**
เพราะมันทำให้ผมนึกถึงก็ไม่ควรได้รับการปฏิบัติเป็น array sizeof[arr] / sizeof[*arr]
จริงชอบทำ
มันไม่ได้สร้างความแตกต่าง แต่ฉันใช้char *argv[]
เพราะมันแสดงให้เห็นว่าเป็นอาร์เรย์ขนาดคงที่ของสตริงความยาวตัวแปร (ซึ่งโดยปกติchar *
)
มันไม่ได้สร้างความแตกต่าง แต่อย่างหลังอ่านได้มากขึ้น สิ่งที่คุณจะได้รับคืออาร์เรย์ของตัวชี้ถ่านเช่นเดียวกับรุ่นที่สองกล่าว สามารถแปลงโดยปริยายเป็นตัวชี้ถ่านคู่ได้เหมือนในเวอร์ชันแรกอย่างไรก็ตาม
คุณควรประกาศว่าเป็นchar *argv[]
เพราะวิธีการที่เทียบเท่ากันทั้งหมดในการประกาศสิ่งนั้นใกล้เคียงกับความหมายที่เข้าใจง่ายที่สุดนั่นคืออาร์เรย์ของสตริง
ถ่าน ** →ตัวชี้ไปยังตัวชี้อักขระและถ่าน * argv [] หมายถึงอาร์เรย์ของตัวชี้อักขระ เนื่องจากเราสามารถใช้ตัวชี้แทนอาร์เรย์ได้จึงสามารถใช้ได้ทั้งสองอย่าง
ฉันไม่เห็นข้อดีพิเศษของการใช้วิธีใดวิธีหนึ่งแทนที่จะใช้วิธีอื่น - ใช้หลักการที่สอดคล้องกับส่วนที่เหลือของโค้ดของคุณมากที่สุด
หากคุณต้องการจำนวนสตริงที่แตกต่างกันหรือแบบไดนามิก char ** อาจทำงานได้ง่ายกว่า หากจำนวนสตริงของคุณได้รับการแก้ไขแล้วควรใช้ char * var []
ฉันรู้ว่าสิ่งนี้ล้าสมัย แต่ถ้าคุณเพิ่งเรียนรู้ภาษาโปรแกรม C และไม่ได้ทำอะไรที่สำคัญกับมันอย่าใช้ตัวเลือกบรรทัดคำสั่ง
หากคุณไม่ได้ใช้อาร์กิวเมนต์บรรทัดคำสั่งอย่าใช้อย่างใดอย่างหนึ่ง เพียงแค่ประกาศฟังก์ชันหลักเป็นint main()
ถ้าคุณ
-help
, /?
หรือสิ่งอื่นใดที่จะไปหลังจากที่program name
ใน terminal หรือพร้อมรับคำสั่ง)ใช้สิ่งที่เหมาะสมกับคุณมากกว่า มิฉะนั้นให้ใช้int main()
หลังจากทั้งหมดหากคุณต้องการเพิ่มตัวเลือกบรรทัดคำสั่งคุณสามารถแก้ไขได้อย่างง่ายดายในภายหลัง