ทำไมเราควรพิมพ์ struct บ่อยครั้งใน C?


406

ฉันได้เห็นหลายโปรแกรมที่ประกอบด้วยโครงสร้างเช่นเดียวกับด้านล่าง

typedef struct 
{
    int i;
    char k;
} elem;

elem user;

ทำไมมันจำเป็นบ่อยครั้ง เหตุผลเฉพาะหรือพื้นที่ที่บังคับใช้?


14
คำตอบที่ละเอียดและแม่นยำยิ่งขึ้น: stackoverflow.com/questions/612328/…
AbiusX

มีข้อเสียที่ฉันคิดว่าคุณไม่สามารถสร้างรายการลิงก์ที่มีโครงสร้างแบบไม่ระบุชื่อได้เนื่องจากบรรทัดstruct * ptrภายในโครงสร้างจะทำให้เกิดข้อผิดพลาด
Raghib Ahsan

9
'คำตอบที่ละเอียดและแม่นยำมากขึ้น' คือความแตกต่างระหว่าง struct และ typedef struct ใน C ++และมีความแตกต่างที่สำคัญระหว่าง C และ C ++ ในพื้นที่นี้ซึ่งทำให้คำตอบนั้นไม่เหมาะสมสำหรับคำถามเกี่ยวกับ C.
Jonathan Leffler

คำถามนี้มีคำนิยาม typedef struct เทียบกับ structที่มีคำตอบเป็นตัวเอก
Jonathan Leffler

2
OTOH, kernel.org/doc/html/v4.10/process/coding-style.htmlบอกเราว่าเราไม่ควรทำการพิมพ์ดีดดังกล่าว
glglgl

คำตอบ:


454

ที่เกร็ก Hewgill กล่าวว่า typedef หมายความว่าคุณไม่ต้องเขียนstructทั่วทุกสถานที่ ซึ่งไม่เพียงบันทึกการกดแป้นพิมพ์เท่านั้น แต่ยังสามารถสร้างรหัสที่สะอาดขึ้นอีกด้วย

สิ่งที่ชอบ

typedef struct {
  int x, y;
} Point;

Point point_new(int x, int y)
{
  Point a;
  a.x = x;
  a.y = y;
  return a;
}

สะอาดขึ้นเมื่อคุณไม่ต้องการเห็นคำหลัก "struct" ทั่วสถานที่ดูเหมือนว่ามีประเภทที่เรียกว่า "Point" ในภาษาของคุณ ซึ่งหลังจากtypedefนั้นเป็นกรณีที่ฉันเดา

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

typedef struct Point Point;

Point * point_new(int x, int y);

จากนั้นให้structคำจำกัดความในไฟล์การนำไปใช้งาน:

struct Point
{
  int x, y;
};

Point * point_new(int x, int y)
{
  Point *p;
  if((p = malloc(sizeof *p)) != NULL)
  {
    p->x = x;
    p->y = y;
  }
  return p;
}

ในกรณีหลังนี้คุณไม่สามารถส่งคืนค่า Point ตามเนื่องจากคำจำกัดความถูกซ่อนจากผู้ใช้ไฟล์ส่วนหัว นี่เป็นเทคนิคที่ใช้กันอย่างแพร่หลายในGTK +เป็นต้น

อัพเดตโปรดทราบว่านอกจากนี้ยังมีโครงการ C ที่ได้รับการยอมรับอย่างสูงซึ่งการใช้typedefเพื่อซ่อนstructนี้ถือเป็นความคิดที่ไม่ดีเคอร์เนล Linux น่าจะเป็นโครงการที่รู้จักกันดีที่สุด ดูบทที่ 5 ของเอกสาร Linux Kernel CodingStyleสำหรับคำพูดที่ทำให้โกรธของ Linus :) จุดของฉันคือว่า "ควร" ในคำถามอาจไม่ได้ตั้งอยู่ในหินหลังจากทั้งหมด


58
คุณไม่ควรใช้ตัวระบุที่มีเครื่องหมายขีดล่างตามด้วยตัวอักษรตัวพิมพ์ใหญ่พวกเขาจะสงวนไว้ (ดูหัวข้อ 7.1.3 วรรค 1) แม้ว่าจะไม่เป็นปัญหามากนัก แต่เป็นพฤติกรรมที่ไม่ได้กำหนดทางเทคนิคเมื่อคุณใช้ (7.1.3 วรรค 2)
dreamlax

16
@dreamlax: ในกรณีที่คนอื่นไม่เห็นชัดนั่นเป็นเพียงการเริ่มต้นตัวระบุด้วยการขีดเส้นใต้และตัวพิมพ์ใหญ่ที่คุณไม่ควรทำ คุณมีอิสระที่จะใช้สิ่งนั้นในกึ่งกลางของตัวระบุ
brianmearns

12
เป็นที่น่าสนใจว่าตัวอย่างที่ให้ไว้ที่นี่ (ซึ่ง typedef ป้องกันการใช้ "struct" "ทั่วทุกที่") จริง ๆ แล้วยาวกว่ารหัสเดียวกันโดยไม่ต้องพิมพ์ typedef เนื่องจากมันช่วยประหยัดหนึ่งใช้คำว่า "struct" smidgen ของสิ่งที่เป็นนามธรรมได้รับการเปรียบเทียบที่ค่อนข้างดีกับการทำให้งงงวยเพิ่มเติม
วิลเลียม Pursell

7
@Rerito fyi, หน้า 166 ของC99 ฉบับร่าง , ตัวระบุทั้งหมดที่เริ่มต้นด้วยเครื่องหมายขีดล่างและตัวอักษรตัวพิมพ์ใหญ่หรือเครื่องหมายขีดล่างอื่นสงวนไว้เสมอสำหรับการใช้งานใด ๆ และตัวระบุทั้งหมดที่ขึ้นต้นด้วยเครื่องหมายขีดเส้นใต้จะถูกสงวนไว้เสมอเพื่อใช้เป็นตัวระบุที่มีขอบเขตในทั้งช่องว่างทั่วไปและช่องว่างชื่อแท็ก
e2-e4

10
น่าสนใจพอแล้วแนวทางการเขียนโค้ดเคอร์เนลของ linux บอกว่าเราควรระมัดระวังมากกว่านี้เกี่ยวกับการใช้ typedefs (ส่วนที่ 5): kernel.org/doc/Documentation/CodingStyle
gsingh2011

206

เป็นเรื่องที่น่าอัศจรรย์ว่ามีคนกี่คนที่ทำสิ่งนี้ผิด โปรดอย่าพิมพ์ structsef ใน C โดยไม่จำเป็นต้องสร้างมลภาวะเนมสเปซส่วนกลางซึ่งโดยทั่วไปแล้วจะมีมลพิษมากในโปรแกรม C ขนาดใหญ่

นอกจากนี้ typedef'd structs ที่ไม่มีชื่อแท็กเป็นสาเหตุสำคัญของการกำหนดความสัมพันธ์ในไฟล์ส่วนหัวโดยไม่จำเป็น

พิจารณา:

#ifndef FOO_H
#define FOO_H 1

#define FOO_DEF (0xDEADBABE)

struct bar; /* forward declaration, defined in bar.h*/

struct foo {
  struct bar *bar;
};

#endif

ด้วยคำจำกัดความดังกล่าวไม่ใช้ typedefs จึงเป็นไปได้ที่หน่วยคอมไพล์จะรวม foo.h เพื่อให้ได้FOO_DEFคำจำกัดความ หากไม่พยายามยกเลิกการลงทะเบียนสมาชิก 'bar' ของfoostruct คุณไม่จำเป็นต้องรวมไฟล์ "bar.h"

นอกจากนี้เนื่องจากเนมสเปซมีความแตกต่างระหว่างชื่อแท็กและชื่อสมาชิกจึงเป็นไปได้ที่จะเขียนโค้ดที่อ่านได้มากเช่น:

struct foo *foo;

printf("foo->bar = %p", foo->bar);

เนื่องจากเนมสเปซนั้นแยกจากกันจึงไม่มีความขัดแย้งในการตั้งชื่อตัวแปรที่ตรงกับชื่อแท็ก struct

ถ้าฉันต้องรักษารหัสของคุณฉันจะลบ structeded typedef ของคุณ


37
สิ่งที่น่าอัศจรรย์ยิ่งกว่านั้นคือเมื่อ 13 เดือนหลังจากได้รับคำตอบฉันเป็นคนแรกที่โหวตขึ้น! typedef'ing structs เป็นหนึ่งในการละเมิดที่ยิ่งใหญ่ที่สุดของ C และไม่มีสถานที่ในรหัสที่เป็นลายลักษณ์อักษร typedef มีประโยชน์สำหรับการลบประเภทตัวชี้ฟังก์ชันที่ซับซ้อนและไม่มีประโยชน์อื่น ๆ
วิลเลียม Pursell

28
ปีเตอร์แวนเดอร์ลินเด็นยังทำคดีต่อต้านการพิมพ์ที่ซับซ้อนในหนังสือ "การเขียนโปรแกรม Expert C - Deep C Secrets" แก่นสารคือ: คุณต้องการที่จะรู้ว่าบางสิ่งบางอย่างเป็น struct หรือสหภาพไม่ซ่อนมัน
Jens

34
สไตล์การเขียนโค้ดเคอร์เนล Linux ห้ามกำหนดให้พิมพ์ structs อย่างชัดเจน บทที่ 5: Typedefs: "เป็นความผิดพลาดที่จะใช้ typedef สำหรับโครงสร้างและพอยน์เตอร์" kernel.org/doc/Documentation/CodingStyle
jasso

63
ประโยชน์ที่ได้รับคือการพิมพ์ "struct" ซ้ำแล้วซ้ำอีก และเมื่อพูดถึงมลภาวะทำไมคุณต้องการให้โครงสร้างและฟังก์ชัน / ตัวแปร / typedef ด้วยชื่อเดียวกันในเนมสเปซส่วนกลาง (เว้นแต่ว่าเป็น typedef สำหรับฟังก์ชันเดียวกันนั้น) typedef struct X { ... } Xรูปแบบที่ปลอดภัยคือการใช้งาน ด้วยวิธีนี้คุณสามารถใช้รูปแบบย่อXเพื่อกำหนดโครงสร้างได้ทุกที่ที่มีคำจำกัดความ แต่ยังสามารถประกาศล่วงหน้าและใช้struct Xเมื่อต้องการ
Pavel Minaev

7
ฉันเป็นส่วนตัวมากน้อยมากถ้าเคยใช้ typedef ฉันจะไม่พูดว่าคนอื่นไม่ควรใช้มันมันไม่ใช่สไตล์ของฉัน ฉันชอบที่จะเห็นโครงสร้างก่อนประเภทตัวแปรดังนั้นฉันรู้ว่ามันเป็นโครงสร้างทันที อาร์กิวเมนต์ที่พิมพ์ได้ง่ายกว่าคือบิตง่อยการมีตัวแปรที่เป็นตัวอักษรตัวเดียวก็ง่ายกว่าในการพิมพ์เช่นเดียวกันกับการทำให้สมบูรณ์อัตโนมัติว่าการพิมพ์ struct นั้นยากแค่ไหนในทุกที่
วันนาธาน

138

จากบทความเก่าโดย Dan Saks ( http://www.ddj.com/cpp/184403396?pgno=3 ):


กฎภาษา C สำหรับการตั้งชื่อ structs นั้นผิดปกติเล็กน้อย แต่ก็ไม่เป็นอันตรายเลย อย่างไรก็ตามเมื่อขยายไปยังคลาสใน C ++ กฎเดียวกันเหล่านั้นจะเปิดรอยแตกเล็กน้อยเพื่อให้ข้อบกพร่องในการรวบรวมข้อมูล

ใน C ชื่อปรากฏใน

struct s
    {
    ...
    };

เป็นแท็ก ชื่อแท็กไม่ใช่ชื่อประเภท รับคำนิยามข้างต้นประกาศเช่น

s x;    /* error in C */
s *p;   /* error in C */

เป็นข้อผิดพลาดใน C. คุณต้องเขียนเป็น

struct s x;     /* OK */
struct s *p;    /* OK */

ชื่อของสหภาพและการแจกแจงยังเป็นแท็กแทนชนิด

ใน C แท็กจะแตกต่างจากชื่ออื่น ๆ (สำหรับฟังก์ชั่นประเภทตัวแปรและค่าคงที่การแจงนับ) คอมไพเลอร์ C รักษาแท็กในตารางสัญลักษณ์ที่เป็นแนวคิดหากไม่แยกทางร่างกายจากตารางที่มีชื่ออื่นทั้งหมด ดังนั้นจึงเป็นไปได้ที่โปรแกรม C จะมีทั้งแท็กและชื่ออื่นที่มีตัวสะกดเหมือนกันในขอบเขตเดียวกัน ตัวอย่างเช่น,

struct s s;

เป็นการประกาศที่ถูกต้องซึ่งประกาศตัวแปร s ของ type struct s มันอาจไม่ใช่วิธีปฏิบัติที่ดี แต่คอมไพเลอร์ C ต้องยอมรับมัน ฉันไม่เคยเห็นเหตุผลว่าทำไม C จึงได้รับการออกแบบด้วยวิธีนี้ ฉันคิดเสมอว่ามันเป็นความผิดพลาด แต่ก็มี

โปรแกรมเมอร์หลายคน (รวมถึงของคุณอย่างแท้จริง) ชอบคิดชื่อ struct เป็นชื่อประเภทดังนั้นพวกเขาจึงกำหนดนามแฝงสำหรับแท็กโดยใช้ typedef ตัวอย่างเช่นการกำหนด

struct s
    {
    ...
    };
typedef struct s S;

ให้คุณใช้ S แทน struct s เช่นเดียวกับใน

S x;
S *p;

โปรแกรมไม่สามารถใช้ S เป็นชื่อของทั้งสองประเภทและตัวแปร (หรือฟังก์ชั่นหรือค่าคงที่การแจงนับ):

S S;    // error

ดีจัง.

ชื่อแท็กในนิยาม struct, union หรือ enum เป็นทางเลือก โปรแกรมเมอร์หลายคนพับนิยาม struct ลงใน typedef และแจกจ่ายด้วยแท็กโดยสิ้นเชิงดังที่:

typedef struct
    {
    ...
    } S;

บทความที่เชื่อมโยงยังมีการอภิปรายเกี่ยวกับวิธีการทำงานของ C ++ ที่ไม่ต้องการการใช้typedefอาจทำให้เกิดปัญหาการซ่อนชื่อที่ละเอียด เพื่อป้องกันปัญหาเหล่านี้เป็นความคิดที่ดีสำหรับtypedefคลาสและโครงสร้างของคุณใน C ++ เช่นกันแม้ว่าจะเห็นได้อย่างรวดเร็วก่อนว่ามันไม่จำเป็น ใน C ++ ด้วยการtypedefซ่อนชื่อกลายเป็นข้อผิดพลาดที่คอมไพเลอร์บอกคุณเกี่ยวกับแทนที่จะเป็นแหล่งซ่อนเร้นของปัญหาที่อาจเกิดขึ้น


2
ตัวอย่างหนึ่งที่ซึ่งชื่อแท็กเหมือนกับชื่อที่ไม่ใช่แท็กอยู่ในโปรแกรม (POSIX หรือ Unix) ที่มีint stat(const char *restrict path, struct stat *restrict buf)ฟังก์ชัน ที่นั่นคุณมีฟังก์ชั่นstatในพื้นที่ชื่อสามัญและstruct statในพื้นที่ชื่อแท็ก
Jonathan Leffler

2
คำสั่งของคุณ SS; // ข้อผิดพลาด .... ไม่ถูกต้องมันทำงานได้ดี ฉันหมายถึงคำสั่งของคุณว่า "เราไม่สามารถมีชื่อเดียวกันสำหรับแท็ก typedef และ var" คือผิด ... กรุณาตรวจสอบ
eRaisedToX

63

การใช้typedefavoids ต้องเขียนstructทุกครั้งที่คุณประกาศตัวแปรประเภทนั้น:

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct

2
ตกลงเราไม่ได้มีปัญหานั้นใน C ++ เหตุใดจึงไม่มีใครลบความผิดพลาดนั้นออกจากคอมไพเลอร์ของ C และทำให้เหมือนกับใน C ++ ok C ++ มีพื้นที่แอปพลิเคชันที่แตกต่างกันและดังนั้นจึงมีฟีเจอร์ขั้นสูง แต่เราไม่สามารถสืบทอดบางส่วนใน C ได้ เดิม C
Manoj Doubts

4
Manoj ชื่อแท็ก ("struct foo") เป็นสิ่งจำเป็นเมื่อคุณจำเป็นต้องกำหนด struct ที่อ้างอิงตัวเอง เช่นตัวชี้ "ถัดไป" ในรายการที่เชื่อมโยง ยิ่งไปกว่านั้นคอมไพเลอร์ใช้มาตรฐานและนั่นคือสิ่งที่มาตรฐานบอกว่าจะทำ
Michael Carman

41
มันไม่ใช่ความผิดพลาดในคอมไพเลอร์ C แต่เป็นส่วนหนึ่งของการออกแบบ พวกเขาเปลี่ยนมันสำหรับ C ++ ซึ่งฉันคิดว่าทำให้สิ่งต่าง ๆ ง่ายขึ้น แต่นั่นไม่ได้หมายความว่าพฤติกรรมของ C นั้นผิด
Herms

5
น่าเสียดายที่ 'โปรแกรมเมอร์' หลายคนกำหนด struct แล้วพิมพ์มันด้วยชื่อ 'ไม่เกี่ยวข้อง' บางอย่าง (เช่น struct myStruct ... ; typedef struct myStruct susan *; ในเกือบทุกกรณีที่ typedef นำไปสู่อะไรเลย ตัวแปร / พารามิเตอร์และนำไปสู่การผิดพลาดทุกคนรวมถึงผู้เขียนต้นฉบับของรหัส
user3629249

1
@ user3629249 ฉันเห็นด้วยกับคุณว่ารูปแบบการเขียนโปรแกรมดังกล่าวน่ากลัว แต่นี่ไม่ใช่เหตุผลในการลบล้างtypedefโครงสร้างไอเอ็นจีโดยทั่วไป typedef struct foo foo;นอกจากนี้คุณยังสามารถทำ แน่นอนว่าstructคำหลักนั้นไม่จำเป็นต้องมากกว่านั้นซึ่งอาจเป็นคำใบ้ที่มีประโยชน์ว่านามแฝงประเภทที่เราดูเป็นนามแฝงสำหรับโครงสร้าง แต่มันก็ไม่ได้เลวร้ายโดยทั่วไป ยังพิจารณากรณีที่ระบุของนามแฝงประเภทส่งผลให้การtypedefแสดงให้เห็นว่ามันเป็นนามแฝงสำหรับโครงสร้าง typedef struct foo foo_struct;fe:
RobertS สนับสนุน Monica Cellio

38

อีกหนึ่งเหตุผลที่ดีในการพิมพ์ enums และ structs เป็นผลมาจากปัญหานี้:

enum EnumDef
{
  FIRST_ITEM,
  SECOND_ITEM
};

struct StructDef
{
  enum EnuumDef MyEnum;
  unsigned int MyVar;
} MyStruct;

สังเกตเห็นการพิมพ์ผิดใน EnumDef ใน struct (Enu u mDef)? สิ่งนี้รวบรวมโดยไม่มีข้อผิดพลาด (หรือคำเตือน) และเป็น (ขึ้นอยู่กับการตีความตามตัวอักษรของมาตรฐาน C) ที่ถูกต้อง ปัญหาคือฉันเพิ่งสร้างนิยามการแจงนับใหม่ (ว่างเปล่า) ภายในโครงสร้างของฉัน ฉันไม่ได้ (ตามที่ตั้งใจ) โดยใช้คำนิยาม EnumDef ก่อนหน้า

ด้วยความผิดพลาดประเภท typdef ที่คล้ายกันจะส่งผลให้เกิดข้อผิดพลาดในการรวบรวมสำหรับการใช้ประเภทที่ไม่รู้จัก:

typedef 
{
  FIRST_ITEM,
  SECOND_ITEM
} EnumDef;

typedef struct
{
  EnuumDef MyEnum; /* compiler error (unknown type) */
  unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */

ฉันจะสนับสนุนให้พิมพ์คำสั่งและการแจกแจงตลอดเวลา

ไม่เพียง แต่จะบันทึกการพิมพ์บางส่วน (ไม่ได้ตั้งใจเล่นสำนวน)) แต่เพราะมันปลอดภัยกว่า


1
ยิ่งพิมพ์ผิดของคุณอาจตรงกับแท็กที่แตกต่างกัน ในกรณีของ struct สิ่งนี้อาจส่งผลให้โปรแกรมทั้งหมดคอมไพล์อย่างถูกต้องและมีพฤติกรรมที่ไม่ได้กำหนดรันไทม์
MM

3
คำจำกัดความนี้: 'typedef {FIRST_ITEM, SECOND_ITEM} EnumDef;' ไม่ได้กำหนด enum ฉันได้เขียนโปรแกรมขนาดใหญ่หลายร้อยรายการและมีโชคไม่ดีที่จะทำการบำรุงรักษาโปรแกรมอื่น ๆ ที่เขียนไว้ จากมือที่มีประสบการณ์การใช้ typedef บน struct จะทำให้เกิดปัญหาเท่านั้น หวังว่าโปรแกรมเมอร์จะไม่พิการดังนั้นพวกเขามีปัญหาในการพิมพ์คำจำกัดความเต็มเมื่อพวกเขาประกาศอินสแตนซ์ struct C ไม่ใช่แบบพื้นฐานดังนั้นการพิมพ์อักขระเพิ่มเติมบางรายการจึงไม่เป็นอันตรายต่อการทำงานของโปรแกรม
user3629249

2
สำหรับผู้ที่รู้สึกไม่พอใจที่จะพิมพ์มากกว่าจำนวนอักขระขั้นต่ำฉันขอแนะนำให้เข้าร่วมกลุ่มที่พยายามเขียนแอปพลิเคชันด้วยการกดแป้นจำนวนน้อยที่สุด อย่าใช้ 'ทักษะ' ใหม่ของพวกเขาในสภาพแวดล้อมการทำงานโดยเฉพาะอย่างยิ่งสภาพแวดล้อมการทำงานที่ดำเนินการอย่างจริงจังกับบทวิจารณ์ของเพื่อนร่วมงาน
3629249

หมายเหตุมันมักจะไม่แนะนำให้ใช้enumเป็นประเภทเพราะคอมไพเลอร์จำนวนมากปล่อยคำเตือนแปลก ๆ เมื่อใช้ 'ผิด' ตัวอย่างเช่นการเริ่มต้นenumถึง 0 อาจให้คำเตือน 'จำนวนเต็มคงที่ไม่ได้อยู่ใน enum' เตือน enumไม่อนุญาตให้ส่งต่อการประกาศไปข้างหน้า ควรใช้int(หรือunsigned int) แทน
yyny

5
ตัวอย่างนั้นไม่ได้รวบรวมและฉันจะคาดหวังให้ การรวบรวม Debug / test.o test.c: 10: 17: ข้อผิดพลาด: เขตข้อมูลมีประเภท 'enum EnuumDef' ที่ไม่สมบูรณ์ enum EnuumDef MyEnum; ^ test.c: 10: 8: หมายเหตุ: การประกาศไปข้างหน้าของ 'enum EnuumDef' enum EnuumDef MyEnum; สร้างข้อผิดพลาด ^ 1 แล้ว gnuc โดยมี std = c99
natersoz

30

สไตล์ลินุกซ์เคอร์เนลการเข้ารหัสบทที่ 5 ให้ดีข้อดีและข้อเสีย (ส่วนใหญ่ข้อเสีย) typedefของการใช้

โปรดอย่าใช้สิ่งต่าง ๆ เช่น "vps_t"

มันเป็น ความผิดพลาดในการใช้ typedef สำหรับโครงสร้างและพอยน์เตอร์ เมื่อคุณเห็น

vps_t a;

ในแหล่งข้อมูลมันหมายความว่าอย่างไร

ในทางตรงกันข้ามถ้ามันบอกว่า

struct virtual_container *a;

คุณสามารถบอกได้ว่า "a" คืออะไร

ผู้คนจำนวนมากคิดว่าคำว่า "ช่วยให้อ่านง่าย" ไม่เช่นนั้น มีประโยชน์สำหรับ:

(a) วัตถุทึบแสงทั้งหมด (ที่ typedef ใช้อย่างแข็งขันเพื่อซ่อนสิ่งที่เป็น)

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

บันทึก! ความทึบและ "ฟังก์ชั่นการเข้าถึง" ไม่ดีในตัวเอง เหตุผลที่เรามีพวกเขาสำหรับสิ่งต่าง ๆ เช่น pte_t เป็นต้นนั่นคือไม่มีข้อมูลที่สามารถเข้าถึงได้ที่นี่

(b) Clear จำนวนเต็มชนิดซึ่งสิ่งที่เป็นนามธรรมช่วยหลีกเลี่ยงความสับสนไม่ว่าจะเป็น "int" หรือ "long"

u8 / u16 / u32 นั้นเป็นตัวพิมพ์ดีดที่สมบูรณ์แบบแม้ว่ามันจะอยู่ในหมวดหมู่ (d) ดีกว่าที่นี่

บันทึก! อีกครั้ง - จะต้องมีเหตุผลสำหรับสิ่งนี้ หากสิ่งที่ "ไม่ได้ลงนามนาน" แล้วไม่มีเหตุผลที่จะทำ

typedef unsigned long myflags_t;

แต่ถ้ามีเหตุผลที่ชัดเจนว่าทำไมภายใต้สถานการณ์บางอย่างอาจเป็น "int ไม่ได้ลงนาม" และภายใต้การกำหนดค่าอื่น ๆ อาจจะ "ไม่ได้ลงนามยาว" แล้วโดยวิธีทั้งหมดไปข้างหน้าและใช้ typedef

(c) เมื่อคุณใช้ sparse เพื่อสร้าง a ชนิดใหม่สำหรับการตรวจสอบชนิด

(d) ประเภทใหม่ที่เหมือนกันกับประเภท C99 มาตรฐานในสถานการณ์พิเศษบางอย่าง

แม้ว่าจะใช้เวลาเพียงระยะเวลาสั้น ๆ เท่านั้นที่ตาและสมองจะคุ้นเคยกับประเภทมาตรฐานเช่น 'uint32_t' บางคนคัดค้านการใช้งานของพวกเขาอย่างไรก็ตาม

ดังนั้นประเภทเฉพาะ 'u8 / u16 / u32 / u64' ของ Linux และรายการเทียบเท่าที่ลงนามซึ่งเหมือนกันกับประเภทมาตรฐานจะได้รับอนุญาต - แม้ว่าจะไม่บังคับในรหัสใหม่ของคุณเอง

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

(e) ประเภทปลอดภัยสำหรับใช้ใน userspace

ในโครงสร้างบางอย่างที่ผู้ใช้สามารถมองเห็นได้เราไม่สามารถต้องการชนิด C99 และไม่สามารถใช้แบบฟอร์ม 'u32' ด้านบน ดังนั้นเราจึงใช้ __u32 และประเภทที่คล้ายกันในโครงสร้างทั้งหมดซึ่งแชร์กับ userspace

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

โดยทั่วไปตัวชี้หรือ struct ที่มีองค์ประกอบที่สมควรได้รับการเข้าถึงได้โดยตรงควรไม่เคยเป็น typedef


4
'ความโอ้อวดและ "ฟังก์ชั่นการเข้าถึง" ไม่ดีในตัวเอง' มีคนอธิบายได้ไหม ฉันคิดว่าการซ่อนข้อมูลและการห่อหุ้มจะเป็นความคิดที่ดีมาก
Yawar

5
@ เยาวราชฉันเพิ่งอ่านเอกสารนี้และมีความคิดเดียวกัน แน่นอน C ไม่ใช่วัตถุที่มุ่งเน้น แต่สิ่งที่เป็นนามธรรมยังคงเป็นสิ่งที่
Baldrickk

12

ปรากฎว่ามีข้อดีและข้อเสีย แหล่งข้อมูลที่มีประโยชน์คือหนังสือ "การเขียนโปรแกรมผู้เชี่ยวชาญ C" ( บทที่ 3 ) สั้น ๆ ใน C คุณมี namespaces หลายแท็กประเภทชื่อสมาชิกและตัวบ่งชี้ typedefแนะนำนามแฝงสำหรับประเภทและตั้งอยู่ในแท็กเนมสเปซ กล่าวคือ

typedef struct Tag{
...members...
}Type;

กำหนดสองสิ่ง หนึ่งแท็กในแท็กเนมสเปซและหนึ่งประเภทในเนมสเปซชนิด เพื่อให้คุณสามารถทำทั้งสองอย่างและType myType struct Tag myTagTypeคำประกาศเหมือนstruct Type myTypeหรือTag myTagTypeผิดกฎหมาย นอกจากนี้ในการประกาศเช่นนี้:

typedef Type *Type_ptr;

เรากำหนดตัวชี้ไปที่ประเภทของเรา ดังนั้นถ้าเราประกาศ:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;

แล้วvar1, var2และmyTagType1เป็นตัวชี้การพิมพ์ แต่myTagType2ไม่ได้

ในหนังสือที่กล่าวถึงข้างต้นกล่าวถึงว่าการพิมพ์การจัดเรียงไม่ได้มีประโยชน์มากเพราะช่วยให้โปรแกรมเมอร์ไม่สามารถเขียนคำว่า struct ได้ อย่างไรก็ตามฉันมีข้อคัดค้านเหมือนโปรแกรมเมอร์ C คนอื่น ๆ แม้ว่าบางครั้งมันจะทำให้สับสนในบางชื่อ (นั่นคือเหตุผลที่ไม่แนะนำให้ใช้ในรหัสฐานขนาดใหญ่เช่นเคอร์เนล) เมื่อคุณต้องการใช้ความหลากหลายใน C มันช่วยให้ดูรายละเอียดได้ที่นี่ ตัวอย่าง:

typedef struct MyWriter_t{
    MyPipe super;
    MyQueue relative;
    uint32_t flags;
...
}MyWriter;

คุณทำได้:

void my_writer_func(MyPipe *s)
{
    MyWriter *self = (MyWriter *) s;
    uint32_t myFlags = self->flags;
...
}

ดังนั้นคุณสามารถเข้าถึงสมาชิกด้านนอก ( flags) โดย struct ภายใน ( MyPipe) ผ่านการคัดเลือกนักแสดง สำหรับฉันมันมีความสับสนน้อยกว่าที่จะแสดงทั้งแบบ(struct MyWriter_ *) s;ทุกครั้งที่คุณต้องการใช้งานฟังก์ชั่นดังกล่าว ในกรณีเหล่านี้อ้างอิงสั้น ๆ เป็นเรื่องใหญ่โดยเฉพาะอย่างยิ่งถ้าคุณใช้เทคนิคในรหัสของคุณ

ในที่สุดด้านสุดท้ายที่มีtypedefประเภท ed คือการไม่สามารถขยายพวกเขาในทางตรงกันข้ามกับแมโคร ตัวอย่างเช่นคุณมี:

#define X char[10] or
typedef char Y[10]

จากนั้นคุณสามารถประกาศ

unsigned X x; but not
unsigned Y y;

เราไม่สนใจสิ่งนี้สำหรับ structs เพราะไม่ได้ใช้กับตัวระบุหน่วยเก็บข้อมูล ( volatileและconst)


1
MyPipe *s; MyWriter *self = (MyWriter *) s;และคุณเพิ่งเสียนามแฝงที่เข้มงวด
Jonathon Reinhart

@JonathonReinhart มันจะเป็นตัวอย่างที่จะกล่าวถึงวิธีการนี้สามารถหลีกเลี่ยงได้ตัวอย่างเช่น GTK + ที่มีความสุขมาก ๆ รอบตัวมันทำงานอย่างไร: bugzilla.gnome.org/show_bug.cgi?id=140722 / mail.gnome.org/archives/gtk -devel-list / 2004-April / msg00196.html
underscore_d

"typedef แนะนำนามแฝงสำหรับประเภทและตั้งอยู่ในเนมสเปซ tag กล่าวคือ" typedef struct Tag{ ...members... }Type; "กำหนดสองสิ่ง" ไม่สมเหตุสมผล หาก typedef กำหนดแท็กแล้ว 'ประเภท' ที่นี่ควรเป็นแท็ก ความจริงก็คือความหมายกำหนด 2 แท็กและประเภท 1 (หรือ 2 ประเภทและ 1 แท็กไม่แน่ใจว่า.) struct Tag, และTag เป็นประเภทแน่นอน เป็นแท็ก แต่ความสับสนก็คือไม่ว่าจะเป็นแท็กหรือประเภทTypestruct TagTagType
Qwertyzw

12

ฉันไม่คิดว่าการประกาศล่วงหน้าจะเป็นไปได้ด้วย typedef การใช้ struct, enum และ union อนุญาตให้ส่งต่อการประกาศเมื่อการพึ่งพา (รู้เกี่ยวกับ) เป็นแบบสองทิศทาง

สไตล์: การใช้ typedef ใน C ++ ค่อนข้างสมเหตุสมผล เกือบจะจำเป็นเมื่อจัดการกับแม่แบบที่ต้องการพารามิเตอร์หลายตัวและ / หรือตัวแปร typedef ช่วยให้การตั้งชื่อตรง

ไม่เช่นนั้นในภาษา C การใช้ typedef ส่วนใหญ่มักจะไม่มีจุดประสงค์ แต่เพื่อทำให้สับสนการใช้งานโครงสร้างข้อมูล เนื่องจากมีเพียง {struct (6), enum (4), union (5)} จำนวน keystrokes ที่ใช้ในการประกาศชนิดข้อมูลที่แทบจะไม่ใช้นามแฝงของ struct เป็นข้อมูลที่พิมพ์สหภาพหรือ struct หรือไม่? การใช้การประกาศที่ไม่พิมพ์ผิดตรงไปตรงมาช่วยให้คุณทราบได้ทันทีว่ามันเป็นประเภทใด

โปรดสังเกตว่าการเขียนลีนุกซ์ด้วยการหลีกเลี่ยงอย่างเข้มงวดเกี่ยวกับเรื่องไร้สาระนามแฝง typedef นี้นำมาอย่างไร ผลลัพธ์ที่ได้คือสไตล์ที่เรียบง่ายและสะอาดตา


10
ทำความสะอาดจะไม่ทำซ้ำstructทุกที่ ... ประเภทของ Typedef ที่สร้างขึ้นใหม่ คุณใช้อะไร ประเภท เราไม่สนใจว่ามันคือโครงสร้างสหภาพหรือ enum นั่นคือสาเหตุที่เราพิมพ์มัน
GManNickG

13
ไม่มีเราไม่สนใจว่ามันเป็น struct หรือสหภาพเมื่อเทียบกับ enum หรือบางประเภทอะตอม คุณไม่สามารถบีบบังคับให้เป็นจำนวนเต็มหรือตัวชี้ (หรือประเภทอื่น ๆ สำหรับเรื่องนั้น) ซึ่งเป็นสิ่งที่คุณต้องเก็บบริบทบางครั้ง การมีคำหลัก 'struct' หรือ 'union' ช่วยปรับปรุงสถานที่ให้เหตุผล ไม่มีใครบอกว่าคุณต้องรู้ว่ามีอะไรอยู่ในโครงสร้าง
Bernd Jendrissek

1
@BerndJendrissek: Structs และ unions แตกต่างจากประเภทอื่น ๆ แต่รหัสลูกค้าควรดูแลเกี่ยวกับสองสิ่งเหล่านี้ (struct หรือ union) สิ่งที่FILEเป็น?
supercat

4
@supercat FILE คือการใช้ typedef ให้ดี ฉันคิดว่า typedef มากเกินไปไม่ใช่ว่ามันเป็นความผิดของภาษา IMHO ที่ใช้ typedef สำหรับทุกสิ่งคือกลิ่นรหัส "การเก็งกำไรมากเกินไป" โปรดสังเกตว่าคุณประกาศตัวแปรเป็น FILE * foo ไม่เหมือนกับ FILE foo สำหรับฉันแล้วเรื่องนี้
Bernd Jendrissek

2
@supercat: "หากตัวแปรที่ระบุไฟล์เป็นประเภทไฟล์แทนที่จะเป็นไฟล์ * ... " แต่นั่นเป็นความคลุมเครือที่ typedefs เปิดใช้งาน! เราเพิ่งคุ้นเคยกับการใช้ไฟล์ * ดังนั้นเราจึงไม่หงุดหงิดกับมัน แต่ทุกครั้งที่คุณเพิ่ม typedef คุณจะแนะนำอีกเล็กน้อยของค่าใช้จ่ายในการรับรู้: API นี้ต้องการ foo_t args หรือ foo_t * หรือไม่ การถือ 'struct' อย่างชัดเจนพร้อมปรับปรุงตำแหน่งของการให้เหตุผลหากมีค่าใช้จ่ายเพิ่มเติมอีกสองสามตัวอักษรต่อหนึ่งนิยามฟังก์ชัน
Bernd Jendrissek

4

มาเริ่มกันที่พื้นฐานและหาทางกัน

นี่คือตัวอย่างของคำนิยามโครงสร้าง:

struct point
  {
    int x, y;
  };

ที่นี่ชื่อpointเป็นตัวเลือก

โครงสร้างสามารถประกาศได้ในระหว่างการนิยามหรือหลังจากนั้น

ประกาศในระหว่างการกำหนด

struct point
  {
    int x, y;
  } first_point, second_point;

ประกาศหลังจากนิยาม

struct point
  {
    int x, y;
  };
struct point first_point, second_point;

ตอนนี้ให้ระวังกรณีสุดท้ายข้างต้นอย่างระมัดระวัง คุณต้องเขียนstruct pointเพื่อประกาศโครงสร้างของประเภทนั้นถ้าคุณตัดสินใจที่จะสร้างประเภทนั้นในภายหลังในรหัสของคุณ

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

typedef struct point
  {
    int x, y;
  } Points;

Points first_point, second_point;

คำเตือนขณะตั้งชื่อประเภทที่คุณกำหนดเอง

ไม่มีสิ่งใดป้องกันคุณจากการใช้คำต่อท้าย _t ในตอนท้ายของชื่อประเภทที่กำหนดเองของคุณ แต่ POSIX มาตรฐานขอสงวนการใช้คำต่อท้าย _t เพื่อแสดงถึงชื่อประเภทไลบรารีมาตรฐาน


3

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

GTK + นอกเหนือจากกันฉันไม่แน่ใจว่ามีการใช้ tagname เหมือนปกติเหมือนกับ typedef ของประเภท struct ดังนั้นใน C ++ ที่เป็นที่รู้จักและคุณสามารถละเว้นคำหลัก struct และใช้ tagname เป็นชื่อประเภทด้วย:


    struct MyStruct
    {
      int i;
    };

    // The following is legal in C++:
    MyStruct obj;
    obj.i = 7;


1

typedef จะไม่จัดเตรียมชุดโครงสร้างข้อมูลที่ขึ้นอยู่กับผู้ร่วม สิ่งนี้คุณไม่สามารถทำได้ด้วย typdef:

struct bar;
struct foo;

struct foo {
    struct bar *b;
};

struct bar {
    struct foo *f;
};

แน่นอนคุณสามารถเพิ่ม:

typedef struct foo foo_t;
typedef struct bar bar_t;

อะไรคือจุดประสงค์ของสิ่งนั้น?


1

A> โรคเอดส์ typdef ในความหมายและเอกสารของโปรแกรมโดยการอนุญาตให้สร้างคำพ้องความหมายมากขึ้นสำหรับชนิดข้อมูล นอกจากนี้ยังช่วยปรับพารามิเตอร์โปรแกรมสำหรับปัญหาการพกพา (K&R, pg147, C prog lang)

B> งาน โครงสร้างกำหนดประเภท โครงสร้างช่วยให้จัดกลุ่มคอลเลกชัน vars ได้สะดวกเพื่อความสะดวกในการจัดการ (K&R, pg127, C prog lang.) เป็นหน่วยเดียว

C> typedef'ing struct อธิบายไว้ใน A ข้างต้น

D> สำหรับฉัน structs เป็นประเภทที่กำหนดเองหรือภาชนะบรรจุหรือคอลเลกชันหรือเนมสเปซหรือประเภทที่ซับซ้อนในขณะที่ typdef เป็นเพียงวิธีในการสร้างชื่อเล่นมากขึ้น


0

ปรากฎใน C99 typedef เป็นสิ่งจำเป็น มันล้าสมัย แต่เครื่องมือจำนวนมาก (ala HackRank) ใช้ c99 เป็นการใช้งาน C บริสุทธิ์ และจำเป็นต้องมี typedef

ฉันไม่ได้บอกว่าพวกเขาควรเปลี่ยน (อาจมีสองตัวเลือก C) หากความต้องการเปลี่ยนแปลงไปพวกเราที่กำลังศึกษาเพื่อสัมภาษณ์ในเว็บไซต์จะเป็น SOL


2
"ต้องเปิดใน C99 typedef" คุณหมายถึงอะไร
Julien Lopez

คำถามคือเกี่ยวกับการไม่ได้C C++ในCtypedefs คือ 'ต้องการ' (และมักจะเป็น) 'ต้องใช้' ในขณะที่คุณจะไม่สามารถที่จะประกาศตัวแปรPoint varName;และมีประเภทจะตรงกันกับที่ไม่มีstruct Point; typedef struct Point Point;
yyny

0

ในภาษาการเขียนโปรแกรม 'C' คำหลัก 'typedef' จะใช้เพื่อประกาศชื่อใหม่สำหรับวัตถุบางอย่าง (struct, array, function..enum type) ตัวอย่างเช่นฉันจะใช้ 'struct-s' ใน 'C' เรามักจะประกาศ 'struct' นอกฟังก์ชั่น 'main' ตัวอย่างเช่น:

struct complex{ int real_part, img_part }COMPLEX;

main(){

 struct KOMPLEKS number; // number type is now a struct type
 number.real_part = 3;
 number.img_part = -1;
 printf("Number: %d.%d i \n",number.real_part, number.img_part);

}

ทุกครั้งที่ฉันตัดสินใจใช้ประเภท struct ฉันจะต้องใช้คำสำคัญ 'struct' บางอย่าง '' ชื่อ '' typedef 'นี้จะเปลี่ยนชื่อประเภทนั้นและฉันสามารถใช้ชื่อใหม่ในโปรแกรมของฉันทุกครั้งที่ฉันต้องการ ดังนั้นรหัสของเราจะเป็น:

typedef struct complex{int real_part, img_part; }COMPLEX;
//now COMPLEX is the new name for this structure and if I want to use it without
// a keyword like in the first example 'struct complex number'.

main(){

COMPLEX number; // number is now the same type as in the first example
number.real_part = 1;
number.img)part = 5;
printf("%d %d \n", number.real_part, number.img_part);

}

หากคุณมีวัตถุท้องถิ่น (struct, array, มีค่า) ที่จะใช้ในโปรแกรมทั้งหมดของคุณคุณสามารถตั้งชื่อโดยใช้ 'typedef'


-2

ในภาษา C, struct / union / enum เป็นคำสั่งแมโครที่ประมวลผลโดยตัวประมวลผลภาษา C (อย่าทำผิดพลาดกับตัวประมวลผลล่วงหน้าที่ใช้ "#include" และอื่น ๆ )

ดังนั้น:

struct a
{
   int i;
};

struct b
{
   struct a;
   int i;
   int j;
};

struct b ถูกใช้เป็นอย่างนี้:

struct b
{
    struct a
    {
        int i;
    };
    int i;
    int j;
}

และในเวลาคอมไพล์มันก็พัฒนาบนสแต็คตามที่ต้องการ: b: int ai int i int j

นั่นก็เป็นเหตุผลว่าทำไมมันถึงไร้สาระที่จะมีโครงสร้างแบบพึ่งพาตนเอง, ตัวประมวลผล pre C ในรอบการวนซ้ำที่ไม่สามารถยุติได้

typedef เป็นตัวระบุชนิดซึ่งหมายถึงเฉพาะคอมไพเลอร์ C ที่ประมวลผลและสามารถทำเช่นเดียวกับที่เขาต้องการสำหรับการปรับใช้โค้ดแอสเซมเบลอร์ให้เหมาะสม นอกจากนี้ยังไม่ใช้จ่ายสมาชิกประเภท par อย่างโง่เขลาเหมือนpréprocessorทำกับ structs แต่ใช้อัลกอริทึมการก่อสร้างอ้างอิงที่ซับซ้อนมากขึ้นดังนั้นการก่อสร้างเช่น:

typedef struct a A; //anticipated declaration for member declaration

typedef struct a //Implemented declaration
{
    A* b; // member declaration
}A;

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

นี่หมายความว่าใน C typedefs นั้นใกล้เคียงกับคลาส C ++ มากกว่า structon ที่อ้างว้าง


3
ไม่ยากเลยที่จะมีโครงสร้างอ้างอิงตนเอง struct foo {struct foo * ถัดไป; สิ่ง int; }
Bernd Jendrissek

4
...อะไร? การบอกผู้ประมวลผลล่วงหน้าเพื่ออธิบายความละเอียดstructsและtypedefs นั้นไม่ดีพอ แต่การเขียนที่เหลือของคุณนั้นสับสนจนฉันคิดว่ามันยากที่จะรับข้อความใด ๆ จากมัน สิ่งหนึ่งที่ผมสามารถพูดได้ แต่เป็นที่ความคิดของคุณว่าไม่ใช่typedefd structไม่สามารถคาดการณ์ล่วงหน้าหรือประกาศใช้เป็นสีขาวขุ่น (ตัวชี้) สมาชิกเป็นเท็จโดยสิ้นเชิง ในตัวอย่างที่ 1 ของคุณstruct bสามารถมี a struct a *, ไม่typedefจำเป็น การยืนยันที่structเป็นเพียงการขยายตัวในระดับมหึมาและนั่นtypedefทำให้พวกเขามีพลังอำนาจใหม่ที่ปฏิวัติวงการได้อย่างไม่ถูกต้องอย่างเจ็บปวด
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.