Sử dụng KDevelop thay thế cho Visual C++

Đối với những bạn đang học lập trình thì tìm một công cụ lập trình trên Linux thay thế cho Visual Studio trên Windows là một điều hết sức quan trọng. Bài viết này sẽ giới thiệu phần mềm KDevelop, một công cụ lập trình khá mạnh không kém gì Visual Studio.

KDevelop hỗ trợ rất nhiều ngôn ngữ khác nhau như ADA, C, C++, Fortran, Java, Pascal, Perl,… Vì mình mới học C/C++ nên mình chỉ nói về cách lập trình C/C++ trên KDevelop.

I/ Cài đặt KDevelop:

Vào Applications -> Add/Remove… tìm KDevelop (trong mục Programming) và cài. Tuy nhiên, KDevelop là chương trình tối ưu cho Kubuntu, nên nếu cài trong Ubuntu, bạn cần cài thêm Konsole (trong Add/Remove…).

Để cài trình biên dịch cho KDevelop, bạn mở Terminal và gõ vào:

$ sudo apt-get install g++

Sau đó vào System -> Administration -> Synaptic Package Manager, tìm với từ khóa automake, cài autoconf 2.61-4automake 1.9.

Thế là xong!

II/ Sử dụng KDevelop:

Tạo 1 project mới: Project -> New Project -> C++ -> Simple Hello world program. Bạn gõ tên Project, next vài lần rồi nhấn Shift + F9 để chạy thử. Nếu hiện ra cửa sổ Konsole là thành công!

Thật sự mình cũng chẳng biết làm thế nào để tạo 1 project rỗng như Visual C++ nên lúc nào cũng chọn Simple Hello world program rồi xoá hết nội dung file để code lại. Sử dụng cách này khá tiện vì nó tạo sẵn makefile (makefile là file “hướng dẫn” cho g++ biên dịch project).

KDevelop tự động đặt tên file chứa hàm main trùng với tên project. Nếu không thích kiểu đặt tên này, bạn có thể sửa lại như sau:

Ví dụ mình có 1 project tên test và muốn đổi tên file test.cpp thành main.cpp

Cột bên trái chọn File Selector (trên cùng) -> src (tất cả những file .cpp và .h đều nằm ở thư mục src). Click phải lên file test.cpp -> Properties. Đổi tên lại rồi nhấn OK.

Sau đó vài makefile.am, đổi dòng test_SOURCES = test.cpp thành test_SOURCES = main.cpp.

Nhấn Shift -> F9 để chạy. Nếu Konsole lại hiện ra là OK. Trông có vẻ phức tạp nhưng nếu bạn biết sử dụng makefile thì nó sẽ rất hay và tiện lợi.

Một số hiệu chỉnh khi sử dụng KDevelop (không bắt buộc và tùy theo phong cách mỗi người, mình chỉnh lại để dễ nhìn hơn)

Vào Setting -> Configure Editor… -> Indentation. Intentation mode chọn S&S C Style (bạn có thể thử nhiều style khác nhau để chọn cái thích hợp).

Vào Project -> Project Options -> Debugger. Đánh dấu chọn Enable seperate terminal for application IO. Khi debug, đến câu lệnh cin để nhập liệu thì cửa sổ Konsole sẽ mở ra (lúc trước không biết, debug đến lệnh cin nó đơ luôn 😀 )

Bấy nhiêu đấy là bạn có thể vui vẻ với KDevelop được rồi đấy.

Bạn có thể tìm hiểu thêm về KDevelop tại http://women.kde.org/articles/tutorials.php . Có cả tài liệu tiếng việt do Phan Anh Vu dịch đấy 🙂

Nói thêm: bạn có thể không cần cài KDevelop mà vẫn có thể dùng g++ để biên dịch và thực thi 1 file C/C++. Mở terminal và vào thư mục chứa file C/C++:

Tạo file thực thi:

$ g++ main.cpp -o main

Chạy file:

$ ./main

Debug trong terminal: sử dụng gdb. Khi biên dịch file main.cpp bạn phải dùng:

$ g++ -g main.cpp -o main

Và debug:

$ gdb main

– Để chạy chương trình gõ r
– Đặt breakpoint tại dòng thứ i gõ b i
– Đặt breakpoint tại hàm tên func gõ b func
– Xem file n dòng đầu gõ l n
– Compile dòng tiếp theo gõ n
– Xem giá trị 1 biến x gõ p x

Nhớ có nhiêu đó 🙂 Ai muốn biết rõ hơn lên mạng search vậy 😀

Để compile nhiều file cùng lúc (.cpp, .h) bạn nên sử dụng makefile. Tiếc là mình học lâu quá nên bây giờ không nhớ rõ nữa.

Cách tổ chức file C và C++ (phần 3)

Khắc phục trường hợp thứ nhất:

May mắn thay, trường hợp này khá đơn giản, và dễ tránh nếu bạn hiểu nó.

Lỗi thứ nhất, khi file nguồn không biên dịch bởi vì một trong số những định nghĩa chưa được khai báo, dễ dàng để sửa. Đơn giản là #include file chứa định nghĩa cần thiết. Nếu file header của bạn được tổ chức hợp lý và đặt tên tốt, điều này sẽ rất dễ dàng. Nếu bạn cần sử dụng cấu trúc Sprite, thì bạn chi cần thêm vào #include “Sprite.h” vào bất cứ file nào có nó. 1 lỗi mà nhiều lập trình viên thường mắc phải là cho rằng 1 file đã được #include đơn giản vì nó đã được #include trong 1 file khác.

Example:
/* Header1.h */
#include “header2.h”
class ClassOne { … };

.

/* Header2.h */
class ClassTwo { … };

.

/* File1.cpp */
#include “Header1.h”
ClassOne myClassOne_instance;
ClassTwo myClassTwo_instance;

Trong trường hợp này, File1.cpp sẽ biên dịch tốt, vì đã include Header1.h và include gián tiếp Header2.h, có nghĩa là File1.cpp có thể truy xuất đến lớp Class2. Nhưng chuyện gì sẽ xảy ra nếu một thời gian sau, ai đó cho rằng Header1.h không cần thiết phải #include Header2.h? Họ có thể xoá dòng #include đó, và đột nhiên File1.cpp sẽ không thể biên dịch được.

Chìa khóa ở đây là, cần phải dứt khoát khi #include bất kì header nào bạn cần cho file nguồn để biên dịch. Bạn không nên dựa vào những file header include gián tiếp các header bổ sung, mà có thể sẽ thay đổi. Những #include bổ sung cũng xử lý như tài liệu, mà các mã trong file phải phụ thuộc vào. Do đó đừng xoá chúng nếu bạn biết bạn cần header đó cần phải include đâu đó.

Khắc phục trường hợp thứ 2:

Sự phụ trường vòng tròn là vấn đề thường gặp trong kĩ thuật phần mềm. Nhiều cấu trúc liên kết lẫn nhau, và tất cả đều phải biết lẫn nhau. Thông thường nó có dạng như thế này:

/* Parent.h */
#include “child.h”
class Parent
{
Child* theChild;
};

.

/* Child.h */
#include “parent.h”
class Child
{
Parent* theParent;
};

Trường hợp này thật sự đơn giản. Cấu trúc Parent thật ra không cần phải biết chi tiết lớp Child, khi nó chỉ chứa 1 con trỏ của lớp Child. Con trỏ không cần biết nó chỉ đến đâu, do đó bạn không cần phải định nghĩa cấn trúc hoặc lớp để chứa con trỏ. Do đó dòng #include ở đây là không cần thiết. Tuy nhiên, lỗi “undeclared identifier” sẽ xuất hiện khi nó gặp từ ‘Child’, nên bạn cần cho trình biên dịch biết rằng Child là 1 lớp mà bạn cần chỉ đến. Sử dụng khai báo trước, đưa ra dạng hoặc định nghĩa lớp mà không có chi tiết bên trong. Ví dụ:

/* Parent.h */
class Child; /* Forward declaration of Child; */
class Parent
{
Child* theChild;
};

Chú ý rằng dòng #include được thay thế bằng một khai báo trước. Điều này cho phép thoát khỏi sự phụ thuộc vòng tròn giữa Parent.h và Child.h. Hơn nữa, tốc độ biên dịch sẽ tăng lên khi bạn có ít file include hơn. Trong trường hợp này, file Child.h cũng phải khai báo trước “class Parent;” Chỉ khi bạn chỉ tham chiếu đến con trỏ mà không phải dạng thật sự, bạn không cần phải #include toàn bộ định nghĩa. Trong 99% trường hợp, điều này có thể áp dụng cho cả 2 phía của vòng tròn và không cần #include 1 header trong file header kia, để loại trừ sự phụ thuộc vòng tròn.

Dĩ nhiên, trong những file parent.c và child.c, bạn cần phải #include cả parent.h và child.h.

Một trường hợp khác làm xuất hiện sự phụ thuộc vòng tròn là những hàm inline định nghĩa bên trong header (để tăng tốc độ). Để cho hàm có thể thực thi, nó cần phải biết chi tiết của lớp. Do đó, file header có chứa hàm inline cần phải #include khi những hàm đó cần được biên dịch. Lời khuyên đầu tiên là bạn chỉ nên tạo 1 hàm inline khi nó thật sự cần thiết. Khi bạn kiểm tra và chắc chắn những hàm cần phải ở dạng inline (và dĩ nhiên là trong file header), cố gắng và đảm bảo rằng trong 2 file header phụ thuộc lẫn nhau chỉ có 1 file là được chứa những hàm inline.

Chú ý rằng bạn việc loại trừ sự phụ thuộc không phải lúc nào cũng thực hiện được. Nhiều cấu trúc và lớp bao gồm nhiều cấu trúc và lớp khác, mà sự phụ thuộc là không thể tránh khỏi. Tuy nhiên, chỉ khi sự phụ thuộc là 1 chiều, nó sẽ được sửa chữa khi biên dịch, và sẽ không có vấn đề gì xảy ra.

Có nhiều phương pháp phức tạp hơn để xử lý sự phụ thuộc vòng tròn, nhưng nó không liên quan đến mục đích của bài viết. Trong 99% trường hợp, sử dụng định nghĩa trước và các hàm thông thường trong file .C/.CPP cũng như không có hàm inline trong file header là đủ.
(còn tiếp)

Nguồn: http://www.gamedev.net/reference/programming/features/orgfiles/page3.asp

Cách tổ chức file C và C++ (phần 2)

Những bước cơ bản

Bây giờ bạn đã tin chắc rằng có những ưu điểm khi phân chia đề án của bạn thành nhiều file nhỏ hơn. Do đó, làm cách nào để thực hiện điều này? Mặc dù bạn có thể thực hiện bằng nhiều cách tùy ý, nhưng có một vài quy tắc cơ bản mà bạn nên tuân theo để chắc rằng nó hoạt động.

Đầu tiên, nhìn vào cách bạn phân chia các đoạn mã thành nhiều phần. Thông thường nó được phân chia vào trong các hệ thống con riêng biệt, hoặc các ‘module’, như là âm thanh nhạc, đồ hoạ, file điều khiển… Tạo những file mới với những tên có nghĩa sẽ giúp bạn biết loại mã bên trong khi nhìn lướt qua. Sau đó di chuyển toàn bộ mã bên trong theo các module bên trong file đó. Đôi khi bạn không có những module rõ ràng – vài người cho rằng nên có những cảnh báo về chất lượng thiết kế của bạn! Bạn cần có những tiêu chuẩn khác khi phân chia mã nguồn, như là cấu trúc nó theo tác dụng ở trên (thông thường những hàm “mục đích chung” có thể nằm trong chuỗi-điều khiển hoặc số-điều khiển). Và thỉnh thoảng 1 module có thể chia ra thành 2 hay nhiều file, với những lý do hợp lý.

Khi bạn phân chia nó thành nhiều file theo cách này, vấn đề tiếp theo cần được xem xét là những gì sẽ đến trong file header. Rất cơ bản, những mã mà bạn đặt vào đầu file nguồn như như thành phần không thể thiếu sẽ được chuyển vào các file header. Đó có thể là lý do tại sao chúng có tên gọi là file ‘header’.

Những dạng thường nằm trong file header:

  • Định nghĩa lớp hoặc cấu trúc
  • typedef
  • tên hàm
  • biến toàn cục (xem thêm bên dưới)
  • hằng số
  • #define macros
  • #pragma directives

(Hơn nữa, khi sử sụng C++, biểu mẫu <template> và các hàm bên trong thường nằm trong file header. Lý do sẽ được trình bày rõ ở phần sau).

Ví dụ, hãy nhìn vào những thư viện C chuẩn của bất kì trình biên dịch C hoặc C++. Stdlib.h là 1 ví dụ tốt; hãy mở nó ra bằng trình soạn thảo và xem nó. (Hoặc để tiết kiệm thời gian trong LS Visual C++ 6, gõ nó vào trong file nguồn, click chuột phải lên vàc chọn “Open Document “stdlib.h”‘). Bạn sẽ chú ý rằng chúng chỉ có vài hoặc toàn bộ những thành phần như trên, nhưng không có bất kì mã thực nào. Tương tự, bạn có thể thấy rằng bất kì định nghĩa biến toàn cục nào cũng có từ ‘extern’ phía trước. Điều này là quan trọng, ta sẽ biết nhiều hơn ở phần sau.

Nhìn chung bạn cần có 1 file header cho tất cả file nguồn. Có nghĩa là, một file SPRITES.CPP chắc chắn cần file SPRITES.H, file SOUND.CPP cần SOUND.H,… Giữ những cái tên phù hợp để bạn biết ngay những file header nào sẽ đi với file thông thường nào.

Những file header này trở thành phần chung giữa những hệ thống con. Bằng cách #include một header, bạn có thể truy xuất và toàn bộ những định nghĩa cấu trúc, tên hàm, hàng số… cho hệ thống con. Do đó, mỗi file nguồn sử dụng sprites cần phải #include “sprite.h”, mỗi file nguồn sử dụng âm thanh cần phải #include “sound.h”,… Chú ý rằng bạn nên dùng ngoặc kép hơn à dấu <> khi #include những file riêng. Dấu ngoặc kép cho trình biên dịch biết phải tìm kiếm header của bạn trong thư mục chương trình trước, rồi mới đến các header chuẩn của trình biên dịch.

Nên nhớ rằng, đối với trình biên dịch, hoàn toàn không có sự khác nhau giữa file header và file nguồn. (Ngoại trừ một vài trình biên dịch không biên dịch file header trực tiếp). Đơn giản hơn, chúng chỉ là những file văn bản đơn thuần chứa đầy dòng lệnh. Sự phân biệt chỉ là một quan niệm rằng lập trình viên phải dựa vào đó để giữ cấu trúc file hợp lý và đầy đủ. Ý tưởng chính là những header chỉ chứa những cái chung, và file nguồn chứ những thực thi thật sự. Bạn có thể áp dụng chúng ở bất cứ đâu khi làm việc với C hoặc C++, trong lập trình hướng đối tượng hay lập trình cấu trúc. Điều này có nghĩa là 1 file nguồn dùng 1 file nguồn khác thông qua header của file nguồn thứ hai.

Những cạm bẫy tìm ẩn

Trong những trường hợp đơn giản, bạn có thể tạo ra những chương trình làm việc hoàn chỉnh theo những hướng dẫn đó. Tuy nhiên có một vài chi tiết cần để ý, và nó thường là những chi tiết khiến những lập trình viên mới vào nghề khổ sở khi họ lần đầu tiên phân chia mã ra thành file header và file thông thường.

Theo kinh nghiệm của tôi, có 4 lỗi cơ bản mà người ta thường gặp phải:

  1. Những file nguồn không biên dịch nữa khi chúng không tìm thấy hàm hoặc biến cần thiết (Thường xuất hiện dòng lỗi tương tự như: “error C2065: ‘MyStruct’ : undeclared identifier” trong Visual C++)
  1. Phụ thuộc vòng tròn, khi những header xuất hiện khi cần #include lẫn nhau để làm việc một cách có dụng ý. Một Sprite chứa 1 con trỏ đến Creature, và 1 Creature có thể chứa 1 con trỏ đến Sprite. Bất kể bạn làm như thế nào, cả Creature lẫn Sprite đều phải khai báo trước tiên, và hệ quả là nó không làm việc khi dạng khác chưa được khai báo.
  1. Định nghĩa chồng khi 1 lớp hoặc cấu trúc được gọi 2 lần trong 1 file nguồn. Đây là lỗi thời gian biên dịch và thường xuất hiện khi gọi nhiều file header trong 1 file header khác, làm cho header được gọi 2 lần khi bạn biên dịch file nguồn (trong MSVC, lỗi này có dạng như “error CS2011: ‘MyStruct’ : ‘struct’ type redefinition).
  1. Trường hợp lặp lại các đối tượng đã được biên dịch tốt. Đây là lỗi liên kết, thường rất khó hiểu. (Trong MSVC, bạn có thể thấy cái gì đó như “”error LNK2005: “int myGlobal” (?myGlobal@@3HA) already defined in myotherfile.obj”).

Làm sao để sửa những trường hợp này?
(còn tiếp)

Nguồn: http://www.gamedev.net/reference/programming/features/orgfiles/page2.asp

Cách tổ chức file C và C++ (phần 1)

Giới thiệu:

Trong khi nhiều chương trình đơn giản chỉ gói gọn trong 1 file C hoặc CPP duy nhất, thì nhiều đề án lớn cần phải chia nhỏ thành nhiều file nguồn để tiện quản lý. Tuy nhiên, nhiều lập trình viên mới không nhận thấy tầm quan trọng của điều này – ngay cả khi họ cố gắng và chạy chương trình với quá nhiều vấn đề bên trong mà họ cho rằng nó không đáng để nỗ lực. Bài viết này sẽ giải thích tại sao ta cần phải làm như vậy, và làm sao để thực hiện đúng. Khi cần thiết, tôi sẽ đưa ra các giải thích cách làm việc của trình biên dịch và các mối liên kết để bạn hiểu rõ tại sao chúng ta phải làm theo cách đó.

Thuật ngữ:

Trong bài này, tôi sẽ gọi những file C và C++ tiêu chuẩn (thường có tên mở rộng C và CPP) là “file nguồn”. Điều này sẽ phân biệt chúng với những “file header” (thường có tên mở rộng là .H hay .HPP). Những thuật ngữ này cũng được sử dụng trong Visual C++ và hầu hết các sách. Chú ý rằng sự khác biệt hoàn toàn thuộc về quan niệm – chúng chỉ là những file văn bản với mã bên trong. Tuy nhiên, như những vấn đề sẽ được trình bày, sự khác biệt trong việc xử lý file nguồn và file header là rất quan trọng.

Tại sao phải chia nhỏ mã nguồn thành nhiều file?

Câu hỏi đầu tiên mà nhiều lập trình viên mới vào nghề thắc mắc khi họ nhìn thấy 1 thư mục chứa đầy những tập tin là “tại sao không nhét tất cả vào 1 file duy nhất?”. Đối với họ, họ không thấy được tầm quan trọng của việc phân tán mã nguồn.

Việc phân chia đề án đạt được một số thuận lợi, và những điều quan trọng nhất là:

  • Tăng tốc độ biên dịch: hầu hết các trình biên dịch làm việc trên 1 file trong 1 lúc. Do đó nếu bạn có 10000 dòng lệnh trong 1 file, vá bạn thay đổi 1 dòng, bạn phải biên dịch lại 10000 dòng lệnh. Mặt khác, nếu 10000 dòng lệnh của bạn được chia ra thành 10 file, thì sự thanh đổi 1 dòng chỉ yêu cầu 1000 dòng lệnh được biên dịch lại. 9000 dòng lệnh trong 9 file còn lại không cần phải biên dịch lại (Thời gian liên kết không bị ảnh hưởng).
  • Tăng cường sự tổ chức: phân chia code của bạn một cách hợp lý sẽ làm bạn (và bất kì lập trình viên khác trong đề án) dễ dàng hơn để tìm kiếm những hàm, biến, định nghĩa các cấu trúc/lớp, v.v… Thấy chí với khả năng nhảy trực tiếp để đưa ra những định nghĩa được cung cấp trong nhiều môi trường soạn thảo và lập trình (như Microsoft Visual C++), điều này vẫn có ích khi bạn cần xem 1 đoạn mã để tìm kiếm cái gì đó. Không những phân chia mã sẽ làm giảm số lượng mã cần phải biên dịch lại, mà còn giảm số lượng mã bạn cần phải đọc để tìm ra những gì bạn cần. Tưởng tượng rằng bạn cần tìm và sửa 1 lỗi mã âm thanh mà bạn đã làm nhiều tuần trước đây. Nếu bạn có 1 file lớn là GAME.C, thì cần phải tìm kiếm rất nhiều. Nếu bạn có vài file nhỏ GRAPHICS.C, MAINLOOP.C, SOUND.C, và INPUT.C, bạn biết nơi bạn cần tìm, giảm 3/4 số thời gian làm việc.
  • Giảm bớt sự viết lại: Nếu mã của bạn được phân chia cẩn thận thành những đoạn độc lập với nhau, bạn có thể sử dụng chúng trong đề án khác, tiết kiệm thời gian phải viết lại lần sau. Có nhiều thuận lợi khi viết mã có thể dùng lại được hơn là chỉ dùng 1 cách tổ chức file chặt chẽ, nhưng nếu không có sự tổ chức, thì sẽ rất khó khăn để biết được những đoạn mã nào liên quan đến nhau hay không. Do đó, đặt những thành phần chính và các lớp vào 1 file duy nhất hoặc mô tả cẩn thận tập hợp các file sẽ giúp bạn về sau nếu bạn sử dụng lại những mã đó trong đề án khác.
  • Chia sẽ mã giữa các đề án: nguyên tắc này giống như việc giảm bớt sự viết lại. Chia nhỏ 1 cách cẩn thận trong những file chính xác, bạn tạo khả năng cho nhiều đề án cùng sử dụng chung 1 file mà nguồn mà không cần nhân chúng lên. Lợi ích của việc chia sẽ file giữa các đề án so với sử dụng cắt-và-dán là bất cứ lỗi nào được sửa trong 1 hay nhiều file trong 1 đề án sẽ có tác dụng với những đề án khác, và tất cả đề án có thể chắc chắn được cập nhật bản mới nhất.
  • Phân chia trách nhiệm giữa các lập trình viên: trong những đề án lớn thật sự, đây có lẽ là lý do chính để phân chia mã trong nhiều file. Sẽ không thực tế khi nhiều hơn 1 lập trình viên cùng thay đổi 1 file duy nhất bất cứ lúc nào anh ta muốn. Do đó, bạn sẽ phải dùng nhiều file để mỗi lập trình viên có thể làm việc trên từng phần riêng biệt mà không ảnh hưởng đến file mà lập trình viên khác đang chỉnh sửa. Dĩ nhiên, vẫn cần có những sự kiểm tra để 2 lập trình viên không thay đổi trên cùng 1 file; hệ thống quản lý cấu hình và hệ thống điều khiển phiên bản như là CVS hoặc MS SourceSafe sẽ giúp bạn làm điều này.

Tất cả những gì ở trên cho ta thấy hình dáng của modularity, một thành phần quan trọng của cả lập tình cấu trúc và hướng đối tượng.
(còn tiếp)

Nguồn: http://www.gamedev.net/reference/programming/features/orgfiles/default.asp

————
Cảm ơn thầy Đ.B.Tiến đã cung cấp 1 tài liệu hữu ích 🙂