Khai báo hàm bên trong hoặc bên ngoài lớp


91

Tôi là một nhà phát triển JAVA đang cố gắng học C ++, nhưng tôi thực sự không biết phương pháp tốt nhất cho khai báo hàm chuẩn là gì.

Trong lớp:

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

Hoặc bên ngoài:

class Clazz
{
public:
    void Fun1();
}

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

Tôi có cảm giác rằng cái thứ hai có thể khó đọc hơn ...


1
Thực tế có 3 tùy chọn ở đây. Ví dụ thứ hai của bạn có thể có định nghĩa hàm trong tệp tiêu đề (nhưng vẫn không được nội tuyến) hoặc trong một .cpptệp riêng biệt .
Cody Grey

Câu hỏi này có thể giúp bạn hiểu.
Björn Pollex

3
Chỉ cần lưu ý: khai báo luôn ở bên trong lớp, nhưng định nghĩa là bên trong hoặc bên ngoài. Tiêu đề và nội dung câu hỏi nên được đặt theo s / tuyên bố / định nghĩa / Không tin tôi? stackoverflow.com/q/1410563/1143274
Evgeni Sergeev

1
Các định nghĩa hàm bên trong lớp phải được tránh. Chúng được coi là ngầm inline.
John Strood

@JohnStrood vậy? inlinechỉ giảm nhẹ các quy tắc một định nghĩa, đó là cần thiết nếu một dịch sử dụng đơn vịClazz
Caleth

Câu trả lời:


57

C ++ là hướng đối tượng, theo nghĩa là nó hỗ trợ mô hình hướng đối tượng để phát triển phần mềm.

Tuy nhiên, khác với Java, C ++ không buộc bạn phải nhóm các định nghĩa hàm trong các lớp: cách C ++ chuẩn để khai báo một hàm là chỉ khai báo một hàm mà không cần bất kỳ lớp nào.

Thay vào đó, nếu bạn đang nói về khai báo / định nghĩa phương thức thì cách tiêu chuẩn là chỉ đặt khai báo trong một tệp bao gồm (thường được đặt tên .hhoặc .hpp) và định nghĩa trong một tệp triển khai riêng biệt (thường có tên .cpphoặc .cxx). Tôi đồng ý rằng điều này thực sự hơi khó chịu và đòi hỏi một số trùng lặp nhưng đó là cách ngôn ngữ được thiết kế.

Đối với các thử nghiệm nhanh và các dự án tệp đơn lẻ thì mọi thứ sẽ hoạt động ... nhưng đối với các dự án lớn hơn, sự tách biệt này là điều thực tế bắt buộc.

Lưu ý: Ngay cả khi bạn biết Java, C ++ là một ngôn ngữ hoàn toàn khác ... và đó là một ngôn ngữ không thể học được bằng cách thử nghiệm. Lý do là vì nó là một ngôn ngữ khá phức tạp với rất nhiều sự bất đối xứng và các lựa chọn có vẻ phi logic, và quan trọng nhất là khi bạn mắc lỗi sẽ không có "thiên thần lỗi thời gian chạy" để cứu bạn như trong Java ... mà thay vào đó là " daemon hành vi không xác định ".

Cách hợp lý duy nhất để học C ++ là đọc ... cho dù bạn có thông minh đến đâu thì cũng không có cách nào bạn có thể đoán được quyết định của ủy ban (thực sự thông minh đôi khi thậm chí là một vấn đề vì câu trả lời đúng là phi logic và là hệ quả của lịch sử gia tài.)

Chỉ cần chọn một hoặc hai cuốn sách hay và đọc chúng từ đầu đến cuối.


7
Nếu ai đó đến từ Java và yêu cầu trợ giúp về C ++, thì điều gì sẽ nói với anh ta nếu bạn nói "ngôn ngữ mà bạn biết bị ám ảnh bởi điều gì đó"? Anh ta không có sự so sánh với các ngôn ngữ khác, vì vậy điều này không cho anh ta biết nhiều điều. Tốt hơn là sử dụng một từ ngữ có hàm ý cảm xúc như bị ám ảnh, điều không nói với OP nhiều, bạn có thể cân nhắc bỏ qua phần này. Hơn nữa, bối cảnh của "sử dụng một lớp cho mọi người" là gì? Trong Java, bạn không sử dụng một lớp cho một phương thức. Bạn không sử dụng một lớp cho một biến. Bạn không sử dụng một lớp cho một tệp..Vậy "mọi thứ" ở đây là gì? Ranting?
Daniel S.

3
@DanielS: Đã xóa phần đó vì dường như đã xúc phạm bạn (không biết tại sao). Chắc chắn rằng tôi không nói về Java bởi vì tôi thực sự không sử dụng Java, lúc đó tôi chỉ nghĩ rằng OOP với tư cách là Lập trình bị ám ảnh đối tượng là một trò đùa vui, trong khi rõ ràng là không. Tôi đã là một lập trình viên được chứng nhận Java 1.1 nhưng khi đó tôi đã quyết định rằng, trừ khi bị ép buộc vì lý do nào đó, tôi sẽ không sử dụng "ngôn ngữ lập trình" đó và cho đến nay tôi đã thành công trong việc tránh nó.
6502

Cảm ơn, tôi nghĩ bây giờ nó đọc tốt hơn nhiều. Xin lỗi nếu tôi nghe có vẻ khó chịu. Tôi sẽ cố gắng tích cực hơn vào lần sau.
Daniel S.

15
Không trả lời câu hỏi
Petr Peller

1
@PetrPeller: phần thứ 3 mà bạn chưa rõ là gì?
6502

27

Hàm đầu tiên xác định hàm thành viên của bạn là một hàm nội tuyến , trong khi hàm thứ hai thì không. Định nghĩa của hàm trong trường hợp này nằm trong chính tiêu đề.

Việc triển khai thứ hai sẽ đặt định nghĩa của hàm trong tệp cpp.

Cả hai đều khác nhau về mặt ngữ nghĩa và nó không chỉ là vấn đề về phong cách.


2
cplusplus.com/doc/tutorial/classes đưa ra câu trả lời tương tự: "Sự khác biệt duy nhất giữa việc xác định một hàm thành viên lớp hoàn toàn trong lớp của nó hoặc chỉ bao gồm nguyên mẫu và sau đó là định nghĩa của nó, là trong trường hợp đầu tiên, hàm sẽ tự động được coi là một hàm thành viên nội tuyến bởi trình biên dịch, trong khi ở hàm thứ hai, nó sẽ là một hàm thành viên lớp bình thường (không phải nội tuyến), trên thực tế giả sử không có sự khác biệt về hành vi. "
Buttons840

18

Định nghĩa hàm tốt hơn bên ngoài lớp. Bằng cách đó, mã của bạn có thể vẫn an toàn nếu được yêu cầu. Tệp tiêu đề chỉ nên cung cấp các khai báo.

Giả sử ai đó muốn sử dụng mã của bạn, bạn chỉ có thể cung cấp cho họ tệp .h và tệp .obj (có được sau khi biên dịch) của lớp bạn. Anh ấy không cần tệp .cpp để sử dụng mã của bạn.

Bằng cách đó, việc triển khai của bạn sẽ không hiển thị với bất kỳ ai khác.


10

Phương thức "Bên trong lớp" (I) hoạt động tương tự như phương thức "bên ngoài lớp" (O).

Tuy nhiên, (I) có thể được sử dụng khi một lớp chỉ được sử dụng trong một tệp (bên trong tệp .cpp). (O) được sử dụng khi nó nằm trong tệp tiêu đề. các tệp cpp luôn được biên dịch. Tệp tiêu đề được biên dịch khi bạn sử dụng #include "header.h".

Nếu bạn sử dụng (I) trong tệp tiêu đề, hàm (Fun1) sẽ được khai báo mỗi khi bạn bao gồm #include "header.h". Điều này có thể dẫn đến việc khai báo cùng một hàm nhiều lần. Điều này khó biên dịch hơn và thậm chí có thể dẫn đến lỗi.

Ví dụ để sử dụng đúng:

Tệp 1: "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: "Cũng sử dụng đượcClazz.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. 

Ý bạn là Clazz MyClazzClazz MyClazz2?
Chupo_cro

4

Các hàm thành viên có thể được định nghĩa trong định nghĩa lớp hoặc sử dụng riêng biệt toán tử phân giải phạm vi, ::. Việc xác định một hàm thành viên trong định nghĩa lớp khai báo hàm nội tuyến, ngay cả khi bạn không sử dụng bộ chỉ định nội tuyến. Vì vậy, bạn có thể xác định hàm Volume () như sau:

class Box
{
  public:

     double length;
     double breadth;    
     double height;     

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

Nếu bạn muốn, bạn có thể xác định cùng một hàm bên ngoài lớp bằng cách sử dụng toán tử phân giải phạm vi, :: như sau

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

Ở đây, điểm quan trọng duy nhất là bạn sẽ phải sử dụng tên lớp ngay trước toán tử ::. Một hàm thành viên sẽ được gọi bằng cách sử dụng toán tử dấu chấm (.) Trên một đối tượng nơi nó sẽ thao tác dữ liệu liên quan đến đối tượng đó chỉ như sau:

Box myBox;           

myBox.getVolume();  

(từ: http://www.tutorialspoint.com/cplusplus/cpp_class_member_functions.htm ), cả hai cách đều hợp pháp.

Tôi không phải là một chuyên gia, nhưng tôi nghĩ, nếu bạn chỉ đặt một định nghĩa lớp trong một tệp, thì điều đó không thực sự quan trọng.

nhưng nếu bạn áp dụng một cái gì đó như lớp bên trong, hoặc bạn có nhiều định nghĩa lớp, định nghĩa thứ hai sẽ khó đọc và khó duy trì.


1
Bạn có thể đưa nội dung có liên quan từ liên kết đó vào phần nội dung bài đăng của mình và do đó có thể chống lại các liên kết chết trong tương lai không? Cảm ơn
JustinJDavies

2

Cái đầu tiên phải được đặt trong tệp tiêu đề (nơi khai báo của lớp). Thứ hai có thể ở bất kỳ đâu, tiêu đề hoặc thường là tệp nguồn. Trong thực tế, bạn có thể đặt các hàm nhỏ trong khai báo lớp (khai báo chúng nội tuyến hoàn toàn, mặc dù trình biên dịch cuối cùng quyết định xem chúng có được nội tuyến hay không). Tuy nhiên, hầu hết các hàm đều có khai báo trong tiêu đề và triển khai trong tệp cpp, như trong ví dụ thứ hai của bạn. Và không, tôi không thấy lý do gì khiến nó khó đọc hơn. Chưa kể bạn thực sự có thể phân chia việc triển khai cho một loại trên một số tệp cpp.


1

Theo mặc định, một hàm được định nghĩa bên trong một lớp được coi là một hàm nội tuyến. Một lý do đơn giản tại sao bạn nên xác định chức năng của mình bên ngoài:

Một hàm tạo của lớp kiểm tra các hàm ảo và khởi tạo một con trỏ ảo để trỏ đến VTABLE thích hợp hoặc bảng phương thức ảo , gọi hàm tạo của lớp cơ sở và khởi tạo các biến của lớp hiện tại, vì vậy nó thực sự hoạt động.

Các hàm nội tuyến được sử dụng khi các hàm không quá phức tạp và tránh phí tổn của lệnh gọi hàm. (Chi phí bao gồm một bước nhảy và nhánh ở cấp phần cứng.) Và như đã mô tả ở trên, hàm tạo không đơn giản để được coi là nội tuyến.


"nội tuyến" thực tế không liên quan gì đến nội tuyến. Thực tế là các chức năng thành viên được xác định trong dòng được khai báo nội tuyến một cách ngầm định là ở đó để tránh vi phạm ODR.
Big Temp

0

Các hàm nội tuyến (các hàm khi bạn khai báo chúng trong lớp) mỗi khi gọi chúng, chúng được dán vào mã bộ nhớ chính của bạn. Trong khi khi bạn khai báo hàm bên ngoài lớp, khi bạn gọi hàm nó đến từ cùng một bộ nhớ. Đó là lý do tại sao nó tốt hơn nhiều.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.