การประกาศฟังก์ชันภายในหรือภายนอกคลาส


91

ฉันเป็นนักพัฒนา JAVA ที่พยายามเรียนรู้ C ++ แต่ฉันไม่รู้จริงๆว่าแนวทางปฏิบัติที่ดีที่สุดสำหรับการประกาศฟังก์ชันมาตรฐานคืออะไร

ในห้องเรียน:

class Clazz
{
 public:
    void Fun1()
    {
        //do something
    }
}

หรือภายนอก:

class Clazz
{
public:
    void Fun1();
}

Clazz::Fun1(){
    // Do something
}

ฉันมีความรู้สึกว่าอันที่สองอ่านได้น้อย ...


1
จริงๆมี 3 ตัวเลือกที่นี่ ตัวอย่างที่สองของคุณอาจมีนิยามฟังก์ชันในไฟล์ส่วนหัว (แต่ยังไม่ได้อยู่ในบรรทัด) หรือใน.cppไฟล์แยกต่างหาก
โคดี้เกรย์

คำถามนี้อาจช่วยให้คุณเข้าใจ
Björn Pollex

3
หมายเหตุ: การประกาศจะอยู่ในชั้นเรียนเสมอ แต่คำจำกัดความจะอยู่ภายในหรือภายนอก ชื่อคำถามและเนื้อหาควรอยู่ภายใต้ s / ประกาศ / คำจำกัดความ / ไม่เชื่อฉัน? stackoverflow.com/q/1410563/1143274
Evgeni Sergeev

1
ต้องหลีกเลี่ยงนิยามฟังก์ชันภายในคลาส inlineพวกเขาจะถือว่าโดยปริยาย
John Strood

@JohnStrood งั้นเหรอ? inlineClazz
ผ่อนปรน

คำตอบ:


57

C ++ เป็นเชิงวัตถุในแง่ที่สนับสนุนกระบวนทัศน์เชิงวัตถุสำหรับการพัฒนาซอฟต์แวร์

อย่างไรก็ตาม C ++ แตกต่างจาก Java ไม่ได้บังคับให้คุณจัดกลุ่มนิยามฟังก์ชันในคลาส: วิธี C ++ มาตรฐานสำหรับการประกาศฟังก์ชันคือการประกาศฟังก์ชันโดยไม่ต้องมีคลาสใด ๆ

แต่หากคุณมีการพูดคุยเกี่ยวกับวิธีการประกาศ / คำนิยามแล้ววิธีมาตรฐานคือการใส่เพียงการประกาศในแฟ้มรวม (ปกติชื่อ.hหรือ.hpp) และความหมายในแฟ้มการดำเนินงานแยกต่างหาก (ปกติชื่อ.cppหรือ.cxx) ฉันยอมรับว่านี่ค่อนข้างน่ารำคาญและต้องมีการทำซ้ำ แต่มันเป็นวิธีการออกแบบภาษา

สำหรับการทดลองอย่างรวดเร็วและโครงการไฟล์เดียวอะไร ๆ ก็ใช้ได้ ... แต่สำหรับโครงการที่ใหญ่กว่าการแยกนี้เป็นสิ่งที่จำเป็นในทางปฏิบัติ

หมายเหตุ: แม้ว่าคุณจะรู้จัก Java แต่ C ++ ก็เป็นภาษาที่แตกต่างกันโดยสิ้นเชิง ... และเป็นภาษาที่ไม่สามารถเรียนรู้ได้จากการทดลอง เหตุผลก็คือมันเป็นภาษาที่ค่อนข้างซับซ้อนมีตัวเลือกที่ไม่สมมาตรและดูเหมือนจะไร้เหตุผลและที่สำคัญที่สุดคือเมื่อคุณทำผิดพลาดจะไม่มี "runtime error angels" ที่จะช่วยคุณได้เหมือนใน Java ... แต่มีแทน " ภูตพฤติกรรมที่ไม่ได้กำหนด ".

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

เพียงแค่เลือกหนังสือดีๆสักเล่มหรือสองเล่มแล้วอ่านให้ครอบคลุม


7
ถ้ามีคนมาจาก Java และขอความช่วยเหลือเกี่ยวกับ C ++ มันจะบอกอะไรเขาได้ถ้าคุณพูดว่า "ภาษาที่คุณรู้ว่าหมกมุ่นอยู่กับบางสิ่ง" เขาไม่มีการเปรียบเทียบกับภาษาอื่นดังนั้นสิ่งนี้จึงบอกเขาว่าไม่มีอะไรมาก ดีกว่าการใช้คำที่สื่อความหมายด้วยอารมณ์รุนแรงเช่นการหมกมุ่นซึ่งไม่ได้บอก OP มากนักคุณอาจพิจารณาเพียงแค่ปล่อยส่วนนี้ออกไป ยิ่งไปกว่านั้นบริบทของ "use a class for everyting" คืออะไร? ใน Java คุณไม่ได้ใช้คลาสสำหรับวิธีการ คุณไม่ได้ใช้คลาสสำหรับตัวแปร คุณไม่ได้ใช้คลาสสำหรับไฟล์ .. แล้ว "ทุกอย่าง" ที่นี่คืออะไร? โวยวาย?
Daniel S.

3
@DanielS: ลบส่วนนั้นออกเพราะเห็นได้ชัดว่าทำให้คุณขุ่นเคือง (ไม่รู้ว่าทำไม) แน่นอนว่าฉันไม่ได้คุยโวเกี่ยวกับ Java เพราะฉันไม่ได้ใช้ Java เลยฉันแค่คิดว่า OOP ในฐานะ Object Obsessed Programming เป็นเรื่องตลกขบขันในขณะที่เห็นได้ชัดว่าไม่ใช่ ฉันเป็นโปรแกรมเมอร์ที่ได้รับการรับรอง Java 1.1 แต่ตัดสินใจในตอนนั้นว่าเว้นแต่จะถูกบังคับด้วยเหตุผลบางประการฉันจะไม่ใช้ "ภาษาโปรแกรม" นั้นและจนถึงตอนนี้ฉันก็ประสบความสำเร็จในการหลีกเลี่ยง
6502

ขอบคุณฉันคิดว่าตอนนี้อ่านดีขึ้นมาก ขออภัยหากฉันฟังดูไม่พอใจ ฉันจะพยายามเป็นบวกมากขึ้นในครั้งต่อไป
Daniel S.

15
ไม่ตอบคำถาม
Petr Peller

1
@ PetrPeller: ส่วนใดของย่อหน้าที่สามที่คุณไม่ชัดเจน?
6502

27

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

การใช้งานครั้งที่สองจะวางนิยามของฟังก์ชันในไฟล์ cpp

ทั้งสองมีความแตกต่างกันทางความหมายและไม่ใช่แค่เรื่องของรูปแบบเท่านั้น


2
cplusplus.com/doc/tutorial/classesให้คำตอบเดียวกัน: "ความแตกต่างเพียงอย่างเดียวระหว่างการกำหนดฟังก์ชันสมาชิกคลาสอย่างสมบูรณ์ภายในคลาสหรือรวมเฉพาะต้นแบบและคำจำกัดความในภายหลังคือในกรณีแรกฟังก์ชันจะเป็นโดยอัตโนมัติ ถือว่าเป็นฟังก์ชันสมาชิกแบบอินไลน์โดยคอมไพลเลอร์ในขณะที่ฟังก์ชันที่สองจะเป็นฟังก์ชันสมาชิกคลาสปกติ (ไม่ใช่อินไลน์) ซึ่งในความเป็นจริงแล้วไม่มีความแตกต่างในพฤติกรรม "
ปุ่ม 840

18

นิยามฟังก์ชันดีกว่านอกคลาส ด้วยวิธีนี้รหัสของคุณจะปลอดภัยหากจำเป็น ไฟล์ส่วนหัวควรให้การประกาศ

สมมติว่ามีคนต้องการใช้รหัสของคุณคุณสามารถให้ไฟล์. h และไฟล์. obj (ได้มาจากการรวบรวม) ของชั้นเรียนของคุณ เขาไม่ต้องการไฟล์. cpp เพื่อใช้รหัสของคุณ

วิธีนี้จะไม่ปรากฏให้ใครเห็น


10

เมธอด "ภายในคลาส" (I) ทำเช่นเดียวกับเมธอด "นอกคลาส" (O)

อย่างไรก็ตาม (I) สามารถใช้ได้เมื่อใช้คลาสในไฟล์เดียวเท่านั้น (ภายในไฟล์. cpp) (O) ใช้เมื่ออยู่ในไฟล์ส่วนหัว ไฟล์ cpp จะถูกคอมไพล์เสมอ ไฟล์ส่วนหัวจะถูกคอมไพล์เมื่อคุณใช้ #include "header.h"

หากคุณใช้ (I) ในไฟล์ส่วนหัวฟังก์ชัน (Fun1) จะถูกประกาศทุกครั้งที่คุณใส่ #include "header.h" ซึ่งอาจนำไปสู่การประกาศฟังก์ชันเดียวกันหลายครั้ง คอมไพล์ยากกว่าและอาจทำให้เกิดข้อผิดพลาดได้

ตัวอย่างการใช้งานที่ถูกต้อง:

File1: "Clazz.h"

//This file sets up the class with a prototype body. 

class Clazz
{
public:
    void Fun1();//This is a Fun1 Prototype. 
};

File2: "Clazz.cpp"

#include "Clazz.h" 
//this file gives Fun1() (prototyped in the header) a body once.

void Clazz::Fun1()
{
    //Do stuff...
}

File3: "UseClazz.cpp"

#include "Clazz.h" 
//This file uses Fun1() but does not care where Fun1 was given a body. 

class MyClazz;
MyClazz.Fun1();//This does Fun1, as prototyped in the header.

File4: "alsoUseClazz.cpp"

#include "Clazz.h" 
//This file uses Fun1() but does not care where Fun1 was given a body. 

class MyClazz2;
MyClazz2.Fun1();//This does Fun1, as prototyped in the header. 

File5: "DoNotUseClazzHeader.cpp"

//here we do not include Clazz.h. So this is another scope. 
class Clazz
{
public:
    void Fun1()
    {
         //Do something else...
    }
};

class MyClazz; //this is a totally different thing. 
MyClazz.Fun1(); //this does something else. 

คุณหมายถึงClazz MyClazzและClazz MyClazz2?
Chupo_cro

4

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

class Box
{
  public:

     double length;
     double breadth;    
     double height;     

     double getVolume(void)
     {
        return length * breadth * height;
     }
};

หากคุณต้องการคุณสามารถกำหนดฟังก์ชันเดียวกันนอกคลาสโดยใช้ตัวดำเนินการความละเอียดขอบเขต :: ดังนี้

double Box::getVolume(void)
{
   return length * breadth * height;
}

ที่นี่ประเด็นสำคัญเพียงอย่างเดียวคือคุณจะต้องใช้ชื่อคลาสก่อน :: operator ฟังก์ชันสมาชิกจะถูกเรียกใช้โดยใช้ตัวดำเนินการจุด (.) บนวัตถุซึ่งจะจัดการกับข้อมูลที่เกี่ยวข้องกับวัตถุนั้นเท่านั้นดังนี้:

Box myBox;           

myBox.getVolume();  

(จาก: http://www.tutorialspoint.com/cplusplus/cpp_class_member_functions.htm ) ทั้งสองวิธีถูกต้องตามกฎหมาย

ฉันไม่ใช่ผู้เชี่ยวชาญ แต่ฉันคิดว่าถ้าคุณใส่นิยามคลาสเดียวในไฟล์เดียวมันก็ไม่สำคัญ

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


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

2

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


1

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

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

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


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

0

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

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