วิธีง่ายๆในการใช้ตัวแปรประเภท enum เป็นสตริงใน C?


89

นี่คือสิ่งที่ฉันพยายามทำ:

ฉันกำลังพยายามเขียนฟังก์ชันที่จะทำกรณีสวิตช์คล้ายกับต่อไปนี้:

แทนที่จะกำหนดทุกกรณีมีวิธีตั้งค่าโดยใช้ตัวแปร enum เหมือนที่ฉันพยายามทำข้างต้นหรือไม่?

คำตอบ:


16

ไม่มีโซลูชันในตัว วิธีที่ง่ายที่สุดคือการใช้อาร์เรย์char*ที่ค่า int ของ enum ทำดัชนีเป็นสตริงที่มีชื่อสื่อความหมายของ enum นั้น หากคุณมีค่าเบาบางenum(รายการที่ไม่ได้เริ่มต้นที่ 0 หรือมีช่องว่างในการกำหนดหมายเลข) โดยที่การintแมปบางส่วนนั้นสูงพอที่จะทำให้การแมปแบบอาร์เรย์ไม่สามารถทำได้คุณสามารถใช้ตารางแฮชแทนได้


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

70

เทคนิคจากการสร้างบางสิ่งบางอย่างทั้งตัวระบุ C และสตริง? สามารถใช้ได้ที่นี่

ตามปกติสำหรับสิ่งก่อนโปรเซสเซอร์เช่นนี้การเขียนและทำความเข้าใจส่วนของตัวประมวลผลล่วงหน้าอาจเป็นเรื่องยากและรวมถึงการส่งผ่านมาโครไปยังมาโครอื่น ๆ และเกี่ยวข้องกับการใช้ตัวดำเนินการ # และ ## แต่การใช้งานนั้นง่ายมาก ฉันพบว่าสไตล์นี้มีประโยชน์มากสำหรับ enums ที่ยาวนานซึ่งการรักษารายการเดิมสองครั้งอาจเป็นปัญหา

รหัสโรงงาน - พิมพ์เพียงครั้งเดียวโดยปกติจะซ่อนอยู่ในส่วนหัว:

enumFactory.h:

โรงงานที่ใช้

someEnum.h:

someEnum.cpp:

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

เปรียบเทียบกับ X-Macros โดยใช้ #include / #define / #undef

แม้ว่าสิ่งนี้จะคล้ายกับ X-Macros อื่น ๆ ที่กล่าวถึง แต่ฉันคิดว่าโซลูชันนี้มีความสง่างามกว่าที่ไม่ต้องการ #undefing อะไรเลยซึ่งช่วยให้คุณซ่อนสิ่งที่ซับซ้อนได้มากขึ้นในไฟล์ส่วนหัว - ไฟล์ส่วนหัว เป็นสิ่งที่คุณไม่ได้สัมผัสเลยเมื่อคุณต้องการกำหนด enum ใหม่ดังนั้นนิยาม enum ใหม่จึงสั้นและสะอาดกว่ามาก


2
ฉันไม่แน่ใจว่าคุณจะพูดได้อย่างไรว่านี่ดีกว่า / แย่กว่า x-macros - นี่คือ x-macros SOME_ENUM(XX)เป็นสิ่ง X-มาโคร (จะแม่นยำแบบฟอร์ม "ผู้ใช้" ที่ผ่านXXฟังก์ชั่นมากกว่าการใช้#def #undef) และจากนั้นในเปิดทั้ง X-MACRO จะผ่านไปแล้ว DEFINE_ENUM ที่จะใช้มัน อย่านำอะไรออกไปจากการแก้ปัญหา - ได้ผลดี เพียงเพื่อชี้แจงว่าเป็นการใช้มาโคร X
BeeOnRope

1
@BeeOnRope ความแตกต่างที่คุณสังเกตเห็นมีความสำคัญและทำให้โซลูชันนี้แตกต่างจากมาโคร X ที่เป็นสำนวน (เช่นตัวอย่างของ Wikipedia ) ข้อดีของการส่งXXต่อซ้ำ#defineคือรูปแบบเดิมสามารถใช้ในการขยายมาโครได้ โปรดสังเกตว่าโซลูชันอื่น ๆ เพียงวิธีเดียวที่กระชับเท่านี้ล้วนจำเป็นต้องมีการสร้างและการรวมไฟล์แยกต่างหากหลายไฟล์เพื่อกำหนด enum ใหม่
pmttavara

1
เคล็ดลับอีกประการหนึ่งคือการใช้ชื่อ enum เป็นชื่อมาโคร คุณก็สามารถเขียน#define DEFINE_ENUM(EnumType) ...แทนENUM_DEF(...)ด้วยและมีการพูดของผู้ใช้EnumType(...) #define SomeEnum(XX) ...ตัวประมวลผลก่อน C จะขยายตามบริบทเป็นการSomeEnumเรียกมาโครเมื่อตามด้วยวงเล็บและเป็นโทเค็นปกติ (แน่นอนว่าสิ่งนี้ทำให้เกิดปัญหาหากผู้ใช้ชอบที่จะใช้SomeEnum(2)เพื่อส่งไปยังประเภท enum มากกว่า(SomeEnum)2หรือstatic_cast<SomeEnum>(2))
pmttavara

1
@pmttavara - แน่ใจว่าถ้าการค้นหาอย่างรวดเร็วบ่งชี้ใด ๆ ใช้บ่อยที่สุดของ X-แมโครใช้ชื่อภายในแมโครคงพร้อมด้วยและ#define #undefคุณไม่เห็นด้วยหรือไม่ว่า "ฟอร์มผู้ใช้" (แนะนำเช่นที่ด้านล่างของบทความนี้ ) เป็นประเภทของ x-macro หรือไม่? แน่นอนว่าฉันมักจะเรียกมันว่า x-macro เช่นกันและใน C codebases ที่ฉันเคยอยู่เมื่อเร็ว ๆ นี้มันเป็นรูปแบบที่พบบ่อยที่สุด (นั่นคือการสังเกตที่เอนเอียงอย่างชัดเจน) ฉันอาจจะแยกวิเคราะห์ OP ผิด
BeeOnRope

2
@BeeOnRope ข้อความปัจจุบันเป็นผลมาจากการแก้ไขในขณะที่คุณทำให้ฉันเชื่อว่านี่คือ x-macro แม้ว่าจะเป็นรูปแบบที่ใช้น้อยกว่า (หรืออย่างน้อยหนึ่งรายการที่กล่าวถึงในบทความ) ในตอนนั้น
สุมา

62

3
นี่คือสิ่งที่ cpp สร้างขึ้นมาเพื่อ +1.
Derrick Turk

6
นี่เป็นคำตอบที่ดีดูเหมือนว่าจะเป็นสิ่งที่ดีที่สุดที่สามารถทำได้โดยไม่ต้องใช้เครื่องมือพิเศษและฉันเคยทำแบบนี้มาก่อน แต่มันก็ยังไม่เคยรู้สึกว่า 'ถูกต้อง' จริงๆและฉันไม่เคยชอบทำมันเลย ...
Michael Burr

การเปลี่ยนแปลงเล็กน้อย: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];ในdefs.hไฟล์ - สิ่งนี้จะประกาศตารางชื่อของคุณในไฟล์ที่คุณใช้ (ไม่สามารถหาวิธีที่ดีในการประกาศขนาดตารางได้) นอกจากนี้โดยส่วนตัวแล้วฉันจะทิ้งอัฒภาคสุดท้ายไว้ แต่ข้อดีก็เป็นที่ถกเถียงกันไม่ว่าจะด้วยวิธีใด
Chris Lutz

1
@ Bill, รำคาญกับทำไมtypในบรรทัด#define ENUM_END(typ) };?
Pacerier

สิ่งนี้ใช้ไม่ได้ในที่ที่ฉันต้องการกำหนดมาโครเป็น "ONE = 5"
UKMonkey

13

มีแน่นอนวิธีที่จะทำเช่นนี้ - การใช้งานX () แมโคร มาโครเหล่านี้ใช้ตัวประมวลผลก่อน C เพื่อสร้าง enums อาร์เรย์และบล็อกโค้ดจากรายการแหล่งข้อมูล คุณจะต้องเพิ่มรายการใหม่ใน #define ที่มีมาโคร X () เท่านั้น คำสั่งสวิตช์จะขยายโดยอัตโนมัติ

ตัวอย่างของคุณสามารถเขียนได้ดังนี้:

มีวิธีที่มีประสิทธิภาพมากขึ้น (เช่นการใช้ X Macros เพื่อสร้างสตริงอาร์เรย์และดัชนี enum) แต่นี่เป็นการสาธิตที่ง่ายที่สุด


8

ฉันรู้ว่าคุณมีคำตอบที่ดีสองสามข้อ แต่คุณรู้เกี่ยวกับตัวดำเนินการ # ในตัวประมวลผลก่อนหน้าหรือไม่

ช่วยให้คุณทำสิ่งนี้:


char const *kConstStr[]
Anne van Rossum

6

C หรือ C ++ ไม่มีฟังก์ชันนี้แม้ว่าฉันจะต้องการบ่อยครั้งก็ตาม

รหัสต่อไปนี้ใช้งานได้แม้ว่าจะเหมาะที่สุดสำหรับ enums ที่ไม่กระจัดกระจาย

โดยไม่กระจัดกระจายฉันไม่ได้หมายถึงรูปแบบ

เนื่องจากมีช่องว่างมาก

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


6

จูบ. คุณจะทำสวิตช์ / เคสอื่น ๆ ทุกประเภทด้วย enums ของคุณดังนั้นทำไมการพิมพ์จึงแตกต่างกัน? การลืมเคสในกิจวัตรการพิมพ์ของคุณไม่ใช่เรื่องใหญ่เมื่อคุณคิดว่ามีสถานที่อื่น ๆ อีกประมาณ 100 แห่งที่คุณสามารถลืมเคสได้ เพียงคอมไพล์ -Wall ซึ่งจะเตือนถึงกรณีที่ไม่ตรงกันทั้งหมด อย่าใช้ "ค่าเริ่มต้น" เพราะจะทำให้สวิตช์หมดจดและคุณจะไม่ได้รับคำเตือน แทนที่จะปล่อยให้สวิตช์ออกและจัดการกับกรณีเริ่มต้นเช่นนั้น ...



4

การใช้boost :: preprocessorทำให้เป็นไปได้ที่จะแก้ปัญหาที่สวยงามดังต่อไปนี้:

ขั้นตอนที่ 1: รวมไฟล์ส่วนหัว:

ขั้นตอนที่ 2: ประกาศอ็อบเจ็กต์การแจงนับด้วยไวยากรณ์ต่อไปนี้:

ขั้นตอนที่ 3: ใช้ข้อมูลของคุณ:

รับจำนวนองค์ประกอบ:

รับสตริงที่เกี่ยวข้อง:

การรับค่า enum จากสตริงที่เกี่ยวข้อง:

มันดูสะอาดและกะทัดรัดโดยไม่มีไฟล์เพิ่มเติมให้รวม รหัสที่ฉันเขียนภายใน EnumUtilities.h มีดังต่อไปนี้:

มีข้อ จำกัด บางประการ ได้แก่ ตัวประมวลผล boost :: preprocessor ในกรณีนี้รายการค่าคงที่ต้องมีขนาดไม่เกิน 64 องค์ประกอบ

ตามตรรกะเดียวกันคุณสามารถคิดที่จะสร้าง enum กระจัดกระจาย:

ในกรณีนี้ไวยากรณ์คือ:

การใช้งานจะคล้ายกับด้านบน (ลบฟังก์ชัน eName ## 2Enum ซึ่งคุณสามารถลองอนุมานจากไวยากรณ์ก่อนหน้านี้ได้)

ฉันทดสอบบน mac และ linux แต่โปรดทราบว่า boost :: preprocessor อาจไม่สามารถพกพาได้อย่างสมบูรณ์


3

ด้วยการรวมเทคนิคบางอย่างเข้าด้วยกันฉันได้รูปแบบที่ง่ายที่สุด:


2

หากคุณใช้ gcc สามารถใช้:

จากนั้นก็โทรหาเช่น


1

ตรวจสอบความคิดที่หมู่ Dynamics ห้องปฏิบัติการวิจัย - คลังบทความของบล็อก ฉันพบสิ่งนี้เมื่อต้นปีนี้ - ฉันลืมบริบทที่แน่นอนที่ฉันเจอ - และได้ดัดแปลงเป็นรหัสนี้ เราสามารถอภิปรายข้อดีของการเพิ่ม E ที่ด้านหน้า ใช้ได้กับปัญหาเฉพาะที่ได้รับการแก้ไข แต่ไม่ใช่ส่วนหนึ่งของวิธีแก้ปัญหาทั่วไป ฉันเก็บสิ่งนี้ไว้ในโฟลเดอร์ 'สะเปะสะปะ' - ซึ่งฉันเก็บเรื่องที่น่าสนใจของโค้ดไว้เผื่อว่าฉันต้องการมันในภายหลัง ฉันอายที่จะบอกว่าฉันไม่ได้จดบันทึกว่าความคิดนี้มาจากไหนในเวลานั้น

ส่วนหัว: paste1.h

แหล่งที่มาตัวอย่าง:

ไม่จำเป็นต้องใช้ก่อนโปรเซสเซอร์ C ที่สะอาดที่สุดในโลก - แต่จะป้องกันการเขียนเนื้อหาหลายครั้ง



0

หากดัชนี enum เป็น 0 คุณสามารถใส่ชื่อในอาร์เรย์ของถ่าน * และจัดทำดัชนีด้วยค่า enum



0

ฉันได้สร้างง่ายๆเทมเพลตระดับstreamable_enumที่ใช้สตรีมผู้ประกอบการ<<และ>>และขึ้นอยู่กับstd::map<Enum, std::string>:

การใช้งาน:


0

นี่คือวิธีแก้ปัญหาโดยใช้มาโครที่มีคุณสมบัติดังต่อไปนี้:

  1. เขียนแต่ละค่าของ enum เพียงครั้งเดียวดังนั้นจึงไม่มีรายการคู่ที่ต้องรักษา

  2. อย่าเก็บค่า enum ไว้ในไฟล์แยกต่างหากซึ่งเป็น #included ในภายหลังดังนั้นฉันจึงสามารถเขียนได้ทุกที่ที่ต้องการ

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

  4. การค้นหาควรเป็นไปอย่างรวดเร็วดังนั้นจึงไม่ควรเปลี่ยนเคสสำหรับ enums ขนาดใหญ่เหล่านั้น

https://stackoverflow.com/a/20134475/1812866


0

ฉันคิดว่าวิธีแก้ปัญหาเช่น Boost Fusion one สำหรับการปรับโครงสร้างและคลาสน่าจะดีพวกเขายังมีอยู่ในบางจุดเพื่อใช้ enums เป็นลำดับฟิวชั่น

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

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

คำตอบเก่า ๆ ด้านล่างค่อนข้างแย่โปรดอย่าใช้คำตอบนั้น :)

คำตอบเก่า:

ฉันได้ค้นหาวิธีที่สามารถแก้ปัญหานี้ได้โดยไม่ต้องเปลี่ยนไวยากรณ์การประกาศ enums มากเกินไป ฉันมาถึงโซลูชันที่ใช้ตัวประมวลผลล่วงหน้าเพื่อดึงสตริงจากการประกาศ enum แบบสตริง

ฉันสามารถกำหนด enums ที่ไม่กระจัดกระจายเช่นนี้:

และฉันสามารถโต้ตอบกับพวกเขาได้หลายวิธี:

ตามคำจำกัดความต่อไปนี้:

เมื่อใดที่ฉันต้องการการสนับสนุนสำหรับ sparse enum และเมื่อฉันมีเวลามากขึ้นฉันจะปรับปรุงการใช้งานto_stringและfrom_stringด้วย boost :: xpressive แต่สิ่งนี้จะเสียค่าใช้จ่ายในการรวบรวมเนื่องจากการเทมเพลตที่สำคัญและการดำเนินการที่สร้างขึ้นคือ น่าจะใหญ่กว่าจริงๆ แต่สิ่งนี้มีข้อได้เปรียบที่จะสามารถอ่านและดูแลรักษาได้มากกว่ารหัสการจัดการสตริงด้วยตนเองที่น่าเกลียดนี้ : ง

มิฉะนั้นฉันมักจะใช้ boost :: bimap เพื่อทำการแมปดังกล่าวระหว่างค่า enums และสตริง แต่ต้องได้รับการดูแลด้วยตนเอง


0

เนื่องจากฉันไม่ต้องการใช้มาโครด้วยเหตุผลปกติทั้งหมดฉันจึงใช้โซลูชันมาโครที่ จำกัด มากขึ้นซึ่งมีข้อดีในการทำให้มาโครการประกาศ enum ว่าง ข้อเสียรวมถึงการต้องคัดลอกวางข้อกำหนดแมโครสำหรับแต่ละ enum และต้องเพิ่มการเรียกใช้แมโครอย่างชัดเจนเมื่อเพิ่มค่าลงใน enum

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