This programming guide is only available in Vietnamese. Switch to Vietnamese to read the full article.
Sau khi thiết lập môi trường lập trình và viết chương trình đầu tiên, bạn cần nắm vững các khái niệm nền tảng của Modern C++. Bài học này sẽ giới thiệu các tính năng cốt lõi: Namespaces (quản lý không gian tên), Type Modifiers (const, constexpr, volatile), Auto Keyword (suy luận kiểu tự động), iostream I/O Basics, và các kỹ thuật Type Casting an toàn. Những khái niệm này là nền móng cho việc viết code C++ sạch, an toàn và hiệu năng cao.
1. Namespaces: Quản lý không gian tên trong dự án lớn
Namespace là một cơ chế cho phép bạn tổ chức mã nguồn thành các "vùng tên" riêng biệt, tránh xung đột
tên gọi khi làm việc với nhiều thư viện. Ví dụ, cả OpenGL Graphics Library và một thư viện Graphics tự
viết của bạn đều có thể có hàm drawTriangle(), nhưng bằng cách sử dụng namespace, bạn có
thể phân biệt chúng:
// Namespace std (Standard Library)
namespace std {
void cout_example() {
std::cout << "Hello from std namespace" << std::endl;
}
}
// Namespace tự định nghĩa
namespace Graphics {
void drawTriangle() {
std::cout << "Drawing triangle..." << std::endl;
}
}
// Namespace lồng nhau
namespace Graphics::Engine {
void initRenderer() {
std::cout << "Initializing graphics engine..." << std::endl;
}
}
int main() {
// Gọi hàm từ namespace Graphics
Graphics::drawTriangle();
// Gọi hàm từ namespace lồng
Graphics::Engine::initRenderer();
return 0;
}
Using Directive & Using Declaration
Thay vì viết std::cout mỗi lần, bạn có thể sử dụng using để "nhập" hàm vào
scope hiện tại. Tuy nhiên, cần cẩn thận vì việc dùng using namespace std; ở mức global có
thể gây xung đột tên:
// Using declaration: chỉ nhập một hàm cụ thể
using std::cout;
using std::endl;
int main() {
cout << "Safer than using namespace std" << endl;
// Trong phạm vi hàm
{
using std::cin;
int x;
cin >> x;
}
return 0;
}
Namespace Alias
Khi tên namespace quá dài (ví dụ: boost::asio::ssl::stream), bạn có thể tạo alias ngắn
hơn:
namespace fs = std::filesystem; // Alias
namespace ba = boost::asio; // Alias
// Bây giờ có thể sử dụng ngắn hơn
fs::path my_path = "/tmp/file.txt";
ba::io_context io;
2. Type Modifiers: const, constexpr & volatile
Các từ khóa này giúp bạn kiểm soát cách dữ liệu có thể bị thay đổi, giúp compiler phát hiện lỗi logic sớm.
const: Hằng số không thay đổi
const chỉ ra rằng một biến không thể bị thay đổi sau khi khởi tạo. Nó có thể áp dụng cho
biến, con trỏ, hoặc phương thức class:
int main() {
// Biến const - giá trị không thay đổi
const int MAX_SIZE = 100;
// MAX_SIZE = 200; // LỖI: không thể thay đổi const
// Con trỏ const - con trỏ không thể thay đổi địa chỉ
int x = 10;
int* const ptr = &x; // Địa chỉ không đổi, nhưng *ptr có thể thay
*ptr = 20; // OK
// ptr = &y; // LỖI
// Const pointer - con trỏ có thể thay đổi, nhưng dữ liệu không
const int* const_ptr = &x;
// *const_ptr = 30; // LỖI
const_ptr = &y; // OK
return 0;
}
constexpr: Hằng số tính toán lúc biên dịch
constexpr yêu cầu giá trị phải được tính toán lúc biên dịch (compile-time), cho phép
compiler tối ưu hóa. Điều này hữu ích cho các hằng số phức tạp:
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
// Tính toán lúc biên dịch
constexpr int result = factorial(5); // Compiler tính 5! = 120
// Mảng const
constexpr int arr[] = {1, 2, 3, 4, 5};
return 0;
}
volatile: Chỉ định giá trị có thể thay đổi ngoài kiểm soát chương trình
volatile báo cho compiler rằng giá trị có thể thay đổi ngoài dự kiến (ví dụ: do phần
cứng, signal handler, hay luồng khác), nên không được tối ưu hóa:
// Volatile thường dùng cho hardware registers hoặc shared variables
volatile int hardware_counter = 0; // Có thể bị thay đổi bởi phần cứng
// Compiler sẽ không cache giá trị này, mà luôn đọc từ bộ nhớ
while (hardware_counter < 100) {
// Compiler sẽ đọc lại hardware_counter mỗi lần
}
// Const volatile: không thể thay qua con trỏ, nhưng giá trị có thể thay
const volatile int* hw_ptr = &hardware_counter;
3. Auto Keyword: Suy luận kiểu tự động
auto yêu cầu compiler tự động suy luận kiểu dữ liệu dựa vào giá trị gán. Điều này giúp
code ngắn gọn hơn, đặc biệt khi làm việc với STL containers:
#include <vector>
#include <map>
int main() {
// Auto với biến thông thường
auto x = 42; // int
auto name = "John"; // const char*
auto pi = 3.14159; // double
// Auto với STL containers
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Thay vì: std::vector<int>::iterator it = numbers.begin();
// Viết ngắn hơn:
auto it = numbers.begin();
// Range-based for loop với auto
for (auto num : numbers) {
std::cout << num << " "; // auto sẽ là int
}
// Map iterator: thường phức tạp, auto rất hữu ích
std::map<std::string, int> scores;
for (const auto& [name, score] : scores) { // Structured binding (C++17)
std::cout << name << ": " << score << std::endl;
}
return 0;
}
Cẩn thận: Auto không phải lúc nào cũng là tốt
Mặc dù tiện lợi, quá sử dụng auto có thể làm code khó đọc. Nên dùng
auto khi:
-
Kiểu dữ liệu rõ ràng từ giá trị (ví dụ:
auto x = std::make_unique<MyClass>();). - Làm việc với iterator hoặc template types phức tạp.
- Không dùng
autokhi kiểu dữ liệu không rõ ràng để tránh nhầm lẫn.
4. iostream & I/O Basics: Xuất nhập dữ liệu an toàn
C++ cung cấp iostream (Input/Output Stream) thay thế an toàn cho
scanf/printf của C. Stream là một luồng dữ liệu hai chiều giữa chương trình và thiết bị
(terminal, file, mạng).
std::cout: Xuất dữ liệu
#include <iostream>
#include <iomanip>
int main() {
std::cout << "Hello, C++!" << std::endl;
// Xuất nhiều giá trị cùng lúc
int age = 25;
double height = 1.75;
std::cout << "Age: " << age << ", Height: " << height << std::endl;
// Định dạng số
std::cout << std::fixed << std::setprecision(2) << height << std::endl;
// Hexadecimal và octal
std::cout << std::hex << 255 << std::endl; // ff
std::cout << std::oct << 255 << std::endl; // 377
std::cout << std::dec << 255 << std::endl; // 255
return 0;
}
std::cin: Nhập dữ liệu từ người dùng
#include <iostream>
#include <string>
int main() {
// Nhập số nguyên
int x;
std::cout << "Enter an integer: ";
std::cin >> x;
// Nhập một từ (dừng ở space)
std::string word;
std::cout << "Enter a word: ";
std::cin >> word;
// Nhập cả dòng (bao gồm space)
std::string line;
std::cout << "Enter a sentence: ";
std::getline(std::cin, line);
std::cout << "You entered: " << line << std::endl;
return 0;
}
5. Type Casting: Chuyển đổi kiểu an toàn vs không an toàn
Type casting là việc chuyển đổi từ kiểu dữ liệu này sang kiểu khác. C++ cung cấp hai cách: C-style (nguy hiểm) và C++-style (an toàn hơn).
C-style Cast (không khuyến nghị)
Cú pháp: (type) value. Cách này mạnh mẽ nhưng rất nguy hiểm vì compiler không kiểm tra
tính hợp lệ:
int main() {
double d = 3.14159;
int i = (int)d; // Truncate to 3 (mất dữ liệu)
// Nguy hiểm: cast địa chỉ thành địa chỉ không liên quan
int* ptr = (int*) "dangerous string cast"; // UB!
return 0;
}
static_cast: Chuyển đổi kiểu tương thích
Cú pháp: static_cast<type>(value). Được dùng khi bạn chắc chắn rằng hai kiểu tương
thích:
int main() {
double d = 3.14159;
int i = static_cast<int>(d); // Explicit conversion, loss of precision OK
// Chuyển đổi giữa numeric types
float f = static_cast<float>(42);
// Chuyển đổi con trỏ trong hierarchy class
class Base {};
class Derived : public Base {};
Derived d_obj;
Base* base_ptr = static_cast<Base*>(&d_obj); // Derived -> Base (safe)
return 0;
}
const_cast: Loại bỏ const qualifier
Cú pháp: const_cast<type>(value). Được dùng khi bạn biết rằng dữ liệu thực sự không
phải const nhưng được khai báo const:
void modifyData(int* ptr) {
*ptr = 999;
}
int main() {
const int x = 10;
// const_cast để loại bỏ const
modifyData(const_cast<int*>(&x));
// CẢNH BÁO: Nếu x thực sự nằm trong read-only memory,
// việc này sẽ dẫn đến Undefined Behavior!
return 0;
}
reinterpret_cast: Cast bất cứ con trỏ nào sang con trỏ khác
Cú pháp: reinterpret_cast<type>(value). Cách cast nguy hiểm nhất, chỉ dùng khi thực
sự cần thiết (ví dụ: tương tác với APIs cổ điển hoặc hardware):
int main() {
int x = 42;
// Cast địa chỉ thành unsigned long long
unsigned long long addr = reinterpret_cast<unsigned long long>(&x);
std::cout << "Address: 0x" << std::hex << addr << std::endl;
// Cast ngược lại
int* ptr = reinterpret_cast<int*>(addr);
// RẤT NGUY HIỂM: chỉ dùng khi thực sự biết mình làm gì!
return 0;
}
Tóm tắt Type Casting
| Cast Type | Mục đích | Độ an toàn |
|---|---|---|
(type) value |
Cast kiểu cũ (C-style) | Thấp - tránh sử dụng |
static_cast<T>() |
Chuyển đổi kiểu tương thích (int↔float, Base→Derived) | Cao |
const_cast<T>() |
Loại bỏ const/volatile qualifier | Trung bình |
reinterpret_cast<T>() |
Cast bất kỳ con trỏ sang con trỏ khác | Thấp - chỉ dùng khi cần thiết |
dynamic_cast<T>() |
Runtime type checking cho polymorphic types | Cao |
Câu hỏi ôn tập kiến thức
Khi nào bạn nên sử dụng const auto& thay vì auto trong range-based for
loop?
Tải mã nguồn mẫu bài học
Bạn có thể tải file mã nguồn C++ mẫu hoàn chỉnh của bài học này để tiến hành thực hành trực tiếp trên máy tính cá nhân.
Tải cpp_fundamentals.cpp
Comments
Bình luận