ความแตกต่างระหว่างคำจำกัดความและคำประกาศคืออะไร?


857

ความหมายของทั้งคู่ทำให้ฉันหลง


91
@ Lasse: ไม่จริง ความหมายทั้งกำหนดและประกาศ ;-)
สตีฟเจสซอพ

13
ตรงไปตรงมาฉันมีปัญหาในการเรียนรู้มากมายซึ่งฉันไม่พบชื่อที่ชัดเจน ฉันไม่มีปัญหากับความหมายแค่ชื่อที่เชื่อมโยงกับความหมาย
David Thornley

6
ถึงกระนั้นก็ไม่ใช่คำถามที่ซ้ำกันเนื่องจากสิ่งนี้ถามเกี่ยวกับ C / C ++ ในขณะที่คำถามอื่น ๆ ถามเกี่ยวกับภาษาทั้งหมดหรือไม่มีเลย มันมีคำตอบที่ซ้ำกัน (เนื่องจากในคำถามอื่นนั้นบางคำตอบเลือกที่จะไม่สนใจภาษาทั้งหมดยกเว้น C และ / หรือ C ++)
Steve Jessop

5
@ DavidThornley ฉันใช้เคล็ดลับนี้: ความหมายจะให้คำอธิบายที่ดีขึ้นของตัวแปรหรือฟังก์ชั่น ในการจำสิ่งนี้ฉันจำได้ว่าคำกลาง "คำจำกัดความ" นั้นมีความคล้ายคลึงกับคำว่า "ปลีกย่อย" :)
Marco Leogrande

4
น่าประหลาดใจที่มีคำถามมากขนาดนี้ เพียงไปเพื่อแสดงให้เห็นว่าภาษานี้มีความเข้าใจผิดมากน้อยเพียงใดและความเข้าใจผิดเหล่านั้นถูกเผยแพร่เป็นประจำอย่างไร มันเศร้าจริงๆ
Lightness Races ในวงโคจร

คำตอบ:


858

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

extern int bar;
extern int g(int, int);
double f(int, double); // extern can be omitted for function declarations
class foo; // no extern allowed for type declarations

นิยามจริง instantiates การดำเนินการ / ตัวระบุนี้ มันเป็นสิ่งที่ตัวเชื่อมโยงความต้องการเพื่อการอ้างอิงการเชื่อมโยงไปยังหน่วยงานเหล่านั้น เหล่านี้เป็นคำจำกัดความที่สอดคล้องกับการประกาศข้างต้น:

int bar;
int g(int lhs, int rhs) {return lhs*rhs;}
double f(int i, double d) {return i+d;}
class foo {};

นิยามสามารถใช้แทนการประกาศ

ตัวระบุสามารถประกาศได้บ่อยเท่าที่คุณต้องการ ดังนั้นสิ่งต่อไปนี้ถูกต้องตามกฎหมายใน C และ C ++:

double f(int, double);
double f(int, double);
extern double f(int, double); // the same as the two above
extern double f(int, double);

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


ตั้งแต่การโต้วาทีการประกาศชั้นเรียนกับคำจำกัดความของชั้นเรียนใน C ++ ยังคงเกิดขึ้น (ในคำตอบและความคิดเห็นต่อคำถามอื่น ๆ ) ฉันจะวางใบเสนอราคาจากมาตรฐาน C ++ ที่นี่
ที่ 3.1 / 2, C ++ 03 พูดว่า:

การประกาศเป็นคำนิยามเว้นแต่ว่า [... ] เป็นการประกาศชื่อคลาส [... ]

3.1 / 3 แล้วให้ตัวอย่าง ในหมู่พวกเขา:

[ตัวอย่าง: [... ]
struct S {int a; int b; }; // กำหนด S, S :: a, และ S :: b [... ]
struct S; // ประกาศ S
- ยกตัวอย่าง

เพื่อรวมขึ้น: c ++ มาตรฐานพิจารณาstruct x;จะเป็นประกาศและนิยาม (กล่าวอีกนัยหนึ่งคือ"การประกาศไปข้างหน้า" การเรียกชื่อผิดเนื่องจากไม่มีการประกาศคลาสรูปแบบอื่นใน C ++)struct x {};

ขอบคุณlitb (โยฮันเนสชอบ)ผู้ขุดบทและบทร้อยกรองจริง ๆ ในคำตอบของเขา


2
@unknown: คอมไพเลอร์ของคุณเสียว่าคุณมีรหัส sbi ที่คัดลอกผิด ตัวอย่างเช่น 6.7.2 (2) ใน N1124: "การประกาศทั้งหมดที่อ้างถึงวัตถุหรือฟังก์ชั่นเดียวกันจะต้องมีประเภทที่เข้ากันได้มิฉะนั้นพฤติกรรมจะไม่ได้กำหนด"
Steve Jessop

4
@Brian: "extern int i;" บอกว่าฉันเป็น int ที่ไหนซักแห่งไม่ต้องกังวลกับมัน "int i;" หมายความว่าฉันเป็น int และที่อยู่และขอบเขตของมันถูกกำหนดที่นี่
David Thornley

12
@Brian: คุณผิด คือการประกาศเพราะมันเป็นเพียงแค่แนะนำextern int i / ระบุ iคุณสามารถมีได้มากextern int iในแต่ละหน่วยรวบรวมตามที่คุณต้องการ int iอย่างไรก็ตามเป็นคำจำกัดความ มันหมายถึงพื้นที่สำหรับจำนวนเต็มที่จะอยู่ในหน่วยการแปลนี้และแนะนำให้ linker เพื่อเชื่อมโยงการอ้างอิงทั้งหมดไปยังiหน่วยงานนี้ หากคุณมีคำจำกัดความเหล่านี้มากกว่าหรือน้อยกว่าหนึ่งตัวลิงก์จะบ่น
sbi

4
@Brian int i;ในขอบเขตไฟล์ / โกลบอลหรือขอบเขตการทำงานเป็นนิยามทั้งใน C และ C ++ ใน C เพราะมันจัดสรรหน่วยเก็บข้อมูลและใน C ++ เพราะมันไม่มีตัวระบุภายนอกหรือข้อกำหนดการเชื่อมโยง จำนวนเงินเหล่านี้เป็นสิ่งเดียวกันซึ่งเป็นสิ่งที่ sbi พูดว่า: ในทั้งสองกรณีการประกาศนี้ระบุวัตถุที่การอ้างอิงทั้งหมดถึง "i" ในขอบเขตนั้นจะต้องเชื่อมโยง
Steve Jessop

4
@unknown ระวังคุณไม่สามารถประกาศสมาชิกใหม่ในขอบเขตของคลาส : struct A { double f(int, double); double f(int, double); };ไม่ถูกต้องแน่นอน มันได้รับอนุญาตที่อื่นแม้ว่า : มีบางสถานที่ที่คุณสามารถประกาศสิ่ง แต่ไม่ได้กำหนดเกินไปvoid f() { void g(); }ไม่ได้ดังต่อไปนี้ถูกต้อง void f() { void g() { } };แต่: คำจำกัดความคืออะไรและสิ่งที่การประกาศมีกฎที่ลึกซึ้งเมื่อมันมาถึงแม่แบบ - ระวัง! +1 สำหรับคำตอบที่ดี
Johannes Schaub - litb

168

จากส่วนมาตรฐาน C ++ 3.1:

ประกาศชื่อแนะนำเป็นหน่วยการแปลหรือชื่อ redeclares แนะนำให้รู้จักกับการประกาศก่อนหน้านี้ การประกาศระบุการตีความและคุณสมบัติของชื่อเหล่านี้

ย่อหน้าถัดไประบุว่าการประกาศเป็นคำจำกัดความเว้นแต่ ...

... มันประกาศฟังก์ชั่นโดยไม่ระบุร่างกายของฟังก์ชัน:

void sqrt(double);  // declares sqrt

... มันประกาศสมาชิกแบบคงที่ภายในคำจำกัดความของคลาส:

struct X
{
    int a;         // defines a
    static int b;  // declares b
};

... มันประกาศชื่อคลาส:

class Y;

... มันมีexternคำหลักโดยไม่มี initializer หรือเนื้อหาของฟังก์ชัน:

extern const int i = 0;  // defines i
extern int j;  // declares j
extern "C"
{
    void foo();  // declares foo
}

... หรือเป็นคำสั่งtypedefหรือusing

typedef long LONG_32;  // declares LONG_32
using namespace std;   // declares std

ตอนนี้ด้วยเหตุผลใหญ่ว่าทำไมจึงเป็นเรื่องสำคัญที่จะต้องเข้าใจความแตกต่างระหว่างการประกาศและคำจำกัดความ: กฎความหมายเดียว จากส่วน 3.2.1 ของมาตรฐาน C ++:

ไม่มีหน่วยการแปลใดที่จะมีคำจำกัดความของตัวแปรฟังก์ชั่นประเภทคลาสประเภทการแจกแจงหรือเทมเพลตมากกว่าหนึ่งรายการ


"ประกาศสมาชิกแบบคงที่ภายในคำจำกัดความของคลาส" - สิ่งนี้เป็นจริงแม้ว่าสมาชิกแบบคงที่จะเริ่มต้นถูกต้องหรือไม่ เราสามารถทำตัวอย่างได้struct x {static int b = 3; };หรือไม่?
RJFalconer

@RJFalconer คุณถูกต้อง; การกำหนดค่าเริ่มต้นไม่จำเป็นต้องเปลี่ยนการประกาศเป็นคำจำกัดความ (ตรงกันข้ามกับสิ่งที่เราคาดหวัง การดัดแปลงตัวอย่างของคุณนั้นผิดกฎหมายจริง ๆ เว้นแต่bจะมีการประกาศconstด้วย ดูstackoverflow.com/a/3536513/1858225และdaniweb.com/software-development/cpp/threads/140739/...
Kyle Strand

1
สิ่งนี้น่าสนใจสำหรับฉัน ตามที่คำตอบของคุณมันก็ดูเหมือนว่าใน C ++ ประกาศเป็นยังหมาย (มีข้อยกเว้น) ในขณะที่ในมาตรฐาน C จะถูกเรียบเรียงจากมุมมองอื่น ๆ (C99 ส่วน 6.7 Declarations): "เป็นคำนิยามของตัวระบุเป็น การประกาศสำหรับตัวระบุนั้น: [ตามด้วยเกณฑ์สำหรับกรณีที่แตกต่างกัน] " วิธีต่าง ๆ ในการดูฉันคิดว่า :)
Victor Zamanian

การประกาศสำหรับคอมไพเลอร์ที่จะยอมรับชื่อ (เพื่อบอกคอมไพเลอร์ว่าชื่อถูกกฎหมายชื่อจะถูกนำมาใช้ด้วยความตั้งใจที่จะไม่พิมพ์) คำจำกัดความคือที่ซึ่งชื่อและเนื้อหาเชื่อมโยงกัน linker ใช้นิยามเพื่อเชื่อมโยงการอ้างอิงชื่อกับเนื้อหาของชื่อ
Gab 是好人

137

คำแถลงการณ์: "อยู่ที่ไหนซักแห่ง"

คำจำกัดความ: "... และนี่คือ!"


3
การประกาศสำหรับคอมไพเลอร์ที่จะยอมรับชื่อ (เพื่อบอกคอมไพเลอร์ว่าชื่อถูกกฎหมายชื่อจะถูกนำมาใช้ด้วยความตั้งใจที่จะไม่พิมพ์) คำจำกัดความคือที่ซึ่งชื่อและเนื้อหาเชื่อมโยงกัน linker ใช้นิยามเพื่อเชื่อมโยงการอ้างอิงชื่อกับเนื้อหาของชื่อ
Gab 是好人

46

มีขอบกรณีที่น่าสนใจใน C ++ (บางกรณีเป็น C เช่นกัน) พิจารณา

T t;

นั่นอาจเป็นคำจำกัดความหรือการประกาศทั้งนี้ขึ้นอยู่กับประเภทของสิ่งที่T:

typedef void T();
T t; // declaration of function "t"

struct X { 
  T t; // declaration of function "t".
};

typedef int T;
T t; // definition of object "t".

ใน C ++ เมื่อใช้เทมเพลตจะมีตัวพิมพ์เล็กอีกตัวหนึ่ง

template <typename T>
struct X { 
  static int member; // declaration
};

template<typename T>
int X<T>::member; // definition

template<>
int X<bool>::member; // declaration!

การประกาศครั้งสุดท้ายไม่ใช่คำจำกัดความ X<bool>มันเป็นคำประกาศของความเชี่ยวชาญอย่างชัดเจนของสมาชิกคงของ มันบอกคอมไพเลอร์: "ถ้าเป็นอินสแตนซ์X<bool>::memberแล้วอย่ายกตัวอย่างนิยามของสมาชิกจากเทมเพลตหลัก แต่ใช้นิยามที่พบที่อื่น" เพื่อให้เป็นคำจำกัดความคุณต้องจัดหา initializer

template<>
int X<bool>::member = 1; // definition, belongs into a .cpp file.

35

การประกาศ

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

คำนิยาม

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


อืม, ไม่ใช่ว่าคุณสามารถกำหนดคลาสและ enums ในแต่ละหน่วยการคอมไพล์ได้? อย่างน้อยฉันก็ใส่คำจำกัดความของชั้นเรียนไว้ในส่วนหัวและรวมไว้ในนั้น เอ่อclass foo {}; เป็นคำจำกัดความของคลาสใช่มั้ย
sbi

1
ใช่. อย่างไรก็ตาม "class foo;" คือการประกาศ มันบอกคอมไพเลอร์ว่า foo เป็นคลาส "class foo {};" เป็นคำจำกัดความ มันบอกคอมไพเลอร์ว่าประเภทของ class foo คืออะไร
David Thornley

1
ข้อยกเว้นคือชื่อสมาชิกคลาสซึ่งอาจใช้ก่อนที่จะประกาศ
Johannes Schaub - litb

1
ใช่นั่นคือสิ่งที่ฉันหมายถึง ดังนั้นคุณสามารถทำสิ่งต่อไปนี้: struct foo {void b () {f (); } ถือเป็นโมฆะ f (); }, f สามารถมองเห็นได้แม้ว่าจะยังไม่ประกาศ การทำงานต่อไปนี้เกินไป: struct foo {void b (int = bar ()); typedef int bar; } ;. มันสามารถมองเห็นได้ก่อนที่จะประกาศใน "ทุกฟังก์ชั่นร่างกายอาร์กิวเมนต์เริ่มต้นคอนสตรัคเตอร์ ctor-initializers" ไม่อยู่ในประเภทผลตอบแทน :(
Johannes Schaub - litb

1
@litb: ไม่สามารถมองเห็นได้ก่อนที่จะมีการประกาศเป็นเพียงว่าการใช้ตัวระบุถูกย้ายไปด้านหลังการประกาศ ใช่ฉันรู้ว่าผลจะเหมือนกันในหลายกรณี แต่ไม่ใช่สำหรับทุกกรณีซึ่งเป็นสาเหตุที่ฉันคิดว่าเราควรใช้คำอธิบายที่ชัดเจน - โอ๊ะรอ มันสามารถมองเห็นได้ในข้อโต้แย้งเริ่มต้น? ดีที่แน่นอนความเสียหายด้วยความเข้าใจของฉัน บ้า! <
pouts

22

จากมาตรฐาน C99, 6.7 (5):

การประกาศระบุการตีความและคุณสมบัติของชุดของตัวระบุ นิยามของตัวระบุเป็นประกาศสำหรับตัวบ่งชี้ที่ว่า:

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

จากมาตรฐาน C ++, 3.1 (2):

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

จากนั้นก็มีตัวอย่างบางส่วน

น่าสนใจมาก (หรือไม่ แต่ฉันแปลกใจเล็กน้อยกับมัน) typedef int myint;เป็นคำจำกัดความใน C99 แต่มีการประกาศใน C ++ เท่านั้น


@onebyone: เกี่ยวกับtypedefนั่นไม่ได้หมายความว่าจะสามารถทำซ้ำใน C ++ แต่ไม่ใช่ใน C99?
sbi

นั่นคือสิ่งที่ทำให้ฉันประหลาดใจและเท่าที่หน่วยการแปลเดียวเกี่ยวข้องใช่มีความแตกต่างที่ แต่ชัดเจนสามารถพิมพ์ซ้ำใน C99 ในหน่วยการแปลที่แตกต่างกัน C ไม่มี "กฎนิยามหนึ่งกฎ" อย่างชัดเจนเช่น C ++ ดังนั้นกฎที่มีเพียงอนุญาต C ++ เลือกที่จะเปลี่ยนเป็นการประกาศ แต่ยังมีกฎข้อกำหนดหนึ่งรายการที่ใช้กับสิ่งนั้นและ typedefs ไม่ใช่หนึ่งในนั้น ดังนั้นการทำซ้ำจะได้รับอนุญาตใน C ++ ภายใต้ ODR เนื่องจากเป็น worded แม้ว่า typedef เป็นคำจำกัดความ ดูเหมือนจู้จี้จุกจิกโดยไม่จำเป็น
Steve Jessop

... แต่ฉันเดาว่ารายการใน ODR จะแสดงรายการทุกสิ่งที่เป็นไปได้ที่จะมีคำจำกัดความ ถ้าเป็นเช่นนั้นรายการจะซ้ำซ้อนและมีประโยชน์
Steve Jessop

คำนิยาม ODR ของ std พูดถึงคำจำกัดความของชั้นเรียนว่าอย่างไร? พวกเขาได้มีการทำซ้ำ
sbi

2
@sbi: ODR บอกว่า "(1) ไม่มีหน่วยการแปลใดที่จะมีคำจำกัดความของคลาสประเภทใดประเภทหนึ่งมากกว่าหนึ่งประเภท" และ "(5) อาจมีคำจำกัดความประเภทคลาสมากกว่าหนึ่งรายการ ... ในโปรแกรมที่ให้ แต่ละคำนิยามจะปรากฏในหน่วยการแปลที่แตกต่างกัน "และจากนั้นข้อกำหนดพิเศษบางอย่างซึ่งจำนวนเงินที่" คำจำกัดความเหมือนกัน "
Steve Jessop

17

จาก wiki.answers.com:

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

ตัวอย่างเช่นต่อไปนี้คือการประกาศทั้งหมด:

extern int a; 
struct _tagExample { int a; int b; }; 
int myFunc (int a, int b);

คำจำกัดความในทางกลับกันหมายความว่านอกเหนือจากทุกสิ่งที่ประกาศทำพื้นที่จะถูกสงวนไว้ในหน่วยความจำ คุณสามารถพูดว่า "DEFINITION = DECLARATION + SPACE RESERVATION" ต่อไปนี้เป็นตัวอย่างของคำนิยาม:

int a; 
int b = 0; 
int myFunc (int a, int b) { return a + b; } 
struct _tagExample example; 

ดูคำตอบ


3
ที่นี่เหมือนกันที่เป็นธรรม (ถึงแม้จะใกล้ชิดมากกว่าคนอื่น ๆ ): struct foo {};เป็นความหมายไม่ได้ประกาศ การประกาศของจะfoo struct foo;จากนั้นคอมไพเลอร์ไม่ทราบจำนวนเนื้อที่ที่จะสำรองสำหรับfooวัตถุ
sbi

1
@Marcin: sbi กำลังบอกว่า "คอมไพเลอร์รู้ว่าต้องจองพื้นที่เท่าใดในกรณีที่สร้างตัวแปรประเภทนี้" ไม่จริงเสมอไป struct foo;เป็นการประกาศ แต่ไม่ได้บอกขนาดของ foo ให้คอมไพเลอร์ ฉันจะเพิ่มนั่นstruct _tagExample { int a; int b; };คือคำจำกัดความ ดังนั้นในบริบทนี้จึงเป็นความเข้าใจผิดที่จะเรียกมันว่าการประกาศ แน่นอนมันเป็นสิ่งหนึ่งเนื่องจากคำจำกัดความทั้งหมดเป็นการประกาศ แต่คุณดูเหมือนจะแนะนำว่าไม่ใช่คำจำกัดความ มันเป็นคำจำกัดความของ _tagExample
Steve Jessop

1
@Marcin Gil: ซึ่งหมายความว่า wiki "คำตอบ" นั้นไม่ถูกต้องเสมอไป ฉันต้อง downvote สำหรับข้อมูลที่ผิดที่นี่
David Thornley

1
เราเรียนรู้ว่าสิ่งที่ adatapost อ้างถึงนั้นจริง แต่ไม่ (IMO) ตอบคำถามอย่างแท้จริง สิ่งที่ Marcin อ้างถึงนั้นเป็นเท็จ การอ้างถึงมาตรฐานนั้นเป็นจริงและตอบคำถาม แต่ยากมากที่จะสร้างหัวหรือหาง
Steve Jessop

1
@David Thornley - ไม่เป็นปัญหา :) นี่คือสิ่งที่เว็บไซต์นี้เกี่ยวกับ เราเลือกและตรวจสอบข้อมูล
Marcin Gil

13

การอัปเดต C ++ 11

เนื่องจากฉันไม่เห็นคำตอบที่เกี่ยวข้องกับ C ++ 11 นี่คือคำตอบเดียว

การประกาศเป็นคำนิยามเว้นแต่จะประกาศ a / n:

  • ทึบแสง enum - enum X : int;
  • พารามิเตอร์เทมเพลต - T intemplate<typename T> class MyArray;
  • การประกาศพารามิเตอร์ - xและyในint add(int x, int y);
  • ประกาศนามแฝง - using IntVector = std::vector<int>;
  • ประกาศยืนยันแบบคงที่ - static_assert(sizeof(int) == 4, "Yikes!")
  • การประกาศคุณสมบัติ (กำหนดโดยการนำไปปฏิบัติ)
  • ประกาศว่างเปล่า ;

ส่วนคำสั่งเพิ่มเติมที่สืบทอดมาจาก C ++ 03 โดยรายการด้านบน:

  • ประกาศฟังก์ชั่น - เพิ่มในint add(int x, int y);
  • ตัวระบุภายนอกที่มีการประกาศหรือตัวระบุการเชื่อมโยง - extern int a;หรือextern "C" { ... };
  • สมาชิกข้อมูลแบบคงที่ในชั้นเรียน - xในclass C { static int x; };
  • ประกาศคลาส / struct - struct Point;
  • ประกาศ typedef - typedef int Int;
  • ใช้การประกาศ - using std::cout;
  • ใช้คำสั่ง - using namespace NS;

แม่แบบประกาศคือการประกาศ เท็มเพลต - ประกาศเป็นคำนิยามถ้าประกาศกำหนดฟังก์ชั่นชั้นเรียนหรือสมาชิกข้อมูลคงที่

ตัวอย่างจากมาตรฐานที่แตกต่างระหว่างการประกาศและคำจำกัดความที่ฉันพบว่ามีประโยชน์ในการทำความเข้าใจความแตกต่างระหว่างพวกเขา:

// except one all these are definitions
int a;                                  // defines a
extern const int c = 1;                 // defines c
int f(int x) { return x + a; }          // defines f and defines x
struct S { int a; int b; };             // defines S, S::a, and S::b
struct X {                              // defines X
    int x;                              // defines non-static data member x
    static int y;                       // DECLARES static data member y
    X(): x(0) { }                       // defines a constructor of X
};
int X::y = 1;                           // defines X::y
enum { up , down };                     // defines up and down
namespace N { int d; }                  // defines N and N::d
namespace N1 = N;                       // defines N1
X anX;                                  // defines anX


// all these are declarations
extern int a;                           // declares a
extern const int c;                     // declares c
int f(int);                             // declares f
struct S;                               // declares S
typedef int Int;                        // declares Int
extern X anotherX;                      // declares anotherX
using N::d;                             // declares N::d


// specific to C++11 - these are not from the standard
enum X : int;                           // declares X with int as the underlying type
using IntVector = std::vector<int>;     // declares IntVector as an alias to std::vector<int>
static_assert(X::y == 1, "Oops!");      // declares a static_assert which can render the program ill-formed or have no effect like an empty declaration, depending on the result of expr
template <class T> class C;             // declares template class C
;                                       // declares nothing

6

คำจำกัดความ:

extern int a;      // Declaration 
int a;             // Definition
a = 10             // Initialization
int b = 10;        // Definition & Initialization

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

* อย่าสับสนกับการกำหนดค่าเริ่มต้น ทั้งสองแตกต่างกันการกำหนดค่าเริ่มต้นให้ค่ากับตัวแปร ดูตัวอย่างข้างต้น

ต่อไปนี้เป็นตัวอย่างของคำจำกัดความ

int a;
float b;
double c;

ตอนนี้ประกาศฟังก์ชั่น:

int fun(int a,int b); 

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

int b=fun(x,y,z);

คอมไพเลอร์จะโยนข้อผิดพลาดที่บอกว่าไม่มีฟังก์ชั่นดังกล่าว เพราะมันไม่มีต้นแบบใด ๆ สำหรับฟังก์ชั่นนั้น

บันทึกความแตกต่างระหว่างสองโปรแกรม

โปรแกรม 1

#include <stdio.h>
void print(int a)
{
     printf("%d",a);
}
main()
{
    print(5);
}

ในที่นี้ฟังก์ชั่นการพิมพ์จะประกาศและกำหนดเช่นกัน เนื่องจากการเรียกใช้ฟังก์ชั่นจะมาหลังจากความหมาย ตอนนี้ดูโปรแกรมถัดไป

โปรแกรม 2

 #include <stdio.h>
 void print(int a); // In this case this is essential
 main()
 {
    print(5);
 }
 void print(int a)
 {
     printf("%d",a);
 }

มันเป็นสิ่งจำเป็นเนื่องจากการเรียกใช้ฟังก์ชันนำหน้านิยามเพื่อให้คอมไพเลอร์ต้องทราบว่ามีฟังก์ชันดังกล่าวหรือไม่ ดังนั้นเราจึงประกาศฟังก์ชั่นซึ่งจะแจ้งให้คอมไพเลอร์ทราบ

คำจำกัดความ:

ส่วนหนึ่งของการนิยามฟังก์ชั่นนี้เรียกว่านิยาม มันบอกว่าจะทำอย่างไรในฟังก์ชั่น

void print(int a)
{
    printf("%d",a);
}

2
int a; //declaration; a=10; //definitionนี่เป็นสิ่งที่ผิดอย่างสิ้นเชิง เมื่อพูดถึงวัตถุระยะเวลาการจัดเก็บอัตโนมัติ (วัตถุที่ประกาศภายในนิยามฟังก์ชันที่ไม่ได้ประกาศพร้อมตัวระบุคลาสหน่วยเก็บข้อมูลอื่นเช่น extern) สิ่งเหล่านี้จะเป็นคำจำกัดความเสมอ
Joey Pabalinas

ความแตกต่างที่สำคัญที่ต้องเข้าใจคือการประกาศว่า "สิ่งที่มีอยู่บางแห่งที่มีคุณสมบัติเหล่านี้ (ประเภท ฯลฯ )" ในขณะที่คำจำกัดความกำลังพูดว่า "ฉันกำลังประกาศสิ่งที่มีลักษณะเหล่านี้และฉันก็ยกตัวอย่างเช่น ดี." เนื่องจากคุณไม่สามารถส่งต่อวัตถุเวลาการจัดเก็บอัตโนมัติเช่นนั้นพวกเขาจะเป็นคำจำกัดความ
Joey Pabalinas

ยกเว้นบางทีบางมุมพิมพ์ดีดแปลก ๆ ที่ฉันมักจะลืมกฎง่ายๆคือว่าคำจำกัดความทั้งหมดมีการประกาศ ลองคิดดู เมื่อคุณกำลังสร้างอินสแตนซ์ให้คุณต้องบอกคอมไพเลอร์ว่ามีสิ่งนั้นอยู่บ้างและมีลักษณะอย่างไร
Joey Pabalinas

อัปเดตคำตอบตามความคิดเห็นแรกของคุณ แต่ฉันไม่เห็นด้วยกับความคิดเห็นนี้ "เมื่อคุณกำลังยกตัวอย่างบางสิ่งคุณต้องบอกผู้แปลว่าสิ่งนั้นมีอยู่" เราไม่ได้ระบุประเภทของ lhs เสมอเมื่อสร้างอินสแตนซ์ ตัวอย่าง: a = 10 เราไม่ได้ระบุ "ลักษณะ" ใด ๆ ของที่นี่
SRIDHARAN

4

ความหมายหมายถึงฟังก์ชั่นที่เกิดขึ้นจริงเขียนและประกาศหมายถึงฟังก์ชั่นประกาศง่าย ๆ สำหรับเช่น

void  myfunction(); //this is simple declaration

และ

void myfunction()
{
 some statement;    
}

นี่คือนิยามของฟังก์ชั่น myfunction


1
แล้วประเภทและวัตถุล่ะ?
sbi

4

หลักการง่ายๆ:

  • การประกาศบอกคอมไพเลอร์วิธีการตีความข้อมูลของตัวแปรในหน่วยความจำ สิ่งนี้จำเป็นสำหรับการเข้าถึงทุกครั้ง

  • นิยามขอสงวนหน่วยความจำที่จะทำให้ตัวแปรที่มีอยู่ สิ่งนี้จะต้องเกิดขึ้นอย่างแน่นอนหนึ่งครั้งก่อนการเข้าถึงครั้งแรก


2
สิ่งนี้มีไว้สำหรับวัตถุเท่านั้น แล้วประเภทและฟังก์ชั่นล่ะ?
Lightness Races ในวงโคจร

4

เพื่อให้เข้าใจคำนามให้เน้นคำกริยาก่อน

ประกาศ - ประกาศอย่างเป็นทางการ; ประกาศ

define - เพื่อแสดงหรืออธิบาย (บางคนหรือบางสิ่ง) อย่างชัดเจนและครบถ้วน

ดังนั้นเมื่อคุณประกาศอะไรคุณแค่บอกว่ามันคืออะไร

// declaration
int sum(int, int);

บรรทัดนี้ประกาศฟังก์ชัน C เรียกsumว่าใช้เวลาสองขัดแย้งของประเภทและส่งกลับint intอย่างไรก็ตามคุณยังไม่สามารถใช้งานได้

เมื่อคุณระบุว่ามันใช้งานได้จริงมันคือนิยามของมัน

// definition
int sum(int x, int y)
{
    return x + y;
}

3

เพื่อให้เข้าใจถึงความแตกต่างระหว่างการประกาศและคำจำกัดความเราจำเป็นต้องเห็นรหัสการประกอบ:

uint8_t   ui8 = 5;  |   movb    $0x5,-0x45(%rbp)
int         i = 5;  |   movl    $0x5,-0x3c(%rbp)
uint32_t ui32 = 5;  |   movl    $0x5,-0x38(%rbp)
uint64_t ui64 = 5;  |   movq    $0x5,-0x10(%rbp)
double   doub = 5;  |   movsd   0x328(%rip),%xmm0        # 0x400a20
                        movsd   %xmm0,-0x8(%rbp)

และนี่คือคำจำกัดความเท่านั้น:

ui8 = 5;   |   movb    $0x5,-0x45(%rbp)
i = 5;     |   movl    $0x5,-0x3c(%rbp)
ui32 = 5;  |   movl    $0x5,-0x38(%rbp)
ui64 = 5;  |   movq    $0x5,-0x10(%rbp)
doub = 5;  |   movsd   0x328(%rip),%xmm0        # 0x400a20
               movsd   %xmm0,-0x8(%rbp)

อย่างที่คุณเห็นว่าไม่มีอะไรเปลี่ยนแปลง

การประกาศแตกต่างจากคำจำกัดความเนื่องจากให้ข้อมูลที่คอมไพเลอร์ใช้เท่านั้น ตัวอย่างเช่น uint8_t บอกคอมไพเลอร์ให้ใช้ฟังก์ชัน asm movb

ดูว่า:

uint def;                  |  no instructions
printf("some stuff...");   |  [...] callq   0x400450 <printf@plt>
def=5;                     |  movb    $0x5,-0x45(%rbp)

การประกาศไม่ได้มีคำสั่งที่เทียบเท่ากันเพราะไม่มีสิ่งที่จะต้องดำเนินการ

นอกจากนี้การประกาศบอกผู้รวบรวมขอบเขตของตัวแปร

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


2

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

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


1
คำจำกัดความของคุณไม่ผิดปกติ แต่ "คำจำกัดความการจัดเก็บข้อมูล" ดูเหมือนจะอึดอัดเสมอเมื่อพูดถึงคำจำกัดความของฟังก์ชั่น เกี่ยวกับแม่แบบนี้template<class T> struct foo;เป็นแม่แบบการประกาศtemplate<class T> void f();และเพื่อให้เป็นนี้ นิยามเทมเพลตมิร์เรอร์นิยามคลาส / ฟังก์ชันในวิธีเดียวกัน (โปรดทราบว่าชื่อเทมเพลตไม่ใช่ชื่อประเภทหรือฟังก์ชั่นที่เดียวที่คุณสามารถดูได้คือเมื่อคุณไม่สามารถส่งเทมเพลตเป็นพารามิเตอร์ประเภทของเทมเพลตอื่นได้หากคุณต้องการส่งเทมเพลตแทนประเภทคุณต้องใช้พารามิเตอร์เทมเพลตเทมเพลต )
sbi

ตกลงกันว่า 'คำจำกัดความการจัดเก็บข้อมูล' เป็นที่น่าอึดอัดใจโดยเฉพาะอย่างยิ่งเกี่ยวกับคำนิยามฟังก์ชั่น การประกาศคือ int foo () และคำนิยามคือ int foo () {// รหัสบางอย่างที่นี่ .. } ฉันมักจะต้องห่อสมองเล็ก ๆ ของฉันด้วยแนวคิดที่ฉันคุ้นเคย - 'การจัดเก็บข้อมูล' เป็นวิธีหนึ่งที่จะทำให้มันตรงกับฉันอย่างน้อย ... :)

2

ค้นหาคำตอบที่คล้ายกันที่นี่: คำถามสัมภาษณ์ทางเทคนิคใน C

ประกาศให้ชื่อโปรแกรม; นิยามให้คำอธิบายที่ไม่ซ้ำกันของกิจการ (เช่นชนิดเช่นและฟังก์ชั่น) ภายในโปรแกรม การประกาศสามารถทำซ้ำในขอบเขตที่กำหนดโดยจะแนะนำชื่อในขอบเขตที่กำหนด

การประกาศเป็นคำนิยามเว้นแต่:

  • ประกาศประกาศฟังก์ชั่นโดยไม่ต้องระบุร่างกายของมัน
  • การประกาศประกอบด้วยตัวระบุ extern และไม่มี initializer หรือ body body
  • การประกาศเป็นการประกาศของสมาชิกคลาสข้อมูลสแตติกที่ไม่มีนิยามคลาส
  • การประกาศเป็นคำจำกัดความของชื่อคลาส

คำจำกัดความคือการประกาศเว้นแต่:

  • นิยามนิยามสมาชิกคลาสข้อมูลสแตติก
  • นิยามนิยามฟังก์ชันที่ไม่ใช่สมาชิกแบบอินไลน์

1

นี่เป็นเสียงที่วิเศษจริงๆ แต่มันเป็นวิธีที่ดีที่สุดที่ฉันสามารถรักษาคำศัพท์ไว้ในหัวของฉันได้:

คำแถลง: รูปภาพโทมัสเจฟเฟอร์สันกล่าวสุนทรพจน์ ... "ฉันขอประกาศว่านี่เป็นเรื่องโกหกในรหัสแหล่งที่มานี้!"

คำจำกัดความ: รูปภาพพจนานุกรมคุณกำลังค้นหา Foo และสิ่งที่มันหมายถึงจริง


1

การประกาศแสดงชื่อสัญลักษณ์ให้คอมไพเลอร์ คำจำกัดความคือการประกาศที่จัดสรรพื้นที่สำหรับสัญลักษณ์

int f(int x); // function declaration (I know f exists)

int f(int x) { return 2*x; } // declaration and definition

1

ตามคู่มือห้องสมุด GNU C ( http://www.gnu.org/software/libc/manual/html_node/Header-Files.html )

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


0

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


1
สิ่งนี้สร้างความสับสนให้กับการประกาศที่มีคำนิยาม
sbi

0

ตัวอย่างที่ฉันชอบคือ "int Num = 5" ที่นี่ตัวแปรของคุณคือ 1 กำหนดเป็น int 2 ซึ่งประกาศเป็น Num และ 3 สร้างอินสแตนซ์ที่มีค่าห้า เรา

  • กำหนดประเภทของวัตถุซึ่งอาจเป็นในตัวหรือชั้นหรือโครงสร้าง
  • ประกาศชื่อของวัตถุดังนั้นสิ่งที่มีชื่อได้รับการประกาศซึ่งรวมถึงตัวแปร Funtions ฯลฯ

คลาสหรือโครงสร้างช่วยให้คุณเปลี่ยนวิธีการที่วัตถุจะถูกกำหนดเมื่อมีการใช้ในภายหลัง ตัวอย่างเช่น

  • หนึ่งอาจประกาศตัวแปรที่แตกต่างกันหรืออาร์เรย์ซึ่งไม่ได้กำหนดไว้โดยเฉพาะ
  • การใช้อ็อฟเซ็ตใน C ++ คุณสามารถกำหนดวัตถุที่ไม่มีชื่อที่ประกาศ

เมื่อเราเรียนรู้การเขียนโปรแกรมคำสองคำนี้มักจะสับสนเพราะเรามักจะทำทั้งสองอย่างพร้อมกัน


ฉันไม่เข้าใจว่าทำไมผู้คนมากมายตอบคำตอบของ sbi ฉันลงคะแนนคำตอบโดย bjhend ซึ่งค่อนข้างดีกระชับถูกต้องและทันเวลามากกว่าของฉัน ฉันเสียใจที่เห็นว่าฉันเป็นคนแรกที่ทำเช่นนี้ใน 4 ปี
Jason K.

0

ขั้นตอนของการสร้างไฟล์ที่ปฏิบัติการได้:

(1) ตัวประมวลผลล่วงหน้า -> (2) ตัวแปล / คอมไพเลอร์ -> (3) ตัวเชื่อมโยง

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

นักแปลตรวจสอบให้แน่ใจว่า: อะไรคืออะไร หมายถึงการประกาศ

และ (3) stage (linker) ต้องการคำจำกัดความเพื่อผูกสิ่งต่าง ๆ

Linker ตรวจสอบให้แน่ใจว่า: อะไรอยู่ที่ไหน หมายถึงความหมาย


0

มีคำจำกัดความที่ชัดเจนบางอย่างที่ปรากฏใน K&R (ฉบับที่ 2); มันจะช่วยให้พวกเขาอยู่ในที่เดียวและอ่านพวกเขาเป็นหนึ่ง:

"คำจำกัดความ" หมายถึงสถานที่ที่ตัวแปรถูกสร้างขึ้นหรือที่เก็บข้อมูลที่ได้รับมอบหมาย "การประกาศ" หมายถึงสถานที่ที่ระบุลักษณะของตัวแปร แต่ไม่มีการจัดสรรที่เก็บข้อมูล [p 33]

...

มันเป็นสิ่งสำคัญที่จะแยกแยะความแตกต่างระหว่างการประกาศของตัวแปรภายนอกและความหมาย ประกาศประกาศคุณสมบัติของตัวแปร (ส่วนใหญ่ประเภทของมัน); คำจำกัดความยังทำให้การจัดเก็บถูกตั้งค่าไว้ ถ้าเป็นเส้น

int sp;
double val[MAXVAL]

ปรากฏขึ้นนอกฟังก์ชั่นใด ๆ พวกเขากำหนดตัวแปรภายนอกspและvalทำให้การจัดเก็บจะถูกเก็บไว้และยังทำหน้าที่เป็นการประกาศสำหรับส่วนที่เหลือของไฟล์ต้นฉบับที่

ในทางกลับกันเส้น

extern int sp;
extern double val[];

ประกาศส่วนที่เหลือของไฟล์ต้นฉบับที่spเป็นintและนั่นvalคือdoubleอาเรย์ (ซึ่งมีการกำหนดขนาดที่อื่น) แต่พวกเขาไม่ได้สร้างตัวแปรหรือสำรองที่เก็บสำหรับพวกเขา

จะต้องมีคำจำกัดความเดียวของตัวแปรภายนอกในไฟล์ทั้งหมดที่ประกอบขึ้นเป็นซอร์สโปรแกรม ... ขนาดอาร์เรย์ต้องระบุด้วยคำจำกัดความ แต่เป็นทางเลือกที่มีการexternประกาศ [PP 80-81]

...

การประกาศระบุการตีความที่ให้กับตัวระบุแต่ละตัว ไม่จำเป็นต้องจองพื้นที่เก็บข้อมูลที่เชื่อมโยงกับตัวระบุ การประกาศว่าการจัดเก็บข้อมูลสำรองจะเรียกว่าคำจำกัดความ [p 210]


-1

การประกาศหมายถึงการตั้งชื่อและประเภทให้กับตัวแปร (ในกรณีของการประกาศตัวแปร) เช่น:

int i;

หรือให้ชื่อชนิดส่งคืนและพารามิเตอร์ประเภทให้กับฟังก์ชันที่ไม่มีเนื้อหา (ในกรณีที่มีการประกาศฟังก์ชัน) เช่น:

int max(int, int);

ในขณะที่ความหมายหมายถึงกำหนดค่าให้กับตัวแปร (ในกรณีของคำนิยามตัวแปร) เช่น:

i = 20;

หรือให้ / เพิ่มเนื้อหา (ฟังก์ชัน) ไปยังฟังก์ชันที่เรียกว่านิยามฟังก์ชันเช่น:

int max(int a, int b)
{
   if(a>b)   return a;
   return b;  
}

การประกาศและการกำหนดเวลาจำนวนมากสามารถทำได้พร้อมกันดังนี้:

int i=20;

และ:

int max(int a, int b)
{
    if(a>b)   return a;
    return b;    
} 

ในกรณีดังกล่าวข้างต้นที่เรากำหนดและประกาศตัวแปรและifunction max()


หมายถึงความหมายที่แท้จริงของถ้าการกำหนดค่า / ร่างกายให้กับตัวแปร / ฟังก์ชั่นในขณะที่การประกาศหมายถึงการให้ชื่อพิมพ์ให้ตัวแปร / ฟังก์ชั่น
Puneet Purohit

คุณสามารถกำหนดบางสิ่งได้โดยไม่ต้องกำหนดค่า
Lightness Races ในวงโคจร

1
เช่นนี้:int x;
Lightness Races ใน Orbit

เป็นการประกาศตัวแปร x ไม่ใช่การนิยาม
Puneet Purohit

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