Thuật ngữ static là một trong những thuật ngữ khó hiểu nhất trong ngôn ngữ C ++, một phần lớn là vì static có ý nghĩa khác nhau trong các ngữ cảnh khác nhau.

Trong các bài học trước, chúng ta đã đề cập rằng các biến toàn cục có thời lượng tĩnh, có nghĩa là chúng được tạo khi chương trình bắt đầu và bị hủy khi chương trình kết thúc.

Chúng ta cũng đã thảo luận về cách từ khóa tĩnh cung cấp liên kết nội bộ số nhận dạng toàn cầu, có nghĩa là số nhận dạng chỉ có thể được sử dụng trong tệp mà nó được định nghĩa.

Trong bài học này, cafedev sẽ khám phá cách sử dụng từ khóa static khi áp dụng cho một biến cục bộ.

1. Biến cục bộ tĩnh

Trong bài học – Giới thiệu về phạm vi cục bộ, bạn đã biết rằng các biến cục bộ có thời lượng tự động theo mặc định, có nghĩa là chúng được tạo tại điểm xác định và bị hủy khi khối được thoát.

Việc sử dụng từ khóa static trên một biến cục bộ sẽ thay đổi thời lượng của nó từ thời lượng tự động thành thời lượng tĩnh. Điều này có nghĩa là biến bây giờ được tạo khi bắt đầu chương trình và bị hủy ở cuối chương trình (giống như biến toàn cục). Kết quả là, biến static sẽ giữ nguyên giá trị của nó ngay cả khi nó vượt ra khỏi phạm vi!

Cách dễ nhất để chỉ ra sự khác biệt giữa các biến thời lượng tự động và thời lượng tĩnh là bằng ví dụ.

2. Thời lượng tự động (mặc định):

#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 lần incrementAndPrint () được gọi, một giá trị có tên biến sẽ được tạo và gán giá trị của 1. Giá trị tăng lên của incrementAndPrint () thành 2, rồi in ra giá trị của 2. Khi incrementAndPrint () chạy xong, biến sẽ hết phạm vi và bị phá hủy. Do đó, chương trình này xuất ra:

2
2
2

Bây giờ hãy xem xét phiên bản tĩnh của chương trình này. Sự khác biệt duy nhất giữa chương trình này và chương trình trên là chúng ta đã thay đổi biến cục bộ từ thời lượng tự động thành thời lượng tĩnh bằng cách sử dụng từ khóa tĩnh.

3. Thời lượng tĩnh (sử dụng từ khóa tĩnh):

/*
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
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <iostream>
 
void incrementAndPrint()
{
    static int s_value{ 1 }; // static duration via static keyword.  This initializer is only executed once.
    ++s_value;
    std::cout << s_value << '\n';
} // s_value is not destroyed here, but becomes inaccessible because it goes out of scope
 
int main()
{
    incrementAndPrint();
    incrementAndPrint();
    incrementAndPrint();
 
    return 0;
}

Trong chương trình này, vì s_value đã được khai báo là tĩnh nên s_value được tạo và khởi tạo một lần (khi bắt đầu chương trình). Nếu chúng ta không sử dụng một biểu thức hằng để khởi tạo s_value, nó sẽ được khởi tạo bằng 0 khi bắt đầu chương trình và sau đó được khởi tạo với giá trị khởi tạo đã cung cấp của chúng ta trong lần đầu tiên gặp định nghĩa biến (nhưng nó không được khởi động lại trong các lần gọi tiếp theo).

Khi s_value vượt ra khỏi phạm vi ở cuối hàm, nó không bị hủy. Mỗi khi hàm incrementAndPrint () được gọi, giá trị của s_value vẫn ở bất kỳ giá trị nào chúng ta đã để trước đó. Do đó, chương trình này xuất ra:

2
3
4

Giống như chúng ta sử dụng “g_” để đặt tiền tố cho các biến toàn cục, thông thường chúng ta sử dụng “s_” để đặt trước các biến cục bộ tĩnh (thời lượng tĩnh).

Một trong những cách sử dụng phổ biến nhất cho các biến cục bộ thời lượng tĩnh là cho các trình tạo ID duy nhất. Hãy tưởng tượng một chương trình trong đó bạn có nhiều đối tượng giống nhau (ví dụ: một trò chơi mà bạn đang bị nhiều thây ma tấn công hoặc một mô phỏng mà bạn đang hiển thị nhiều hình tam giác). Nếu bạn nhận thấy một khiếm khuyết, có thể gần như không thể phân biệt được đối tượng nào đang gặp vấn đề. Tuy nhiên, nếu mỗi đối tượng được cấp một code định danh duy nhất khi tạo, thì việc phân biệt các đối tượng để gỡ lỗi thêm có thể dễ dàng hơn.

Việc tạo một số ID duy nhất rất dễ thực hiện với biến cục bộ thời lượng tĩnh:

int generateID()
{
    static int s_itemID{ 0 };
    return s_itemID++; // makes copy of s_itemID, increments the real s_itemID, then returns the value in the copy
}

Lần đầu tiên hàm này được gọi, nó trả về 0. Lần thứ hai, nó trả về 1. Mỗi lần nó được gọi, nó sẽ trả về một số cao hơn lần trước nó được gọi. Bạn có thể gán những số này làm ID duy nhất cho các đối tượng của mình. Bởi vì s_itemID là một biến cục bộ, nó không thể bị “giả mạo” bởi các hàm khác.

Các biến tĩnh mang lại một số lợi ích của các biến toàn cục (chúng không bị hủy cho đến khi kết thúc chương trình) trong khi hạn chế khả năng hiển thị của chúng đối với phạm vi khối. Điều này làm cho chúng an toàn để sử dụng ngay cả khi bạn thay đổi giá trị của chúng thường xuyên.

4. Lạm dụng các biến cục bộ tĩnh

Hãy xem xét đoạn code sau

#include <iostream>
 
int getInteger()
{
  static bool s_isFirstCall{ true };
 
  if (s_isFirstCall)
  {
    std::cout << "Enter an integer: ";
    s_isFirstCall = false;
  }
  else
  {
    std::cout << "Enter another integer: ";
  }
 
  int i{};
  std::cin >> i;
  return i;
}
 
int main()
{
  int a{ getInteger() };
  int b{ getInteger() };
 
  std::cout << a << " + " << b << " = " << (a + b) << '\n';
 
  return 0;
}

Kết quả

Enter an integer: 5
Enter another integer: 9
5 + 9 = 14

code này thực hiện những gì nó phải làm, nhưng vì chúng ta đã sử dụng một biến cục bộ tĩnh nên chúng ta đã làm cho code khó hiểu hơn. Nếu ai đó đọc code trong `main ()` mà không đọc quá trình triển khai của `getInteger () ‘, họ sẽ không có lý do gì để cho rằng hai lệnh gọi getInteger () làm điều gì đó khác nhau. Nhưng hai cuộc gọi thực hiện một cái gì đó khác nhau, có thể rất khó hiểu nếu sự khác biệt nhiều hơn một lời nhắc đã thay đổi.

Giả sử bạn nhấn nút +1 trên lò vi sóng và lò vi sóng sẽ thêm 1 phút vào thời gian còn lại. Bữa ăn của bạn thật ấm áp và bạn hạnh phúc. Trước khi lấy bữa ăn ra khỏi lò vi sóng, bạn nhìn thấy một con mèo bên ngoài cửa sổ và hãy quan sát nó một lúc, vì mèo rất ngầu. Khoảnh khắc này hóa ra lâu hơn bạn mong đợi và khi bạn ăn miếng đầu tiên của bữa ăn, nó lại nguội lạnh. Không sao, chỉ cần đặt nó trở lại lò vi sóng và nhấn +1 để chạy nó trong một phút. Nhưng lần này lò vi sóng chỉ thêm 1 giây chứ không phải 1 phút. Đó là khi bạn nói “Tôi không thay đổi gì và bây giờ nó bị hỏng” hoặc “Nó hoạt động lần trước”. Nếu bạn làm lại điều tương tự, bạn sẽ mong đợi hành vi tương tự như lần trước. Tương tự đối với các hàm.

Giả sử chúng ta muốn thêm phép trừ vào máy tính sao cho kết quả đầu ra giống như sau

Addition
Enter an integer: 5
Enter another integer: 9
5 + 9 = 14
Subtraction
Enter an integer: 12
Enter another integer: 3
12 - 3 = 9

Chúng ta có thể cố gắng sử dụng getInteger () để đọc hai số nguyên tiếp theo giống như chúng tôi đã làm để cộng.

int main()
{
  std::cout << "Addition\n";
 
  int a{ getInteger() };
  int b{ getInteger() };
 
  std::cout << a << " + " << b << " = " << (a + b) << '\n';
 
  std::cout << "Subtraction\n";
 
  int c{ getInteger() };
  int d{ getInteger() };
 
  std::cout << c << " - " << d << " = " << (c - d) << '\n';
 
  return 0;
}

Kết quả:

Addition
Enter an integer: 5
Enter another integer: 9
5 + 9 = 14
Subtraction
Enter another integer: 12
Enter another integer: 3
12 - 3 = 9

(“Nhập một số nguyên khác” thay vì “Nhập một số nguyên”)

getInteger () không thể sử dụng lại được, vì nó có trạng thái bên trong (Biến cục bộ tĩnh s_isFirstCall) không thể được đặt lại từ bên ngoài. s_isFirstCall không phải là một biến phải là duy nhất trong toàn bộ chương trình. Mặc dù chương trình của chúng tôi hoạt động tốt khi chúng tôi viết lần đầu tiên, nhưng biến cục bộ tĩnh ngăn chúng tôi sử dụng lại hàm sau này.

Một cách tốt hơn để triển khai getInteger là chuyển s_isFirstCall làm tham số. Điều này cho phép người gọi chọn lời nhắc nào sẽ được in.

Biến cục bộ tĩnh chỉ nên được sử dụng nếu trong toàn bộ chương trình của bạn và trong tương lai gần của chương trình, biến là duy nhất và việc đặt lại biến sẽ không có ý nghĩa gì.

Bạn nên

Tránh các biến cục bộ tĩnh trừ khi biến không bao giờ cần được đặt lại. các biến cục bộ tĩnh làm giảm khả năng sử dụng lại và làm cho các hàm khó lý luận hơn.

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

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