Thao Tác Với Constant trong C++ — Modern C++

Mở đầu

const (Constant – Hằng) là từ khóa để chỉ định một biến hay đối tượng nào đó là hằng, tức là không thay đổi được giá trị, mọi hành động thay đổi giá trị của hằng đều được compiler báo lỗi.

Sử dụng biến hằng khi muốn giá trị của biến, đối tượng đó không bị thay đổi trong mọi trường hợp, ví dụ như số PI luôn bằng 3.14.

Cơ bản

Bắt đầu với những khai báo đơn giản nhất:

int main()
{
	const int x = 10;
	int const y = 50;

	x = 5;	// Báo lỗi
	y = 7;	// Báo lỗi

	return 0;
}

Vị trí đặt const dù trước hay sau int thì đều có chung một chức năng là chỉ định x, y là hằng, thay đổi giá trị của 2 biến đó sẽ bị báo lỗi.

Biến tham chiếu

Biến tham chiếu là biến có thể thay đổi giá trị của vùng nhớ mà nó tham chiếu tới một cách trực tiếp. Trong trường hợp biến tham chiếu là hằng thì sao? Xét ví dụ sau:

int main()
{
	int x = 10;
	const int &A = x;	// Khai báo A là hằng tham chiếu tới vùng nhớ của x

	A = 6;	// Báo lỗi do A là biến tham chiếu hằng
	x = 6;	// Không báo lỗi do x là một biến bình thường

	return 0;
}

Hằng tham chiếu A sẽ không thể thay đổi giá trị của x được nữa, cũng có thể khai báo int const &A = x;

Biến con trỏ

Biến con trỏ là biến lưu địa chỉ vùng nhớ mà nó trỏ tới. Khi biến con trỏ có thêm từ khóa const thì chuyện gì sẽ xảy ra? Xét ví dụ sau:

int main()
{
	int x = 7;
	int y = 13;

	const int *p = &x;	// Đọc là Non-const pointer to a const value

	*p = 100;	// Báo lỗi
	p = &y;	// Không báo lỗi

	return 0;
}

Không thể thay đổi giá trị vùng nhớ mà p chỉ tới thông qua p,  nhưng vẫn có thể thay đổi được địa chỉ vùng nhớ mà p trỏ tới.

Xét ví dụ tiếp theo:

int main()
{
	int x = 7;
	int y = 13;

	int *const p = &x;	// Đọc là Const pointer to a non-const value

	*p = 100;	// Không báo lỗi
	p = &y;	// Báo lỗi

	return 0;
}

p đã có thể thay đổi giá trị vùng nhớ mà nó trỏ tới, nhưng không thể thay đổi địa chỉ mà nó trỏ tới được nữa.

Nếu muốn con trỏ p vừa không thể thay đổi địa chỉ của vùng nhớ, vừa không thể thay đổi giá trị vùng nhớ đó thì sao?

const int *const p = &x;    // Đọc là Constant pointer to a const value

Tham số hàm là const reference

Khi xây dựng một hàm, đôi lúc hay sử dụng tham số truyền vào là tham chiếu do tác dụng tiết kiệm chi phí của nó, một ưu điểm nữa của nó là có thể thay đổi được giá trị của tham số truyền vào, nhưng đôi khi lại không mong muốn có bất kì sự thay đổi nào. Khi muốn sử dụng tham chiếu để vừa tiết kiệm chi phí, vừa ngăn được tình trạng thay đổi giá trị thì sẽ dùng tới tham số const reference. Cách dùng như sau:

int func(const int &number)
{
	//Your Codes
}

Constant trong lập trình hướng đối tượng

Sử dụng lớp sau làm tiền đề để giải thích:

class MyClass
{
private:
	int m_Data;

public:
	MyClass(int Data) : m_Data(Data) {}

	// Message() Function for non-const object
	void Message()
	{
		cout << "This is non-const object" << endl;
	}

	// Message() Function for const object
	void Message() const
	{
		cout << "This is const object" << endl;
	}

	// Get value of m_Data to use or modify
	int &getData()
	{
		return m_Data;
	}
};

Phương thức Message() thứ 2 có một từ khóa const ở cuối, nghĩa là Message() đó là một phương thức hằng thành viên. Khi một phương thức là phương thức hằng, thì tất cả các thuộc tính (biến thành viên) của lớp đó sẽ không được thay đổi giá trị trong quá trình hiện thực hàm, mọi hành động thay đổi giá trị của thuộc tính trong hàm compiler đều báo lỗi, trừ khi thuộc tính đó được khai báo với từ khóa mutable đằng trước. Hơn nữa, đối với các đối tượng hằng, thì đối tượng hằng chỉ sử dụng được các phương thức hằng.

Có hàm main như sau:

int main()
{
	MyClass obj1(13);
	const MyClass obj2(100);

	obj1.getData() = 86;
	//obj2.getData() = 1;	//báo lỗi do getData() không phải phương thức hằng

	obj1.Message();
	obj2.Message();

	return 0;
}

Khi chương trình được thực thi, kết quả in ra màn hình như sau:

This is non-const object

This is const object

Như vậy đối tượng obj1 đã gọi phương thức Message() thứ nhất, còn obj2 gọi phương thức Message() thứ 2. Lý do đơn giản: obj1 là đối tượng bình thường, còn obj2 là đối tượng hằng nên chỉ có thể gọi hàm Message() thứ 2.

Lưu ý: Phương thức hằng có thể luôn được gọi. Tức là obj1 vẫn có thể gọi được phương thức Message() thứ 2, khi phương thức Message() thứ 1 bị xóa đi, thì kết quả in ra màn hình sẽ là:

This is const object
This is const object

Một vấn đề nữa, thấy obj2 không thể dùng phương thức getData(). Vậy nếu muốn dùng thì sao? Theo như nãy giờ đã nói thì chỉ cần thêm const vào sau getData() là xong. Nhưng do getData() trả về tham chiếu, nên giá trị nó trả về phải là 1 Lvalue có thể thay đổi giá trị, nên nếu chỉ thêm const vào sau thì nhất định sẽ có lỗi. Hãy overload lại 1 phương thức getData() như sau:

const int &getData() const
{
	return m_Data;
}

Như vậy có thể dùng obj2 để gọi getData() được, nhưng do trả về hằng tham chiếu nên không thể dùng getData() để thay đổi giá trị của m_Data giống như đối với obj1 được.

Const Cast

Xét hàm sau:

int func(int &number)
{
	number += 100;
	return number;
}

Khi truyền một biến int vào hàm func(), thì hàm sẽ tăng giá trị của số đó thêm 100, đồng thời trả về giá trị đó. Nhưng trong trường hợp có một biến const, muốn sử dụng hàm func() để trả về số lớn hơn 100, nhưng do biến đó là const nên khi truyền vào compiler sẽ báo lỗi? Dùng toán tử const_cast để giải quyết như sau:

int main()
{
	const int x = 13;
	int y;

	//y = func(x);	// Báo lỗi

	y = func(const_cast<int &>(x));	// Dùng const_cast

	printf("y = %d\n", y);
	printf("x = %d\n", x);

	return 0;
}

Kết quả xuất ra màn hình như sau:

y = 113;
x = 13;

Như vậy là vẫn có giá trị của y như mong muốn và giá trị của x vẫn giữ nguyên, đã có thể sử dụng hàm func().