1. Thành viên công khai (Public members) và thành viên riêng tư (Private members)

Cùng xem struct 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/
*/

struct DateStruct // members are public by default
{
    int month; // public by default, can be accessed by anyone
    int day; // public by default, can be accessed by anyone
    int year; // public by default, can be accessed by anyone
};
 
int main()
{
    DateStruct date;
    date.month = 10;
    date.day = 14;
    date.year= 2020;
 
    return 0;
}

Trong đoạn chương trình này, chúng ta đã khai báo một struct tên là DateStruct và sau đó chúng ta truy cập trực tiếp vào các biến thành viên của nó để khởi tạo chúng. Sẽ không có lỗi nào xảy ra vì tất cả các thành viên (bao gồm biến thành viên và hàm thành viên) của một struct đều có chỉ định phạm vi truy cập là Public (công khai) theo mặc định. Thành viên công khai (Public members) là những thành viên của một struct hoặc class mà có thể được truy cập từ bên ngoài struct hoặc class. Trong ví dụ vừa xét, hàm main() nằm bên ngoài struct, nhưng nó có thể truy cập trực tiếp đến các (biến) thành viên như month, day và year, bởi vì các (biến) thành viên này đều có chỉ định phạm vi truy cập là Public (công khai) theo mặc định.

Mặt khác, hãy xem phiên bản sử dụng class của ví dụ trên:

/**
* 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/
*/

class DateClass // members are private by default
{
    int m_month; // private by default, can only be accessed by other members
    int m_day; // private by default, can only be accessed by other members
    int m_year; // private by default, can only be accessed by other members
};
 
int main()
{
    DateClass date;
    date.m_month = 10; // error
    date.m_day = 14; // error
    date.m_year = 2020; // error
 
    return 0;
}

Nếu biện dịch đoạn chương trình này, bạn sẽ nhận được thông báo lỗi. Điều này là do theo mặc định, tất cả các thành viên của một class đều có chỉ định phạm vi truy cập là Private (bí mật). Các thành viên riêng tư (Private members) là các thành viên của một class mà chỉ có thể được truy cập bởi các thành viên khác của class này. Vì hàm main() không phải là một (hàm) thành viên của class DateClass, nên nó sẽ không có quyền truy cập vào các thành viên riêng tư của class này.

2. Phạm vi truy cập

Mặc dù các thành viên của class đều được thiết lập phạm vi truy cập là private (riêng tư) theo mặc định, chúng ta có thể thay đổi thành public (công khai) bằng cách sử dụng từ khóa public:

/**
* 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/
*/

class DateClass
{
public: // note use of public keyword here, and the colon
    int m_month; // public, can be accessed by anyone
    int m_day; // public, can be accessed by anyone
    int m_year; // public, can be accessed by anyone
};
 
int main()
{
    DateClass date;
    date.m_month = 10; // okay because m_month is public
    date.m_day = 14;  // okay because m_day is public
    date.m_year = 2020;  // okay because m_year is public
 
    return 0;
}

Bởi vì lúc này các thành viên của class DateClass đều có chỉ định phạm vi truy cập là public, chúng đã có thể được truy cập  trực tiếp bởi hàm main().

Từ khóa public, theo sau bởi dấu hai chấm, được gọi là một chỉ định phạm vi truy cập (access specifier). Các chỉ định phạm vi truy cập sẽ xác định ai có quyền truy cập vào các thành viên đang áp dụng chi định phạm vi truy cập này. Mỗi thành viên trong class đều sẽ có được cấp độ truy cập của chỉ định phạm vi truy cập được định nghĩa ngay trước các thành viên này (hoặc là, nếu không có chỉ định phạm vi truy cập nào được xác định, chỉ định phạm vi truy cập mặc định sẽ được áp dụng).

C++ cung cấp 3 từ khóa chỉ định phạm vi truy cập là: public, private, và protected. Public và private được sử dụng để làm cho các thành viên mà tuân của chúng tương ứng sẽ trở thành các thành viên công khai hoặc thành viên riêng tư. Chỉ định phạm vi truy cập thứ ba là protected, hoạt động rất giống với private. Chúng ta sẽ thảo luận về sự khác nhau giữa private và protected trong bài viết về thừa kế trong lập trình hướng đối tượng.

3. Kết hợp các chỉ định phạm vi truy cập

Một class có thể (và hầu như luôn luôn) sử dụng nhiều chỉ định phạm vi truy cập để thiết lập các cấp độ truy cập cho từng thành viên của nó. Không có giới hạn nào về số lượng các chỉ định phạm vi truy cập mà bạn có thể sử dụng trong một class.

Thường thì các biến thành viên hay được gán cấp độ truy cập là private (riêng tư), còn các hàm thành viên thì hay được gán cấp độ truy cập public (công khai). Chúng ta sẽ xem xét kỹ hơn tại sao lại như vậy trong bài học tiếp theo.

Quy tắc: Hãy thiếp lập cấp độ truy cập private (riêng tư) cho các biến thành viên, và cấp độ truy cập public (công khai) cho các hàm thành viên, trừ khi bạn tìm được một lý do chính đáng để không làm vậy.

Cùng xem một ví dụ về class sử dụng cả cấp độ truy cập private và public:

#include <iostream>
 
class DateClass // members are private by default
{
    int m_month; // private by default, can only be accessed by other members
    int m_day; // private by default, can only be accessed by other members
    int m_year; // private by default, can only be accessed by other members
 
public:
    void setDate(int month, int day, int year) // public, can be accessed by anyone
    {
        // setDate() can access the private members of the class because it is a member of the class itself
        m_month = month;
        m_day = day;
        m_year = year;
    }
 
    void print() // public, can be accessed by anyone
    {
        std::cout << m_month << '/' << m_day << '/' << m_year;
    }
};
 
int main()
{
    DateClass date;
    date.setDate(10, 14, 2020); // okay, because setDate() is public
    date.print(); // okay, because print() is public
 
    return 0;
}

Kết quả in ra là:

10/14/2020

Lưu ý rằng, mặc dù chúng ta không thể truy cập trực tiếp tới các biến thành viên m_month, m_day và m_year của class DateClass (bởi vì chúng là private – riêng tư), nhưng chúng ta vẫn có thể truy cập chúng một cách gián tiếp thông qua các hàm thành viên có cấp độ truy cập là public – công khai như setDate() và print()!

Nhóm các thành viên có cấp độ truy cập public của một class thường được gọi là một public interface. Bởi vì chỉ có các thành viên public mới có thể được truy cập từ bên ngoài của class, public interface sẽ định nghĩa cách mà chương trình đang sử dụng class sẽ tương tác thế nào với class này. Lưu ý rằng, hàm main() đã bị giới hạn để chỉ có thể thiết lập được các giá trị biến thành viên cho một đối tượng thuộc kiểu clsas DateClass, và in ra đối tượng này. Ở đây, class DateClass sẽ bảo vệ các biến thành viên của nó khỏi việc bị truy cập hoặc chỉnh sửa trực tiếp.

Một số lập trình viên thích liệt kê các thành viên private trước, bởi vì các thành viên public thường sẽ cần sử dụng tới các thành viên private này, vì vậy sẽ rất hợp lý khi định nghĩa các thành viên private trước. Tuy nhiên, có người lại phản biện rằng, những người dùng của class sẽ không quan tâm đến các thành viên private, vì vậy các thành viên public nên được liệt kê để khai báo trước. Nói chung, cách nào cũng đều ổn.

4. Kiểm soát hoạt động truy cập trên từng class

Xét đoạn chương trình 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>
 
class DateClass // members are private by default
{
	int m_month; // private by default, can only be accessed by other members
	int m_day; // private by default, can only be accessed by other members
	int m_year; // private by default, can only be accessed by other members
 
public:
	void setDate(int month, int day, int year)
	{
		m_month = month;
		m_day = day;
		m_year = year;
	}
 
	void print()
	{
		std::cout << m_month << '/' << m_day << '/' << m_year;
	}
 
	// Note the addition of this function
	void copyFrom(const DateClass &d)
	{
		// Note that we can access the private members of d directly
		m_month = d.m_month;
		m_day = d.m_day;
		m_year = d.m_year;
	}
};
 
int main()
{
	DateClass date;
	date.setDate(10, 14, 2020); // okay, because setDate() is public
	
	DateClass copy;
	copy.copyFrom(date); // okay, because copyFrom() is public
	copy.print();
 
	return 0;
}

Có một nguyên tắc trong C++ thường bị bỏ qua hoặc hiểu sai, đó là : Kiểm soát hoạt động truy cập trên cơ sở từng class chứ không phải trên cơ sở từng đối tượng. Điều này có nghĩa là khi một hàm có quyền truy cập vào các thành viên private của một class, thì nó cũng có thể truy cập vào các thành viên private của bất kỳ đối tượng nào của kiểu class đó, mà nó có thể nhìn thấy.

Trong ví dụ trên, copyFrom() là một hàm thành viên của class DateClass, điều này cho phép nó truy cập được vào các thành viên private của class DateClass. Điều này có nghĩa là hàm copyFrom() không chỉ có thể truy cập được trực tiếp vào các thành viên private của đối tượng ngầm (implicit object) nó đang làm việc cùng (chính là đối tượng copy), mà nó còn có quyền truy cập trực tiếp vào các thành viên private của biến tham số d thuộc kiểu DateClass! Trong trường hợp biến tham số d thuộc kiểu dữ liệu khác thì điều này sẽ không xảy ra.

Điều này vô cùng hữu ích khi chúng ta cần sao chép các thành viên từ một đối tượng của một class sang một đối tượng khác cũng cùng class. Chúng ta sẽ nhắc lại về chủ để này khi tìm hiểu về toán tử overloading (nạp chồng hàm) << để in ra các thành viên của một class, trong chương tiếp theo.

5. Nói thêm về struct và class

Tính đến thời điểm hiện tại, chúng ta đã nói về các chỉ định phạm vi truy cập (access specifiers), chúng ta có thể nói về những sự khác biệt thực sự giữa class và struct trong C++. Một class sẽ mặc định thiết lập cấp độ kiểm soát truy cập là private (riêng tư) cho các thành viên của nó. Còn struct sẽ mặc định thiết lập cấp độ kiểm soát truy cập là public (công khai) cho các thành viên của nó.

(Ngoài ra, còn có thêm một sự khác biệt nhỏ nữa – struct kế thừa từ các class khác một cách công khai, còn class thì kế thừa từ các class khác một cách bí mật. Chúng ta sẽ thảo luận về ý nghĩa của điều này trong một chương khác, nhưng trên thực tế, điểm vụn vặt này này thường không quan trọng cho lắm bởi vì dù sao đi nữa bạn cũng không bao giờ nên chỉ dựa vào các cấp độ kiểm soát truy cập mặc định).

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!