Mặc định Cách sử dụng từ khóa const trong C++


A. NỘI DUNG:
I. Mở đầu
Như ta đã biết, trong các đời sống cũng như trong mọi lĩnh vực, luôn có các giá trị không thể thay đổi gọi là hằng số,
Vd: số π với 100 chữ sô thập phân:
3,141592653589793238462643383279502884197169399375 10582097494459230781640628620899862803482534211706 79
Vấn đề là trong tin học, cứ mỗi khi dùng đến giá trị π thì chả lẽ cứ viết hết tất cả các chử số???


II. Hằng
Nếu dùng một biến để lưu trữ giá trị của hằng số thì có một vấn đề là biến thì thay đổi được giá trị ==>lỗi.
VD:

float PI=3.14;
//////
PI=8;
//////

Đoạn mã trên không báo lỗi.
Loại lỗi này rất khó nhận thấy nhưng để lại hậu quả rất to lớn. nếu sau đó ta không chú ý cứ dùng biến đó trên các đoạn mã cần số π, vậy ta phải dùng hằng để khai báo cho giá trị này.
a. Khai báo hằng:
Có 2 cách khai báo hằng
– Dùng chỉ thị #define:

Code:
#define A "gia tri"

tác dụng: định nghĩa macro A đại diện cho giá trị dùng để thay thế A trong đoạn mã trừ những đoạn trong “” thành giá trị.
chỉ thị #define còn có nhiều tác dụng khác.
– Dùng từ khóa const:

Code:
const kiểu tên_hằng=giá_trí;
kiểu const tên_hằng=giá_trí;

VD:

const int x=8;

Tác dụng: Khai báo 1 hằng kiểu int tên x có giá trị bằng 8.
Sau khi khai báo thì ta không thể thay đổi x, ví dụ:

x=8; //lỗi

Câu lệnh trên báo lỗi vì ta cố gắng thay đỏi 1 hằng số.
III. Từ khóa const trong C++
Như ta đã biết ở trên, từ khóa const dùng để khai báo hằng:
const kiểu tên_hằng=giá_trí;
kiểu const tên_hằng=giá_trí;
VD:

const int x=8;
int const y=4;

Vấn đề ở đây là hai cách khai báo này có khác nhau không? Và từ khóa const còn tác dụng nào không?
Câu trả lời là có.
1. Từ khóa const với con trỏ
Đối với biến con trỏ ta nên phân biệt hai điều: vùng nhớ con trỏ trỏ đến và giá trị của vùng nhớ đó.
Vd:

int x = 10, y = 20;
const int *px = &x;

Khai báo như vậy, giá trị của vùng nhớ mà px đang trỏ đến là không thể thay đổi được thông qua thay đổi (*px). Do đó, ta có các câu lệnh sau:

*px = 15;// lỗi
px = &y;
x = 16;

Con trỏ này gọi là con trỏ hằng

Vì ta dùng *px để thay đỏi giá trị của vùng nhớ mà px đang trỏ đến.
Chú ý: Giá trị của x vẫn có thể được thay đổi, ta chỉ không thể thay đổi giá trị này thông qua px.
Nhưng với các khai báo sau:

int x = 10, y = 20;
int* const px = &x;

Thì px không thể thay đổi vùng nhớ đang trỏ đến, nhưng giá trị của vùng nhớ có thể thay đổi thông qua px

*px=y;
*px=&y;//lỗi

Con trỏ này gọi là hằng con trỏ

Với khai báo sau:

int x = 10;
const int* const px = &x;

Bạn không thể thay đổi nơi px đang trỏ đến và thông qua (*px) cũng không thể thay đổi giá trị vùng nhớ đó.
2. Từ khóa const với đối tượng
Giả sử ta có lớp với hàm tạo sau:

Code:

class A
{
int a;
public:
A(int t=0){a=t;}
//…
};

a. Hằng đối tượng
Để khai báo hằng đối tượng ta dùng từ khóa const như sau:

A const a(4);
const A b;

Ta không thể thay đổi giá trị các thuộc tính của hằng đối tượng, kể cả thông qua con trỏ.
VD: ta có hàm sau

void A::set(int x)
{
a=x;
}

Thì ta không thể dùng hàm này để thay đổi giá trị của hằng đối tượng lớp A.
b. Tham chiếu hằng, con trỏ hằng
Giả sử có hàm có đối tượng đầu vào sau

Code:
type C(A a)
{
	//Các câu lệnh
}

Một đối tượng object sẽ được tạo và máy sẽ copy toàn bộ giá trị của a vào object rồi xử lý. Vấn đề phát sinh là khi ta có một class với kích thước lớn thì việc chép giá trị vào tham số hình thức ở hàm sẽ làm cho chương trình trở nên chậm đi và đặc biệt là sẽ hao phí bộ nhớ và sau khi ra khỏi hàm, bộ nhớ bị hủy khi đó sẽ gọi hàm hủy, rất dễ dẫn đến tình trạng giải phóng lại vùng nhớ đã được giả phóng.
Vì vậy người ta thường dùng tham số chuyền vào là con trỏ hay tham chiếu:

Code:
type C(A *a)
{
	//Các câu lệnh
}

hoặc

Code:
type C(A &a)
{
	//Các câu lệnh
}

Hàm trên sử dụng tham số hình thức là một biến con trỏ và hàm dưới dùng một biến tham chiếu. Hai cách khai báo này sẽ cho kết quả tương tự nhau nếu ta không dùng con trỏ để trỏ vào đối tượng khác trong khi dùng hàm. Khi này, với lời gọi hàm như trên thì địa chỉ của x sẽ được truyền vào, do đó sẽ tránh được việc phải chép cả cấu trúc với kích thước lớn.
Nhưng với khai báo như thế thì giá trị của biến truyền vào có thể bị thay đổi thông qua biến object (vì là biến con trỏ hoặc tham chiếu), trong khi với cách khai báo như cũ thì ta không hề muốn giá trị này bị sửa đổi chút nào. Do đó, từ khóa const được sử dụng:

Code:
type C(const A *a)
{
	//Các câu lệnh
}
Code:
type C(const A &a)
{
	//Các câu lệnh
}

Lưu ý: 1. dạng khai báo A const &a không còn được khuyến khích.
2. Hàm trả về con trỏ có thể có dạng

    const A* functionName(/*các đối số*/);

hay

    A const * functionName(/*các đối số*/);

hay

    const A const * functionName(/*các đối số*/);

Khi đó nó được hiểu như trường hợp con trỏ ở trên.
c. Phương thức hằng.
Ở trên ta đã nhắc đến phương thức hằng, vậy phương thức hằng là gì? Tác dụng của nó?
Như ta đã biêt, một hằng đối tượng thì không thể thay đổi các thuộc tính của nó và không được phép gọi các phương thức có thể làm thay đổi các thuộc tính của nó, trừ trường hợp đặc biệt: các thuộc tính mutable. các phương thức được các hằng đối tượng sử dụng gọi là phương thức hằng, hay nói cách khác, các hằng đối tượng chỉ làm việc trên các phương thức hằng.
Giả sử với lớp A có một phương thức print() như sau:

class A
{
int a;
public:
A(int t=0){a=t;}
//…
void print();
};
void A::print()
{
cout<<a;
}

Ta thấy phương thức này không hề làm thay đổi các thuộc tính của a, Nhưng nó không thể làm việc được với hằng đối tượng.
Giả sử ta có 2 câu lệnh sau:

const A a;
a.print();//////lỗi

Vì sao trình dịch báo lỗi?
Ta thấy trong hàm print() không hề có lệnh thay đổi thuộc tính???
Lí do là trình dịch không thể biết được là ta có thay đổi thuộc tính hay không ở phương thức không hằng vì một phương thức không hằng có thể thay đổi được thuộc tính.
Vậy làm sao để có thể cho hàm trên thực hiện được với hằng đối tượng?
Ta phải khai báo hàm print() là một phương thức hằng bằng cách thêm từ khóa const vào sau khai báo của nó trong khai báo lớp như sau:

void print() const;

Khi đó print() trở thành mọt phương thức hằng và lớp A Cần được khai báo như sau:

class A
{
int a;
public:
A(int t=0){a=t;}
//…
void print() const;
};

Khi đó 2 câu lệnh này sẽ chạy được:

const A a;
a.print();//////OK

Nếu ta thay đổi một thuộc tính của đối tượng thuộc lớp A thông qua phương thức print() thì sẽ có lỗi.
VD:

void A::print()
{
cout<<a;
a=7; //////lỗi
}

Vì ta cố gắng thay đổi thuộc tính thông qua phương thức print() là một phương thức hằng.
Điều này khá hay trong môi trường làm việc cộng tác. Nếu nhiều người cùng xây dựng một lớp: thì cần xác định cái gì cần thay đổi, thay đổi bằng phương thức nào?…
Ở trên có nhắc đến thuộc tính mutable. Vậy thuộc tính này là gì?
Ta đã biết một phương thức hằng thì không được thay đổi các thuộc tính của đối tượng gọi nó. nhưng đôi khi cần phải thay đổi các thuộc tính này thông qua phương thức hằng, kể cả hằng đối tượng, khi đó chỉ cần khai báo trước thuộc tính đó từ khóa mutable.
Vậy nếu ta muốn có một phương thức hằng trả về một con trỏ không thể thay đổi giả trị của vùng nhớ, cũng như vùng nhớ mà con trỏ ấy đang trỏ tới, có tham số đầu vào là một con trỏ cũng không thể thay đổi giả trị của vùng nhớ, và vùng nhớ mà nó trỏ tới thì ta có một câu lệnh khai báo như sau:

    const A const * functionName(const A const *) const; //5 từ const!!

UPDATE
trong C++ giả sử có hàm:

Code:
type1 ham(type&);//không thay đổi đối số trong thân hàm nha

Khi đó bạn gọi hàm như sau

Code:
ham(6);////////loi

Khi đó có lỗi, vì sao?
Vì 6 là 1 hằng.
bạn cần khai báo tường minh hơn.
Và khi đó cần thay đổi hàm này như sau

Code:
type1 ham(const type&);/////////

khi đó lời gọi hàm trên có tác dụng

Nguồn : http://diendan.congdongcviet.com/showthread.php?t=41764

Bình luận về bài viết này