Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.
Trong chương quản lý mã nguồn này, chúng ta sẽ cùng tìm hiểu một số cách cơ bản để:
- Tổ chức mã nguồn hiệu quả hơn.
- Tập làm quen với cách làm việc trong một nhóm cho một dự án nhỏ.
- …
Nhưng trước hết, mình xin giới thiệu với các bạn một từ khóa gây bối rối cho nhiều bạn mới học lập trình: static. Nó khó hiểu bởi những ý nghĩa khác nhau tùy vào nơi từ khóa này được sử dụng.
Các bạn chắc hẳn đã tìm hiểu về khái niệm biến toàn cục (global variable) trong bài học phạm vi của biến rồi phải không? Biến toàn cục được khai báo bên ngoài những khối lệnh (scope), có thể được truy xuất tại nhiều khối lệnh khác nằm bên dưới biến đó trong cùng một file chương trình (Để truy xuất tại các file chương trình khác, chúng ta sẽ cùng tìm hiểu trong các bài sau).
Từ khóa static có thể được sử dụng để khai báo biến, dù đặt bên trong hay bên ngoài những khối lệnh, vẫn khiến cho biến đó có hiệu lực tương tự như biến toàn cục.
Từ khóa static còn có thể được sử dụng trong việc định nghĩa hàm, các thuộc tính và phương thức trong một lớp (class).
Biến static (static variables)
Biến static được tạo ra bên trong một khối lệnh có khả năng lưu giữ giá trị của nó cho dù chương trình đã chạy ra bên ngoài khối lệnh chứa nó.
Biến static chỉ cần được khai báo một lần duy nhất, và tiếp tục được duy trì sự tồn tại xuyên suốt cho đến khi chương trình kết thúc.
Dưới đây là một ví dụ đơn giản cho thấy sự khác biệt giữa việc sử dụng biến cục bộ và biến static:
#include <iostream>
void incrementAndPrint()
{
int value = 1; // automatic duration by default
++value;
std::cout << value << '\n';
} // value is destroyed here
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
Mỗi khi hàm incrementAndPrint được gọi, một biến có tên gọi “value” được tạo ra và được gán giá trị 1. Hàm incrementAndPrint sẽ tăng giá trị của biến “value” lên 1 đơn vị, giá trị in ra màn hình là 2. Khi hàm incrementAndPrint thực hiện xong tác vụ, biến “value” bị hủy. Cứ thực hiện liên tiếp như vậy, kết quả in ra màn hình là:
2
2
2
Bây giờ, chúng ta thêm từ khóa static trước nơi biến “value” được khai báo:
#include <iostream>
void incrementAndPrint()
{
static int s_value = 1; // static duration variable
++s_value;
std::cout << s_value << '\n';
} // s_value is not destroyed here, but became inaccessible
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
Lần này, vì biến “s_value” được khai báo với từ khóa static, nó sẽ chỉ được khởi tạo một lần duy nhất, dù hàm incrementAndPrint được gọi nhiều lần. Mỗi lần hàm incrementAndPrint được gọi, giá trị của biến “s_value” được tăng thêm 1, và còn được lưu giữ lại đó cho đến khi chương trình kết thúc. Kết quả in ra như sau:
2
3
4
Hàm static (static functions)
Từ khóa static cũng có thể được sử dụng cho các hàm, thường là những hàm được đặt bên trong các struct hoặc class (gọi là phương thức). Trong bài học này, mình lấy một ví dụ về việc sử dụng hàm static bên trong một struct mẫu như sau:
#include <iostream>
struct Test
{
static void foo()
{
std::cout << "Called static function: foo" << std::endl;
}
};
int main()
{
Test::foo();
return 0;
}
Các bạn có thể thấy, hàm foo được gọi mà không cần tạo ra thực thể kiểu Test.
Điểm đáng lưu ý trong việc sử dụng hàm static trong struct/class là chúng không thể truy xuất đến non-static members trong struct/class đó:
struct Test
{
int value;
void nonstatic_foo()
{
}
static void foo()
{
value = 1; // error
nonstatic_foo(); // error
}
};
Ưu/nhược điểm khi sử dụng từ khóa static
Điều đầu tiên mình muốn nói đến là nhược điểm khi sử dụng nhiều biến static trong một chương trình, đặc biệt hơn là lúc đặt biến static ngoài các khối lệnh tương đương với biến toàn cục. Điều này khiến cho chương trình không chỉ trở nên rắc rối, khó kiểm soát hơn, nhưng còn gây lãng phí bộ nhớ trong suốt thời gian chương trình đang chạy.
Các bạn thử tưởng tượng nếu khai báo một biến static kiểu mảng như vậy:
static bool pressedKeys[1024];
Dù có đang sử dụng chúng hay không, chúng vẫn ở đó và chiếm một diện tích không nhỏ trên vùng nhớ.
Trong các bài học tiếp theo, chúng ta tìm hiểu về viết những chương trình đơn giản và phân bố mã nguồn trong nhiều file, nếu mỗi file chúng ta đều có khai báo biến static trong đó, thật khó kiểm soát.
Tuy nhiên, không phải do rãnh rỗi mà người ta làm ra kiểu biến static, có một số trường hợp áp dụng biến static khá tiện lợi. Mình lấy một ví dụ đơn giản như sau:
int generateID()
{
static int s_id = 0;
return ++s_id;
}
struct Employee
{
int ID;
Employee()
{
ID = generateID();
}
};
int main()
{
Employee emp1;
Employee emp2;
Employee emp3;
return 0;
}
Trong chương trình trên, hàm generateID có chức năng sinh ra ID khi có đối tượng Employee mới được tạo ra. Như vậy, chúng ta không lo về việc ID của đối tượng Employee mới bị trùng lặp. Bằng cách khai báo biến “s_id” bên trong hàm generateID, nó không thể truy xuất hoặc thay đổi từ phía các hàm khác.
Bên cạnh đó, từ khóa static thường được sử dụng phổ biến trong khi thiết kế chương trình có chứa Singleton design pattern.
Tổng kết
Trên đây không phải là tất cả những gì mình biết về từ khóa static. Nó có nhiều ích lợi trong khi thiết kế chương trình, nhưng sẽ gây một chút rắc rối nếu dùng quá nhiều biến static trong một chương trình lớn.
Hẹn gặp lại các bạn trong bài học tiếp theo trong khóa học lập trình C++ hướng thực hành.
Mọi ý kiến đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp tại diễn đàn.