Giả sử bạn đang lái xe đến nhà một người bạn lần đầu tiên và địa chỉ cho bạn là 245 Front Street ở Mill City. Khi đến Thành phố Mill, bạn kéo bản đồ lên, chỉ để phát hiện ra rằng Thành phố Mill thực sự có hai tên Front Street khác nhau trên khắp thị trấn! Bạn sẽ đi đến cái nào? Trừ khi có thêm manh mối nào đó để giúp bạn quyết định (ví dụ: bạn nhớ nhà của anh ấy ở gần sông), bạn sẽ phải gọi cho bạn mình và hỏi thêm thông tin. Bởi vì điều này sẽ gây nhầm lẫn và không hiệu quả (đặc biệt là đối với người đưa thư của bạn), ở hầu hết các quốc gia, tất cả tên đường và địa chỉ nhà trong thành phố bắt buộc phải là duy nhất.

Tương tự, C ++ yêu cầu tất cả các định danh không được mơ hồ. Nếu hai ký tự nhận dạng giống hệt nhau được đưa vào cùng một chương trình theo cách mà trình biên dịch hoặc trình liên kết không thể phân biệt chúng, trình biên dịch hoặc trình liên kết sẽ tạo ra lỗi. Lỗi này thường được gọi là xung đột đặt tên.

1. Ví dụ về xung đột đặt tên

a.cpp:

#include <iostream>
 
void myFcn(int x)
{
    std::cout << x;
}

main.cpp:

/*
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 myFcn(int x)
{
    std::cout << 2 * x;
}
 
int main()
{
    return 0;
}

Khi trình biên dịch biên dịch chương trình này, nó sẽ biên dịch a.cpp và main.cpp một cách độc lập, và mỗi tệp sẽ biên dịch không có vấn đề gì.

Tuy nhiên, khi trình liên kết thực thi, nó sẽ liên kết tất cả các định nghĩa trong a.cpp và main.cpp với nhau, đồng thời phát hiện ra các định nghĩa xung đột cho hàm myFcn. Trình liên kết sau đó sẽ hủy bỏ do lỗi. Lưu ý rằng lỗi này xảy ra mặc dù myFcn không bao giờ được gọi!

Hầu hết các xung đột đặt tên xảy ra trong hai trường hợp:

1) Hai (hoặc nhiều) định nghĩa cho một hàm (hoặc biến toàn cục) được đưa vào các tệp riêng biệt được biên dịch vào cùng một chương trình. Điều này sẽ dẫn đến lỗi trình liên kết, như được hiển thị ở trên.

2) Hai (hoặc nhiều) định nghĩa cho một hàm (hoặc biến toàn cục) được đưa vào cùng một tệp (thường thông qua #include). Điều này sẽ dẫn đến lỗi trình biên dịch.

Khi các chương trình ngày càng lớn hơn và sử dụng nhiều ký hiệu nhận dạng hơn, khả năng xảy ra va chạm đặt tên được đưa vào sẽ tăng lên đáng kể. Tin tốt là C ++ cung cấp rất nhiều cơ chế để tránh va chạm đặt tên. Phạm vi cục bộ, giữ cho các biến cục bộ được xác định bên trong các hàm khỏi xung đột với nhau, là một trong những cơ chế như vậy. Nhưng phạm vi cục bộ không hoạt động đối với tên hàm. Vậy làm cách nào để giữ cho các tên hàm không bị xung đột với nhau?

2. namespace là gì?

Quay lại sự tương đồng về địa chỉ của chúng ta trong giây lát, việc có hai đường phía trước chỉ là vấn đề vì những con đường đó tồn tại trong cùng một thành phố. Mặt khác, nếu bạn phải chuyển thư đến hai địa chỉ, một ở 209 Front Street ở Mill City, và một địa chỉ khác ở 417 Front Street ở Jonesville, thì sẽ không có gì phải phân vân. Nói một cách khác, các thành phố cung cấp các nhóm cho phép chúng ta xác định các địa chỉ có thể xung đột với nhau. namespace hoạt động giống như các thành phố trong trường hợp tương tự này.

namespace là một vùng cho phép bạn khai báo các tên bên trong nó nhằm mục đích phân định. namespace cung cấp một phạm vi (được gọi là phạm vi của namespace) cho các tên được khai báo bên trong nó – điều này đơn giản có nghĩa là bất kỳ tên nào được khai báo bên trong namespace sẽ không bị nhầm với các tên giống hệt nhau trong các phạm vi khác.

Tên được khai báo trong namespace sẽ không bị nhầm lẫn với tên giống được khai báo trong phạm vi khác.

Trong một namespace, tất cả các tên phải là duy nhất, nếu không sẽ dẫn đến xung đột đặt tên.

namespace thường được sử dụng để nhóm các số nhận dạng có liên quan trong một dự án lớn để giúp đảm bảo chúng không vô tình xung đột với các số nhận dạng khác. Ví dụ: nếu bạn đặt tất cả các hàm toán học của mình trong một namespace gọi là math, thì các hàm toán học của bạn sẽ không xung đột với các hàm có tên giống hệt nhau bên ngoài namespace math.

Chúng ta sẽ nói về cách tạo không gian tên của riêng bạn trong một bài học trong tương lai.

3. namespace toàn cục

Trong C ++, bất kỳ tên nào không được xác định bên trong một lớp, hàm hoặc một namespace được coi là một phần của namespace được xác định ngầm được gọi là namespace toàn cục (đôi khi còn được gọi là phạm vi toàn cục).

Trong ví dụ ở đầu bài học, các hàm main() và cả hai phiên bản của myFcn() đều được định nghĩa bên trong namespace toàn cục. Xung đột đặt tên gặp phải trong ví dụ xảy ra vì cả hai phiên bản của myFcn() đều nằm trong vùng namespace toàn cục, vi phạm quy tắc rằng tất cả các tên trong vùng tên phải là duy nhất.

4. namespace std

Khi C ++ ban đầu được thiết kế, tất cả các mã định danh trong thư viện chuẩn C ++ (bao gồm std :: cin và std :: cout) đều có sẵn để sử dụng mà không có tiền tố std :: (chúng là một phần của namespace toàn cục). Tuy nhiên, điều này có nghĩa là bất kỳ ký hiệu  nhận dạng nào trong thư viện chuẩn đều có khả năng xung đột với bất kỳ tên nào bạn đã chọn cho ký hiệu nhận dạng của riêng mình (cũng được xác định trong namespace toàn cục). Mã đang hoạt động có thể đột nhiên có xung đột đặt tên khi bạn #included một tệp mới từ thư viện chuẩn. Hoặc tệ hơn, các chương trình sẽ biên dịch theo một phiên bản C ++ có thể không biên dịch theo phiên bản C ++ trong tương lai, vì số nhận dạng mới được đưa vào thư viện chuẩn có thể có xung đột đặt tên với mã đã được viết sẵn. Vì vậy, C ++ đã chuyển tất cả các hàm trong thư viện chuẩn vào một namespace có tên là “std” (viết tắt của standard).

Hóa ra tên của std :: cout không thực sự là std :: cout. Nó thực sự chỉ là cout và std là tên của namespace mà cout nhận dạng là một phần của nó. Vì cout được xác định trong namespace std nên tên cout sẽ không xung đột với bất kỳ đối tượng hoặc hàm nào có tên cout mà chúng ta tạo trong namespace toàn cục.

Tương tự, khi truy cập số nhận dạng được xác định trong namespace (ví dụ: std :: cout), bạn cần thông báo với trình biên dịch rằng chúng ta đang tìm kiếm số nhận dạng được xác định bên trong namespace (std).

Lưu ý:

Khi bạn sử dụng một số nhận dạng được xác định bên trong một namespace (chẳng hạn như namespace std), bạn phải cho trình biên dịch biết rằng ký hiệu nhận dạng nằm bên trong namespace

Có một số cách khác nhau để làm điều này.

5. Định nghĩa namespace rõ ràng với std ::

Cách đơn giản nhất để nói với trình biên dịch rằng chúng ta muốn sử dụng cout từ namespace std là sử dụng tiền tố std :: một cách rõ ràng. Ví dụ:

#include <iostream>
 
int main()
{
    std::cout << "Hello world!"; // when we say cout, we mean the cout defined in the std namespace
    return 0;
}

Ký hiệu :: là một toán tử được gọi là toán tử phân giải phạm vi. Định danh ở bên trái của biểu tượng :: xác định namespace mà tên ở bên phải biểu tượng :: được chứa bên trong. Nếu không có code định danh nào ở bên trái của biểu tượng :: được cung cấp, thì namespace toàn cục sẽ được giả định.

Vì vậy, khi chúng ta nói std :: cout, chúng ta đang nói “cout sống trong namespace std”.

Đây là cách an toàn nhất để sử dụng cout, vì không có sự mơ hồ nào về cout mà chúng ta đang tham chiếu (một namespace tên std).

Bạn nên

Sử dụng tiền tố namespace rõ ràng để truy cập ký hiệu nhận dạng được xác định trong namespace.

6. Sử dụng namespace std (và tại sao phải tránh nó)

Một cách khác để truy cập ký hiệu nhận dạng bên trong namespace là sử dụng câu lệnh using. Đây là chương trình “Hello world” ban đầu của chúng ta với chỉ thị sử dụng:

#include <iostream>
 
using namespace std; // this is a using directive telling the compiler to check the std namespace when resolving identifiers with no prefix
 
int main()
{
    cout << "Hello world!"; // cout has no prefix, so the compiler will check to see if cout is defined locally or in namespace std
    return 0;
}

Một chỉ thị sử dụng yêu cầu trình biên dịch kiểm tra một namespace cụ thể khi cố gắng giải quyết một mã định danh không có tiền tố namespace. Vì vậy, trong ví dụ trên, khi trình biên dịch xác định cout định danh là gì, nó sẽ kiểm tra cục bộ (nơi nó không được xác định) và trong namespace std (nơi nó sẽ khớp với std :: cout).

Nhiều văn bản, hướng dẫn và thậm chí một số trình biên dịch giới thiệu hoặc sử dụng chỉ thị using ở đầu chương trình. Tuy nhiên, sử dụng theo cách này, đây là một cách làm không tốt và rất không được khuyến khích.

Hãy xem xét chương trình sau:

/*
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> // imports the declaration of std::cout
 
using namespace std; // makes std::cout accessible as "cout"
 
int cout() // declares our own "cout" function
{
    return 5;
}
 
int main()
{
    cout << "Hello, world!"; // Compile error!  Which cout do we want here?  The one in the std namespace or the one we defined above?
 
    return 0;
}

Chương trình trên không biên dịch, bởi vì trình biên dịch bây giờ không thể cho biết liệu chúng ta muốn hàm cout mà chúng ta đã xác định hay hàm cout được xác định bên trong namespace std.

Khi sử dụng chỉ thị using theo cách này, bất kỳ số nhận dạng nào mà chúng ta xác định có thể xung đột với bất kỳ số nhận dạng được đặt tên giống nhau nào trong namespace std. Thậm chí tệ hơn, mặc dù tên định danh có thể không xung đột ngày hôm nay, nhưng nó có thể xung đột với ký hiệu nhận dạng mới được thêm vào namespace std trong các bản sửa đổi ngôn ngữ trong tương lai. Đây là toàn bộ điểm của việc di chuyển tất cả các ký hiệu nhận dạng trong thư viện chuẩn vào namespace std ngay từ đầu!

Lưu ý

Tránh sử dụng các chỉ thị (chẳng hạn như using namespace std;) ở đầu chương trình của bạn. Chúng vi phạm lý do tại sao namespaces được thêm vào ngay từ đầu.

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!