Tại sao tôi không nên bao gồm các tệp cpp và thay vào đó sử dụng một tiêu đề?


146

Vì vậy, tôi đã hoàn thành bài tập lập trình C ++ đầu tiên của mình và nhận được điểm. Nhưng theo chấm điểm, tôi bị mất điểm including cpp files instead of compiling and linking them. Tôi không quá rõ điều đó có nghĩa là gì.

Nhìn lại mã của tôi, tôi đã chọn không tạo các tệp tiêu đề cho các lớp của mình, nhưng đã làm mọi thứ trong các tệp cpp (có vẻ như nó hoạt động tốt mà không cần các tệp tiêu đề ...). Tôi đoán rằng học sinh có nghĩa là tôi đã viết '#include "mycppfile.cpp";' trong một số tập tin của tôi

Lý do của tôi để #include'các tệp cpp là: - Mọi thứ được cho là vào tệp tiêu đề đều nằm trong tệp cpp của tôi, vì vậy tôi giả vờ nó giống như một tệp tiêu đề - Trong thời trang khỉ-khỉ-khỉ, tôi thấy cái khác các tệp tiêu đề là #include'd trong các tệp, vì vậy tôi đã làm tương tự cho tệp cpp của mình.

Vì vậy, chính xác những gì tôi đã làm sai, và tại sao nó là xấu?


36
Đây thực sự là một câu hỏi tốt. Tôi hy vọng rất nhiều người mới c ++ sẽ được giúp đỡ bởi điều này.
Mia Clarke

Câu trả lời:


174

Theo hiểu biết tốt nhất của tôi, tiêu chuẩn C ++ biết không có sự khác biệt giữa các tệp tiêu đề và các tệp nguồn. Đối với ngôn ngữ có liên quan, bất kỳ tệp văn bản có mã hợp pháp là giống như bất kỳ ngôn ngữ khác. Tuy nhiên, mặc dù không phải là bất hợp pháp, bao gồm các tệp nguồn vào chương trình của bạn sẽ loại bỏ khá nhiều lợi thế bạn có được khi tách các tệp nguồn của mình ở vị trí đầu tiên.

Về cơ bản, điều gì #includesẽ nói với bộ xử lý trước lấy toàn bộ tệp bạn đã chỉ định và sao chép nó vào tệp hoạt động của bạn trước khi trình biên dịch xử lý nó. Vì vậy, khi bạn bao gồm tất cả các tệp nguồn trong dự án của bạn với nhau, về cơ bản không có sự khác biệt giữa những gì bạn đã làm và chỉ tạo một tệp nguồn lớn mà không có bất kỳ sự tách biệt nào.

"Ồ, đó không phải là vấn đề lớn. Nếu nó chạy, nó ổn", tôi nghe thấy bạn khóc. Và theo một nghĩa nào đó, bạn sẽ đúng. Nhưng ngay bây giờ bạn đang xử lý một chương trình nhỏ bé nhỏ xíu và một CPU đẹp và tương đối không bị cản trở để biên dịch nó cho bạn. Bạn sẽ không luôn may mắn như vậy.

Nếu bạn từng đi sâu vào các lĩnh vực lập trình máy tính nghiêm túc, bạn sẽ thấy các dự án có số lượng dòng có thể lên tới hàng triệu, thay vì hàng chục. Đó là rất nhiều dòng. Và nếu bạn cố gắng biên dịch một trong những thứ này trên một máy tính để bàn hiện đại, có thể mất vài giờ thay vì vài giây.

"Ồ không! Nghe có vẻ kinh khủng! Tuy nhiên tôi có thể ngăn chặn số phận thảm khốc này không?!" Thật không may, bạn không thể làm gì nhiều về điều đó. Nếu phải mất hàng giờ để biên dịch, phải mất hàng giờ để biên dịch. Nhưng điều đó chỉ thực sự quan trọng trong lần đầu tiên - một khi bạn đã biên dịch nó một lần, không có lý do gì để biên dịch lại.

Trừ khi bạn thay đổi một cái gì đó.

Bây giờ, nếu bạn có hai triệu dòng mã được hợp nhất với nhau thành một khổng lồ và cần thực hiện một sửa lỗi đơn giản, chẳng hạn x = y + 1, điều đó có nghĩa là bạn phải biên dịch lại tất cả hai triệu dòng để kiểm tra điều này. Và nếu bạn phát hiện ra rằng bạn muốn làm x = y - 1thay thế, thì một lần nữa, hai triệu dòng biên dịch đang chờ bạn. Đó là lãng phí nhiều thời gian mà có thể tốt hơn để làm bất cứ điều gì khác.

"Nhưng tôi ghét việc không hiệu quả! Nếu chỉ có một cách nào đó để biên dịch các phần riêng biệt của cơ sở mã của tôi, và bằng cách nào đó liên kết chúng lại với nhau sau đó!" Một ý tưởng tuyệt vời, trên lý thuyết. Nhưng nếu chương trình của bạn cần biết những gì đang diễn ra trong một tệp khác thì sao? Không thể tách hoàn toàn cơ sở mã của bạn trừ khi bạn muốn chạy một loạt các tệp .exe nhỏ xíu thay thế.

"Nhưng chắc chắn nó phải được thể! Lập trình âm thanh như tra tấn tinh khiết khác! Nếu tôi tìm thấy một số cách để tách biệt giao diện từ việc thực hiện ? Nói bằng cách lấy vừa đủ thông tin từ các đoạn mã riêng biệt để xác định họ với phần còn lại của chương trình, và đưa Thay vào đó, chúng trong một số loại tệp tiêu đề ? Và bằng cách đó, tôi có thể sử dụng #include chỉ thị tiền xử lý để chỉ mang lại thông tin cần thiết để biên dịch! "

Hừm. Bạn có thể đang ở một cái gì đó ở đó. Hãy cho tôi biết làm thế nào mà làm việc cho bạn.


13
Câu trả lời tốt, thưa ông. Đó là một niềm vui đọc, và dễ hiểu. Tôi muốn sách giáo khoa của tôi được viết như thế này.
ialm

@veol Tìm kiếm Đầu loạt sách đầu tiên - Tôi không biết liệu chúng có phiên bản C ++ hay không. headfirstlabs.com
Amarghosh

2
Đây là (chắc chắn) từ ngữ tốt nhất cho đến nay mà tôi đã nghe hoặc suy ngẫm. Justin Case, một người mới bắt đầu hoàn thành, đã đạt được một dự án với một triệu lần nhấn phím, chưa được giao và một "dự án đầu tiên" đáng khen ngợi đang nhìn thấy ánh sáng của ứng dụng trong một cơ sở người dùng thực, đã nhận ra một vấn đề được giải quyết bằng cách đóng cửa. Âm thanh khá giống với các trạng thái tiên tiến của định nghĩa vấn đề ban đầu của OP trừ đi "được mã hóa gần một trăm lần và không thể tìm ra cách làm cho null (như không có đối tượng) so với null (như cháu trai) mà không sử dụng lập trình bởi các ngoại lệ."
Nicholas Jordan

Tất nhiên, tất cả điều này không phù hợp với các mẫu vì hầu hết các trình biên dịch không hỗ trợ / triển khai từ khóa 'xuất'.
KitsuneYMG

1
Một điểm khác là bạn có nhiều thư viện hiện đại (nếu nghĩ về BOOST) chỉ sử dụng các tiêu đề chỉ các lớp ... Ho, chờ đã? Tại sao lập trình viên có kinh nghiệm không tách giao diện khỏi thực hiện? Một phần của câu trả lời có thể là những gì Blindly nói, một phần khác có thể là một tệp tốt hơn hai khi có thể và một phần khác là liên kết có chi phí cao hơn có thể khá cao. Tôi đã thấy các chương trình chạy nhanh hơn mười lần với việc bao gồm trực tiếp nguồn và trình biên dịch tối ưu hóa. Bởi vì liên kết chủ yếu là tối ưu hóa khối.
kriss

45

Đây có lẽ là một câu trả lời chi tiết hơn bạn muốn, nhưng tôi nghĩ rằng một lời giải thích hợp lý là hợp lý.

Trong C và C ++, một tệp nguồn được định nghĩa là một đơn vị dịch . Theo quy ước, các tệp tiêu đề giữ các khai báo hàm, định nghĩa kiểu và định nghĩa lớp. Việc triển khai chức năng thực tế nằm trong các đơn vị dịch thuật, tức là các tệp .cpp.

Ý tưởng đằng sau điều này là các hàm và các hàm thành viên lớp / struct được biên dịch và lắp ráp một lần, sau đó các hàm khác có thể gọi mã đó từ một nơi mà không tạo ra các bản sao. Các chức năng của bạn được khai báo là "bên ngoài" ngầm.

/* Function declaration, usually found in headers. */
/* Implicitly 'extern', i.e the symbol is visible everywhere, not just locally.*/
int add(int, int);

/* function body, or function definition. */
int add(int a, int b) 
{
   return a + b;
}

Nếu bạn muốn một hàm là cục bộ cho một đơn vị dịch thuật, bạn xác định nó là 'tĩnh'. Điều đó có nghĩa là gì? Điều đó có nghĩa là nếu bạn bao gồm các tệp nguồn với các hàm extern, bạn sẽ gặp các lỗi xác định lại, bởi vì trình biên dịch đi qua cùng một triển khai nhiều lần. Vì vậy, bạn muốn tất cả các đơn vị dịch thuật của bạn xem khai báo hàm nhưng không phải là thân hàm .

Vì vậy, làm thế nào để tất cả được nghiền với nhau vào cuối? Đó là công việc của linker. Một trình liên kết đọc tất cả các tệp đối tượng được tạo bởi giai đoạn trình biên dịch và giải quyết các ký hiệu. Như tôi đã nói trước đó, một biểu tượng chỉ là một cái tên. Ví dụ, tên của một biến hoặc một hàm. Khi các đơn vị dịch thuật gọi các hàm hoặc khai báo các kiểu không biết việc triển khai cho các hàm hoặc kiểu đó, các ký hiệu đó được cho là không được giải quyết. Trình liên kết giải quyết biểu tượng chưa được giải quyết bằng cách kết nối đơn vị dịch giữ biểu tượng không xác định cùng với biểu tượng có chứa triển khai. Phù. Điều này đúng với tất cả các biểu tượng có thể nhìn thấy bên ngoài, cho dù chúng được triển khai trong mã của bạn hay được cung cấp bởi một thư viện bổ sung. Một thư viện thực sự chỉ là một kho lưu trữ với mã có thể tái sử dụng.

Có hai ngoại lệ đáng chú ý. Đầu tiên, nếu bạn có một chức năng nhỏ, bạn có thể làm cho nó nội tuyến. Điều này có nghĩa là mã máy được tạo không tạo ra lệnh gọi hàm ngoài, nhưng được nối tại chỗ theo nghĩa đen. Vì chúng thường nhỏ, nên kích thước trên không thành vấn đề. Bạn có thể tưởng tượng họ tĩnh tại trong cách họ làm việc. Vì vậy, nó là an toàn để thực hiện các chức năng nội tuyến trong các tiêu đề. Các triển khai hàm bên trong một định nghĩa lớp hoặc cấu trúc cũng thường được trình biên dịch tự động nội tuyến.

Ngoại lệ khác là các mẫu. Vì trình biên dịch cần phải xem toàn bộ định nghĩa kiểu mẫu khi khởi tạo chúng, nên không thể tách rời việc thực hiện khỏi định nghĩa như với các hàm độc lập hoặc các lớp bình thường. Chà, có lẽ điều này là có thể ngay bây giờ, nhưng việc hỗ trợ trình biên dịch rộng rãi cho từ khóa "xuất khẩu" mất một thời gian dài. Vì vậy, không có sự hỗ trợ cho 'xuất khẩu', các đơn vị dịch thuật có được các bản sao địa phương của các loại và chức năng tạo khuôn mẫu tức thời, tương tự như cách các chức năng nội tuyến hoạt động. Với sự hỗ trợ cho 'xuất khẩu', đây không phải là trường hợp.

Đối với hai trường hợp ngoại lệ, một số người thấy nó "đẹp hơn" khi đặt việc triển khai các hàm nội tuyến, hàm templated và các kiểu templated trong các tệp .cpp, sau đó #inc loại trừ tệp .cpp. Cho dù đây là tiêu đề hay tệp nguồn không thực sự quan trọng; bộ tiền xử lý không quan tâm và chỉ là một quy ước.

Tóm tắt nhanh toàn bộ quá trình từ mã C ++ (một số tệp) và đến tệp thực thi cuối cùng:

  • Bộ tiền xử lý được chạy, phân tích tất cả các lệnh bắt đầu bằng '#'. Chẳng hạn, lệnh #incoide kết hợp các tệp được bao gồm với kém hơn. Nó cũng thay thế macro và dán mã thông báo.
  • Trình biên dịch thực tế chạy trên tệp văn bản trung gian sau giai đoạn tiền xử lý và phát ra mã trình biên dịch .
  • Các lắp ráp chạy vào file lắp ráp và phát ra mã máy, điều này thường được gọi là một tập tin đối tượng và theo định dạng thực thi nhị phân của hệ thống tác trong câu hỏi. Ví dụ: Windows sử dụng PE (định dạng thực thi di động), trong khi Linux sử dụng định dạng ELF của Hệ thống Unix, với các phần mở rộng GNU. Ở giai đoạn này, các biểu tượng vẫn được đánh dấu là không xác định.
  • Cuối cùng, trình liên kết được chạy. Tất cả các giai đoạn trước được chạy trên mỗi đơn vị dịch theo thứ tự. Tuy nhiên, giai đoạn trình liên kết hoạt động trên tất cả các tệp đối tượng được tạo bởi trình biên dịch chương trình. Trình liên kết giải quyết các biểu tượng và thực hiện nhiều phép thuật như tạo các phần và phân đoạn, phụ thuộc vào nền tảng đích và định dạng nhị phân. Các lập trình viên không bắt buộc phải biết điều này nói chung, nhưng nó chắc chắn sẽ giúp ích trong một số trường hợp.

Một lần nữa, điều này chắc chắn là nhiều hơn bạn yêu cầu, nhưng tôi hy vọng các chi tiết khó chịu sẽ giúp bạn nhìn thấy bức tranh lớn hơn.


2
Cảm ơn bạn đã giải thích kỹ lưỡng của bạn. Tôi thừa nhận, điều đó hoàn toàn không có ý nghĩa với tôi và tôi nghĩ rằng tôi sẽ cần đọc lại câu trả lời của bạn (và một lần nữa).
ialm

1
+1 cho một lời giải thích tuyệt vời. quá tệ, có lẽ nó sẽ khiến tất cả những người mới của C ++ sợ hãi. :)
goldPseudo

1
Heh, đừng cảm thấy xấu. Trên Stack Overflow, câu trả lời dài nhất hiếm khi là câu trả lời tốt nhất.

int add(int, int);là một khai báo hàm . Phần nguyên mẫu của nó chỉ là int, int. Tuy nhiên, tất cả các chức năng trong C ++ đều có nguyên mẫu, vì vậy thuật ngữ này thực sự chỉ có ý nghĩa trong C. Tôi đã chỉnh sửa câu trả lời của bạn cho hiệu ứng này.
melpomene

exportcho các mẫu đã bị xóa khỏi ngôn ngữ vào năm 2011. Nó không bao giờ thực sự được hỗ trợ bởi trình biên dịch.
melpomene

10

Giải pháp điển hình là chỉ sử dụng .hcác tệp để khai báo và .cppcác tệp để thực hiện. Nếu bạn cần sử dụng lại việc triển khai, bạn bao gồm .htệp tương ứng vào .cpptệp trong đó lớp / hàm cần thiết / bất cứ thứ gì được sử dụng và liên kết với .cpptệp đã được biên dịch (một .objtệp - thường được sử dụng trong một dự án - hoặc tệp .lib - thường được sử dụng để tái sử dụng từ nhiều dự án). Bằng cách này, bạn không cần phải biên dịch lại mọi thứ nếu chỉ thực hiện thay đổi.


6

Hãy nghĩ về các tệp cpp như một hộp đen và các tệp .h như các hướng dẫn về cách sử dụng các hộp đen đó.

Các tập tin cpp có thể được biên dịch trước thời hạn. Điều này không hoạt động trong bạn # bao gồm chúng, vì nó cần thực sự "bao gồm" mã vào chương trình của bạn mỗi khi nó biên dịch nó. Nếu bạn chỉ bao gồm tiêu đề, nó chỉ có thể sử dụng tệp tiêu đề để xác định cách sử dụng tệp cpp được biên dịch trước.

Mặc dù điều này sẽ không tạo ra nhiều sự khác biệt cho dự án đầu tiên của bạn, nhưng nếu bạn bắt đầu viết các chương trình cpp lớn, mọi người sẽ ghét bạn vì thời gian biên dịch sẽ bùng nổ.

Cũng có một đọc về điều này: Tập tin tiêu đề bao gồm các mẫu


Cảm ơn bạn cho ví dụ cụ thể hơn. Tôi đã thử đọc qua liên kết của bạn, nhưng bây giờ tôi bối rối ... sự khác biệt giữa bao gồm một tiêu đề rõ ràng và một tuyên bố chuyển tiếp là gì?
ialm

Đây là một bài viết tuyệt vời. Veol, ở đây chúng bao gồm các tiêu đề trong đó trình biên dịch cần một thông tin liên quan đến kích thước của lớp. Khai báo chuyển tiếp được sử dụng khi bạn chỉ sử dụng con trỏ.
pankajt

chuyển tiếp từ chối: int someFunction (int NeedValue); chú ý việc sử dụng thông tin loại và (thông thường) không có dấu ngoặc nhọn. Điều này, như đã cho, nói với trình biên dịch rằng tại một số điểm bạn sẽ cần một hàm lấy int và trả về một int, trình biên dịch có thể dành một cuộc gọi cho nó bằng cách sử dụng thông tin này. Đó sẽ được gọi là một tuyên bố chuyển tiếp. Trình biên dịch Fancier được cho là có thể tìm thấy hàm mà không cần điều này, bao gồm một tiêu đề có thể là một cách thuận tiện để khai báo một loạt các khai báo chuyển tiếp.
Nicholas Jordan

6

Các tệp tiêu đề thường chứa các khai báo của các hàm / lớp, trong khi các tệp .cpp chứa các triển khai thực tế. Tại thời điểm biên dịch, mỗi tệp .cpp được biên dịch thành tệp đối tượng (thường là phần mở rộng .o) và trình liên kết kết hợp các tệp đối tượng khác nhau vào tệp thực thi cuối cùng. Quá trình liên kết thường nhanh hơn nhiều so với quá trình biên dịch.

Lợi ích của việc phân tách này: Nếu bạn đang biên dịch lại một trong các tệp .cpp trong dự án của mình, bạn không phải biên dịch lại tất cả các tệp khác. Bạn chỉ cần tạo tệp đối tượng mới cho tệp .cpp cụ thể đó. Trình biên dịch không phải xem các tệp .cpp khác. Tuy nhiên, nếu bạn muốn gọi các hàm trong tệp .cpp hiện tại của mình đã được triển khai trong các tệp .cpp khác, bạn phải báo cho trình biên dịch biết các đối số họ đưa ra; đó là mục đích bao gồm các tệp tiêu đề.

Nhược điểm: Khi biên dịch một tệp .cpp nhất định, trình biên dịch không thể 'nhìn thấy' những gì bên trong các tệp .cpp khác. Vì vậy, nó không biết làm thế nào các chức năng được thực hiện, và kết quả là không thể tối ưu hóa mạnh mẽ như vậy. Nhưng tôi nghĩ bạn chưa cần quan tâm đến vấn đề đó (:


5

Ý tưởng cơ bản rằng các tiêu đề chỉ được bao gồm và các tệp cpp chỉ được biên dịch. Điều này sẽ trở nên hữu ích hơn khi bạn có nhiều tệp cpp và biên dịch lại toàn bộ ứng dụng khi bạn sửa đổi chỉ một trong số chúng sẽ quá chậm. Hoặc khi các chức năng trong các tập tin sẽ bắt đầu tùy thuộc vào nhau. Vì vậy, bạn nên tách các khai báo lớp vào các tệp tiêu đề của mình, để lại việc thực hiện trong các tệp cpp và viết Makefile (hoặc một cái gì đó khác, tùy thuộc vào công cụ nào bạn đang sử dụng) để biên dịch các tệp cpp và liên kết các tệp đối tượng kết quả vào một chương trình.


3

Nếu bạn loại bỏ một tệp cpp trong một số tệp khác trong chương trình của mình, trình biên dịch sẽ cố gắng biên dịch tệp cpp nhiều lần và sẽ phát sinh lỗi vì sẽ có nhiều triển khai của cùng một phương thức.

Quá trình biên dịch sẽ mất nhiều thời gian hơn (trở thành một vấn đề đối với các dự án lớn), nếu bạn thực hiện các chỉnh sửa trong các tệp cpp #included, sau đó buộc biên dịch lại bất kỳ tệp nào trong đó bao gồm chúng.

Chỉ cần đặt các khai báo của bạn vào các tệp tiêu đề và bao gồm các tệp khai báo (vì chúng không thực sự tạo mã mỗi lần) và trình liên kết sẽ nối các khai báo với mã cpp tương ứng (sau đó chỉ được biên dịch một lần).


Vì vậy, ngoài thời gian biên dịch dài hơn, tôi sẽ bắt đầu gặp vấn đề khi tôi #inc loại tệp cpp của mình trong nhiều tệp khác nhau sử dụng các chức năng trong các tệp cpp đi kèm?
ialm

Vâng, đây được gọi là va chạm không gian tên. Quan tâm ở đây là liệu liên kết với libs có đưa ra các vấn đề về không gian tên hay không. Nói chung, tôi thấy rằng trình biên dịch tạo thời gian biên dịch tốt hơn cho phạm vi đơn vị dịch (tất cả trong một tệp) giới thiệu các vấn đề về không gian tên - dẫn đến việc tách lại .... bạn có thể bao gồm tệp bao gồm trong mỗi đơn vị dịch, (được cho là) thậm chí còn có một pragma (#pragma một lần) được cho là để thực thi điều này nhưng đó là một giả định giả định. Hãy cẩn thận để không mù quáng dựa vào libs (tệp .O) từ bất cứ nơi nào vì các liên kết 32 bit không được thi hành.
Nicholas Jordan

2

Mặc dù chắc chắn có thể làm như bạn đã làm, nhưng thông lệ tiêu chuẩn là đưa các khai báo được chia sẻ vào các tệp tiêu đề (.h) và các định nghĩa về các hàm và biến - triển khai - vào các tệp nguồn (.cpp).

Như một quy ước, điều này giúp làm rõ mọi thứ ở đâu và phân biệt rõ ràng giữa giao diện và việc thực hiện các mô-đun của bạn. Điều đó cũng có nghĩa là bạn không bao giờ phải kiểm tra xem liệu tệp .cpp có được bao gồm trong tệp khác hay không, trước khi thêm một cái gì đó có thể phá vỡ nếu nó được xác định trong một số đơn vị khác nhau.


2

tái sử dụng, kiến ​​trúc và đóng gói dữ liệu

đây là một ví dụ:

giả sử bạn tạo một tệp cpp chứa một dạng chuỗi đơn giản tất cả trong một lớp bí ẩn, bạn đặt lớp khai báo cho điều này trong tệp mystring.h biên dịch mystring.cpp thành tệp .obj

bây giờ trong chương trình chính của bạn (ví dụ: main.cpp), bạn bao gồm tiêu đề và liên kết với mystring.obj. để sử dụng bí ẩn trong chương trình của bạn, bạn không quan tâm đến các chi tiết về cách thức thực hiện bí ẩn vì tiêu đề cho biết những gì nó có thể làm

bây giờ nếu một người bạn muốn sử dụng lớp học bí ẩn của bạn, bạn đưa cho anh ta mystring.h và mystring.obj, anh ta cũng không nhất thiết phải biết nó hoạt động như thế nào miễn là nó hoạt động.

sau này nếu bạn có nhiều tệp .obj như vậy, bạn có thể kết hợp chúng thành tệp .lib và liên kết với tệp đó.

bạn cũng có thể quyết định thay đổi tệp mystring.cpp và triển khai nó hiệu quả hơn, điều này sẽ không ảnh hưởng đến chương trình main.cpp hoặc bạn bè của bạn.


2

Nếu nó hiệu quả với bạn thì không có gì sai với nó - ngoại trừ việc nó sẽ xù lông của những người nghĩ rằng chỉ có một cách để làm mọi việc.

Nhiều câu trả lời được đưa ra ở đây giải quyết tối ưu hóa cho các dự án phần mềm quy mô lớn. Đây là những điều tốt để biết, nhưng không có điểm nào trong việc tối ưu hóa một dự án nhỏ như thể đó là một dự án lớn - đó là cái được gọi là "tối ưu hóa sớm". Tùy thuộc vào môi trường phát triển của bạn, có thể có sự phức tạp thêm đáng kể liên quan đến việc thiết lập cấu hình xây dựng để hỗ trợ nhiều tệp nguồn cho mỗi chương trình.

Nếu theo thời gian, dự án của bạn phát triển và bạn thấy rằng quá trình xây dựng mất quá nhiều thời gian, thì bạn có thể cấu trúc lại mã của mình để sử dụng nhiều tệp nguồn để xây dựng gia tăng nhanh hơn.

Một số câu trả lời thảo luận về giao diện tách biệt với việc thực hiện. Tuy nhiên, đây không phải là một tính năng vốn có của các tệp bao gồm và nó khá phổ biến đối với các tệp "tiêu đề" kết hợp trực tiếp với việc triển khai của chúng (ngay cả Thư viện chuẩn C ++ cũng thực hiện điều này ở một mức độ đáng kể).

Điều duy nhất thực sự "độc đáo" về những gì bạn đã làm là đặt tên cho các tệp được bao gồm của bạn ".cpp" thay vì ".h" hoặc ".hpp".


1

Khi bạn biên dịch và liên kết một chương trình, trước tiên trình biên dịch sẽ biên dịch các tệp cpp riêng lẻ và sau đó chúng liên kết (kết nối) chúng. Các tiêu đề sẽ không bao giờ được biên dịch, trừ khi được bao gồm trong tệp cpp trước.

Thông thường các tiêu đề là khai báo và cpp là các tệp thực hiện. Trong các tiêu đề, bạn định nghĩa một giao diện cho một lớp hoặc hàm nhưng bạn bỏ qua cách bạn thực sự triển khai các chi tiết. Bằng cách này, bạn không phải biên dịch lại mọi tệp cpp nếu bạn thực hiện thay đổi trong một.


nếu bạn bỏ việc triển khai ra khỏi tệp tiêu đề, xin lỗi nhưng điều đó nghe có vẻ như giao diện Java với tôi phải không?
gansub

1

Tôi sẽ đề nghị bạn đi qua Thiết kế phần mềm quy mô lớn C ++ của John Lakos . Ở trường đại học, chúng tôi thường viết những dự án nhỏ mà chúng tôi không gặp phải những vấn đề như vậy. Cuốn sách nhấn mạnh tầm quan trọng của việc tách biệt các giao diện và việc triển khai.

Các tệp tiêu đề thường có giao diện được cho là không được thay đổi thường xuyên. Tương tự như vậy, việc nhìn vào các mẫu như thành ngữ Virtual Con constructor sẽ giúp bạn nắm bắt khái niệm này hơn nữa.

Tôi vẫn đang học như bạn :)


Cảm ơn lời đề nghị của cuốn sách. Tôi không biết liệu mình có từng bước đến giai đoạn thực hiện các chương trình C ++ quy mô lớn hay không ...
ialm

thật thú vị khi mã hóa các chương trình quy mô lớn và cho nhiều thách thức. Tôi bắt đầu thích nó :)
pankajt

1

Giống như viết một cuốn sách, bạn chỉ muốn in ra các chương đã hoàn thành một lần

Nói rằng bạn đang viết một cuốn sách. Nếu bạn đặt các chương trong các tệp riêng biệt thì bạn chỉ cần in ra một chương nếu bạn đã thay đổi nó. Làm việc trên một chương không thay đổi bất kỳ chương nào khác.

Nhưng bao gồm các tệp cpp, theo quan điểm của người biên soạn, giống như chỉnh sửa tất cả các chương của cuốn sách trong một tệp. Sau đó, nếu bạn thay đổi nó, bạn phải in tất cả các trang của toàn bộ cuốn sách để có được chương sửa đổi của bạn được in. Không có tùy chọn "in các trang được chọn" trong việc tạo mã đối tượng.

Quay lại phần mềm: Tôi có Linux và Ruby src nằm xung quanh. Một thước đo sơ bộ của các dòng mã ...

     Linux       Ruby
   100,000    100,000   core functionality (just kernel/*, ruby top level dir)
10,000,000    200,000   everything 

Bất kỳ một trong bốn loại này có rất nhiều mã, do đó cần phải có tính mô đun. Loại cơ sở mã này là điển hình đáng ngạc nhiên của các hệ thống trong thế giới thực.

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.