Chúng ta cùng xem đoạn chương trình ngắn sau:
/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
#include <iostream>
enum FruitType
{
APPLE,
BANANA,
CHERRY
};
class Fruit
{
private:
FruitType m_type;
int m_percentageEaten = 0;
public:
Fruit(FruitType type) :
m_type(type)
{
}
FruitType getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
};
int main()
{
Fruit apple(APPLE);
if (apple.getType() == APPLE)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}
Đoạn chương trình này hoàn toàn đúng. Tuy nhiên, enum FruitType nên được sử dụng kết hợp với class Fruit, vì vậy việc để cho enum này tồn tại độc lập với class Fruit trông rất không hợp lý.
Nội dung chính
1. Các kiểu dữ liệu lồng nhau
Không giống như đối với các hàm (không thể được lồng bên trong nhau), trong C++, các kiểu dữ liệu có thể được định nghĩa (lồng) bên trong một class. Để làm được điều này, bạn chỉ cần định nghĩa kiểu dữ liệu mong muốn ở bên trong class, và chỉ định mức kiểm soát truy cập phù hợp cho nó (public, protected, private).
Ta sẽ sửa lại ví dụ đầu tiên một chút, bằng cách định nghĩa enum FruitType ở bên trong class Fruit:
/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
#include <iostream>
class Fruit
{
public:
// Note: we've moved FruitType inside the class, under the public access specifier
enum FruitType
{
APPLE,
BANANA,
CHERRY
};
private:
FruitType m_type;
int m_percentageEaten = 0;
public:
Fruit(FruitType type) :
m_type(type)
{
}
FruitType getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
};
int main()
{
// Note: we access the FruitType via Fruit now
Fruit apple(Fruit::APPLE);
if (apple.getType() == Fruit::APPLE)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}
Đầu tiên, cần lưu ý rằng FruitType bây giờ đã được định nghĩa ở bên trong class Fruit. Thứ hai, chúng ta đã chỉ định cho nó mức kiểm soát truy cập là public, do đó phần code định nghĩa kiểu dữ liệu này (ý là cái enum FruitType) có thể được truy cập từ bên ngoài class Fruit.
Các class về cơ bản hoạt động giống như một namespace dành cho các kiểu dữ liệu lồng (nested type). Trong ví dụ trước, chúng ta có thể truy cập trực tiếp tới giá trị enum là APPLE (hay còn gọi là enumerator APPLE), bởi vì giá trị này được đặt trong phạm vi toàn cục – global scope (chúng ta có thể hạn chế điều này bằng cách sử dụng một enum class (tức là enum nằm ở bên trong một class) thay vì một enum, khi đó chúng ta sẽ có thể có truy cập tới APPLE thông qua cú pháp FruitType::APPLE). Bây giờ, bởi vì FruitType được coi là một phần của class Fruit, nên chúng ta có thể truy cập tới giá trị enum APPLE bằng cách sử dụng tiền tố là tên của class mà nó đang nằm trong, tức là Fruit::APPLE.
Lưu ý rằng, bởi vì các enum class (enum nằm ở bên trong một class thì gọi là enum class) cũng hoạt động giống như các namespace, nên nếu chúng ta đã lồng enum FruiTtype vào bên trong class Fruit, thì lúc này FruitType sẽ trở thành một enum class thay vì một enum bình thường. Do đó, chúng ta sẽ truy cập tới giá trị enum APPLE thông qua cú pháp Fruit::FruitType:APPLE.
2. Các kiểu dữ liệu khác cũng có thể được lồng
Mặc dù enum (kiểu dữ liệu liệt kê) có lẽ là kiểu dữ liệu phổ biến nhất được lồng bên trong một class, tuy nhiên C++ cũng cho phép bạn định nghĩa các kiểu dữ liệu dữ liệu khác bên trong phạm vi một class cụ thể, bằng cách sử dụng typedefs và type alias, thậm chí bạn có thể lồng cả các class vào bên trong nhau.
Giống như bất kỳ thành viên bình thường nào của class, các class lồng (nested class) cũng có quyền truy cập tới tất cả các thành viên của class hiện tại mà nó đang nằm trong. Tuy nhiên, class lồng không thể sử dụng được con trỏ *this của class hiện tại mà nó đang nằm trong.
Một hạn chế khác nữa của các kiểu dữ liệu lồng là chúng không thể được forward declared – tức là chúng không thể được khai báo trước. Giới hạn này có thể được gỡ bỏ trong một phiên bản tương lai của C++.
Việc định nghĩa các class lồng thường không phổ biến, nhưng thư viện standard của C++ thực sự có áp dụng điều này trong một số trường hợp, chẳng hạn như với các iterator class. Chúng ta sẽ tìm hiểu về các iterators trong một bài học tương lai.