ฉันควรใช้ char ** argv หรือ char * argv []?


125

ฉันเพิ่งเรียนรู้ C และสงสัยว่าฉันควรใช้ตัวไหนในวิธีหลักของฉัน มีความแตกต่างกันหรือไม่? อันไหนธรรมดากว่ากัน?


2
ฉันชอบ 'char ** argv' ดังนั้นฉันจึงเห็นบ่อยกว่า แต่ทั้งสองอย่างถูกต้องและฉันจะไม่เปลี่ยนคำประกาศเพียงเพราะมันเขียนว่า 'char * argv []'
Jonathan Leffler

+1 เนื่องจากปัญหาที่ลึกกว่าของอาร์เรย์เทียบกับตัวชี้เป็นสิ่งสำคัญที่ต้องทำความเข้าใจให้ดี
RBerteig

2
เป็นคำถามที่ดีจริงๆ ขอบคุณ.
Frank V

5
อ่านหัวข้อที่ 6 ของคำถามที่พบบ่อยของcomp.lang.c ; เป็นคำอธิบายที่ดีที่สุดเกี่ยวกับความสัมพันธ์ระหว่างอาร์เรย์ C และตัวชี้ที่ฉันเคยเห็น สองจุดที่เกี่ยวข้อง: 1. char **argvเป็นว่าเทียบเท่ากับการchar *argv[]เป็นประกาศพารามิเตอร์ (และเพียงเป็นประกาศพารามิเตอร์) 2. อาร์เรย์ไม่ใช่ตัวชี้
Keith Thompson

คำตอบ:


160

ในฐานะที่คุณเป็นเพียงการเรียนรู้ 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;

2
ไม่รู้ว่ามีสิ่งนี้อยู่: * argv [คงที่ 1]
superlukas

12

คุณสามารถใช้อย่างใดอย่างหนึ่ง เทียบเท่ากันอย่างสมบูรณ์ ดู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 ที่มีประสบการณ์อ่านรหัสของคุณจะเห็นว่าทั้งสองอย่างใช้แทนกันได้ (ภายใต้เงื่อนไขที่เหมาะสม) เช่นเดียวกับผู้พูดภาษาอังกฤษที่มีประสบการณ์อ่านว่า "พวกเขา" และ "พวกเขา" ได้อย่างง่ายดาย

สิ่งที่สำคัญกว่าคือคุณต้องเรียนรู้ที่จะอ่านและรับรู้ว่าพวกเขามีความคล้ายคลึงกันเพียงใด คุณจะอ่านโค้ดมากกว่าที่คุณเขียนและคุณจะต้องรู้สึกสบายใจกับทั้งสองอย่างเท่า ๆ กัน


7
char * argv [] เทียบเท่ากับ char ** argv ได้ 100% เมื่อใช้เป็นประเภทพารามิเตอร์ของฟังก์ชัน ไม่มี "const" เกี่ยวข้องและไม่โดยปริยาย ทั้งสองเป็นตัวชี้ไปยังตัวชี้ไปยังอักขระ มันแตกต่างกันในเรื่องที่คุณประกาศ แต่คอมไพลเลอร์จะปรับประเภทของพารามิเตอร์ให้เป็นตัวชี้เป็นตัวชี้แม้ว่าคุณจะบอกว่าเป็นอาร์เรย์ก็ตาม ดังนั้นสิ่งต่อไปนี้จึงเหมือนกันทั้งหมด: void f (char * p [100]); เป็นโมฆะ f (ถ่าน * p []); เป็นโมฆะ f (ถ่าน ** p);
Johannes Schaub - litb

4
ใน C89 (ซึ่งคนส่วนใหญ่ใช้) ไม่มีวิธีใดที่จะได้รับประโยชน์จากการที่คุณประกาศว่าเป็นอาร์เรย์ (ดังนั้นในแง่ความหมายไม่สำคัญว่าคุณจะประกาศตัวชี้หรืออาร์เรย์ที่นั่น - ทั้งสองอย่างจะถูกนำมาใช้เป็น ตัวชี้) เริ่มต้นด้วย C99 คุณจะได้รับประโยชน์จากการประกาศเป็นอาร์เรย์ ต่อไปนี้กล่าวว่า: "p ไม่เป็นค่าว่างเสมอและชี้ไปยังพื้นที่ที่มีอย่างน้อย 100 ไบต์": โมฆะ f (ถ่าน p [คงที่ 100]); โปรดสังเกตว่า type-wise อย่างไรก็ตาม p ยังคงเป็นตัวชี้
Johannes Schaub - litb

5
(โดยเฉพาะอย่างยิ่ง & p จะให้ถ่าน ** แต่ไม่ใช่ถ่าน ( ) [100] ซึ่งในกรณีนี้ถ้า p *เป็นอาร์เรย์) ฉันประหลาดใจที่ยังไม่มีใครพูดถึงในคำตอบ ฉันถือว่าเป็นสิ่งสำคัญมากที่จะเข้าใจ
Johannes Schaub - litb

ส่วนตัวผมชอบchar**เพราะมันทำให้ผมนึกถึงก็ไม่ควรได้รับการปฏิบัติเป็น array sizeof[arr] / sizeof[*arr]จริงชอบทำ
raymai97

9

มันไม่ได้สร้างความแตกต่าง แต่ฉันใช้char *argv[]เพราะมันแสดงให้เห็นว่าเป็นอาร์เรย์ขนาดคงที่ของสตริงความยาวตัวแปร (ซึ่งโดยปกติchar *)


9

คุณสามารถใช้สองรูปแบบใดก็ได้เนื่องจากในอาร์เรย์ C และตัวชี้สามารถใช้แทนกันได้ในรายการพารามิเตอร์ของฟังก์ชัน ดูhttp://en.wikipedia.org/wiki/C_(programming_language)#Array-pointer_interchangeability


4

มันไม่ได้สร้างความแตกต่าง แต่อย่างหลังอ่านได้มากขึ้น สิ่งที่คุณจะได้รับคืออาร์เรย์ของตัวชี้ถ่านเช่นเดียวกับรุ่นที่สองกล่าว สามารถแปลงโดยปริยายเป็นตัวชี้ถ่านคู่ได้เหมือนในเวอร์ชันแรกอย่างไรก็ตาม


2

คุณควรประกาศว่าเป็นchar *argv[]เพราะวิธีการที่เทียบเท่ากันทั้งหมดในการประกาศสิ่งนั้นใกล้เคียงกับความหมายที่เข้าใจง่ายที่สุดนั่นคืออาร์เรย์ของสตริง


1

ถ่าน ** →ตัวชี้ไปยังตัวชี้อักขระและถ่าน * argv [] หมายถึงอาร์เรย์ของตัวชี้อักขระ เนื่องจากเราสามารถใช้ตัวชี้แทนอาร์เรย์ได้จึงสามารถใช้ได้ทั้งสองอย่าง


0

ฉันไม่เห็นข้อดีพิเศษของการใช้วิธีใดวิธีหนึ่งแทนที่จะใช้วิธีอื่น - ใช้หลักการที่สอดคล้องกับส่วนที่เหลือของโค้ดของคุณมากที่สุด


-2

หากคุณต้องการจำนวนสตริงที่แตกต่างกันหรือแบบไดนามิก char ** อาจทำงานได้ง่ายกว่า หากจำนวนสตริงของคุณได้รับการแก้ไขแล้วควรใช้ char * var []


-2

ฉันรู้ว่าสิ่งนี้ล้าสมัย แต่ถ้าคุณเพิ่งเรียนรู้ภาษาโปรแกรม C และไม่ได้ทำอะไรที่สำคัญกับมันอย่าใช้ตัวเลือกบรรทัดคำสั่ง

หากคุณไม่ได้ใช้อาร์กิวเมนต์บรรทัดคำสั่งอย่าใช้อย่างใดอย่างหนึ่ง เพียงแค่ประกาศฟังก์ชันหลักเป็นint main() ถ้าคุณ

  • ต้องการให้ผู้ใช้โปรแกรมของคุณสามารถลากไฟล์ไปยังโปรแกรมของคุณเพื่อที่คุณจะสามารถเปลี่ยนผลลัพธ์ของโปรแกรมของคุณด้วยหรือ
  • ต้องการจัดการตัวเลือกบรรทัดคำสั่ง ( -help, /?หรือสิ่งอื่นใดที่จะไปหลังจากที่program nameใน terminal หรือพร้อมรับคำสั่ง)

ใช้สิ่งที่เหมาะสมกับคุณมากกว่า มิฉะนั้นให้ใช้int main() หลังจากทั้งหมดหากคุณต้องการเพิ่มตัวเลือกบรรทัดคำสั่งคุณสามารถแก้ไขได้อย่างง่ายดายในภายหลัง


สิ่งนี้ไม่ตอบคำถาม
Lundin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.