20/4/15

Những nguyên tắc cơ bản khi thiết kế class

Khi xây dựng một class chúng ta cũng cần có những nguyên tắc để class hoàn chỉnh hơn, đảm bảo các tính chất của lập trình hướng đối tượng.

Khi nào thì xây dựng class để biểu diễn đối tượng? Khi chúng ta nghĩ đến vấn đề như một khái niệm riêng lẻ thì lúc này nên xây dựng lớp để biểu diễn khái niệm đó. Lớp là biểu diễn của một khái niệm nào đó, nên tên lớp luôn là danh từ.

Các thuộc tính của lớp là các thành phần dữ liệu mô tả lớp nên chúng cũng là danh từ. Các hàm thành phần đặc trưng cho các hành vi của lớp, vì thế tên hàm là động từ.

Ví dụ:
class Point
{
private:
   float x, y;
public:
   void SetX(int);
   void SetY(int);

   ///...

};

Đa số các trường hợp nên đặt phạm vi của các biến thành viên là private để đảm bảo tính đóng gói.

Các thuộc tính có thể suy diễn từ những thuộc tính khác thì nên dùng hàm thành phần để thực hiện tính toán. Tuy nhiên, nếu các thuộc tính suy diễn dòi hỏi nhiều tài nguyên hoặc thời gian để thực hiện tính toán, ta có thể khai báo là dữ liệu thành phần.

Nếu các dữ liệu thành phần có thể được trừu tượng hóa thành lớp khác thì nên định nghĩa lớp đó để kết hợp chúng lại.

Ví dụ: Với lớp TamGiac nên dùng 3 biến thuộc lớp Diem để biểu diễn 3 điểm A, B, C thay vì dùng 6 biến biểu diễn xA, yA, xB, yB, xC, yC.

Trong mọi trường hợp, nên có phương thức thiết lập để khởi động đối tượng.  Nên có phương thức thiết lập có khả năng tự khởi động mà không cần tham số. Nếu đối tượng có nhu cầu cấp phát tài nguyên thì phải có phương thức thiết lập, copy constructor để khởi động đối tượng bằng đối tượng cùng kiểu và có destructor để dọn dẹp. Ngoài ra còn phải định nghĩa lại phép gán (tương tự như copy constructor). Nếu đối tượng đơn giản không cần tài nguyên riêng thì không cần copy constructor và destructor.

Có thể xem thêm bài viết này: Khi nào cần định nghĩa constructor, copy constructor và destructor?

17/4/15

Quy tắc chuyển sang thể Te của động từ tiếng Nhật

Thể Te của động từ rất hay gặp trong tiếng Nhật, vì vậy nó rất quan trọng nên các bạn cần phải nắm vững. Động từ chia ở thể Te có đuôi là  て hoặc で, dùng để sai bảo hoặc liên kết. Ở đây mình sẽ cung cấp cách chuyển sang thể Te của động từ dạng nguyên gốc (~う段) và dạng masu (~ます).

Với động từ nguyên gốc, ta có cách chia của 3 nhóm nhỏ là khác nhau:
Động từ nhóm 1: Với động từ kết thúc bằng các đuôi khác nhau sẽ chia khác nhau.

  •  ~う、~る、~つ  → ~って
  • ~す → ~して
  • ~く → ~いて
  • ~ぐ → ~いで
  • ~む、~ぬ、~ぶ → ~んで
Ví dụ:
会う → 会って「あう」
帰る → 帰って「かえる」
立つ → 立って「たつ」
出す → 出して「だす」
続く → 続いて「つづく」
泳ぐ → 泳いで「およぐ」
飲む → 飲んで「のむ」
死ぬ → 死んで「しぬ」
遊ぶ → 遊んで「あそぶ」

Động từ nhóm 2: Chỉ đơn giản bỏ đuôi る và thêm て

Ví dụ:
見る → 見て
食べる → 食べて

Động từ bất quy tắc: Có 3 động từ là iku (đi), kuru (đến) và suru (làm)
  • 行く「いく」 → 行って「いって」
  • 来る「くる」 → 来て「きて」
  • する → して
Ghi chú: Với dạng động từ ghép bằng danh từ với する thì ta chỉ chia する như động từ bất quy tắc.

Với dạng masu, hầu như tương tự như trên, mình chỉ ghi cụ thể cho các bạn rõ hơn:
Động từ nhóm 1:
  • ~います、~ります、~ちます → ~って
  • ~します → ~して
  • ~きます → ~いて
  • ~ぎます → ~いで
  • ~みます、~にます、~びます → ~んで
Ví dụ:
言います → 言って「いいます」
走ります → 走って「はしります」
立ちます → 立って「たちます」
書きます → 書いて「かきます」
泳ぎます → 泳いで「およぎます」
休みます → 休んで「やすみます」
死にます → 死んで「しにます」
飛びます → 飛んで「とびます」

Động từ nhóm 2: Bỏ ます và thêm て

Ví dụ:
変えます → 変えて
寝ます → 寝て

Động từ bất quy tắc: Không xét する và 来る vì chia giống như động từ nhóm 2
  • 行きます → 行って

15/4/15

Khi nào cần định nghĩa constructor, copy constructor và destructor?

Hàm dựng (constructor) và hàm hủy (destructor) là 2 yếu tố quan trọng luôn có trong một lớp (class) trong lập trình hướng đối tượng. Nếu người dùng không định nghĩa thì trình biên dịch sẽ tự động thêm vào hàm dựng và hàm hủy mặc định. Tuy nhiên, đôi khi chúng ta cần phải định nghĩa lại hàm dựng và hàm hủy để đảm bảo an toàn và hợp logic hơn.

Hàm dựng (constructor)
Hàm dựng là hàm được tự động gọi khi đối tượng được tạo lập. Có 3 loại: mặc định (không tham số), có tham số và hàm dựng sao chép (copy constructor).

Trong hầu hết các trường hợp, bạn nên định nghĩa hàm dựng để khởi tạo giá trị ban đầu cho các biến thành viên.

Nếu khai báo đối tượng mà không cung cấp đối số thì hàm dựng mặc định sẽ được gọi, nếu có đối số thì hàm dựng có tham số sẽ được gọi. Vì vậy, hàm dựng có tham số thường được dùng để khởi động đối tượng với giá trị tùy người dùng thay vì giá trị được định nghĩa trong hàm dựng mặc định. Ngoài ra, còn được dùng để chuyển kiểu ngầm định khi gọi với toán tử.

Hàm dựng sao chép dùng để tạo nên đối tượng mới giống hệt đối tượng cũ và thường được dùng trong phép gán. Tất nhiên trình biên dịch sẽ tự thêm copy constructor mặc định khi không khai báo. Nguyên tắc của copy constructor là copy giá trị theo từng cặp biến thành viên.

Vấn đề chỉ xuất hiện khi lớp có thành viên là con trỏ và có cấp phát tài nguyên động như mảng. Trong trường hợp này, nếu không định nghĩa lại copy constructor thì mặc định chỉ sao chép giá trị của các biến thành viên mà không sao chép mảng động. Cụ thể hơn, với biến thành viên là con trỏ, giá trị (tức địa chỉ của mảng động) sẽ được bê nguyên xi qua con trỏ của đối tượng kia và hiển nhiên 2 con trỏ đang trỏ đến cùng một vùng nhớ. Khi hủy 2 đối tượng thì vùng nhớ sẽ bị thu hồi 2 lần và điều này gây ra lỗi. Hơn nữa, việc 2 đối tượng khác nhau cùng dùng chung vùng nhớ cũng sẽ không hợp logic. Vậy, bạn nên định nghĩa lại copy constructor khi lớp có thành viên là con trỏ và có cấp phát tài nguyên động.

 Nguyên tắc định nghĩa lại:
1. Gán các biến thành viên tĩnh cho nhau.
2. Cấp phát lại vùng nhớ động cho con trỏ.
3. Sao chép lại mảng động.

13/4/15

Làm quen với ngôn ngữ C từ Pascal (Phần 2)

Trong phần này mình sẽ nói về các cú pháp vòng lặp, rẽ nhánh, điểm khác nhau của chúng trong C và Pascal.

Lệnh rẽ nhánh if
Đây là một lệnh rất quen thuộc. Trong C, biểu thức điều kiện được đặt trong cặp dấu ngoặc tròn thay vì giữa 2 từ khóa if .. then như Pascal. Tiếp theo là một lệnh hoặc khối lệnh. Khối lệnh trong C đặt giữa cặp dấu ngoặc nhọn {} còn trong Pascal đặt giữa begin .. end;

Ở dạng có thêm else trong Pascal được tính là một lệnh. Vì vậy nên khối lệnh trước else sẽ không có dấu chấm phẩy. Còn trong C tính là 2 lệnh nên cần dấu chấm phẩy trước else.

Ví dụ:
if a > b then writeln(a)
else writeln(b);

if (a > b) printf("%d\n", a);
else printf("%d\n", b);
Lệnh rẽ nhánh switch .. case
Tương đương với case .. of của Pascal. Nhưng switch .. case có vẻ khó sử dụng hơn, nó đòi hỏi phải có lệnh nhảy break sau mỗi case, nếu không nó sẽ thực hiện tất cả các case. Dưới đây là ví dụ:

case month of
   4, 6, 9, 11 : writeln('30 days');
   2: writeln('28 days');
   else writeln('31 days');
end;

switch (month)
{
case 4: case 6: case 9: case 11:
   printf("30 days\n");
   break;
case 2:
   printf("28 days\n");
   break;
default:
   printf("31 days");
}
Vòng lặp for
Cách vòng lặp for trong C hoạt động khác hoàn toàn với Pascal. Trong C, vòng lặp for có cú pháp:

for(<khởi tạo>;<điều kiện>;<tăng biến chạy>)

Trong đó phần <khởi tạo> dùng để tạo giá trị ban đầu cho biến chạy và chỉ được thực hiện 1 lần duy nhất. Phần <điều kiện> chứa điều kiện để vòng lặp thực hiện và phần <tăng biến chạy> mô tả cách thức tăng/giảm của biến chạy. Vòng for của C có thể thay thế tất cả các vòng lặp còn lại.

Tuy nhiên, vòng for của Pascal chỉ có thể tăng/giảm biến chạy 1 đơn vị.
Dưới đây là ví dụ: Tính S = 2 + 4 + 6 + 8 + ...+ 2n trong Pascal không thể thực hiện bằng for nhưng C thì có thể:
int s = 0;
for (int i = 2; i <= 2*n; i += 2)
   s += i;
Vòng lặp while và do .. while
Vòng lặp while trong C hầu như tương tự Pascal và mình không có gì để bàn. Trong C còn có vòng lặp do .. while, chỉ khác while ở chỗ thực hiện lệnh 1 lần rồi mới kiểm tra điều kiện lặp, nếu đúng mới lặp lại tiếp. Khác với repeat .. until của Pascal, điều kiện trong until là ngược với điều kiện trong while của do .. while.
Ví dụ: Nhập N trong khoảng từ 1 đến 100, nếu ngoài khoảng đó yêu cầu nhập lại.

repeat
   write('Nhap N: ');
   readln(n);
until n >= 1 and n <= 100;

do
{
   printf("Nhap N: ");
   scanf("%d", &n);
} while (n < 1 || n > 100);

10/4/15

Làm quen với ngôn ngữ C từ Pascal (Phần 1)

Bài viết này hướng đến các bạn học sinh muốn tìm hiểu thêm về ngôn ngữ C khi các bạn chỉ biết về Pascal. Ở phần này mình chỉ phân tích chủ yếu về các khái niệm, sự giống và khác nhau trong cú pháp giữa 2 ngôn ngữ và các vấn đề rắc rối thường gặp trong ngôn ngữ C chứ không đi sâu vào cụ thể.

C là một ngôn ngữ hướng cấu trúc và được tổ chức theo đơn vị cơ bản là các hàm. Vì vậy, chương trình chính của C cũng là một hàm. Một lệnh của C cũng được kết thúc bằng dấu chấm phẩy (;), tương tự như Pascal.

Bây giờ chúng ta cùng xem một chương trình đơn giản trong Pascal và C khác nhau như thế nào.
//Đoạn chương trình Pascal
program vi_du;
uses crt;
var
   a: integer;
begin
   writeln('Nhap A: ');
   readln(a);
   writeln('A = ', a);
end.

//Đoạn chương trình C
#include<stdio.h>
int main()
{
   int a;
   printf("Nhap A: ");
   scanf("%d", &a);
   printf("A = %d", a);
   return 0;
}

Lệnh program trong Pascal chỉ tên chương trình, trong C không có lệnh này và nó cũng không cần thiết. Ngay cả khi lập trình trong Pascal không có lệnh này chương trình vẫn chạy bình thường mà phải không? Cho nên đây không phải là một lệnh quan trọng.

Lệnh uses để khai báo thư viện trong Pascal. Thư viện quan trọng là crt (có các hàm như clrscr() chẳng hạn). Lệnh này trong C khai báo là #include<stdio.h> (nhớ là không có dấu chấm phẩy nhé).
Thư viện nhập xuất chuẩn của C là stdio.h, thư viện này chứa các hàm để bạn xuất ra màn hình hay nhập dữ liệu từ bàn phím vào biến, v.v...

Biến trong Pascal được khai báo bằng từ khóa var và phải được đặt trước khối lệnh begin .. end. Trong C biến được khai báo bất kỳ đâu bên trong hàm và nó có phạm vi sử dụng từ lúc khai báo đến hết hàm.

Bonus: Kiểu mảng trong Pascal khai báo khá phức tạp, cho phép đặt kiểu chỉ số (thông thường bắt đầu từ 1). Còn trong C kiểu mảng khai báo như khai báo biến nhưng có thêm cặp ngoặc vuông chỉ số lượng phần tử. Các phần tử được đánh chỉ số từ 0 đến n-1.

Ví dụ:
//Khai báo mảng a có 10 phần tử được đánh chỉ số từ 0 đến 9
int a[10];

Cặp lệnh begin .. end là một đặc trưng của Pascal, nó đánh dấu bắt đầu chương trình và kết thúc chương trình, đồng thời nó cũng chỉ một khối các lệnh (trong if .. then chẳng hạn, khi mà bạn muốn thực hiện nhiều lệnh trong cùng 1 điều kiện).

Như ban đầu mình đã nói, chương trình trong C được tổ chức theo các hàm. Chương trình chính cũng là một hàm và hàm này mang tên là main(). Cặp dấu chỉ khối lệnh trong C là cặp dấu ngoặc nhọn { }. Nếu tìm hiểu về C bạn sẽ thấy những rắc rối như tại sao hàm main() không nên trả về kiểu void, v.v...

Thêm một tí: Trong C, khai báo hàm cũng đặt kiểu trả về trước tên hàm như khai báo biến. Thủ tục (procedure) cũng là hàm nhưng có kiểu trả về là void. Dùng lệnh return để trả về thay vì gán tên hàm với giá trị trả về như Pascal.

C có các hàm nhập xuất chuẩn printf(), scanf() cũng giống như write, writeln, read, readln của Pascal. Tuy nhiên, cú pháp cũng khá rắc rối và không tiện dụng như Pascal, bạn sẽ phải tìm hiểu về mã đặc tả, địa chỉ, v.v...

Có thể thấy Pascal hướng đến sự trong sáng rõ ràng nên thích hợp dạy trong nhà trường, còn C yêu cầu phải tìm hiểu thêm về yếu tố kỹ thuật. Tuy nhiên, nếu nắm được ngôn ngữ C và rèn luyện tư duy lập trình thì các bạn sẽ tìm hiểu các ngôn ngữ khác rất dễ dàng.

Với phần 1 các bạn đã thấy được những điểm giống và khác biệt giữa C và Pascal. Phần tiếp theo mình sẽ nói về cú pháp một số cấu trúc rẽ nhánh, lặp thông dụng giữa 2 ngôn ngữ.