Ứng dụng namespace trong lập trình C++ (via BT Assignment 2 – FPT University)

Ứng dụng namespace trong lập trình C++
Ứng dụng namespace trong lập trình C++

Namespace không phải là một khái niệm khai sinh cùng thời với C++. Tuy nhiên, trong nỗ lực nhằm “quy hoạch” lại ngôn ngữ C++ làm cho nó an toàn hơn, các nhà phát triển đã đưa vào khái niệm namespace mà sau này vẫn còn tồn tại tới các ngôn ngữ hiện đại như Java, C#, … Bài viết đề cập tới vấn đề ứng dụng namespace trong lập trình C++.

Khái niệm namespace

Namespace là một “khối mã” (block) như bao khối mã khác, chỉ khác một điều rằng nó có một nhãn hay tên gọn riêng. Trong khối mã namespace, bạn có thể khai báo bất cứ thứ gì, từ biến, hàm, lớp, hằng… tất cả mọi thứ. Tuy nhiên, nếu bạn muốn gọi một hàm, một lớp, một cái gì đó được khai báo, định nghĩa trong một namespace, bạn phải dùng toán tử phân giải phạm vi. Ví dụ tôi có một namespace sau:

namespace myN
{
     int myVar = 5;
}

Để truy xuất biến myVar được khai báo bên trong namespace myN, bạn phải dùng toán tử phân giải phạm vi:

myN::myVar = 10;

Ý tưởng của namespace là: nếu bạn có 2 namespace và mỗi namespace có 2 biết toàn cục có tên giông nhau, tức là bạn đã có hai phiên bản của biến trên. Hoặc, nếu bạn có 2 namespace và bạn có 2 hàm có cùng kiểu trả về, cùng tên, cùng số lượng tham số và kiểu của tham số tương đồng nhau, bạn đã có 2 phiên bản của cùng một hàm…

Nhìn chung, mục tiêu của namespace là giúp chúng ta truy cập các biến toàn cục một cách an toàn hơn. Rộng hơn nữa, namespace giúp ta sắp xếp, phân chia module/mã nguồn ra một cách hợp lý bởi nhiều cấp độ khác nhau.

Nếu bạn muốn truy cập vào các thành viên của một namespace mà không cần phải dùng toán tử phân giải phảm vi và tên namespace, các bạn có thể dùng lệnh using namespace:

// still use the defination of namespace myN above
using namespace myN;

myVar = 15;

Ta thường dùng câu lệnh “using namespace std;” ở đầu các file .cpp để khi cầu truy cập đối tượng cout, cin để nhập xuất dữ liệu mà không cần dùng tới “std::”. Ví dụ: std::cout << “abc”;

Lưu ý nhỏ khi sử dụng lệnh “using namespace …”, việc sử dụng using namespace chỉ có hiệu lực trong nội bộ một file .cpp đó. Hoặc, nếu một file .cpp include một file .h có lệnh “using namespace”, file .cpp có thể không cần sử dụng lệnh “using namespace”. Tuy nhiên, theo tôi, bất cứ file nào sử dụng thành phần thuộc namespace nào đó mà không muốn dùng toán tử phân giải phạm vi, thì nên khai báo tường minh câu lệnh “using namespace” bất kể nó có include file .h đã có câu lệnh đó rồi.

Nên cẩn thật nêu bạn “using namespace” cả hai namespace có những đối tượng giống nhau về phần chữ ký. Ví dụ:

// test.h
namespace A {
 int a = 10;
}

namespace B {
 int a = 20;
}

Ta có file main.cpp sau:

#include <iostream>
#include "test.h"

using namespace std;
using namespace A;
using namespace B;

int main()
{
   cout << a;

   return 0;
}

Khi biên dịch chương trình trên, chắc chắn gây ra lỗi vì ta đã truy cập tới một biến “a” bất định vì có hai chữ ký “int a” ở cả hai vùng namespace là A và B. Trình biên dịch sẽ không tài nào biết bạn muốn gọi biên a nào. Nếu bạn sửa lại dòng “cout << a;” thành:

cout << A::a; // hoặc cout << B::b;

NOTE: Nếu có hai thành phần thuộc hai namespace có cùng chữ ký định nghĩa, bạn LUÔN LUÔN phải sử dụng tường minh tên namespace và toán tử phân giải phạm vi khi bạn sử dụng lệnh using namespace cho cả hai namespace.

Sau đây chúng ta sẽ làm một bài tập nho nhỏ thuộc khóa học “Mô thức hướng đối tượng với ngôn ngữ C++” trong chương trình đào tạo của Đại học FPT mà tôi đã áp dụng khái niệm namespace để phân hóa các module trong mã nguồn của mình.

Đề bài tập

Bạn hãy viết một lớp Product là abstract class có các thuộc tính sau: code: char [7], name: string , price: int (price >0);

Sau đó, hãy viết hai lớp kế thừa từ lớp product:

electricalProduct [working voltage(int 100 …220), guarantee (int 1..24)]

porcelain, đồ sành sứ, [type ( “glass”, “crystal”, “soil”)]

Xây dựng một chương trình có các tính năng sau:

Product manager
1- Add an electric product
2- Add a porcelain
2- Find a product based it’s name
3- Remove a product based it’s name
4- List
Other: Quit

Thiết kế chương trình

Theo nhãn quan của tôi, tôi sẽ tạo ra một namespace là “products”, namespace này sẽ chứa các lớp “product”, “electricalProduct”, và “porcelain”.

Tôi cũng sẽ tạo một namespace tên là funcs_lib để chứa các hàm linh tinh mà các namespace khác của chương trình cần sử dụng chung. Thật ra tôi có một namespace funcs_lib chứa nhiều hàm linh tinh mà tôi thường hay xài khi code C++.

Ngoài ra, hơi lạ lẫm, tôi quyết định “code C++ theo style Java”. Tôi sẽ tạo một namespace tên là “Assignment2”, bên trong namespace Assignment 1, tôi có viết một lớp là “main_program” có một hàm static là Main(). Nói nôm na nó là như thế này ở file “main.cpp”:

namespace Assignment2 {
   class main_program
   {
   public: 
       static int Main(int argc, _TCHAR* argv[]) {
            return 0;
       }
}

int main(int argc, _TCHAR* argv[]) {
    return Assignment2::main_program::Main(argc, argv);
}

Nói chung, bạn cứ cho là tôi rảnh đi. Thật sự việc chia một namespace lớn chứa các hàm chính của chương trình rất có lợi trong nhiều trường hợp. Đó là lý do vì sao mà Java ngày nay lại thành công đến như vậy: kế thừa mọi ưu điểm của C++, xóa bỏ mọi bất cập của C++, mở rộng khả năng ưu việt mà C++ không có.

Bây giờ tôi sẽ tạo file product.h để chứa abstract class “product”, file có nội dung như sau:

#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include "funcs.h"
using namespace std;

namespace products {
 
 class product
 {
 public:
 // product(void);
 virtual ~product(void);
 private:
 char *code;
 string name;
 int price;
 public:
 product(const char * code=NULL, const string name="", int price=0);
 product(product & input);
 void operator =(product & input);

 const char * getCode(void);
 const string getName(void);
 int getPrice(void) const;
 
 void setCode(const char * code);
 void setName(const string name);
 void setPrice(int price);

 virtual bool isValid();
 private:
 void initProduct(void);
 public:
 virtual void print(void) = 0;

 // its friends:
 friend istream & operator >> (istream & theStream , product & obj);
 friend ostream & operator << (ostream & theStream , const product & obj);
 };

 istream & operator >> (istream & theStream , product & obj);
 ostream & operator << (ostream & theStream , const product & obj);

}

File chứa các phần định nghĩa, bây giờ tôi sẽ tạo file product.cpp để viết code thực thi cho từng hàm:

#include "product.h"
using namespace std;
using namespace funcs_lib;

namespace products {
...
}
// nội dung file này bạn vui lòng xem ở mục download mã nguồn bên dưới.

Tiếp theo, tôi sẽ tạo file “electricalProduct.h” để định nghĩa lớp con electricalPRoduct:

#pragma once
#include "product.h"
#include <iostream>
#include <string>
#include "funcs.h"
using namespace std;
using namespace funcs_lib;

namespace products
{
 
 class electricalProduct :
 public product
 {
 public:
 electricalProduct(const char * code=NULL, const string name="", int price=0, int voltage=0, int guarantee=0);
 ~electricalProduct(void);
 private:
 int voltage;
 int guarantee;
 public:
 electricalProduct(electricalProduct & input);
 void operator =(electricalProduct & input);

 int getVoltage() const;
 int getGuarantee() const;

 void setVoltage(int vol);
 void setGuarantee(int gua);

 virtual void print();
 virtual bool isValid();

 // its friends
 friend istream & operator >> (istream & theStream, electricalProduct & obj);
 friend ostream & operator << (ostream & theStream, const electricalProduct & obj);

 
 };

 istream & operator >> (istream & theStream, electricalProduct & obj);
 ostream & operator << (ostream & theStream, const electricalProduct & obj);


}

Phần định nghĩa code thực thi của các hàm trong “electricalProduct.cpp” các bạn vui lòng đọc trong file dính kèm.

Tương tự, ta cũng sẽ làm với “porcelain.h”:

#pragma once
#include "product.h"
#include <string>
using namespace std;

namespace products
{
 
 enum P_TYPE { GLASS, CRYSTAL, SOIL };
 P_TYPE parse_P_TYPE(const string s);
 const string P_TYPE_toString(P_TYPE type);

 class porcelain :
 public product
 {
 private:
 P_TYPE type;
 public:
 ~porcelain(void);
 porcelain(const char * code=NULL, const string name="", int price=0, P_TYPE type=GLASS);
 porcelain(const string type, const char * code=NULL, const string name="", int price=0);
 porcelain(porcelain & input);
 void operator = (porcelain & input);

 P_TYPE getType() const;
 const string getType(int s);

 void setType(const string type);
 void setType(P_TYPE type);

 virtual void print();
 virtual bool isValid();

 // its friend
 friend istream & operator >> (istream & theStream, porcelain & obj);
 friend ostream & operator << (ostream & theStream, porcelain & obj);

 };

 istream & operator >> (istream & theStream, porcelain & obj);
 ostream & operator << (ostream & theStream, porcelain & obj);
}

Có một điều dễ nhận thấy rằng, dù chúng ta đặt namespace ở nhiều file khác nhau, nhưng chúng vẫn được xem là chung một namespace. Và chỉ cần include các file thích hợp, bạn vẫn có thể sử dụng các thành phần của namespace được chứa trong một file khác hoàn toàn bình thường mà không cần dùng tên namespace và toán tử phân giải phảm vi.

note: Nếu có hai file có khai báo của hai namespace có cùng một tên, chúng được xem là MỘT NAMESPACE DUY NHẤT”.

Điều này có nghĩa là, bạn hoàn toàn có thẻ mở rộng một namespace đã có bằng cách tạo thêm một file nào đó,  thêm phần định nghĩ mới của namespace vào file của bạn, include các file chứa namespace đã có vào file mới của bạn, và include file của bạn mới tạo vào nơi cần sử dụng.

Bây giờ sẽ là file “funcs.h” để chứa các hàm thư viện “made by tui” của tôi:

#pragma once
#include <iostream>
using namespace std;

namespace funcs_lib
{

#define MAX_LENGTH 30

 class funcs
 {
 public:
 static void clearBuffer(istream & theStream);
 static void toUpper(char str[MAX_LENGTH]);
 static const string toUpper(const string &str_in); 
 };

}

Như tôi đã nói, bạn hoàn toàn có thể code theo style của Java với C++, chẳng ai cấm bạn làm điều đó cả! Tôi tạo một lớp funcs chứa các hàm static mà tôi sẽ gọi ở nơi mà tôi muốn sử dụng chúng. Hàm clearBuffer là hàm tôi ưa thích sử dụng để làm rỗng buffer tránh trường hợp người dùng chương trình nhập vào giá trị lỗi làm hỏng chương trình.

Để gọi một hàm của lớp funcs, ví dụ clearBuffer, bạn phải làm như sau:

funcs_lib::funcs::clearBuffer(std::cin);
// HOẶC:
using namespace funcs_lib;
funcs::clearBuffer(std::cin);

Tiếp theo, tôi sẽ include nhưng thứ cần thiết vào file main.cpp chứa chương trình chính:

#include <iostream>
#include <stdlib.h>
#include "funcs.h"
#include "product.h"
#include "electricalProduct.h"

using namespace std;
using namespace funcs_lib;
using namespace products;
...

Nói chung là include những file cần thiết vào và khai báo thêm việc sử dụng các namespace std, funcs_lib, và products.

Trong lớp main_program, tôi sẽ viết thêm một hàm có thuộc tính protected để tạo ra menu cho chương trình:

class main_program
{
    ....
  protected:
     static int menu() {
         cout << "*** Product manager ***" << endl;
         cout << "1- Add electrical product" << endl;
         cout << "2- Add porcelain" << endl;
         cout << "3- Find a product based it's name" << endl;
         cout << "4- Remove a product based it's name" << endl;
         cout << "5- List" << endl;
         cout << "Other: Quit." << endl << endl;
         cout << "Your choice: ";
         int c = 0;
         cin >> c;
         funcs::clearBuffer(cin);
         return c;
     }
 }; // end of class main_program

Lúc này trong hàm main_program::Main, tôi nhận thấy rằng cần phải tạo ra hai mảng chứa danh sách của electrical product và porcelain.  Thay vì phải tạo hai mảng riêng biệt cho mỗi loại tôi sẽ làm như sau:

....
static int Main(int argc, _TCHAR* argv[])
 {
      product * List[2][MAX_ITMS];
      string codes[MAX_ITMS*2];
      int n1 = 0;
      int n2 = 0;

      return 0;
}
...

Tôi tạo ra một mảng con trỏ product * List, đây là một mảng hai chiều. Nếu là List[0][index] thì sẽ là một con trỏ trỏ tới một đối tượng electricalProduct, nếu là List[1][index] là một con trỏ trỏ tới một đối tượng porcelain.

Điều đó có nghĩa, List[0] là mảng chứa các electricalProduct, List[1] à mảng chứa các porcelain. Các biến n1 chứa số lượng các electricalProduct đang có, và n2 chứa số lượng các porcelain hiện có.

Tôi nghĩ cần có một hàm để gán cho từng con trỏ trong mảng List giá trị NULL để tránh gây các lỗi không đáng có:

class main_program {
 protected:
 static void initList(product * List[2][MAX_ITMS]){
    for(int i=0; i<MAX_ITMS; i++)
    {
           List[0][i] = NULL;
           List[1][i] = NULL;
    }
  }

}; // end of class main_program

Bây giờ ở hàm main tôi sẽ gọi hàm initList và hàm menu:

....
static int Main(int argc, _TCHAR* argv[])
 {
      product * List[2][MAX_ITMS];
      string codes[MAX_ITMS*2];
      int n1 = 0;
      int n2 = 0;

      int c = 0;
      do {
         c = menu();
         switch(c) {
            // CASE
         }
      } while(c>=1&&c<=5);

      return 0;
}
...

Nội dung hàm Main() trên bạn vui lòng xem trong tập tin dính kèm.

Tôi nhận ra cần có các hàm riêng biệt cho việc input. Do đó tôi tạo thêm một file “input.h” chứa class input để phục vụ việc input dữ liệu:

#pragma once
#include "stdafx.h"
#include "product.h"
#include "porcelain.h"
#include "electricalProduct.h"
using namespace products;

namespace Assignment2
{
 

 class input
 {
 public:
 static void ElectricalProduct(product * List[2][MAX_ITMS], string codes[MAX_ITMS*2], int & n1, int & n2);
 static void Porcelain(product * List[2][MAX_ITMS], string codes[MAX_ITMS*2], int & n1, int & n2);
 static bool isDupCode(const string codes[MAX_ITMS*2], const string code, int n);
 protected:
 static void Input(int type, product * List[2][MAX_ITMS], string codes[MAX_ITMS*2], int & n1, int & n2);
 };


}

Lớp input này vẫn thuộc namespace Assigment2 để hàm Main() dễ dàng gọi nó.

Tôi nhận ra cần thêm lớp “find” để phục vụ việc tìm kiếm các đối tượng trong danh sách. Tôi tạo file “find.h”:

#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include "stdafx.h"
#include "product.h"
#include "porcelain.h"
#include "electricalProduct.h"

using namespace std;
using namespace products;
using namespace funcs_lib;

namespace Assignment2
{
 class find
 {
 public:
 static int getCodeIndex(const string codes[MAX_ITMS*2], const string str, int n);

 static bool electricalByName(product * List[2][MAX_ITMS], int n1);
 static bool porcelainByName(product * List[2][MAX_ITMS], int n2);
 protected:
 static bool search(int type, product * List[2][MAX_ITMS], int n);
 

 };
}

Nhìn chung tương đối ổn, tôi include thêm hai file “find.h” và “input.h” vào file main.cpp:

#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include "funcs.h"
#include "product.h"
#include "electricalProduct.h"
#include "input.h"
#include "find.h"

using namespace std;
using namespace funcs_lib;
using namespace products;

namespace Assignment2 {
...

Sau đó chỉ cần viết thêm mã cho lớp main_program là được.

Download full mã nguồn

https://drive.google.com/file/d/0B4NFzq4Wpqd9V0ZYeVBYeWRnYm8/view?usp=sharing

 

 

Như vậy, hôm nay chúng ta đã thảo luận việc ứng dụng namespace trong lập trình C++ theo một cách “rất thú vị”, “code C++ theo style Java”. Namespace thật sự là một công cụ tuyệt vời để giúp bạn “quy hoạch” lại code C++ của mình, giúp các module của bạn sáng sủa hơn và dễ dàng làm việc hơn giữa các module. Quen sử dụng namespace trong C++ thì sẽ chẳng có gì khó khi bạn sử dụng namespace ở bất cứ ngôn ngữ nào khác.

One thought on “Ứng dụng namespace trong lập trình C++ (via BT Assignment 2 – FPT University)”

Gửi bình luận