Showing posts with label OOP. Show all posts
Showing posts with label OOP. Show all posts

Apr 28, 2013

Phụ thuộc vòng

Bài viết đang được hoàn thiện

Phụ thuộc vòng - một cách dịch đầy thô thiển. Tên chính xác của nó "Circular Dependency". Nghe khá rờm rà. Từ giờ trở đi gọi là CDepend cho gọn. Wiki có hẳn 1 bài về nó: link đây. Đại để có thể hiểu theo ngôn ngữ C++ như sau: File header A dùng để khai báo lớp A, file header B dùng để khai báo lớp B. Trong lớp A có 1 attribute (thuộc tính) là thể hiện của B, và trong lớp B có một thuộc tính là thể hiện của A. Và như vậy, theo lẽ thông thường, Trong file header A sẽ có dòng #include"B.h" và trong file header B có dòng #include"A.h". Mọi sự rắc rối rồi.

Hậu quả:

  • Phát sinh những vấn đề không mong muốn trong quá trình thiết kế lớp. Như ví dụ nêu trên chắc chắn là sẽ chẳng thể cài đặt với thiết kế và giải pháp đó
  • Gây rò rỉ vùng nhớ (memory leak). Vì sao ư? Đối với các ngôn ngữ không có hỗ trợ việc tự dọn rác (automatic memory management), việc tự thiết kế các phương thức hủy (destructor) hay giải phóng vùng nhớ đối với các lớp phụ thuộc lẫn nhau như thế này rất khó chịu. Nếu "lỡ tay" giải phóng hết các thuộc tính trong A, tức là giải phóng luôn có bé thể hiện B trong đó. Mà bé ý vẫn còn đang sống nhăn răng, vẫn chưa muốn bị hủy thì coi như tách. Cài đặt fail và số quá nhọ. Dĩ nhiên, điều "may mắn" là compiler không bao giờ coi đó là syntax error để thông báo ngay từ đầu cả.
  • Gây hiệu ứng domino: một tác động nhỏ lên lớp này ảnh hưởng đến toàn bộ các lớp khác. Theo nguyên tắc thiết kế, các lớp phải độc lập khi sửa chữa. Tức thay đổi của lớp này không ảnh hưởng đến lớp kia.

Một số điểm thuận lợi:

Không phải cái gì xấu, hại thì xấu hoàn toàn. Cái gì nó cũng có 2 mặt của 1 vấn đề. CDepend cũng có một số điểm tích cực

  1. Có những thiết kế bắt buộc phải dính tới CDepend. Và như vậy buộc ta phải cài đặt nó. Ví dụ: danh sách liên kết, đồ thị ...

Giải quyết

  • Có những tool chuyên trị cái này: JDepend (cho Java), Code Analysis Plugin (cho Java).
  • Sử dụng mẫu thiết kế Observer, Visitor, Factory... blah blah

Một số vấn đề trong C++

Phần này sẽ nêu 1 số ví dụ thường gặp, thường dính khi lập trình trên C++

Tài liệu tham khảo:

Hầu hết những gì chém gió phía trên đều được trích lược và tran sờ lai (translate) sang Tiếng Việt. Những nguồn này khá bổ ích

  1. Avoiding Circular Dependencies of header files
  2. Wikipedia
  3. Một bài viết hữu ích trên stackoverflow
  4. Một bài tập nho nhỏ
  5. Có hẳn 1 mục trong stackoverflow

May 2, 2012

Static: biến, hàm, các vấn đề xung quanh

Định nghĩa:

Trong khoa học máy tính, biến tĩnh (static variable) là biến được cấp phát tĩnh có thời gian sống (lifetime) xuyên suốt quá trình chạy chương trình. Các biến này chỉ được khởi tạo một lần, sau đó sẽ không mất đi.

Ví dụ:

#include <iostream>
void func() {
    static int a = 0;
    ++a;
    std::cout<< "A  = " << a << std::endl;
}
int main(int argc, char** argv) {
    func();
    func();
    func();
    //a = 7; // error.
    return 0;
}

Một điều lưu ý, tuy biến static có thời gian sống suốt cả chương trình, nhưng tầm nhìn (visibility)chỉ trong hàm, tức là chỉ có thể tính toán biến đó trong hàm.


static là một từ khóa trong C/C++ liên quan đến khái niệm lớp lưu trữ (storage class). Trong lớp lưu trữ còn có các đối từ khóa khác đó là : auto, register, extern.


Với chức năng liên quan đến khái niệm lớp lưu trữ, static còn có một số công dụng sau:



  • Static local variables: Như trên.


  • Static function: Khi có 2 hàm hoàn toàn giống nhau về tên, tham số, giá trị trả về, thì chương trình sẽ thực thi hàm chứa từ khóa static trong file đó.

Nói có vẻ khó hiểu như nếu code ví dụ thì sẽ rất rõ ràng:


ví dụ có hàm func1() trong cả 2 file.


file1.h và func1.cpp:



#ifndef _FUNC1_H
#define _FUNC1_H
void func1();
#endif


#include <iostream>
void func1() {
    std::cout << "Hello" << std::endl; 
}

Và func2.cpp



#include <iostream>
#include "func1.h"
//void func1() {
static void func1() {
    std::cout << "Goodbye" << std::endl;
}
int main(int argc, char** argv) {
    func1();
    return 0;
}

Output sẽ là "Goodbye".



  • Static in class: Khi khai báo static một thuộc tính hay phương thức của 1 class. Ta có thể sử dụng ngay mà không cần khai báo đối tượng.

Ví dụ:



#include <iostream>
class NUM {
public:
    static int a;
    int b;
    static void func() {
        std::cout<< "Hi!!" << std::endl;
    }
    void func1() {
        std::cout<< "Aloha!!" << std::endl;
    }
};
int NUM::a = 9;
int main(int argc, char** argv) {
    //NUM::b = 10; 
    //int NUM::a = 9;
    NUM::func();
    //NUM::func1();
    return 0;
}

 


Nguồn tham khảo:



  1. http://en.wikipedia.org/wiki/Static_variable .


  1. Cộng đồng C việt.

  2. Learncpp.com

  3. cprogramming.com

May 1, 2012

Struct, Enum, union.

 

Thực tế enum và struct, union không có sự liên quan mật thiết cho lắm.

enum dùng để khai báo danh sách  các hằng nguyên.

Giả sử ta muốn thiết lập cá giá trị màu, ví dụ: RED = 0; BLACK = 1; WHITE = 2; BLUE =4;

Thông thường ta dùng #define:

#define RED 0
#define BLACK 1
#define WHITE 2
#define BLUE 3
Tuy nhiên, ta có thể rút gọn bằng cách viết sau:



enum color {RED,BLACK,WHITE,BLUE};

Một số điều lưu ý:



  • Giá trị đầu tiên, nếu không khởi tạo giá trị thì mặc định là bằng 0.

  • Nếu không thiết lập giá trị thì hằng đó được tính bằng giá trị đứng trước cộng 1, đến khi nào gặp được hằng có giá trị khởi tạo hoặc giá trị đầu tiên.

Ví dụ:



enum Color{
RED; // = 0
BLACK; // = 1
WHITE; // =2
BLUE; // 3
GREEN = 100; 
YELLOW; // = 101
CYAN; // = 102
};
 

Khai báo và sử dụng:


enum Color cl;


Chỉ sử dụng các hằng đã khai báo trong enum để tính toán và sử dụng.Nếu tính toán với các số nguyên khác thì nên ép kiểu.


Ví dụ chung:


 



#include <iostream>
using namespace std;
enum Shape{
    circle = 0, // first element = 0 default;
    triangle =5,
    ellipse ,
};
int main() {
    enum Shape s1;
    s1 = circle;
    cout << "Circle " << dec << s1 << endl;
    s1 = triangle;
    cout << "Triangle = " << dec << s1 << endl;
    s1 = ellipse;
    //s1 = 0; // error
    s1 = (enum Shape) (triangle + 100);
    cout << "Ellipse = " << dec << s1 << endl;
    cout << "Size of shape:" << sizeof(Shape) << endl;
    return 0;
}

Union và Struct:


Đều mang ý nghĩa là khai báo và thiết lập dữ liệu có cấu trúc. (struct cũng giống class trong C++ để khai báo đối tượng, điểm khác nhau là class mặc định access control là private, còn struct là public). 


Về chức năng của struct và union hoàn toàn giống nhau, chỉ khác một điều union tận dụng tối đa vùng nhớ trống để lưu trữ kiểu dữ liệu. Còn struct thì không. 


Code sau đây mô tả phần nào sự khác biệt của struct và union:



#include <iostream>
using namespace std;
typedef union Struct1{
    char a;
    char b;
    int c;
};
// *************************
typedef struct Struct2{
    char a;
    char b;
    int c;
};
typedef Struct1 STRUCT1;
typedef Struct2 STRUCT2;
int main() {
    STRUCT1 a1;
    STRUCT2 a2;
    cout <<"Size of struct 1" << sizeof(a1) << endl;
    cout << "Size of struct 2" << sizeof(a2) << endl;
    a2.a = 10;
    a2.b = 20;
    int t = a1.a + a1.b;
    int tt = *(&a2.a) + *(&a2.b); 
    cout << "SUM = " << tt << endl;
    return 0;
}

Thông qua đoạn code, ta thấy:



  • Truy cập thành phần bằng dấu ".".

  • Tính toán và sử dụng như các biến bình thường, truy xuất địa chỉ cũng tương tự.

Kiểu dữ liệu có cấu trúc rất mạnh và cần một bài khác để viết. Tạm thời bài này chỉ giới thiệu sơ khởi và cơ bản về các khái niệm này.


 


 


 

Hằng, các vấn đề xung quanh.


Một hôm rảnh rỗi tìm hiểu về cái này. Một khái niệm quá phổ biến, nhưng trong một số ngôn ngữ lập trình, C/C++ chẳng hạn, thì cần phải tìm hiểu một cách rõ ràng.

Định nghĩa: Constant là một giá trị xác định không thể bị thay đổi trong suốt quá trình chạy của chương trình. (Nguồn).
Khai báo: Có 3 cách thông dụng:
  • Trực tiếp.
  • Dùng macro: #define.
  • Dùng từ khóa const.
Sự khác biệt đáng chú ý là macro và const. Ví dụ #define A 200 và const int A = 200; Macro đơn giản chỉ là trước khi dịch chương trình, trình compile sẽ thay thế (replace) tất cả các ký tự 'A' thành '200' cho hợp ngữ. Còn const thì khác, nó sẽ cấp phát 1 vùng nhớ có kích thước của int và gán giá trị của nó là 200, đồng thời vùng nhớ đó không thể bị thay đổi trong lúc chương trình chạy.

Tọa đàm đôi chút về hằng trong C++:
Cú pháp:
Const int x = 5 hay int const x = 5 là tương đương nhau, tuy nhiên, sẽ là rất khác với kiểu con trỏ (pointer).
Const pointer:
Cho đoạn code ví dụ sau:
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
    const int x = 5; 
    int y = 5;
 
    const int *px = &x; //(1) similar: int const *px = &x;
//    int* ppx = &x;
    int *py = &y; // (2)
    const int* ppy = &y;
    const int* const cpx = &x;
    int * const cpy = &y;
    //++x;
    ++y;
    ++px;
    ++py;
    //++cpx;
    //++cpy;
    //++(*ppy);
    //++y;
    return 0;    
}

Có một số điều cần nói ở code:


  • x  là một hằng, không thể thay đổi giá trị của nó được.

  • y là một biến.

  • px là biến con trỏ, nó trỏ đến hằng x và hằng này có kiểu là int nên kiểu dữ liệu của nó là const int* (int const* tương đương).

  • py : biến con trỏ, trỏ đến kiểu int.

  • cpx: hằng con trỏ, trỏ đến đến kiểu "const int".

  • cpy: hằng con trỏ, trỏ đến kiểu "int".

Vậy nên khi viết "int* px = &x" sẽ bị báo lỗi không thể chuyển (converion) được.

Còn nếu viết const int* ppy=&y thì sao?

Ở đây y vẫn là biến, tuy nhiên, ppy sẽ là biến con trỏ, nhưng (*ppy) lại là hằng. Thành ra ++ppy thì ok nhưng ++(*ppy) bị báo lỗi.

Điều cần lưu ý ở đây là xác định rõ, đâu là con trỏ hằng, và đâu là hằng con trỏ.

Chuỗi ký tự: Đâu là sự khác nhau giữa 2 dòng code sau:

char* ch = "Hello"; //(1)

char chs[] = "Hello"; //(2)

Ở (1), ch là một biến con trỏ kiểu cha, và nó có giá trị là địa chỉ đầu tiên của "Hello" (tức là 'H') và "Hello" là một chuỗi hằng, không thể thay đổi được.

chs[] lại khác, nó là một biến chuỗi kí tự và có thể thay đổi giá trị các thành phần trong đó được.

Hàm hằng:

Công thức:

<return-value> <class>::<member-function>(<args>) const

{

        // ...

}



Một số lưu ý:
  • Hàm này phải là một method trong class.
  • Không sử dụng với các phương thức tạo lập (constructor) và phương thức hủy (destructor)
  • Hàm này chỉ đọc (read-only): hàm không thể thay đổi giá trị mà đối tượng nó gọi đến.
  • Mọi đối tượng (non-const hay const) đều có thể gọi hàm này.
  • Đối tượng hằng (const obj) chỉ có thể gọi được các hàm hằng.

Ví dụ:

#include <iostream>
class Num{
private:
    int a;
 
public: 
    Num(int X){
        a = X;
    };
    int GetNum() const { return a;}
    void SetNum(int A) {this->a = A;}
};
int main() {
    Num num1(1);
    const Num num2(1);
    num1.SetNum(2);
    std::cout<< num1.GetNum()<< std::endl;    
    std::cout << num2.GetNum() << std::endl;
    num2.SetNum(4);
 
    return 0;
}