1. Không gian tên (Namespace)
Trong ngôn ngữ C, xung đột định danh là một vấn đề thường xuyên xảy ra, đặc biệt là khi tên biến người dùng tự định nghĩa trùng với tên hàm trong thư viện chuẩn, hoặc giữa các biến toàn cục trong các file khác nhau. Ngôn ngữ C không cung cấp cơ chế nội tại nào để giải quyết triệt để vấn đề này.
C++ đưa ra giải pháp thông qua từ khóa namespace. Không gian tên hoạt động như một bức tường bao vây các thực thể bên trong, ngăn chặn việc truy cập từ bên ngoài nếu không có sự cho phép. Để truy cập các phần tử này, ta sử dụng toán tử giải phạm vi ::.
#include <iostream>
#include <cstdlib>
namespace mySpace {
int countVal = 0; // Biến toàn cục nằm trong mySpace
}
int main() {
std::cout << rand << std::endl; // Gọi hàm rand từ cstdlib
std::cout << mySpace::countVal << std::endl; // Sử dụng :: để truy cập biến trong namespace
return 0;
}
Các namespace có thể lồng nhau và sẽ tự động được gộp lại nếu khai báo trùng tên ở các file khác nhau. Tuy nhiên, việc phải viết namespace_name:: liên tục có thể gây cồng kềnh. Ta có thể mở rộng toàn bộ namespace bằng cú pháp using namespace std;. Lưu ý, trong các dự án thực tế nên hạn chế mở rộng std để tránh xung đột tên, chỉ nên sử dụng trong các bài tập thực hành. Để tối ưu hơn, ta có thể mở rộng từng thành phần cụ thể, ví dụ: using std::cout;.
2. Tham số mặc định (Default Parameters)
C++ cho phép gán một giá trị dự phòng cho tham số của hàm khi khai báo. Nếu lúc gọi hàm không truyền đối số cho vị trí đó, giá trị mặc định sẽ được sử dụng; ngược lại, đối số truyền vào sẽ được ưu tiên.
Tham số mặc định được chia làm hai loại:
- Toàn bộ mặc định: Tất cả tham số đều được gán giá trị dự phòng. Khi gọi hàm, đối số phải được truyền từ trái sang phải, không được bỏ trống ở giữa.
- Một phần mặc định: Chỉ một số tham số có giá trị mặc định. Các tham số này bắt buộc phải nằm liên tiếp từ phải sang trái.
Một quy tắc quan trọng: Giá trị mặc định chỉ nên được khai báo ở phần khai báo hàm (thường nằm trong file header), không được khai báo đồng thời cả ở phần định nghĩa (implementation) để tránh sự không nhất quán khi biên dịch.
3. Nạp chồng hàm (Function Overloading)
C không cho phép tồn tại nhiều hàm cùng tên. C++ khắc phục điều này bằng cách cho phép định nghĩa nhiều hàm có cùng tên trong cùng một phạm vi, miễn là danh sách tham số của chúng khác biệt về số lượng, kiểu dữ liệu hoặc thứ tự kiểu. Kiểu trả về không được dùng để phân biệt hàm nạp chồng, vì trình biên dịch không thể xác định hàm nào đang được gọi nếu chỉ dựa vào giá trị trả về.
Cơ chế hoạt động dựa vào quy tắc chỉnh sửa tên (Name Mangling). Trong C, tên hàm khi liên kết (link) giữ nguyên, dẫn đến xung đột nếu trùng tên. Ngược lại, C++ mã hóa tên hàm bao gồm cả thông tin về tham số (ví dụ: _Z3addii) để tạo ra một ký hiệu duy nhất cho từng hàm nạp chồng.
4. Tham chiếu (References)
Tham chiếu không phải là một biến mới, mà đơn thuần là một biệt danh (alias) trỏ đến một biến đã tồn tại, chia sẻ chung một vùng nhớ.
- Truyền tham số: Tham chiếu giúp đơn giản hóa thao tác với con trỏ. Ví dụ, khi chèn node vào danh sách liên kết rỗng, thay vì phải dùng con trỏ kép để thay đổi con trỏ head, ta chỉ cần dùng tham chiếu đến con trỏ đó.
- Giới hạn: Tham chiếu trong C++ bắt buộc phải khởi tạo khi khai báo và không thể thay đổi để tham chiếu đến đối tượng khác sau đó (đây là điểm khác biệt lớn so với tham chiếu trong Java). Do đó, C++ không thể thay thế hoàn toàn con trỏ bằng tham chiếu.
- Giá trị trả về: Trả về bằng tham chiếu giúp tăng hiệu suất (tránh sao chép) và cho phép sửa đổi trực tiếp đối tượng trả về. Tuy nhiên, tuyệt đối không trả về tham chiếu của biến cục bộ vì vùng nhớ của biến đó sẽ bị hủy khi hàm kết thúc, dẫn đến hành vi không xác định. Chỉ nên dùng tham chiếu trả về cho các đối tượng có vòng đời dài hơn hàm (như biến
statichoặc biến thành viên).
Về mặt cú pháp, tham chiếu không chiếm thêm không gian lưu trữ, nhưng ở cấp độ ngôn ngữ máy (assembly), tham chiếu vẫn được triển khai dưới dạng con trỏ.
5. Hàm nội tuyến (Inline Functions)
Macro trong C có ưu điểm là không tạo khung ngăn xếp (stack frame) nên tốc độ thực thi nhanh. Tuy nhiên, nó có quá nhiều nhược điểm: dễ gây lỗi cú pháp do thiếu dấu ngoặc, không có kiểm tra kiểu an toàn, và rất khó để gỡ lỗi (debug).
C++ giới thiệu hàm nội tuyến với từ khóa inline. Khi biên dịch, trình biên dịch sẽ chèn trực tiếp mã lệnh của hàm nội tuyến vào vị trí gọi, loại bỏ hoàn toàn chi phí tạo và hủy stack frame.
inline int calculateSum(int a, int b) {
return a + b;
}
Tuy nhiên, cơ chế này chỉ nên được áp dụng cho các hàm có dung lượng mã nhỏ (dưới 10 dòng). Nếu lạm dụng inline cho các hàm lớn hoặc được gọi quá thường xuyên, kích thước của mã nhị phân (binary) sẽ bị phình to nghiêm trọng (code bloat).
Hơn nữa, hàm nội tuyến không nên được tách riêng phần khai báo và định nghĩa vào các file khác nhau. Do hàm được chèn trực tiếp vào mã gọi, nó sẽ không tạo ra địa chỉ hàm riêng, khiến quá trình liên kết (linking) thất bại nếu trình liên kết cố gắng tìm kiếm địa chỉ đó ở file khác.