Nội dung chính
1. Biến Const
Cho đến nay, tất cả các biến mà chúng ta đã thấy đều có thể thay đổi – nghĩa là giá trị của chúng có thể thay đổi bất kỳ lúc nào. Ví dụ:
int x { 4 }; // initialize x with the value of 4
x = 5; // change value of x to 5
Tuy nhiên, đôi khi hữu ích khi xác định các biến có giá trị không thể thay đổi. Ví dụ, hãy xem xét lực hấp dẫn của Trái đất (gần bề mặt): 9,8 mét / giây ^ 2. Điều này không có khả năng sớm thay đổi (và nếu có, bạn có thể gặp vấn đề lớn hơn so với việc học C ++). Việc xác định giá trị này là một hằng số giúp đảm bảo rằng giá trị này không vô tình bị thay đổi.
Để tạo một biến không đổi, chỉ cần đặt từ khóa const trước hoặc sau kiểu dữ liệu của biến, như sau:
const double gravity { 9.8 }; // preferred use of const before type
int const sidesInSquare { 4 }; // okay, but not preferred
Mặc dù C ++ sẽ chấp nhận const trước hoặc sau kiểu dữ liệu, nhưng chúng tôi khuyên bạn nên sử dụng const trước kiểu dữ liệu vì nó tuân theo quy ước ngôn ngữ tiếng Anh tiêu chuẩn tốt hơn trong đó các bổ ngữ đến trước đối tượng được sửa đổi.
Biến Const phải được khởi tạo khi bạn định nghĩa chúng, và sau đó giá trị đó không thể thay đổi thông qua phép gán.
Khai báo một biến là const ngăn chúng ta vô tình thay đổi giá trị của nó:
const double gravity { 9.8 };
gravity = 9.9; // not allowed, this will cause a compile error
Việc xác định một biến const mà không khởi tạo nó cũng sẽ gây ra lỗi biên dịch:
const double gravity; // compiler error, must be initialized upon definition
Lưu ý rằng các biến const có thể được khởi tạo từ các biến khác (bao gồm cả những biến không phải là const):
td::cout << "Enter your age: ";
int age{};
std::cin >> age;
const int usersAge { age }; // usersAge can not be changed
Const thường được sử dụng với các tham số hàm:
void printInteger(const int myValue)
{
std::cout << myValue;
}
Tạo một tham số hàm const thực hiện hai điều. Đầu tiên, nó cho người gọi hàm biết rằng hàm sẽ không thay đổi giá trị của myValue. Thứ hai, nó đảm bảo rằng hàm không thay đổi giá trị của myValue.
Khi các đối số được truyền theo giá trị, chúng ta thường không quan tâm đến việc hàm có thay đổi giá trị của tham số hay không (vì dù sao thì nó cũng chỉ là một bản sao sẽ bị hủy ở cuối hàm). Vì lý do này, chúng ta thường không const tham số được truyền theo giá trị. Nhưng sau này, chúng ta sẽ nói về các loại tham số hàm khác (trong đó việc thay đổi giá trị của tham số sẽ thay đổi giá trị của đối số được truyền vào). Đối với những loại tham số này, việc sử dụng hợp lý const là quan trọng.
Thời gian chạy so với hằng số thời gian biên dịch C ++ thực sự có hai loại hằng số khác nhau.
Hằng số thời gian chạy là những hằng số có giá trị khởi tạo chỉ có thể được sử dụng trong thời gian chạy (khi chương trình của bạn đang chạy). Các biến như usersAge và myValue trong các đoạn code ở trên là các hằng số thời gian chạy, vì trình biên dịch không thể xác định giá trị ban đầu của chúng tại thời điểm biên dịch. usersAge dựa vào đầu vào của người dùng (chỉ có thể được cung cấp trong thời gian chạy) và myValue phụ thuộc vào giá trị được truyền vào hàm (chỉ được biết trong thời gian chạy). Tuy nhiên, sau khi khởi tạo, giá trị của các hằng số này không thể thay đổi.
Hằng số thời gian biên dịch là những hằng số mà các giá trị khởi tạo có thể được sử dụng tại thời điểm biên dịch (khi chương trình của bạn đang biên dịch). Trọng lực biến đổi ở trên là một ví dụ về hằng số thời gian biên dịch. Hằng số thời gian biên dịch cho phép trình biên dịch thực hiện các tối ưu hóa mà hằng số thời gian chạy không có sẵn. Ví dụ, bất cứ khi nào cần được sử dụng, trình biên dịch có thể chỉ cần thay thế tên định danh bằng chữ double 9,8.
Khi bạn khai báo một biến const, trình biên dịch sẽ ngầm theo dõi xem đó là hằng số thời gian chạy hay thời gian biên dịch.
Trong hầu hết các trường hợp, điều này không thành vấn đề, nhưng có một vài trường hợp kỳ lạ trong đó C ++ yêu cầu hằng số thời gian biên dịch thay vì hằng số thời gian chạy, ví dụ: trong phần khởi tạo kiểu – điều mà chúng ta sẽ đề cập sau.
2. constexpr
Để giúp cung cấp tính cụ thể hơn, C ++ 11 đã giới thiệu từ khóa constexpr, đảm bảo rằng hằng số phải là hằng số thời gian biên dịch:
constexpr double gravity { 9.8 }; // ok, the value of 9.8 can be resolved at compile-time
constexpr int sum { 4 + 5 }; // ok, the value of 4 + 5 can be resolved at compile-time
std::cout << "Enter your age: ";
int age{};
std::cin >> age;
constexpr int myAge { age }; // not okay, age can not be resolved at compile-time
biến constexpr là const. Điều này sẽ trở nên quan trọng khi chúng ta nói về các tác dụng khác của const trong các bài học sắp tới.
Bạn nên – Bất kỳ biến nào không thể sửa đổi được sau khi khởi tạo và có trình khởi tạo được biết đến tại thời điểm biên dịch nên được khai báo là constexpr.
Bất kỳ biến nào không thể sửa đổi được sau khi khởi tạo và có bộ khởi tạo không được xác định tại thời điểm biên dịch nên được khai báo là const.
3. Đặt tên cho các biến const của bạn
Một số lập trình viên thích sử dụng tất cả các tên viết hoa cho các biến const. Những người khác sử dụng tên biến bình thường với tiền tố ‘k’. Tuy nhiên, chúng ta sẽ sử dụng các quy ước đặt tên biến thông thường, phổ biến hơn. Các biến Const hoạt động chính xác như các biến bình thường trong mọi trường hợp ngoại trừ việc chúng không thể được gán cho, vì vậy không có lý do cụ thể nào mà chúng cần được biểu thị là đặc biệt.
4. Hằng số tượng trưng
Trong bài học trước – Ký tự chữ, chúng ta đã thảo luận về “số ma thuật”, là các chữ được sử dụng trong chương trình để biểu thị một giá trị không đổi. Vì số ma thuật là xấu, bạn nên làm gì thay thế? Câu trả lời là: sử dụng hằng số tượng trưng! Một hằng số tượng trưng là một tên được đặt cho một giá trị chữ không đổi. Có hai cách để khai báo hằng biểu tượng trong C ++. Một trong số chúng là tốt, và một trong số chúng thì không. Chúng ta sẽ cho bạn thấy cả hai.
Trước tiên, chúng ta sẽ chỉ cho bạn cách ít mong muốn hơn để xác định một hằng số tượng trưng. Phương pháp này thường được sử dụng trong nhiều code cũ hơn, vì vậy bạn có thể vẫn thấy nó.
Trong bài học – Giới thiệu về bộ tiền xử lý, bạn đã biết rằng macro giống đối tượng có hai dạng – một dạng không sử dụng giá trị thay thế (thường được sử dụng cho biên dịch có điều kiện) và một dạng có giá trị thay thế. Chúng ta sẽ nói về trường hợp với giá trị thay thế ở đây. Điều đó có dạng:
#define identifier substitution_text
Bất cứ khi nào bộ tiền xử lý gặp chỉ thị này, bất kỳ sự xuất hiện của code định danh sẽ được thay thế bằng substitution_text. code định danh theo truyền thống được viết bằng tất cả các chữ cái in hoa, sử dụng dấu gạch dưới để biểu thị dấu cách.
Hãy xem xét đoạn code sau:
#define MAX_STUDENTS_PER_CLASS 30
int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };
Khi bạn biên dịch code của mình, bộ xử lý trước sẽ thay thế tất cả các phiên bản của MAX_STUDENTS_PER_CLASS bằng giá trị chữ 30, giá trị này sau đó được biên dịch thành tệp thực thi của bạn.
Bạn có thể sẽ đồng ý rằng điều này trực quan hơn nhiều so với việc sử dụng một con số ma thuật vì một vài lý do. MAX_STUDENTS_PER_CLASS cung cấp ngữ cảnh cho những gì chương trình đang cố gắng thực hiện, ngay cả khi không có nhận xét. Thứ hai, nếu số lượng sinh viên tối đa trên mỗi lớp học thay đổi, chúng tôi chỉ cần thay đổi giá trị MAX_STUDENTS_PER_CLASS ở một nơi và tất cả các phiên bản của MAX_STUDENTS_PER_CLASS sẽ được thay thế bằng giá trị chữ mới ở lần biên dịch tiếp theo.
Hãy xem xét ví dụ thứ hai của chúng tôi, sử dụng hằng số biểu tượng #define:
#define MAX_STUDENTS_PER_CLASS 30
#define MAX_NAME_LENGTH 30
int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };
setMax(MAX_NAME_LENGTH);
Trong trường hợp này, rõ ràng MAX_STUDENTS_PER_CLASS và MAX_NAME_LENGTH được dự định là các giá trị độc lập, mặc dù chúng tình cờ chia sẻ cùng một giá trị (30). Bằng cách đó, nếu chúng ta cần cập nhật quy mô lớp học của mình, chúng ta sẽ không vô tình thay đổi độ dài tên.
Vậy tại sao không sử dụng #define để tạo hằng số tượng trưng? Có (ít nhất) ba vấn đề lớn.
Đầu tiên, vì macro được giải quyết bởi bộ xử lý trước, thay thế tên tượng trưng bằng giá trị đã xác định, các hằng số biểu tượng # được xác định không hiển thị trong trình gỡ lỗi (cho bạn thấy code thực của bạn). Vì vậy, mặc dù trình biên dịch sẽ biên dịch int max_students {numClassrooms * 30} ;, trong trình gỡ lỗi, bạn sẽ thấy int max_students {numClassrooms * MAX_STUDENTS_PER_CLASS} ;, và MAX_STUDENTS_PER_CLASS sẽ không thể xem được. Bạn phải đi tìm định nghĩa của MAX_STUDENTS_PER_CLASS để biết giá trị thực tế là bao nhiêu. Điều này có thể làm cho các chương trình của bạn khó gỡ lỗi hơn.
Thứ hai, macro có thể xung đột với code bình thường. Ví dụ:
#include "someheader.h"
#include <iostream>
int main()
{
int beta { 5 };
std::cout << beta;
return 0;
}
Nếu someheader.h xảy ra với #define macro có tên là beta, thì chương trình đơn giản này sẽ bị hỏng vì bộ tiền xử lý sẽ thay thế tên của int biến beta bằng bất kỳ giá trị nào của macro.
Thứ ba, macro không tuân theo quy tắc xác định phạm vi thông thường, có nghĩa là trong một số trường hợp hiếm hoi, macro được xác định trong một phần của chương trình có thể xung đột với code được viết trong một phần khác của chương trình mà nó không được phép tương tác.
Lưu ý – Tránh sử dụng #define để tạo macro hằng số tượng trưng.
Một giải pháp tốt hơn: Sử dụng các biến constexpr
Một cách tốt hơn để tạo các hằng biểu tượng là sử dụng các biến constexpr:
constexpr int maxStudentsPerClass { 30 };
constexpr int maxNameLength { 30 };
Vì đây chỉ là các biến bình thường nên chúng có thể xem được trong trình gỡ lỗi, có phạm vi bình thường và tránh các hành vi kỳ lạ khác.
Bạn nên – Sử dụng các biến constexpr để cung cấp tên và ngữ cảnh cho các số ma thuật của bạn.
Sử dụng hằng số tượng trưng trong một chương trình nhiều file
Trong nhiều ứng dụng, một hằng số tượng trưng nhất định cần được sử dụng trong toàn bộ code của bạn (không chỉ ở một vị trí). Chúng có thể bao gồm các hằng số vật lý hoặc toán học không thay đổi (ví dụ: số pi hoặc số của avogadro) hoặc các giá trị “điều chỉnh” dành riêng cho ứng dụng (ví dụ: hệ số ma sát hoặc trọng lực). Thay vì khai báo lại chúng mỗi khi cần, tốt hơn nên khai báo chúng một lần ở vị trí trung tâm và sử dụng chúng ở bất cứ nơi nào cần thiết. Bằng cách đó, nếu bạn cần thay đổi chúng, bạn chỉ cần thay chúng ở một nơi.
Có nhiều cách để hỗ trợ điều này trong C ++, nhưng cách sau có lẽ là dễ nhất:
1) Tạo tệp header để giữ các hằng số này
2) Bên trong tệp header này, khai báo một không gian tên (chúng ta sẽ nói thêm về điều này trong bài – Không gian tên do người dùng định nghĩa)
3) Thêm tất cả các hằng số của bạn bên trong không gian tên(namespace) (đảm bảo rằng chúng là constexpr trong C ++ 11/14 hoặc constexpr nội tuyến trong C ++ 17 hoặc mới hơn)
4) #include header file bất cứ nơi nào bạn cần
Ví dụ:
constants.h (C ++ 11/14):
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants
{
constexpr double pi { 3.14159 };
constexpr double avogadro { 6.0221413e23 };
constexpr double my_gravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
Trong C ++ 17, ưu tiên “inline constexpr” thay thế:
constants.h (C ++ 17 hoặc mới hơ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
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/
*/
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants
{
inline constexpr double pi { 3.14159 }; // inline constexpr is C++17 or newer only
inline constexpr double avogadro { 6.0221413e23 };
inline constexpr double my_gravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
Sử dụng toán tử phân giải phạm vi (: 🙂 để truy cập các hằng số của bạn trong tệp .cpp:
main.cpp:
#include "constants.h"
#include <iostream>
int main()
{
std::cout << "Enter a radius: ";
int radius{};
std::cin >> radius;
double circumference { 2.0 * radius * constants::pi };
std::cout << "The circumference is: " << circumference << '\n';
return 0;
}
Nếu bạn có cả hằng số vật lý và giá trị điều chỉnh cho mỗi ứng dụng, bạn có thể chọn sử dụng hai file – một cho các giá trị vật lý sẽ không bao giờ thay đổi và một cho các giá trị điều chỉnh cho mỗi chương trình cụ thể cho chương trình của bạn. Bằng cách đó, bạn có thể sử dụng lại các giá trị vật lý trong bất kỳ chương trình nào khác.
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:
- Full series tự học C++ từ cơ bản tới nâng cao tại đây nha.
- Ebook về C++ tại đây.
- Các series tự học lập trình MIỄN PHÍ khác
- Nơi liên hệ hợp tác hoặc quảng cáo cùng Cafedevn tại đây.
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!