Thứ Năm, 17 tháng 9, 2009

Dynamic Memory- Cấp phát động

Thông thường khi lưu trữ dữ liệu cùng kiểu với số lượng lớn hoặc các đối tượng, bạn dùng mảng.
VD:
int a[100];
Nhưng có 1 trở ngại lớn là khi viết chương trình, bạn không biết cụ thể độ dài của mảng là bao nhiêu. Bạn không thể viết:
cin>>n;
int a[n];
Trình biên dịch yêu cầu n phải là hằng hoặc một define nào đó.



Toán tử new


Trong C++, bạn dùng toán tử new để cấp phát động bộ nhớ có kích thước xác định và trả về con trỏ trỏ tới phần tử đầu tiên của mảng.
VD:
pointer = new type   //Cấp phát 1 phần tử
pointer = new type [n_of_elements] //Cấp phát n phần tử


Cách sử dụng

  • Truy cập phần tử đầu tiên: * pointer hoặc pointer[0]
  • Truy cập phần tử thứ i: *(pointer+i) hoặc pointer[i].
Lưu ý:
  1. Là cách cấp phát bằng toán tử new sử dụng vùng nhớ heap ( theo mình biết là đi ngược từ vùng nhớ nào đó trở về null). Còn mảng trong C/C++ thì ngược lại.
  2. Mình thử viết 1 class ( cố tình khai báo hàm tạo và hàm hủy là private), xài malloc, kết quả là dev-C không báo lỗi, nhưng khi chuyển qua new(), thì kết quả biên dịch lại báo lỗi???
  3. Không cấp phát động được cho class. Mình đã thử mãi mà không được. Giải pháp là bạn override operator (dịch tiếng Việt kì quá). VD: new ClassA();

Kiểm tra

Nếu bộ nhớ không đủ, việc cấp phát sẽ thất bại. Trên cplusplus có nêu ra 2 giải pháp:
  1. Sử dụng hàm bad_alloc
  2. Sử dụng nothrow, trả về con trỏ kiểu Null nếu việc cấp pháp không thành công. Bạn nên nhớ khai báo tiêu đề
VD:
bobby = new (nothrow) int [5]; 
Nothrow còn có nhiều công dụng nữa nhưng mình không biết.
Ngoài ra, trình biên dịch sẽ tự động trả về Null khi cấp phát không thành công.

Toán tử Delete

Bạn dùng toán tử delete để giải phóng vùng nhớ được cấp phát.
delete pointer;
delete [] pointer;
Có 1 đều quan trọng mà ít tài liệu nói tới: Khi cấp phát bởi toán tử new với các biến không có constructor (hàm tạo) thì nó sẽ tự giải phóng khi ra khỏi hàm. Còn đối với class, bạn cần phải viết hàm hủy.

Sự kế thừa và các vấn đề liên quan- Phần 3

Xem thêm Sự kế thừa và các vấn đề liên quan- Phần 2

Những thành phần nào được thừa kế:
Về nguyên tắc, lớp dẫn xuất kế thừa tất cả các member của lớp cơ sở ngoại trừ:
  • Constructor và destructor
  • operator=()
  • Friend functions, friend classes. Cái này mình chưa rõ lắm.
Mặc dù hàm tạo (constructor) và hàm hủy(destructor) không được kế thừa nhưng hàm tạo và hàm hủy mặc định luôn được gọi khi tạo hoặc hủy 1 đối tượng lớp dẫn xuất.
Nếu lớp cở sở không có hàm tạo mặc định hoặc bạn muốn gọi hàm tạo khi tạo 1 đối tượng ở lớp dẫn xuất, bạn có thể định nghĩa nó ở hàm tạo của lớp dẫn xuất theo cú pháp:

derived_constructor_name (parameters) : base_constructor_name (parameters) {...}
Ví dụ:

// constructors and derived classes
#include
using namespace std;

class mother {
public:
mother ()
{ cout << "mother: no parameters\n"; }
mother (int a)
{ cout << "mother: int parameter\n"; }
};

class daughter : public mother {
public:
daughter (int a)
{ cout << "daughter: int parameter\n\n"; }
};

class son : public mother {
public:
son (int a) : mother (a)
{ cout << "son: int parameter\n\n"; }
};

int main () {
daughter cynthia (0);
son daniel(0);

return 0;
}
Kết quả:
mother: no parameters
daughter: int parameter

mother: int parameter
son: int parameter
Bạn cần chú ý hàm tạo của daughter và son:
daughter (int a)          // nothing specified: call default
son (int a) : mother (a) // constructor specified: call this
Lưu ý: Chỉ có kế thừa trực tiếp thôi.
Nếu kết hợp tốt sự kế thừa và dùng hàm tạo, bạn có thể dùng nó để bẫy lỗi chương trình ( như kiểm tra tham số nhập vào chẳng hạn)

Sự kế thừa và các vấn đề liên quan- Phần 2

Xem thêm bài Sự kế thừa và các vấn đề liên quan- Phần 1
Trích:
• When a base class is inherited by use of public, its public members become public members of the derived class, and its protected members become protected members of the derived class.

• When a base class is inherited by use of protected, its public and protected members become protected members of the derived class.

• When a base class is inherited by use of private, its public and protected members become private members of the derived class.

• In all cases, private members of a base class remain private to the base class, and are not inherited.
Ở đây mình xin nêu những điểm ở bên ngoài thôi nhé: (chỉ nói về public và private, protected thì tương tự)

Sự kế thừa public:
Bạn xem thử 1 ví dụ:

Khi khai báo:
class alpha{};
      class  beta: public alpha{};
Và hàm void func(alpha a){}
Trong void main() ta viết:
Alpha a;
Beta b;
a=b;     //Đối tượng b được truyền vào a. Trường hợp ngược lại thì không.
func(b);            //Chấp nhận.
Ở đây câu lệnh: a=b được chấp nhận vì b là 1 loại đối tượng của a. Tương tự, chúng ta có thể truyền một đối tượng lớp dẫn xuất cho một hàm có đối số lớp cơ sở.
Lưu ý: câu lệnh b=a; không được vì a không phải là 1 loại đối tượng của b.
Sự kế thừa private:
Khi bạn viết
class beta: private alpha{};
Bạn sẽ không thể sử dụng bất kì biến hoặc hàm nào trong alpha dù được khai báo public.
Kết quả là toàn bộ lớp cơ sở ẩn đối với các đối tượng của lớp dẫn xuất. Đối với các lớp dẫn xuất có thể coi như không có lớp cơ sở.
Tốt hơn hết là bạn thay thế sự kế thừa private bằng sự hợp thành. (Bài trước mình đã nêu)
class alpha{};
class beta
{
    private:
                alpha obj;
};
Đối tượng alpha được đặt ở chế độ private nên nó vẫn che đậy đối với đối tượng beta. Sự hợp thành làm cho mối quan hệ 2 lớp rõ ràng hơn, ít phức tạp hơn và đối với lớp dẫn xuất cũng làm việc như vậy. Mình thử nêu 2 ví dụ:
Ví dụ 1: Kế thừa private
#include
using namespace std;
class alpha
{
      public:
      alpha()
      {      cout<<"Alpha\n";    } 
      void hello()
      {
           cout<<"Hello\n";    
      }
};
class beta: private alpha
{
      public:
      beta()
      {     cout<<"Beta\n";
            hello();
      }
};

int main()
{
    beta b;
    //b.hello();      Không hợp lệ vì class beta xem hello là phương thức private
    system("PAUSE");  
}
Ví dụ 2: Sự hợp thành
class alpha{/*....*/};
class beta
{
    private:
             alpha a;
    public:
             /*..
             a.hello();   //Sẽ rõ ràng hơn ví dụ 1
            ..*/
};

Sự kế thừa và các vấn đề liên quan- Phần 1

Bạn có thể tham khảo thêm bài Giới thiệu về lập trình hướng đối tượng
Ở bài viết này, mình tổng hợp và bổ sung những gì các cuốn sách còn thiếu. Đầu tiên mình sẽ nói 2 khái niệm cực kì quan trọng là kế thừahợp thành. Sau đó mình sẽ nói tiếp về phạm vi của sự kế thừa.
Sự kế thừa và thiết kế chương trình
Ngoài việc làm cho việc thay đổi các chương trình có sẵn dễ dàng hơn, sự kế thừa còn có 1 lợi ích khác. Nó cung cấp một cách để liên kết một thành phần chương trình này với một thành phần chương trình khác. Mối quan hệ mới này làm cho việc thiết kế chương trình linh động hơn và cấu trúc chương trình phản ánh mối quan hệ thế giới thực chính xác hơn. Sự kế hơn đôi khi còn gọi là quan hệ "loại". Để thấy được ý nghĩa này, đầu tiên chúng ta cùng xét một loại quan hệ khác là quan hệ hợp thành.

  1. Hợp thành: Một quan hệ "có":
    Nếu có một lớp employee ( nhân viên) và một trong những mục dữ liệu trong lớp này là tên của nhân viên thì chúng ta có thể nói rằng đối tượng employee "có" tên. Đối tượng employee cũng có thể "có" lương, mã nhân viên... Loại quan hệ này gọi là sự hợp thành bởi vì đối tượng employee là hợp của các biến trên.
    Dữ liệu thành viên của lớp có thể chưa đối tượng của lớp khác cũng như các biến của các kiểu dữ liệu cơ bản. Chúng ta có thể hình dung một lớp xe đạp bao gồm đối tượng khung, hai đối tượng bánh và 1 đối tượng tay lái.
    Sự hợp thành trong OOP mô hình hóa các tình huống thế giới thực trong đó các đối tượng là hợp thành của các đối tượng khác.
  2. Sự kế thừa: Một quan hệ "loại"
    Sự kế thừa trong OOP phản ánh khái niệm mà chúng ta gọi là sự khái quát quá trong thế giới thực. Nếu chúng ta có một chiếc xe đạp đua, một chiếc xe đạp địa hình và một chiếc xe đạp thiếu nhi thì chúng ta có thể nói những chiếc xe đạp này cụ thể rõ ràng của một khái niệm trổng quát hơn là xe đạp. Tất cả các loại xe đạp đều có đặc điểm: hai bánh, một khung... Nhưng một chiếc xe đạp đua ngoài các đặc điểm chung này còn có đặc điểm là lốp nhỏ và nhẹ. Một chiếc xe đạp địa hình cũng có tất cả những đặc điểm của một chiếc xe đạp, ngoài ra còn có lốp to, dày và phanh tốt.
  3. Không hoàn toàn là một gia tộc:
    Sự kế thừa được so sánh với mối quan hệ gia tộc, đó là lý do nó mang tên là sự kế thừa. Tuy nhiên sự kế thừa trong OOP không hoàn toàn giống sự kế thừa trong gia tộc loài người. Vì một điều, một lớp con có thể kế thừa một lớp cha. Lớp con (lớp dẫn xuất) có xu hướng có nhiều đặc điểm hơn lớp cha (lớp cơ sở) trong khi loài người thường hưởng những phẩm chất có sẵn từ bố và mẹ.
Cú pháp của sự kế thừa:
class base_class_name   //Lớp cơ sở
{/*....*/};
class derived_class_name: public base_class_name   //Lớp dẫn xuất
{ /*...*/ };
Từ khóa public có thể thay thế bằng protected hoặc private. Các thành viên trong cùng một lớp có nhiều quyền truy cập hơn là thành viên lớp dẫn xuất.

Access
public
protected
private
members of the same class
yes
yes
yes
members of derived classes
yes
yes
no
not members
yes
no
no
Nếu chúng ta xác định mức độ truy cập là protected, mọi thành viên public trong lớp cơ sở sẽ được thừa kế thành proctected trong lớp dẫn xuất. Tương tự đối với từ khóa private.

Thứ Hai, 14 tháng 9, 2009

Skip List - đối thủ của cây cân bằng

Khi đề cập đến bài toán tìm kiếm chắc bạn đã ít nhiều biết đến cấu trúc cây cân bằng (balanced tree). Cấu trúc này cho phép thực hiện tìm kiếm với độ phức tạp trung bình là O(log2n). Tuy nhiên, để tránh tình trạng cây suy biến, ta phải cân bằng cây mỗi khi chèn một nút mới. Nói chung, cân bằng cây là một thao tác tương đối phức tạp do phải hoán chuyển nhiều nút và do đó cũng ảnh hưởng đến hiệu suất. Skip List giải quyết vấn đề này khá hiệu quả mà vẫn không ảnh hưởng đáng kể đến tốc độ của phép tìm kiếm.

SkipList được giáo sư William Pugh thuộc trường đại học MaryLand giới thiệu vào khoảng tháng 8 năm 1989 trong một bài báo tham dự hội nghị về thuật toán và cấu trúc dữ liệu tại Ottawa Canada.



Skip List là một danh sách liên kết đơn mở rộng

Skip List chỉ là một mở rộng của danh sách liên kết đơn mà chúng ta đã rất quen thuộc. Hình bên dưới minh họa một danh sách liên kết đơn được sắp xếp tăng dần theo khóa.

Để tìm vị trí của một phần tử x trong danh sách liên kết đơn (đã được sắp xếp), ta phải duyệt từ đầu danh sách cho đến khi gặp nút có khóa cần tìm hoặc gặp nút có khóa lớn hơn khóa cần tìm. Trường hợp xấu nhất là phải duyệt qua tất cả nút trong danh sách (trong trường hợp khóa cần tìm lớn hơn tất cả các khóa trong danh sách).
Ý tưởng sơ khởi của Skip List là: để tăng hiệu quả của phép tìm kiếm, ta sẽ thêm vào các con trỏ tại một vài nút cho phép trỏ đến những nút nằm ở “xa” hơn. Chẳng hạn như ở hình dưới đây, các nút 5, 9, 16, 25 sẽ có các con trỏ phụ trỏ đến nút kế tiếp thứ hai (hay nói cách khác là “nhảy” – skip – hai nút một lần)

Với cấu trúc kiểu này, bạn có thể dễ dàng cảm nhận được là trung bình ta sẽ tiết kiệm được một nửa số bước tìm kiếm so với danh sách liên kết đơn bình thường. Nhìn vào hình ảnh, bạn sẽ thấy danh sách của ta bây giờ giống như có hai tầng (level). Tầng 1 là danh sách bình thường. Tầng 2 ở trên cũng là một danh sách liên kết đơn gồm có 5, 9, 16, 25 (nhảy 2 nút).

Dĩ nhiên là chúng ta có thể thêm vào một tầng nữa gồm các phần tử 9,25 (nhảy 4 nút) như hình dưới đây:

Hình ảnh
Để tìm kiếm một phần tử, ta xuất phát từ tầng cao nhất, duyệt qua các nút ở tầng hiện tại cho đến khi nút kế tiếp có khóa lớn hơn nút cần tìm. Lúc đó ta sẽ giảm đi một tầng. Nếu tầng hiện tại là 1 mà nút kế tiếp có giá trị lớn hơn nút cần tìm thì có nghĩa là nút cần tìm không có trong danh sách.
Hình trên minh họa cho quá trình tìm kiếm giá trị khóa 18. Ta khởi đầu với tầng 3, ta lần theo các nút ở tầng 3 cho đến khi gặp nút 9. Ta thấy nút kế tiếp là 25 lớn hơn 18 nên ta sẽ “xuống” tầng 2. Theo con trỏ ở tầng 2, ta sẽ đến nút 16. Nút kế tiếp là 25 lớn hơn 18 nên ta sẽ “xuống” tầng 1. Theo con trỏ ở tầng 1 ta gặp nút 18 chính là khóa cần tìm.

Hình ảnh
Một cách cảm tính là nếu như mỗi tầng ta giảm đi một nửa số phần tử thì cấu trúc này sẽ cho thời gian tìm kiếm y hệt như cây nhị phân tìm kiếm. Tuy nhiên, cấu trúc mở rộng này cũng gặp cùng một vấn đề như cây nhị phân là khi chèn một phần tử mới vào, ta lại phải mất công biến đổi để đảm bảo được “cấu trúc” của nó.

Skip List là cấu trúc dữ liệu có xác suất

Điểm chính giúp tốc độ tìm kiếm trung bình được tăng lên là do chúng ta giảm số nút ở mỗi tầng (để thực hiện phép tìm kiếm bằng cách nhảy). Tính chất “cách đều” chỉ đảm bảo được hiệu quả tìm kiếm lúc nào cũng như nhau.
Như vậy ta chỉ cần đảm bảo một tính chất là: số phần tử ở tầng trên phải xấp xỉ bằng một nửa (không cần chính xác) của tầng dưới. Nếu có 3 tầng, 100% phần tử ở tầng 1, khoảng 50% phần tử ở tầng 2, khoảng 25% phần tử ở tầng 3 và cứ thế. (Số nút ở hai tầng cao nhất sẽ xấp xỉ bằng nhau)

Hình ảnh
Sự đơn giản hóa này sẽ khiến việc chèn một phần tử vào danh sách trở nên rất đơn giản. Quan trọng nhất là chúng ta sẽ không cần phải sắp xếp lại danh sách nữa.

Tuy nhiên, để tiện cài đặt, ta sẽ dùng thuật ngữ khác đi một tí. Thay vì nhìn danh sách theo tầng, ta sẽ dùng khái niệm cấp của nút. Nếu một nút chỉ có một con trỏ, nó sẽ có cấp 1, nếu có hai con trỏ, nó có cấp 2 và cứ thế. Một cách tổng quát, sẽ có khoảng 50% số nút có cấp 1, 25% số nút có cấp 2, 12.5% số nút có cấp 3 và cứ thế.
Để chèn một nút vào danh sách, ta phát sinh ngẫu nhiên cấp của nó theo nguyên tắc là 50% khả năng có cấp 1, 25% có cấp 2, 12.5% có cấp 3 và cứ thế. Sau đó, tìm vị trí cần chèn (theo thuật toán tìm kiếm) rồi chỉ việc điều chỉnh lại các con trỏ là xong.

Phát sinh ngẫu nhiên cấp của một nút

int GenerateLevel(int max_level)
{
level=1;
while ((rand() < 0.5) && (level < max_level))
level++;
return level;
}

Để hiểu hoạt động của hàm này, ta chú ý đến biểu thức

rand() < 0.5

Hàm rand() phát sinh một số thực không âm ngẫu nhiên nhỏ hơn một. Nghĩa là 0≤rand() <1. Xác suất để biểu thức rand()<0.5 đúng hoặc sai đều là 50%.

Dễ dàng thấy được là xác suất để (level = 1 là đúng) là 50%.

Để có level = 2, biểu thức rand()<0.5 phải sai với level = 1 và đúng với level = 2. Xác suất để có level = 1 là sai là 50% và xác suất để có level = 2 là đúng cũng là 50%. Vậy xác suất để có level = 2 là đúng là : 50%×50% = 25%.

Tương tự, để có level = 3, biểu thức rand()<0.5 phải sai với level = 1 và sai với level = 2 và đúng với level = 3. Xác suất để có điều này là: 50%×50%×50% = 12.5%.

Hàm này phụ thuộc vào giá trị max_level (số tầng tối đa của SkipList). Một cách cảm tính, từ cây nhị phân cân bằng, ta thấy rằng max_level nên là log2(n) với n là tổng số nút của danh sách. Để tránh bị phụ thuộc vào số lượng nút, ta có thể dùng hằng số log2(N) trong đó N là chặn trên của n. Trên thực tế max_level = 32 (cho một list có tối đa khoảng 2^32 ~ 4 tỷ nút) là đủ dùng cho hầu hết các trường hợp.

Tìm kiếm

SkipListNode* Search(SkipList *list, int searchKey)
{
x = list->head;
// điều kiện dừng: (x->forward[i]->key >= searchKey) và (level=1)
for (i=list->level;i>1;i--)
while (x->forward[i]->key < searchKey)
x = x->forward[i];
x = x->forward[1];
if (x->key == searchKey)
return x;
else
return 0;
}

Chèn nút vào danh sách

void Insert(SkipList *list, int searchKey, int newValue)
{
SkipListNode* update[max_level+1];
//tìm vị trí nút cần chèn và ghi nhận các nút
//trỏ đến vị trí này
x = list->head;
for (i=list->level;i>1;i--)
{
while (x->forward[i]->key < searchKey)
x = x->forward[i];
update[i] = x; //ghi nhận nút trỏ đến vị trí chèn
}

x = x->forward[1];
if (x->key == searchKey)
x->value = newValue; //cập nhật giá trị nút nếu khóa đã tồn tại
else
{
newLevel = GenerateLevel(max_level);
if (newLevel > list->level)
{
for (i=list->level + 1;i<=newLevel;i++)
update[i] = list->head;
list->level = newLevel;
}

// tạo một nút mới với cấp = , khóa =
x = makeNode(newLevel,searchKey,value);
// cập nhật các nút trỏ đến nút mới và các con trỏ forward của nút mới
for (i=1;i<=newLevel;i++)
{
x->forward[i] = update[i]->forward[i];
update[i]->forward[i] = x;
}
}
}

Khởi tạo SkipList

SkipList rỗng bao gồm hai nút đặc biệt. Một nút head và nút tail (hay NIL). Cấp của SkipList lúc khởi tạo là 1. Khóa của tail phải luôn luôn lớn hơn khóa của tất cả các nút.
Hình ảnh
Xóa một nút

void Delete(SkipList * list, int searchKey)
{
SkipListNode* update[max_level+1];
//tìm nút cần xóa và ghi nhận các nút
//trỏ đến nút này
SkipListNode *x = list->head;
for (i=list->level;i>1;i--)
{
while (x->forward[i]->key < searchKey)
x = x->forward[i];
update[i] = x; //ghi nhận các nút trỏ đến nút cần xóa
}
x = x->forward[1];
if (x->key == searchKey)
{
for (i=1;i<=list->level;i++)
{
if (update[i]->forward[i] != x) break;
update[i]->forward[i] = x->forward[i];
}
free(x);
while ( (list->level > 1) && (list->head->forward[list->level] == 0) )
list->level--;
}
}

Một số bàn luận

Thay vì giới hạn ở hằng số 50% cho biết số lượng nút cấp i+1 bằng khoảng 50% số lượng nút cấp i. Pugh đề nghị kết hợp một giá trị p < 1 vào SkipList để quy định tỷ lệ nút giữa các tầng. Nếu p=0.5, tầng i+1 sẽ có số nút bằng khoảng ½ so với tầng i. Nếu p=0.25, tầng i+1 sẽ có số nút bằng khoảng ¼ so với tầng i (nếu p=1/4, số nút cấp 1 sẽ là 75%, số nút cấp 2 là 25%×75% = 18.75%, số nút cấp 3 sẽ là: 25%×25%×75% = 4.6875%).

Rõ ràng là vì các nút không cách đều nhau nên hiệu suất tìm kiếm sẽ không là hằng số. Nếu quá xui, chẳng hạn như trường hợp tất cả nút đều có cùng cấp, thì hiệu suất tìm kiếm sẽ bị giảm đáng kể. Tuy nhiên, bằng toán học, giáo sư Pugh đã tính ra được ràng khả năng xảy ra những trường hợp xấu như vậy là rất thấp. Chẳng hạn với một SkipList có 4096 nút, p=0.5, khả năng để một thời gian tìm kiếm chậm hơn 3 lần (so với thời gian tìm kiếm trung bình) là một phần 200 triệu !
Một điểm cần lưu ý nữa là khi số lượng nút càng lớn (điều thường xảy ra trong thực tế), hiệu quả trung bình của SkipList càng sát với hiệu suất của cây cân bằng. Pugh cũng đề nghị nên sử dụng p=0.25 trong đa số trường hợp. Nếu như chúng ta đặt nặng tính ổn định của SkipList thì Pugh đề nghị nên dùng p=0.5. Điểm thú vị là với p=0.25, một nút trung bình chỉ có 1.33 con trỏ (trong khi đó cây cân bằng có 2 con trỏ). Điều này làm cho SkipList sử dụng ít bộ nhớ hơn cây cân bằng.

Tài liệu tham khảo

1) William Pugh, A Skip List Cook Book, 6/1990, Department of Computer Science, University of MaryLand, College Park.

2) Thomas A. Anastasio, Skip Lists, 22/12/1999, http://www.csee.umbc.edu/courses/underg ... lists.html

3) Xem ví dụ được thể hiện bằng đồ họa với Skip List Apple, http://iamwww.unibe.ch/~wenger/DA/SkipList/

Nguồn Codepro.vn

Chủ Nhật, 13 tháng 9, 2009

1 đồng nữa biến đi đâu?

"Có ba người vào khách sạn thuê phòng. Họ thuê chung một phòng với giá 30 đồng. Như vậy, mỗi người chỉ phải trả góp 10 đồng. Tên bồi cầm 30 đồng khách đóng đến nộp cho ông chủ khách sạn. Nhưng ông chủ chỉ lấy 25 đồng và bảo tên bồi đem trả lại cho khách 5 đồng. Cầm lấy 5 đồng, tên bồi nghĩ hoài vẫn không biết làm sao chia đều cho ba người, cuối cùng, nó giấu đi 2 đồng, và với 3 đồng còn lại nó trả đều cho mỗi người khách một đồng...

Như vậy là mỗi người khách chỉ đóng có 9 đồng. Chín đồng nhân với 3 người khách thành 27 đồng, đúng chưa? Hai bảy đồng cộng với hai đồng tên bồi giấu đi là 29 đồng cả thảy. Vậy còn 1 đồng nữa biến đi đâu ? "

Thứ Bảy, 12 tháng 9, 2009

Autorun Typhoon Professional



Autorun Typhoon Professional Tạo những bài thuyết trình CD/DVD chuyên nghiệp, an toàn. Giao diện dễ sử dụng của Autorun và hệ thống giúp đỡ trong giúp việc tạo các đề án nhanh chóng và dễ dàng. Từ việc đảm bảo các tập tin của bạn sẽ hoạt động trên mọi hệ thống, đến việc tạo các Menus và Buttons như ý, Autorun sẽ thay bạn làm tất cả. Nếu bạn muốn đem các Menu, trang web, tập tin PDF, bài thuyệt trình bằng PowerPoint, video, SlideShow của bạn, hay bất cứ gì vào một đĩa CD-ROM, Autorun là một công cụ hoàn hảo mà bạn cần.


Tạo Menus và Buttons như ý:
* Kéo-và-thả Menu Creator của Autorun cho phép bạn tạo bất kỳ Menu nào bạn muốn. Menu Themes sẽ giúp công việc của bạn nhẹ bớt phần nào.
* Bạn có thể tạo Button riêng cho mình hoặc chọn từ 14 button có sẵn.

Bảo vệ tập tin và nội dung của bạn. Autorun có 2 loại bảo vệ: cho đĩa và tập tin.
* Disk Security sẽ không cho phép người khác xem CD của bạn trừ phi bạn đồng ý. Có 6 loại bảo vệ bao gồm Serial Number, License Agreement, và CD Expiry Date.
* File Security không cho những tập tin đề án của bạn bị nén lại thành một tập tin duy nhất. Những tập tin của bạn sẽ được trích ra khi cần thiết và được xóa ngay sau đó. Điều này ngăn chặn người khác xem nội dung trong CD của bạn.

Bảo đảm các tập tin của bạn được xem bởi số khán giả cao nhất
* Không phải tất cả các người tiêu dùng đều có phần mềm mới nhất hay giữ phiên bản của họ cập nhật. Failsafe Program sẽ giải quyết vấn đề này. Autorun sẽ phát hiện khi một tập tin không thể chạy và cài đặt một phần mềm thích hợp để các khan giả của bạn xem được đề án.
* Ví dụ cụ thể là mở một tập tin PDF, với Acrobat như Failsafe Program.

10 Viewer mạnh để chọn
* Autorun có 10 Viewer để giúp bạn tạo hầu hết các đề án mà bạn hằng mong ước.: File, Splash Screen, Folder, Web Page, Slide Show, Music JukeBox, Video Player, Flash Animations, Zip Extractor and Menu Creator.
* Hệ thống giúp đỡ có sẵn sẽ chỉ dẫn bạn một cách chi tiết khi tạo các đế án và giải thích cặn kẽ mọi thứ chỉ với một cái nhấn chuột.

Hỗ trợ :

Windows Vista
Windows 2003
Windows XP
Windows 2000
Windows ME
Windows NT 4.0
Windows 98 SE
Windows 98
Windows 95

Cách sử dụng rất đơn giản, các công cụ cần thiết để tạo Autorun được hiện rõ trên cửa sổ của chương trình:

- CD-ROM Base Folder: chọn thư mục chứa file cần tạo autorun.
- Target File(s): chọn file muốn cho autorun khi nạp CD-ROM vào.
– CD-ROM Icon: chọn biểu tượng cho CD.
- Splash Screen: tạo một file hình, nhạc chào mừng trước khi chạy ứng dụng của bạn.
- Project Security: tạo mật khẩu.
- Additional Viewer Checking: nếu bạn lo lắng CD-ROM của bạn khi đưa qua máy khác sẽ không xem được (vì máy đó không có phần mềm để xem), bạn có thể bổ sung chương trình của bạn vào CD rồi sử dụng mục này để chỉ định file chương trình.

Download bản Portable
http://www.mediafire.com/?sbmjm1oyhcu

CSS Syntax


Add Styles to Elements with Particular Attributes

You can also apply styles to HTML elements with particular attributes.
The style rule below will match all input elements that have a type attribute with a value of "text":
input[type="text"] {background-color:blue}




The id Selector

You can also define styles for HTML elements with the id selector. The id selector is defined as a #.
The style rule below will match the element that has an id attribute with a value of "green":
#green {color:green}
The style rule below will match the p element that has an id with a value of "para1":
p#para1
{
text-align:center;
color:red
}
Remark Do NOT start an ID name with a number! It will not work in Mozilla/Firefox.

CSS Comments

Comments are used to explain your code, and may help you when you edit the source code at a later date. A comment will be ignored by browsers. A CSS comment begins with "/*", and ends with "*/", like this:
/*This is a comment*/
p
{
text-align:center;
/*This is another comment*/
color:black;
font-family:arial
}

Thứ Sáu, 11 tháng 9, 2009

9 Practical Ways to Enhance your Web Development Using the Firefox Web Developer Extension

Whether you’re a front-end graphics designer or a back-end web programmer, if you’ve worked long enough in the field of creating web-based solutions, you’ve no doubt heard about an extension for the Mozilla Firefox web browser called (simply enough) the Web Developer extension. If you have no clue what I’m talking about, here’s a brief overview from Webs Tips to get you familiarized with this wonderful tool.
A screenshot of the Mozilla Firefox Web Developer tool
This article lists some practical, everyday uses of the Web Developer extension to help improve your web-building methods. I’ve tried to stay away from the more common and basic uses of the Web Developer extension like troubleshooting layout issues with the Information > Display Div Order option because I feel these have been discussed quite enough in other places. New users, don’t run away quite yet, I think this guide will help you get a rapid jump start into applying this tool into your daily development routine.
So without further ado, here’s nine highly pragmatic uses of the Web Developer extension for Firefox.

1) Change XHTML on-the-fly without changing your web files.

Unfortunately for many developers, we don’t all have the luxury of testing servers and sandbox environments. I for one, confess to developing on live websites even during peak web traffic times.
If you’d like to lessen customer support requests due to an inadvertent display:none; property assignment on the log-in box — use the Web Developer extension to effortlessly check your XHTML modifications before you commit them to the server.
Here’s an (extreme) example of how I was able to change a few of reddit’s XHTML markup.

The original front page:

Screenshot of reddit's front page before editting XHTML markup.

And here’s the modified version:

Screenshots of reddit's front page after changing some XHTML markup.

As you can see in the above picture, I changed the top three stories (to something else I’d much read about) and modified the background color to pink (I have an odd affection towards hot pink for some reason).
You can achieve the same results by using the Miscellaneous > Edit HTML Markup option which will open up the Edit HTML tab panel displaying the XHTML of the web page. Unfortunately, the window isn’t color-coded and the Search HTML function doesn’t quite work properly (yet).
A screenshot of the Edit HTML Panel, Displayed on the left of the page.
Tip: You can change the position of the Edit HTML panel by clicking on the Position icon (right next to the Edit HTML tab on the above screenshot).
To change the CSS styles of the page, use the CSS > Edit CSS option, which will allow you to edit the styles used on the web page.

2) Measure things quickly with the Ruler Tool.

Raise your hand if you’ve ever print-screen’ed, and then copy-&-paste’d the screenshot onto Photoshop just to determine dimensions of certain page objects (like the width of an image) with the selection tool. *Raises hand in shame*
With the Ruler Tool (enable it via Miscellaneous > Display Ruler Tool), you can speedily size-up objects inside the web browser. It’s a great tool in conjunction with outline options such as Information > Display Div Order option or Information > Display Block Size option, allowing you to detect the amount of padding and margin between elements.

Screenshot of the Mozilla Firefox Web Developer extension Ruler Tool.

3) See how web pages look on a non-traditional web browser.

Nowadays, tons of people have mobile devices that lets them view web pages in non-traditional ways. Determine whether your pages render correctly (or close enough) on portable device screens by using the Miscellaneous > Small Screen Rendering option. This saves you from going out and purchasing a Blackberry or a Trio with an internet dataplan just for cross-browser checking.

How the Gamespot website looks on normal browsers:

A screenshot of Gamespot.com viewed through Mozilla Firefox web browser.

What it will look like on a Small Screen Rendering device…

A screenshot of Gamespot.com rendered in a Small Screen Rendering Device as simulated by Mozilla Firefox Web Developer extension.

4) Find out how optimized your page is.

Use the Tools > View Speed Report option to automatically send your page to WebSiteOptimization.com, a site that provides a plethora of information about your web page load times like how quickly your page loads and how many HTTP connections are being used among a ton of other things.
There are built-in tools in Adobe Dreamweaver and Flash (if you even have access to them) that simulates download speeds, but nothing beats a free, comprehensive and actual live speed report.
Screenshot of the result of Six Revision's front page speed report from Web Optimizer

5) Populate web form fields instantly.

Don’t you hate it when you have to fill in your custom-built web form for the nth time because you’re testing it? You can quit tabbing and entering junk information on your form fields and switch to using the Form > Populate Form Fields option in the Web Developer extension.
In the example below, you can see that it populates most web forms somewhat intelligently – It was able to guess the email field — but missed the phone number field.
Screenshot of eBay's registration form automatically filled about using the Forms - Populate Form Fields option of Mozilla Firefox Web Developer extension.

6) Find all the CSS styles that affect an element.

For most fairly-proficient CSS developers, it’s quite easy to find the exact selectors that style an element’s properties - fyi: #selector { property: value; }. This is especially true when you’re the original author and/or the styles are contained in one stylesheet.
But what if you were working on someone else’s project… and the project in question has 1,000+ lines of pure CSS goodness, split into several external stylesheets (because Bob a.k.a. “Mr. Modularity” likes to keep things “simple“)? Another scenario you might encounter is being tasked to theme a content management system like Drupal or Wordpress and you’re not quite sure where all the external stylesheets are.
For example, the Yahoo! home page has over 2,400 lines of CSS, spread over several external stylesheets and inline styles (Bob, you built this page didn’t you?).
Screenshot of Yahoo! front page with CSS - View Style Information of Mozilla Firefox Web Developer extension being used.
If you’re tasked with revising this page, you have two choices: (1) look through, understand, and hunt down the styles you need or (2) decide that you’re smarter (and lazier) than that and so you use the CSS > View Style Information option of the Web Developer extension. With this option enabled, clicking on a page element opens up the Style Information panel which displays all the styles that affect the element.

7) View JavaScript and CSS source code in a jiffy.

One of the ways I troubleshoot rendering issues is by looking at how other web pages do it. JavaScript and CSS are often divided into several files — who wants to look through all of them?
Using the Information > View JavaScript and the CSS > View CSS options instantly displays all the JavaScript and CSS in a new browser tab. This has the side benefit of being able to aggregate all the CSS styles or JavaScript in one web page allowing you to use the Find tool of the Mozilla Firefox browser (keyboard shortcut: ctrl + f for PC users).

8) See how web pages are layered.

It’s often very helpful to determine which page div’s and objects are on a higher plane. Using the Information > View Topographic information gives you a visual representation of the depths of the page elements — darker shades are lower than lighter shades of gray.

Original web design…

Screenshot of before using View Topography Information.

Using the Topographic Information option renders the page to this:

Screenshot of a webpage with Information - View Topographic Information enabled.

9) See if your web page looks OK in different screen sizes.

I use a monitor size between 19 – 22 inches (wide screen). This can be problematic because many of our visitors use smaller monitors. Short of switching to a smaller LCD screen to simulate the user experience, I just use the Resize > Resize window option. It helps test whether my fluid layout works well in smaller windows (sometimes you forget to set min-widths for div elements and it jacks up the layout in smaller screen sizes), or if your fixed-width layout displays important content without user’s having to scroll.
Be sure to enable the Resize > Display Window Size in Title option to help you determine the exact dimensions, and also for documentation purposes when you’re taking screenshots of your webpages.
Screen shot of drupal.org with the width of the page set to 800 pixels.
So there we are, nine ways you can employ the Mozilla Firefox Web Developer extension to better your web development experience. I don’t claim to be an expert, but I certainly know enough about the Web Developer extension to improve my web-building speed.
Do you have other tips and strategies on how to further utilize the Web Developer extension? What are the ways you use Web Developer extension in your job? Share them here.

Thứ Ba, 8 tháng 9, 2009

Ý nghĩa các thẻ meta - phần 1

Meta tag là thẻ dùng để cung cấp các thông tin về website một cách tóm gọn đối với các trình duyệt lẫn người dùng hay bot từ các search engine. Hiện nay, có không ít người đang hiểu sai ý nghĩa của nó và ứng dụng đôi khi không hợp lý trong nhiều website. Bài viết này sẽ giải thích về ý nghĩa của hầu hết các thẻ Meta tag nhằm giúp các bạn ứng dụng một cách hợp lý hơn và gợi ý các Meta tag bạn nên dùng hoặc không nên dùng.
Meta tag là gì? Đây là thẻ HTML được đặt giữa thẻ trong của một tài liệu HTML.
Có 2 kiểu sử dụng meta tag thường thấy:

<META HTTP-EQUIV="name" CONTENT="content">
<META NAME="name" CONTENT="content">

Ở những thời kỳ đầu khi Meta tags được phát triển nhằm hỗ trợ cho việc phát triển chung của website. Tuy nhiên sau đó việc ứng dụng của nó bị thay đổi lớn, nhiều webmasters đã sử dụng nó một cách thái quá trong việc ứng dụng Meta tags cho keywords (từ khóa) đối với các website có nội dung không lành mạnh. Rất nhiều từ khóa không liên quan được đặt vào website nhằm giúp cho website đạt kết quả tốt trong kết quả tìm kiếm của các SE. Ví dụ website có nội dung người lớn nhưng lại đặt một số từ khóa liên quan đến các vấn đề nóng hổi khác hoặc về các ngôi sao nổi tiếng mà người dùng thường hay tìm kiếm.

Hiện nay các cỗ máy tìm kiếm đã giảm bớt độ ảnh hưởng của Meta tags cho việc hiển thị kết quả. Google thường bỏ qua sự ảnh hưởng của Meta tags và chỉ sử dụng Google Meta tags (sẽ được giới thiệu dưới đây). Các cỗ máy tìm kiếm khác cũng có cách đọc thẻ này bằng cách riêng của nó.



Sau đây là nội dung giải thích ý nghĩa của các thẻ Meta tags.

I. Các thẻ Meta Tags được khuyến khích sử dụng:



1. Meta Content Language (Dành cho các website không phải tiếng Anh)


Thẻ này được dùng để khai báo ngôn ngữ của website. Thẻ này cũng được dùng tương tự như Meta Name Language. Các robot của SE thường dùng thẻ này để phân loại ngôn ngữ của website.

Ví dụ:

<meta="" equiv="Content-Language" content="vi">

Bạn nên sử dụng thẻ này nếu website của bạn có ngôn ngữ không phải tiếng Anh. Cá nhân tôi chưa từng thử, tuy nhiên theo như những gì mà tôi tham khảo thì thẻ này rất có ích cho bot phân loại nội dung theo ngôn ngữ.


2. Meta Content Type

Thẻ này dùng để khai báo mã cho website. Bạn nên sử dụng thẻ nay ngay cả khi bạn đã dùng khai báo DTD cho tài liệu HTML. Bởi vì nếu bạn không sử dụng thì có khi người dùng website của bạn sẽ không đọc được nội dung website của bạn do trình duyệt không tự động điều chỉnh mã phù hợp cho website của bạn. Ví dụ: Nội dung website của bạn được nhập liệu thông qua mã UTF-8 nhưng được hiển thị ở chế độ của ISO hay ASCII. Thả này còn có nhiều lợi ích khác, tuy nhiên bạn có thể tự tìm hiểu thêm về vấn đề này thông qua các trang web về SEO.
Ví dụ:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">


3. Meta Description

Thẻ này dùng để mô tả nội dung của một trang web. Nội dung của thẻ này nên được viết ngắn gọn và xúc tích khoảng từ 20 đến 25 từ hoặc ít hơn. Đây là thẻ được hầu hết các SE sử dụng để hiển thị nội dung kết quả tìm kiếm.

Ví dụ:

<meta name="description" content="Website Khoa học kỹ thuật, giải trí và đời sống dành cho cộng đồng người Việt cùng chia sẽ kinh nghiệm và trao đổi học hỏi.">

Thẻ này được khuyến khích sử dụng và nên viết một cách xúc tích nhất nhằm thu hút người dùng bấm vào website của bạn từ kết quả tìm kiếm. Thông thường nếu không dùng thẻ này thì các SE như google cũng sẽ tự động tạo khi index nội dung website. Tuy nhiên bạn nên dùng bởi vì đôi khi các mô tả được index tự động sẽ không được như ý của bạn.


4. Meta Language (Dành cho các website không phải tiếng Anh)


Thẻ này tương tự như Meta Content Language nhưng cấu trúc khác như sau:

<META NAME="Language" CONTENT="english">


II. Các thẻ phụ khác:


Các thẻ sau đây được gọi là thẻ phụ vì cũng được khuyến khích dùng nhưng không thật sự quan trọng, bạn có thể dùng cũng được hoặc không dùng cũng chẳng sao.


1. Meta Abstract

Cung cấp nội dung tóm tắt cho phần mô tả của website. Thẻ này chỉ được dùng để mô tả ngắn gọn hơn để bot có thể xác định được chính xác hơn nội dung website của bạn. Nội dung của thẻ này thường khoảng 10 từ trở lại.

Ví dụ:

<META NAME="Abstract" CONTENT="Website khoa học kỹ thuật, giải trí và đời sống.">

Thẻ này hiện tại không nằm trong các thuật toán của Google, Yahoo!, và MSN.


2. Meta Author


Thẻ này dùng để hiển thị tác giả của một nội dung trên website. Nội dung của thẻ này thường là tên của người đã tạo ra website. Bạn nên dùng thẻ này bằng tên của mình thay vì dùng email để tránh việc bị spam mail. Nếu bạn muốn người dùng liên hệ với mình thì nên dùng một form để liên hệ sẽ tốt hơn.

Ví dụ:

<META NAME="Author" CONTENT="NGUYEN VU TUAN ANH, myemail@mydomain.com">

Thẻ này không được index bởi Google, Yahoo!, hay MSN, do đó cũng không hỗ trợ cho bạn trong việc tăng thứ hạng, nhưng nó được ứng dụng như một chuẩn sử dụng của Meta tag.


3. Meta Copyright


Đây chỉ là thẻ mang tính thương hiệu hay các thông tin bản quyền cá nhân hay sở hữu trí tuệ của bạn.

Ví dụ:

<meta name="copyright" content="Copyright 2008">

Bạn không nhất thiết phải sử dụng thẻ này bởi vì nó chỉ mang tính tượng trưng và không có nghĩa là nó bảo vệ được bản quyền của bạn.


4. Meta Designer


Thẻ này dùng để cung cấp thông tin về người thiết kế giao diện cho website.

Ví dụ:

<META NAME="Designer" CONTENT="BabyWolf">

Các SE cũng không sử dụng thẻ này, thẻ này chỉ ứng dụng cho Designer muốn quảng cáo về mình.


5. Meta Google


Thẻ này chỉ được sử dụng cho việc bạn muốn loại bỏ nội dung khỏi google. Các thuộc tính của thẻ này:

  1. Googlebot: noarchive - không cho phép google hiển thị nội dung cache của site bạn.
  2. Googlebot: nosnippet - Không cho phép google hiển thị nội dung trích dẫn hoặc cache.
  3. Googlebot: noindex - Không index những trang web nào đó của bạn.
  4. Googlebot: nofollow - Loại bỏ việc đánh giá PageRank hoặc link từ trang này.
Bạn không nhất thiết phải sử dụng thẻ này ngoại trừ bạn muốn điều khiển google bot theo ý của mình cho cấu trúc website của bạn. Đây là thẻ mà google chắc chắn quan tâm đến. Hoặc bạn cũng có thể ứng dụng các thẻ này trong trường hợp thực tiễn sau: Bạn thay đổi cấu trúc nội dung và đường dẫn website, bạn sẽ vẫn giữ phiên bản cũ nhưng với thẻ này để google sẽ tự động xóa các index tương ứng với link này. Tuy nhiên, tốt nhất bạn nên dùng Redirect Permanently 301 sẽ giúp cho bạn chuyển PageRank từ trang cũ qua trang mới.
6. Meta Keywords
Thẻ từ khóa được dùng để định dạng nội dung trang web. Từ khóa được sử dụng bởi các SE để index site của bạn có thêm thông tin từ các nội dung của title, body, và các thành phần khác. Từ này thường được dùng để cung cấp các từ khóa liên quan đồng nghĩa hoặc tương tự với các từ khóa của title. Ví dụ: Title của trang web cho bài viết này là "SEO - Ý nghĩa các thẻ meta | Diễn đàn khoa học kỹ thuật". Bạn có thể ứng dụng keywords như sau:
<META NAME="keywords" CONTENT="khái niệm, quảng bá web, tag, forum, technical, science, thảo luận, trao đổi">
Bạn nên sử dụng keywords một cách thận trong và bảo đảm sự tương thích với nội dung. Website của bạn có thể bị phạt hoặc đưa vào blacklist nếu bạn quá lạm dụng nó. Việc sử dụng keywords cũng có thể là một con dao hai lưỡi đối với bạn. Bạn có thể mất vài giờ để nghiên cứu cách viết keywords tốt nhất và đối thủ của bạn chỉ mất vài phút để thừa hưởng từ bạn.
7. Meta MSN (No ODP)
Thẻ này được ứng dụng cho việc mô tả website của bạn ở kết quả tìm kiếm của MSN. Do MSN thường hay sử dụng mô tả của DMOZ nên dùng thẻ này sẽ giúp cho MSN chuyển qua dùng mô tả của bạn. Ví dụ:
<META Name="msnbot" CONTENT="NOODP">
8. Meta Title
Nội dung thẻ này được sử dụng tương tự như thẻ title . Ví dụ:
<META NAME="Title" CONTENT="Page Title Here">
Thẻ này được sử dụng bởi Yahoo! và MSN.

Chủ Nhật, 6 tháng 9, 2009

Giới thiệu về lập trình hướng đối tượng

LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG (OOP) LÀ GÌ ?
Lập trình hướng đối tượng (Object-Oriented Programming, viết tắt là OOP) là một phương pháp mới trên bước đường tiến hóa của việc lập trình máy tính, nhằm làm cho chương trình trở nên linh hoạt, tin cậy và dễ phát triển. Tuy nhiên để hiểu được OOP là gì, chúng ta hãy bắt đầu từ lịch sử của quá trình lập trình – xem xét OOP đã tiến hóa như thế nào.

Lập trình tuyến tính

Máy tính đầu tiên được lập trình bằng mã nhị phân, sử dụng các công tắt cơ khí để nạp chương trình. Cùng với sự xuất hiện của các thiết bị lưu trữ lớn và bộ nhớ máy tính có dung lượng lớn nên các ngôn ngữ lập trình cấp cao đầu tiên được đưa vào sử dụng . Thay vì phải suy nghĩ trên một dãy các bit và byte, lập trình viên có thể viết một loạt lệnh gần với tiếng Anh và sau đó chương trình dịch thành ngôn ngữ máy.
Các ngôn ngữ lập trình cấp cao đầu tiên được thiết kế để lập các chương trình làm các công việc tương đối đơn giản như tính toán. Các chương trình ban đầu chủ yếu liên quan đến tính toán và không đòi hỏi gì nhiều ở ngôn ngữ lập trình. Hơn nữa phần lớn các chương trình này tương đối ngắn, thường ít hơn 100 dòng.
Khi khả năng của máy tính tăng lên thì khả năng để triển khai các chương trình phức tạp hơn cũng tăng lên. Các ngôn ngữ lập trình ngày trước không còn thích hợp đối với việc lập trình đòi hỏi cao hơn. Các phương tiện cần thiết để sử dụng lại các phần mã chương trình đã viết hầu như không có trong ngôn ngữ lập trình tuyến tính. Thật ra, một đoạn lệnh thường phải được chép lặp lại mỗi khi chúng ta dùng trong nhiều chương trình do đó chương trình dài dòng, logic của chương trình khó hiểu. Chương trình được điều khiển để nhảy đến nhiều chỗ mà thường không có sự giải thích rõ ràng, làm thế nào để chương trình đến chỗ cần thiết hoặc tại sao như vậy.
Ngôn ngữ lập trình tuyến tính không có khả năng kiểm soát phạm vi nhìn thấy của các dữ liệu. Mọi dữ liệu trong chương trình đều là dữ liệu toàn cục nghĩa là chúng có thể bị sửa đổi ở bất kỳ phần nào của chương trình. Việc dò tìm các thay đổi không mong muốn đó của các phần tử dữ liệu trong một dãy mã lệnh dài và vòng vèo đã từng làm cho các lập trình viên rất mất thời gian.

Lập trình cấu trúc

Rõ ràng là các ngôn ngữ mới với các tính năng mới cần phải được phát triển để có thể tạo ra các ứng dụng tinh vi hơn. Vào cuối các năm trong 1960 và 1970, ngôn ngữ lập trình có cấu trúc ra đời. Các chương trình có cấu trúc được tổ chức theo các công việc mà chúng thực hiện.
Về bản chất, chương trình chia nhỏ thành các chương trình con riêng rẽ (còn gọi là hàm hay thủ tục) thực hiện các công việc rời rạc trong quá trình lớn hơn, phức tạp hơn. Các hàm này được giữ càng độc lập với nhau càng nhiều càng tốt, mỗi hàm có dữ liệu và logic riêng.Thông tin được chuyển giao giữa các hàm thông qua các tham số, các hàm có thể có các biến cục bộ mà không một ai nằm bên ngoài phạm vi của hàm lại có thể truy xuất được chúng. Như vậy, các hàm có thể được xem là các chương trình con được đặt chung với nhau để xây dựng nên một ứng dụng.
Mục tiêu là làm sao cho việc triển khai các phần mềm dễ dàng hơn đối với các lập trình viên mà vẫn cải thiện được tính tin cậy và dễ bảo quản chương trình. Một chương trình có cấu trúc được hình thành bằng cách bẻ gãy các chức năng cơ bản của chương trình thành các mảnh nhỏ mà sau đó trở thành các hàm. Bằng cách cô lập các công việc vào trong các hàm, chương trình có cấu trúc có thể làm giảm khả năng của một hàm này ảnh hưởng đến một hàm khác. Việc này cũng làm cho việc tách các vấn đề trở nên dễ dàng hơn. Sự gói gọn này cho phép chúng ta có thể viết các chương trình sáng sủa hơn và giữ được điều khiển trên từng hàm. Các biến toàn cục không còn nữa và được thay thế bằng các tham số và biến cục bộ có phạm vi nhỏ hơn và dễ kiểm soát hơn. Cách tổ chức tốt hơn này nói lên rằng chúng ta có khả năng quản lý logic của cấu trúc chương trình, làm cho việc triển khai và bảo dưỡng chương trình nhanh hơn và hữu hiện hơn và hiệu quả hơn.
Một khái niệm lớn đã được đưa ra trong lập trình có cấu trúc là sự trừu tượng hóa (Abstraction). Sự trừu tượng hóa có thể xem như khả năng quan sát một sự việc mà không cần xem xét đến các chi tiết bên trong của nó. Trong một chương trình có cấu trúc, chúng ta chỉ cần biết một hàm đã cho có thể làm được một công việc cụ thể gì là đủ. Còn làm thế nào mà công việc đó lại thực hiện được là không quan trọng, chừng nào hàm còn tin cậy được thì còn có thể dùng nó mà không cần phải biết nó thực hiện đúng đắn chức năng của mình như thế nào. Điều này gọi là sự trừu tượng hóa theo chức năng (Functional abstraction) và là nền tảng của lập trình có cấu trúc.
Ngày nay, các kỹ thuật thiết kế và lập trình có cấu trúc được sử rộng rãi. Gần như mọi ngôn ngữ lập trình đều có các phương tiện cần thiết để cho phép lập trình có cấu trúc. Chương trình có cấu trúc dễ viết, dễ bảo dưỡng hơn các chương trình không cấu trúc.
Sự nâng cấp như vậy cho các kiểu dữ liệu trong các ứng dụng mà các lập trình viên đang viết cũng đang tiếp tục diễn ra. Khi độ phức tạp của một chương trình tăng lên, sự phụ thuộc của nó vào các kiểu dữ liệu cơ bản mà nó xử lý cũng tăng theo. Vấn đề trở rõ ràng là cấu trúc dữ liệu trong chương trình quan trọng chẳng kém gì các phép toán thực hiện trên chúng. Điều này càng trở rõ ràng hơn khi kích thước của chương trình càng tăng. Các kiểu dữ liệu được xử lý trong nhiều hàm khác nhau bên trong một chương trình có cấu trúc. Khi có sự thay đổi trong các dữ liệu này thì cũng cần phải thực hiện cả các thay đổi ở mọi nơi có các thao tác tác động trên chúng. Đây có thể là một công việc tốn thời gian và kém hiệu quả đối với các chương trình có hàng ngàn dòng lệnh và hàng trăm hàm trở lên.
Một yếu điểm nữa của việc lập trình có cấu trúc là khi có nhiều lập trình viên làm việc theo nhóm cùng một ứng dụng nào đó. Trong một chương trình có cấu trúc, các lập trình viên được phân công viết một tập hợp các hàm và các kiểu dữ liệu. Vì có nhiều lập trình viên khác nhau quản lý các hàm riêng, có liên quan đến các kiểu dữ liệu dùng chung nên các thay đổi mà lập trình viên tạo ra trên một phần tử dữ liệu sẽ làm ảnh hưởng đến công việc của tất cả các người còn lại trong nhóm. Mặc dù trong bối cảnh làm việc theo nhóm, việc viết các chương trình có cấu trúc thì dễ dàng hơn nhưng sai sót trong việc trao đổi thông tin giữa các thành viên trong nhóm có thể dẫn tới hậu quả là mất rất nhiều thời gian để sửa chữa chương trình.

Sự trừu tượng hóa dữ liệu

Sự trừu tượng hóa dữ liệu (Data abstraction) tác động trên các dữ liệu cũng tương tự như sự trừu tượng hóa theo chức năng. Khi có trừu tượng hóa dữ liệu, các cấu trúc dữ liệu và các phần tử có thể được sử dụng mà không cần bận tâm đến các chi tiết cụ thể. Chẳng hạn như các số dấu chấm động đã được trừu tượng hóa trong tất cả các ngôn ngữ lập trình, Chúng ta không cần quan tâm cách biểu diễn nhị phân chính xác nào cho số dấu chấm động khi gán một giá trị, cũng không cần biết tính bất thường của phép nhân nhị phân khi nhân các giá trị dấu chấm động. Điều quan trọng là các số dấu chấm động hoạt động đúng đắn và hiểu được.
Sự trừu tượng hóa dữ liệu giúp chúng ta không phải bận tâm về các chi tiết không cần thiết. Nếu lập trình viên phải hiểu biết về tất cả các khía cạnh của vấn đề, ở mọi lúc và về tất cả các hàm của chương trình thì chỉ ít hàm mới được viết ra, may mắn thay trừu tượng hóa theo dữ liệu đã tồn tại sẵn trong mọi ngôn ngữ lập trình đối với các dữ liệu phức tạp như số dấu chấm động. Tuy nhiên chỉ mới gần đây, người ta mới phát triển các ngôn ngữ cho phép chúng ta định nghĩa các kiểu dữ liệu trừu tượng riêng.

Lập trình hướng đối tượng

Khái niệm hướng đối tượng được xây dựng trên nền tảng của khái niệm lập trình có cấu trúc và sự trừu tượng hóa dữ liệu. Sự thay đổi căn bản ở chỗ, một chương trình hướng đối tượng được thiết kế xoay quanh dữ liệu mà chúng ta có thể làm việc trên đó, hơn là theo bản thân chức năng của chương trình. Điều này hoàn toàn tự nhiên một khi chúng ta hiểu rằng mục tiêu của chương trình là xử lý dữ liệu. Suy cho cùng, công việc mà máy tính thực hiện vẫn thường được gọi là xử lý dữ liệu. Dữ liệu và thao tác liên kết với nhau ở một mức cơ bản (còn có thể gọi là mức thấp), mỗi thứ đều đòi hỏi ở thứ kia có mục tiêu cụ thể, các chương trình hướng đối tượng làm tường minh mối quan hệ này.
Lập trình hướng đối tượng (Object Oriented Programming - gọi tắt là OOP) hay chi tiết hơn là Lập trình định hướng đối tượng, chính là phương pháp lập trình lấy đối tượng làm nền tảng để xây dựng thuật giải, xây dựng chương trình. Thực chất đây không phải là một phương pháp mới mà là một cách nhìn mới trong việc lập trình. Để phân biệt, với phương pháp lập trình theo kiểu cấu trúc mà chúng ta quen thuộc trước đây, hay còn gọi là phương pháp lập trình hướng thủ tục (Procedure-Oriented Programming), người lập trình phân tích một nhiệm vụ lớn thành nhiều công việc nhỏ hơn, sau đó dần dần chi tiết, cụ thể hoá để được các vấn đề đơn giản, để tìm ra cách giải quyết vấn đề dưới dạng những thuật giải cụ thể rõ ràng qua đó dễ dàng minh hoạ bằng ngôn ngữ giải thuật (hay còn gọi các thuật giải này là các chương trình con). Cách thức phân tích và thiết kế như vậy chúng ta gọi là nguyên lý lập trình từ trên xuống (top-down), để thể hiện quá trình suy diễn từ cái chung cho đến cái cụ thể.
Các chương trình con là những chức năng độc lập, sự ghép nối chúng lại với nhau cho chúng ta một hệ thống chương trình để giải quyết vấn đề đặt ra. Chính vì vậy, cách thức phân tích một hệ thống lấy chương trình con làm nền tảng, chương trình con đóng vai trò trung tâm của việc lập trình, được hiểu như phương pháp lập trình hướg về thủ tục. Tuy nhiên, khi phân tích để thiết kế một hệ thống không nhất thiết phải luôn luôn suy nghĩ theo hướng “làm thế nào để giải quyết công việc”, chúng ta có thể định hướng tư duy theo phong cách “với một số đối tượng đã có, phải làm gì để giải quyết được công việc đặt ra” hoặc phong phú hơn, “làm cái gì với một số đối tượng đã có đó”, từ đó cũng có thể giải quyết được những công việc cụ thể. Với phương pháp phân tích trong đó đối tượng đóng vai trò trùng tâm của việc lập trình như vậy, người ta gọi là nguyên lý lập trình từ dưới lên (Bôttm-up).
Lập trình hướng đối tượng liên kết cấu trúc dữ liệu với các thao tác, theo cách mà tất cả thường nghĩ về thế giới quanh mình. Chúng ta thường gắn một số các hoạt động cụ thể với một loại hoạt động nào đó và đặt các giả thiết của mình trên các quan hệ đó.
Ví dụ1.1: Để dễ hình dùng hơn, chúng ta thủ nhìn qua các công trình xây dựng hiện đại, như sân vận động có mái che hình vòng cung, những kiến trúc thẩm mĩ với đường nét hình cong. Tất cả những sản phẩm đó xuất hiện cùng với những vật liệu xây dựng. Ngày nay, không chỉ chồng lên nhau những viên gạch, những tảng đá để tạo nên những quần thể kiến trúc (như Tháp Chàm Nha Trang, Kim Tự Tháp,...), mà có thể với bêtông, sắt thép và không nhiều lắm những viên gạch, người xây dựng cũng có thể thiết kế những công trình kiến trúc tuyệt mỹ, những toà nhà hiện đại. Chính các chất liệu xây dựng đã làm ảnh hưởng phương pháp xây dựng, chất liệu xây dựng và nguyên lý kết dính caá chất liệu đó lại với nhau cho chúng ta một đối tượng để khảo sát, Chất liệu xây dựng và nguyên lý kết dính các chất liệu lại với nhau được hiểu theo nghĩa dữ liệu và chương trình con tác động trên dữ liệu đó.
Ví dụ1.2: Chúng ta biết rằng một chiếc xe có các bánh xe, di chuyển được và có thể đổi hướng của nó bằng cách quẹo tay lái. Tương tự như thế, một cái cây là một loại thực vật có thân gỗ và lá. Một chiếc xe không phải là một cái cây, mà cái cây không phải là một chiếc xe, chúng ta có thể giả thiết rằng cái mà chúng ta có thể làm được với một chiếc xe thì không thể làm được với một cái cây. Chẳng hạn, thật là vô nghĩa khi muốn lái một cái cây, còn chiếc xe thì lại chẳng lớn thêm được khi chúng ta tưới nước cho nó.
Lập trình hướng đối tượng cho phép chúng ta sử dụng các quá trình suy nghĩ như vậy với các khái niệm trừu tượng được sử dụng trong các chương trình máy tính. Một mẫu tin (record) nhân sự có thể được đọc ra, thay đổi và lưu trữ lại; còn số phức thì có thể được dùng trong các tính toán. Tuy vậy không thể nào lại viết một số phức vào tập tin làm mẫu tin nhân sự và ngược lại hai mẫu tin nhân sự lại không thể cộng với nhau được. Một chương trình hướng đối tượng sẽ xác định đặc điểm và hành vi cụ thể của các kiểu dữ liệu, điều đó cho phép chúng ta biết một cách chính xác rằng chúng ta có thể có được những gì ở các kiểu dữ liệu khác nhau.
Chúng ta còn có thể tạo ra các quan hệ giữa các kiểu dữ liệu tương tự nhưng khác nhau trong một chương trình hướng đối tượng. Người ta thường tự nhiên phân loại ra mọi thứ, thường đặt mối liên hệ giữa các khái niệm mới với các khái niệm đã có, và thường có thể thực hiện suy diễn giữa chúng trên các quan hệ đó. Hãy quan niệm thế giới theo kiểu cấu trúc cây, với các mức xây dựng chi tiết hơn kế tiếp nhau cho các thế hệ sau so với các thế hệ trước. Đây là phương pháp hiệu quả để tổ chức thế giới quanh chúng ta. Các chương trình hướng đối tượng cũng làm việc theo một phương thức tương tự, trong đó chúng cho phép xây dựng các các cơ cấu dữ liệu và thao tác mới dựa trên các cơ cấu có sẵn, mang theo các tính năng của các cơ cấu nền mà chúng dựa trên đó, trong khi vẫn thêm vào các tính năng mới.
Lập trình hướng đối tượng cho phép chúng ta tổ chức dữ liệu trong chương trình theo một cách tương tự như các nhà sinh học tổ chức các loại thực vật khác nhau. Theo cách nói lập trình đối tượng, xe hơi, cây cối, các số phức, các quyển sách đều được gọi là các lớp (Class).
Một lớp là một bản mẫu mô tả các thông tin cấu trúc dữ liệu, lẫn các thao tác hợp lệ của các phần tử dữ liệu. Khi một phần tử dữ liệu được khai báo là phần tử của một lớp thì nó được gọi là một đối tượng (Object). Các hàm được định nghĩa hợp lệ trong một lớp được gọi là các phương thức (Method) và chúng là các hàm duy nhất có thể xử lý dữ liệu của các đối tượng của lớp đó. Một thực thể (Instance) là một vật thể có thực bên trong bộ nhớ, thực chất đó là một đối tượng (nghĩa là một đối tượng được cấp phát vùng nhớ).
Mỗi một đối tượng có riêng cho mình một bản sao các phần tử dữ liệu của lớp còn gọi là các biến thực thể (Instance variable). Các phương thức định nghĩa trong một lớp có thể được gọi bởi các đối tượng của lớp đó. Điều này được gọi là gửi một thông điệp (Message) cho đối tượng. Các thông điệp này phụ thuộc vào đối tượng, chỉ đối tượng nào nhận thông điệp mới phải làm việc theo thông điệp đó. Các đối tượng đều độc lập với nhau vì vậy các thay đổi trên các biến thể hiện của đối tượng này không ảnh hưởng gì trên các biến thể hiện của các đối tượng khác và việc gửi thông điệp cho một đối tượng này không ảnh hưởng gì đến các đối tượng khác.
Như vậy, đối tợng được hiểu theo nghĩa là một thực thể mà trong đó caá dữ liệu và thủ tục tác động lên dữ liệu đã được đóng gói lại với nhau. Hay “đối tượng được đặc trưng bởi một số thao tác (operation) và các thông tin (information) ghi nhơ sự tác động của caá thao tác này.”
Ví dụ 1.3: Khi nghiên cứ về ngăn xếp (stack), ngoài các dữ liệu vùng chứa ngăn xếp, đỉnh của ngăn xếp, chúng ta phải cài đặt kèm theo các thao tác như khởi tạo (creat) ngăn xếp, kiểm tra ngăn xếp rỗng (empty), đẩy (push) một phần tử vào ngăn xếp, lấy (pop) một phần tử ra khỏi ngăn xếp. Trên quan điểm lấy đối tượng làm nền tảng, rõ ràng dữ liệu và các thao tác trên dữ liệu luôn gắn bó với nhau, sự kết dính chúng chính là đối tượng chúng ta cần khảo sát.
Các thao tác trong đối tượng được gọi là các phương thức hay hành vi của đối tượng đó. Phương thức và dữ liệu của đối tượng luôn tác động lẫn nhau và có vai trò ngang nhau trong đối tượng, Phương thức của đối tượng được qui định bởi dữ liệu và ngược lại, dữ liệu của đối tượng được đặt trưng bởi các phương thức của đối tượng. Chính nhờ sự gắn bó đó, chúng ta có thể gởi cùng một thông điệp đến những đối tượng khác nhau. Điều này giúp người lập trình không phải xử lý trong chương trình của mình một dãy các cấu trúc điều khiển tuỳ theo thông điệp nhận vào, mà chương trình được xử lý vào thời điểm thực hiện.
Tóm lại, so sánh lập trình cấu trúc với chương trình con làm nền tảng:
Chương trình = Cấu trúc dữ liệu + Thuật giải
Trong lập trình hướng đối tượng chúng ta có:
Đối tượng = Phương thức + Dữ liệu
Đây chính là 2 quan điểm lập trình đang tồn tại và phát triển trong thế giới ngày nay.

MỘT SỐ KHÁI NIỆM MỚI TRONG LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG

Trong phần này, chúng ta tìm hiểu các khái niệm như sự đóng gói, tính kế thừa và tính đa hình. Đây là các khái niệm căn bản, là nền tảng tư tưởng của lập trình hướng đối tượng. Hiểu được khái niệm này, chúng ta bước đầu tiếp cận với phong cách lập trình mới, phong cách lập trình dựa vào đối tượng làm nền tảng mà trong đó quan điểm che dấu thông tin thông qua sư đóng gói là quan điểm trung tâm của vấn đề.

Sự đóng gói (Encapsulation)

Sự đóng gói là cơ chế ràng buộc dữ liệu và thao tác trên dữ liệu đó thành một thể thống nhất, tránh được các tác động bất ngờ từ bên ngoài. Thể thống nhất này gọi là đối tượng.
Trong Objetc Oriented Software Engineering của Ivar Jacibson, tất cả các thông tin của một hệ thống định hướng đối tượng được lưu trữ bên trong đối tượng của nó và chỉ có thể hành động khi các đối tượng đó được ra lệnh thực hiện các thao tác. Như vật, sự đóng gói không chỉ đơn thuần là sự gom chung dữ liệu và chương trình vào trong một khối, chúng còn được hiểu theo nghĩa là sự đồng nhất giữa dữ liệu và các thao tác tác động lên dữ liệu đó.
Trong một đối tượng, dữ liệu hay thao tác hay cả hai có thể là riêng (private) hoặc chung (public) của đối tượng đó. Thao tác hay dữ liệu riêng là thuộc về đối tượng đó chỉ được truy cập bởi các thành phần của đối tượng, điều này nghĩa là thao tác hay dữ liệu riêng không thể truy cập bởi các phần khác của chương trình tồn tại ngoài đối tượng. Khi thao tác hay dữ liệu là chung, các phần khác của chương trình có thể truy cập nó mặc dù nó được định nghĩa trong một đối tượng. Các thành phần chung của một đối tượng dùng để cung cấp một giao diện có điều khiển cho các thành thành riêng của đối tượng.
Cơ chế đóng gói là phương thức tốt để thực hiện cơ chế che dấu thông tin so với các ngôn ngữ lập trình cấu trúc.

Tính kế thừa (Inheritance)

Chúng ta có thể xây dựng các lớp mới từ các lớp cũ thông qua sự kế thừa. Một lớp mới còn gọi là lớp dẫn xuất (derived class), có thể thừa hưởng dữ liệu và các phương thức của lớp cơ sở (base class) ban đầu. Trong lớp này, có thể bổ sung các thành phần dữ liệu và các phương thức mới vào những thành phần dữ liệu và các phương thức mà nó thừa hưởng từ lớp cơ sở. Mỗi lớp (kể cả lớp dẫn xuất) có thể có một số lượng bất kỳ các lớp dẫn xuất. Qua cơ cấu kế thừa này, dạng hình cây của các lớp được hình thành. Dạng cây của các lớp trông giống như các cây gia phả vì thế các lớp cơ sở còn được gọi là lớp cha (parent class) và các lớp dẫn xuất được gọi là lớp con (child class).
Ví dụ 1.2: Chúng ta sẽ xây dựng một tập các lớp mô tả cho thư viện các ấn phẩm. Có hai kiểu ấn phẩm: tạp chí và sách. Chúng ta có thể tạo một ấn phẩm tổng quát bằng cách định nghĩa các thành phần dữ liệu tương ứng với số trang, mã số tra cứu, ngày tháng xuất bản, bản quyền và nhà xuất bản. Các ấn phẩm có thể được lấy ra, cất đi và đọc. Đó là các phương thức thực hiện trên một ấn phẩm. Tiếp đó chúng ta định nghĩa hai lớp dẫn xuất tên là tạp chí và sách. Tạp chí có tên, số ký phát hành và chứa nhiều bài của các tác giả khác nhau . Các thành phần dữ liệu tương ứng với các yếu tố này được đặt vào định nghĩa của lớp tạp chí. Tạp chí cũng cần có một phương thức nữa đó là đặt mua. Các thành phần dữ liệu xác định cho sách sẽ bao gồm tên của (các) tác giả, loại bìa (cứng hay mềm) và số hiệu ISBN của nó. Như vậy chúng ta có thể thấy, sách và tạp chí có chung các đặc trưng ấn phẩm, trong khi vẫn có các thuộc tính riêng của chúng.

Thứ Năm, 3 tháng 9, 2009

Nested Functions

Bài viết trích từ  openandfree. Bài gốc tiếng Anh tại trang gotw


Bài viết này không trình bày chi tiết về nested class trong C++ mà chỉ tập trung vào các kĩ thuật sử dụng nested class và function object để mô phỏng các nested function, một yếu tố không có trong C++. Các chi tiết về nested class có thể tìm thấy trong nhiều cuốn sách C++ khác, ví dụ cuốn Thinking in C++, tập 1.

Bài viết đưa ra ba câu hỏi và sau đó lần lượt đi tìm các câu trả lời cho chúng. Ba câu hỏi là:

1- Nested class là gì? Tại sao chúng ta cần các nested class?
2- Local class là gì? Tại sao chúng ta cần các local class?
3- Trong C++ không có khái niệm nested function. Bởi vậy, chúng ta không thể viết một đoạn mã như sau


int f( int i )
{
int j = i*2;
int g( int k )
{
return j+k;
}
j += 4;
return g( 3 );
}

Hãy đưa ra một giải pháp mô phỏng các hàm f và g sao cho đạt được một “hiệu ứng” tương tự như đoạn mã trên.



Type rest of the post hereHãy đưa ra một giải pháp mô phỏng các hàm f và g sao cho đạt được một “hiệu ứng” tương tự như đoạn mã trên.

Trả lời

C++ có rất nhiều công cụ hữu ích dùng để ẩn thông tin (information hiding) và quản lí sự phụ thuộc mã nguồn (dependency management). Các đoạn mã sau đây có thể chưa hoàn toàn chính xác về mặt cú pháp, chúng được dùng để minh họa cho các kĩ thuật thiết kế mà thôi.

1- Nested class là gì? Tại sao chúng ta cần nested class?

Nested class là một class được “viết” (enclosed) bên trong phạm vi (scope) của một class khác.

// Ví dụ 1: Nested class
//
class OuterClass
{
class NestedClass
{
// ...
};
// ...
};

Trong đoạn mã trên, NestedClass là một nested class được “viết” bên trong class OuterClass. Các nested class rất hữu ích cho việc tổ chức mã nguồn, quản lí quyền truy nhập (access) và các phụ thuộc (dependencies). Các nested class tuân theo các quy tắc thông thường về quyền truy nhập giống như các dữ liệu thành phần và các hàm thành phần. Tức là, nếu NestedClass được khai báo là public thì chúng ta có thể sử dụng nó từ bất cứ đâu thông qua tên gọi OuterClass::NestedClass. Ngược lại, nếu NestedClass được khai báo là private thì chỉ có các thành phần và các hàm bạn (friends) của OuterClass là có quyền truy nhập đến nó. Thông thường, các nested class chứa các cài đặt riêng cho OuterClass, do đó thường được khai báo là private.

Chú ý rằng nested class khác với namespace. Các namespace chỉ thuần túy nhóm các tên lại với nhau chứ không mang lại khả năng quản lí quyền truy nhập. Nếu bạn muốn quản lí quyền truy nhập tới một lớp, một trong các giải pháp là viết nó thành nested class trong một class khác.

2- Local class là gì? Tại sao chúng ta cần các local class?

Local class là một class được định nghĩa bên trong một hàm thông thường hoặc một hàm thành phần (member function). Trong ví dụ sau đây, LocalClass là một local class được định nghĩa bên trong một hàm thông thường có tên là f.

// Ví dụ 2: Local class
//
int f()
{
class LocalClass
{
// ...
};
// ...
};

Giống như nested class, local class là một công cụ hữu ích phục vụ việc quản lí những sự phụ thuộc về mã nguồn (code dependencies). Trong ví dụ 2, chỉ có đoạn mã trong thân hàm f mới được phép sử dụng LocalClass. LocalClass thường chứa những cài đặt riêng cho hàm f nên không cần thiết phải có khả năng truy nhập được từ bên ngoài.

Bạn có thể sử dụng local class gần như trong mọi tình huống có thể sử dụng class thông thường. Một ngoại lệ quan trọng cần ghi nhớ là: Các local class không thể đóng vai trò tham số kiểu (template parameter). Ví dụ dưới đây trích từ tài liệu chuẩn C++:

A local type, a type with no linkage, an unnamed
type or a type compounded from any of these types
shall not be used as a template-argument for a
template type-parameter.
[Example:
template
class X { /* ... */ };

void f()
{
struct S { /* ... */ };
X<s> x3; // error: local type used as
// template-argument
X<s> x4; // error: pointer to local type
// used as template-argument
}
--end example]

Tóm lại, cả nested class lẫn local class đều là những công cụ hữu ích của C++ dùng để ẩn thông tin và quản lí quyền truy nhập và các phụ thuộc.

Nested Funtion

Một vài ngôn ngữ (không phải C++) cho phép chúng ta viết các nested function. Giống như các nested class, nested function là một function được viết bên trong một function khác. Những đặc điểm quan trọng của nested function là:

- Các nested function có quyền truy nhập đến các biến cục bộ của hàm chứa nó.
- Các nested function là “cục bộ”, nghĩa là không thể truy nhập tới chúng từ bên ngoài, trừ khi có một con trỏ trỏ đến nested function được cung cấp bởi hàm chứa.

Nếu như các nested class hữu ích bởi chúng cho phép điều kiển sự “ẩn hiện” (visibility) của một lớp thì các nested function hữu ích bởi chúng cho phép điều khiển sự “ẩn hiện” của một hàm.

Trả lời cho câu hỏi 3: Các giải pháp sử dụng class để mô phỏng nested function trong C++

Chú ý: “mô phỏng” ở đây được hiểu theo nghĩa là: Xây dựng một class g bên trong một hàm f, sao cho f có thể sử dụng g như một nested function

void f()
{
class g { … }; g();
}

Nói đến một class được sử dụng như một function, chúng ta nghĩ ngay đến các function object. Giải pháp đầu tiên mà hầu hết mọi người sẽ đưa ra là:

// Giải pháp 3(a): Một giải pháp thô sử dụng function object
//
//
int f( int i )
{
int j = i*2;
class g_
{
public:
int operator()( int k )
{
return j+k; // ERROR!!!: Không thể sử dụng j bên trong g_
}
} g;

j += 4;

return g( 3 ); // f sử dụng g như một nested function
}

Ý tưởng ở đây là mô phỏng nested function bởi một local class có tên là g_, sau đó gọi operator() của g_. (Xem lại bài viết: STL Function Object và các ứng dụng(1) để biết chi tiết về cách viết một function object). Đây là một ý tưởng hay nhưng đáng tiếc là đoạn mã trên không chạy được! Lí do là một local class không thể sử dụng các biến của hàm bên ngoài (ở đây là biến j). Một giải pháp cho vấn đề này là truyền các biến của hàm bên ngoài vào trong local class thông qua các thành phần dữ liệu của class đó.

// Giải pháp 3(b): Sử dụng function object.
// Thành phần dữ liệu của local class là các references trỏ tới
// các biến cục bộ của hàm bên ngoài (phức tạp, khó bảo trì)
//
int f( int i )
{
int j = i*2;
class g_
{
public:
g_( int &j ) : j_( j ) { }
int operator()( int k )
{
return j_+k; // sử dụng j thông qua reference
}
private:
int &j_;
} g( j );

j += 4;

return g( 3 );
}

Bây giờ thì local class g_ đã có thể sử dụng biến j của hàm bên ngoài thông qua thành phần dữ liệu j_ của nó và hàm f() ở trên đã chạy đúng. Tuy nhiên nhược điểm của giải pháp này là rất khó có thể mở rộng. Trong trường hợp class g_ cần sử dụng một biến khác nữa của hàm f() ngoài biến j (giả sử là biến k kiểu int). Khi đó chúng ta phải thực hiện những thao tác sau đây:

- Thêm vào class g_ một thành phần dữ liệu int &k_;
- Thêm tham số int k cho constructor của g_;
- Thêm một phép khởi tạo cho k_: k_( k );

Sô thao tác cần thực hiện sẽ tăng lên rất nhiều nếu có nhiều local class trong f(). Vậy, chúng ta cần tìm ra một giải pháp tốt hơn.

Một giải pháp tốt hơn

Hàm f được cải tiến bằng cách chuyển các biến cục bộ của nó thành các thành phần dữ liệu public của lớp g_

// Giải pháp 3(c): Một giải pháp tốt hơn
//
int f( int i )
{
class g_
{
public:
int j;
int operator()( int k )
{
return j+k;
}
} g;

g.j = i*2;
g.j += 4;

return g( 3 );
}

Bây giờ thì hàm f đã trở nên đẹp đẽ và dễ bảo trì, phát triển. Giải pháp này gợi cho chúng ta suy nghĩ: Tại sao không “mô phỏng” các nested function của f() bởi các hàm thành phần x(), y(), z() của lớp g_? Đoạn mã sau đây hiện thực hóa ý tưởng này. Lớp g_ được đổi tên thành Local_ để mang tính mô tả tốt hơn

// Giải pháp 3(d): Mô phỏng nested function bằng cách hàm thành phần
//
int f( int i )
{

class Local_
{
public:
int j;
int g( int k )
{
return j+k;
}
void x() { /* ... */ }
void y() { /* ... */ }
void z() { /* ... */ }
} local;

local.j = i*2;
local.j += 4;
local.x();
local.y();
local.z();

return local.g( 3 );
}

Giải pháp này không sử dụng function object. Nhược điểm của nó là j không được khởi tạo một cách linh hoạt do lớp Local_ không có constructor.

Giải pháp hoàn thiện

Nếu f không nhất thiết phải là một hàm thông thường mà chỉ cần được sử dụng như một hàm thông thường, chúng ta có thể xây dựng f dưới dạng một function object và xây dựng các nested function dưới dạng các hàm thành phần như sau:

// Giải pháp 3(e): Một giải pháp hoàn thiện. Dễ bảo trì, phát triển
//
//
class f
{
int retval; // Giá trị trả về của “hàm” f
int j;

//Các hàm thành phần của lớp f
//g(), x(), y(), z() có vai trò tương tự như các nested function
int g( int k ) { return j + k; };
void x() { /* ... */ }
void y() { /* ... */ }
void z() { /* ... */ }

public:
f( int i ) // constructor
: j( i*2 )
{
j += 4;
x();
y();
z();
retval = g( 3 );
}
operator int() // operator() trả về giá trị cho “hàm” f
{
return retval;
}
};

Giải pháp 3(e) là một giải pháp hoàn thiện cho việc mô phỏng nested function g cho một hàm f thông thường. Nếu f không phải là một hàm thông thường mà là một hàm thành phần của một lớp C nào đó thì sao? Khi đó, chúng ta mong muốn mô phỏng được đoạn mã sau đây, trong đó g là một nested function của hàm thành phần f của lớp C (tất nhiên đoạn mã này là không hợp lệ trong C++, vì C++ không cho phép nested function)

// Đoạn mã cần mô phỏng: Một nested function bên trong một hàm thành phần
// Chú ý: Đoạn mã chỉ có tính minh họa, không hợp lệ trong C++
//
//
class C
{
int data_;
public:
/* f là hàm thành phần của lớp C */
int f( int i )
{
// g là một nested function của f
int g( int i ) { return data_ + i; }
return g( data_ + i*2 );
}
};

“Bắt chước” giải pháp 3(e), cộng với một chút điều chỉnh, chúng ta có giải pháp 4(a) sau đây:

- Xây dựng C_f như một function object và là một nested class bên trong lớp C
- Xây dựng g như một hàm thành phần của C_f
- Hàm thành phần f của C gọi đến C_f
// Giải pháp 4(a): Giải pháp hoàn thiện, giống 3(e) nhưng áp dụng cho hàm thành phần
//
//
class C
{
int data_;
friend class C_f;

public:
int f( int i );
};

class C_f
{
C* self;
int retval;
int g( int i ) { return self->data_ + i; }

public:
C_f( C* c, int i ) : self( c )
{
retval = g( self->data_ + i*2 );
}
operator int() { return retval; }
};

int C::f( int i ) { return C_f( this, i ); }

Kết luận

Cũng giống như những bài viết khác trong cùng loạt bài GotW của Herb Sutter, bài viết này đã đưa ra một yêu cầu thiết kế thú vị, sau đó tiến hành xem xét đánh giá những giải pháp khác nhau để cuối cùng chọn ra một giải pháp tốt nhất. Bài viết cung cấp nhiều kinh nghiệm lập trình C++, cũng như cho thấy một ứng dụng thú vị nữa của các function object.

Thứ Tư, 2 tháng 9, 2009

Cờ toán Việt Nam

Gần đây may mắn được dịp làm quen với món cờ toán này, cảm nhận đầu tiên là cách chơi đơn giản (chỉ 1 phút là biết chơi ngay) nhưng cũng rất thú vị. Cờ toán được phát mình trong thời gian 20 năm (theo lời tác giả) bởi bác Vũ Văn Bảy, một người mới chỉ học hết lớp 7 (đều là số 7, thật trùng hợp). Nghe nói bác Bảy được người Tàu gạ gẫm mua lại cờ toán với giá rất cao (cái giá mà phải em em bán ngay {#emotions_dlg.sealed}) nhưng bác đã từ chối vì lí do là tay người Tàu bắt bác thay mấy cái chấm bằng kí tự tượng hình của Tàu và không gọi là cờ toán Việt Nam nữa mà goi là cờ toán quốc tế.

Xin hoan nghênh tinh thần dân tộc của bác Bảy, nhưng xin bác đăng kí bản quyền nhanh cho không có số phận của món cờ bác dày công 20 năm lại giống như quần đảo Hoàng Sa thì tiếc lắm lắm.

Luật Cờ toán Việt Nam

Luật cờ toán rất đơn giản, có thể search trên mạng thấy một đống nhưng chung qui lại có thể tóm tắt như sau:

  • Bàn cờ ban đầu xếp như hình vẽ.
  • Di chuyển: Quân có n chấm thì được đi số bước nhỏ hơn hay bằng n theo 8 hướng. Như vậy thì thấy quân số 0 (không) - quân vẽ chấm to đùng - được đi tối đa là 0 bước, tức là không được di chuyển, sống chết hoàn toàn dựa vào các quân khác bảo vệ.
  • Bắt quân: Hai quân của cùng một phe, đứng cạnh nhau (theo 8 hướng), giả sử là quân số x và y (x>y), có thể bắt quân đối phương nằm tại ô cách quân x n ô nếu x-y=n hoặc x+y=n hoặc x*y=n hoặc x mod y = n
  • Thắng/thua: ai mất quân số 0 (không) là thua.

Lập trình cờ toán

Có người hỏi một câu là có nên phát triển trò này hay không thì sau một thời gian tiếp xúc với trò này, mình thấy rất nên. Xã hội cũng đã có một số nỗ lực để lưu giữ và phát triển món cờ này cho Việt Nam chẳng hạn như thành lập các câu lạc bộ Cờ toán Việt Nam, giới thiệu cờ toán tới quần chúng trong các dịp lễ hội, nghiên cứu chiến thuật cho Cờ toán Việt Nam ...

Lập trình viên có một cách ủng hộ Cờ toán Việt Nam là :

  • Viết chương trình cờ toán người - người để nhiều người tiếp cận được với Cờ toán Việt Nam hơn.
  • Viết chương trình cờ toán để máy chơi với người.

Designer có thể ủng hộ bằng cách:

  • Vẽ quân đẹp.
  • Vẽ bàn cờ đẹp.

Anh em lập trình viên, designer vào đây thảo luận phát triển cái chứ nhở.

Sharpdevelop 2 - Lập trình C# và VB .Net 2.0 chỉ với 4 MB

Tôi là một sinh viên CNTT và đang học ngôn ngữ C#, tuy nhiên máy của tôi lại thuộc loại “đồ cổ”, ổ cứng rất khiêm tốn nên tôi không thể cài đặt bộ Visual Studio 2005 với dung lượng đòi hỏi đến vài GB. Và tôi đã tìm đến SharpDevelop. Nếu bạn không phải là một lập trình viên chuyên nghiệp thì SharpDevelop có thể đáp ứng được mọi nhu cầu và sự sáng tạo của bạn trên nền tảng .Net 2.0 .

SharpDevelop (#develop) là một dự án mã nguồn mở được bắt đầu từ ngày 11-9-2000 do Mike Krüger khởi xướng. Đến nay, SharpDevelop đã là một công cụ lập trình hoàn chỉnh, được nhiều người sử dụng . Ưu điểm lớn nhất của công cụ này là dung lượng cực nhỏ chỉ với 4 Mb (sau khi cài đặt là 19 MB) so với bộ cài đặt 4 đĩa của Visual Studio 2005 (VS2005), và giao diện quen thuộc, dễ dùng, giống với VS2005, đặc biệt hoàn toàn miễn phí.

SharpDevelop cài đặt rất đơn giản với 1 file cài đặt duy nhất. Ngoài ra, bạn hoàn toàn yên tâm khi mở các Project, Solution của VS2005 kể cả VS2003 và ngược lại, chúng hoàn toàn tương thích.


Sau đây là một số tính năng đáng chú ý:
- Hỗ trợ khi viết code trực quan, như tự động hiện ra các tên biến, tên class, liệt kê các thuộc tính sau dấu chấm... và hỗ trợ đầy đủ các Events.

- Hỗ trợ chuyển code từ VB sang C# và ngược lại, bạn vào Project -> Covert để sử dụng chức năng này.

- Hỗ trợ gỡ lỗi trong khi chạy (F5), hoặc chạy từng bước (F10), đánh dấu điểm ngắt...

- Hỗ trợ viết ứng dụng ASP.Net, tuy nhiên không thể thiết kế Web Form.

Và còn nhiều tiện ích khác, tuy #develop vẫn chưa thể sánh với VS2005 được, nhưng đối với sinh viên mới học lập trình như tôi vậy là quá đủ.

Bạn có thể tải về file cài đặt SharpDevelop tại:http://www.icsharpcode.net/OpenSource/SD/Download/

Để chạy được file cài đặt này cũng như các ứng dụng trên nền tảng .Net, bạn cần cài đặt bộ .NET Framework Version 2.0, tải về tại:

http://www.microsoft.com/downloads/details.aspx?FamilyID=0856EACB-4362-4B0D-8EDD-AAB15C5E04F5&displaylang=en.

Bài đăng phổ biến