Ngôn ngữ lập trình C++ - Chương 8: Các chủ đề khác
Giả sử ta sử dụng hai thư viện chứa một số lớp/hàm tiện ích, nhưng một số tên trùng nhau
Ngay cả khi không sử dụng Stack và func, ta vẫn gặp rắc rối
khi include cả hai thư viện:
Trình biên dịch báo lỗi
trùng khai báo Stack
Trình liên kết báo lỗi
trùng khai báo func
Ngôn ngữ lập trình C++Chương 8 – Các chủ đề khác Chương 8: Các chủ đề khác Đề mục8.1 Không gian tên 8.2 Định hướng tiền xử lý 8.3 typedef 8.4 Các toán tử bit. 8.1 Vấn đề trùng tên Giả sử ta sử dụng hai thư viện chứa một số lớp/hàm tiện ích, nhưng một số tên trùng nhau Ngay cả khi không sử dụng Stack và func, ta vẫn gặp rắc rối khi include cả hai thư viện: Trình biên dịch báo lỗi trùng khai báo Stack Trình liên kết báo lỗi trùng khai báo func // File: "gnutils.h" class Stack { ... }; class Some_Class{ ... }; void gnome(); int func(int); // File: "msutils.h" class Stack { ... }; class Other_Class{ ... }; void windows2000(); int func(int); #include "gnutils.h" #include "msutils.h" int main() { Some_Class sc; Other_Class oc; ... if (choice == LINUX) gnome(); else if (choice == MSWINDOWS) windows2000(); return 0; } 8.1 Giải pháp: namespace sử dụng namespace, vấn đề tên trùng sẽ không xảy ra namespace định nghĩa phạm vi đặt các định danh/biến bên trong namespace đảm bảo tính duy nhất của định danh các namespace không có tên thuộc phạm vi toàn cục không cần dùng toán tử phạm vi để truy cập Các namespace có thể lồng nhau namespace gnu { class Stack { ... }; class Some_Class{ ... }; void gnome(); int func( int); } namespace microsoft { class Stack { ... }; class Other_Class{ ... }; void windows2000(); int func( int); } 8.1 Giải pháp: namespace Khi sử dụng, dùng toán tử phạm vi (::) để gọi tên trong các namespace #include "gnutils.h" #include "msutils.h" namespace ms = microsoft; // namespace alias int main() { gnu::Some_Class sc; gnu::Stack gnu_stack; ms::Other_Class oc; ms::Stack ms_stack; int i = ms::func( 42 ); if (choice == LINUX) gnu::gnome(); else if (choice == MSWINDOWS) ms::windows2000(); return 0; } 8.1 Khai báo using Có thể sử dụng khai báo using để tránh gọi tên của namespace mỗi khi gọi đến một tên trong namespace đó #include "gnutils.h" #include "msutils.h" namespace ms = microsoft; // namespace alias using gnu::Some_Class; using gnu::Stack; using ms::Other_Class; using ms::func; int main() { Some_Class sc; // refers to gnu::SomeClass Other_Class oc; // refers to ms::OtherClass Stack gnu_stack; // refers to gnu::Stack ms::Stack ms_stack; int i = func( 42 ); // refers to ms::func return 0; } 8.1 Đa nghĩa với khai báo using Có thể đưa tất cả các tên trong một namespace vào chương trình ngay một lần,nhưng phải đảm bảo việc đó sẽ không gây đa nghĩa #include "gnutils.h" #include "msutils.h" namespace ms = microsoft; // namespace alias using namespace gnu; using namespace ms; int main() { Some_Class sc; // refers to gnu::SomeClass Other_Class oc; // refers to ms::OtherClass Stack S; // error: ambiguous ms::Stack ms_stack; // ok gnu::Stack gnu_stack; // ok return 0; } 8.1 Chú ý Có thể vừa dùng khai báo using vừa gọi tên tường minh bằng toán tử phạm vi. Có người cho rằng đây chỉ là vấn đề ý thích cá nhân. Nhưng nó cũng có ảnh hưởng đến mức độ tái sử dụng của mã nguồn. Đối với g++ trước phiên bản 3, các lớp và hàm của thư viện chuẩn (trong đó có STL) không được định nghĩa trong namespace std, mà trong namespace toàn cục. Nếu ta sử dụng g++ cổ hơn phiên bản 3, ta sẽ không bị báo lỗi nếu quên sử dụng khai báo using. Tuy nhiên, từ g++ phiên bản 3, ta nên nhớ dùng. Microsoft VC++ cài đặt namespace std theo chuẩn. 8.2.1 Giới thiệu về tiền xử lý Tiền xử lý – Preprocessing xảy ra trước khi chương trình được dịch, bao gồm kết nối (include) các file ngoại vi định nghĩa các hằng ký hiệu macro biên dịch theo điều kiện (conditional compilation) thực thi theo điều kiện (conditional execution) tất cả các định hướng bắt đầu bằng # chỉ có thể có các ký tự trắng đằng trước các định hướng các định hướng không phải là lệnh C++ không kết thúc bằng dấu chấm phảy ; 8.2.2 Định hướng tiền xử lý #include định hướng #include chép file vào vị trí của định hướng ta đã thấy rất nhiều lần trong các ví dụ hai dạng #include dành cho các file header thư viện chuẩn tìm trong các thư mục đã định sẵn #include "filename" tìm trong thư mục hiện hành thường dùng cho các file lập trình viên tự viết 8.2.3 Định hướng #define vàcác hằng ký hiệu #define các hằng ký hiệu các hằng được biểu diễn bằng các ký hiệu khi chương trình được biên dịch, mọi xuất hiện của ký hiệu được thay thế Format #define identifier replacement-text #define PI 3.14159 mọi thứ bên phải định danh sẽ thay thế ký hiệu #define PI=3.14159 thay PI bằng "=3.14159" có thể gây lỗi không thể định nghĩa lại các hằng 8.2.3 Định hướng #define vàcác hằng ký hiệu thuận lợi không chiếm bộ nhớ bất lợi trình debug không nhìn thấy tên mà chỉ thấy phần text đã thay thế không có kiểu dữ liệu cụ thể nên dùng các biến hằng (const) 8.2.3 Định hướng #definevà các hằng ký hiệu Macro thao tác được định nghĩa bởi #define dành cho các chương trình C macro không có đối số tương tự hằng ký hiệu macro có đối số đối số được thay vào phần text thay thế macro được khai triển thực hiện thay thế text không kiểm tra kiểu dữ liệu 8.2.3 Định hướng #definevà các hằng ký hiệu Ví dụ #define CIRCLE_AREA( x ) ( PI * ( x ) * ( x ) ) area = CIRCLE_AREA( 4 ); trở thành area = ( 3.14159 * ( 4 ) * ( 4 ) ); cần sử dụng ngoặc nếu không, #define CIRCLE_AREA( x ) PI * x * x area = CIRCLE_AREA( c + 2 ); trở thành area = 3.14159 * c + 2 * c + 2; sai 8.2.3 Định hướng #definevà các hằng ký hiệu Nhiều đối số #define RECTANGLE_AREA( x, y ) ( ( x ) * ( y ) ) rectArea = RECTANGLE_AREA( a + 4, b + 7 ); trở thành rectArea = ( ( a + 4 ) * ( b + 7 ) ); #undef hủy định nghĩa hằng ký hiệu hoặc macro có thể được định nghĩa lại sau 8.2.3 Biên dịch theo điều kiện cấu trúc tương tự if #if !defined( NULL ) #define NULL 0 #endif xác định xem hằng ký hiệu NULL có được định nghĩa hay không nếu NULL được định nghĩa, defined( NULL ) có giá trị bằng 1 lệnh #define bị bỏ qua nếu không thực hiện lệnh #define mỗi #if kết thúc với một #endif 8.2.3 Biên dịch theo điều kiện ngoài ra có thể dùng #else #elif là "else if" viết tắt #ifdef là viết tắt của #if defined(name) #ifndef là viết tắt của #if !defined(name) 8.2.3 Biên dịch theo điều kiện "Comment out" code có thể dùng /* ... */ nhưng không thể lồng nhau thay vào đó, sử dụng #if 0 code commented out #endif để cho đoạn mã đó được biên dịch, đổi 0 thành 1 8.2.3 Biên dịch theo điều kiện Tìm lỗi – Debugging #define DEBUG 1 #ifdef DEBUG cerr kiểm tra giá trị của một biểu thức nếu là 0 (false) in thông báo lỗi, gọi abort kết thúc chương trình, in ra số thứ tự dòng và file dùng để kiểm tra các giá trị không hợp lệ nếu là 1 (true), chương trình chạy tiếp như bình thường assert( x > (dịch phải có dấu) Dịch tất cả các bit sang trái một khoảng xác định 8.4 Các toán tử bit Chương trình ví dụ In các giá trị theo biểu diễn nhị phân Ví dụ: unsigned int 3 (máy tính 4-byte) 00000000 00000000 00000000 00000011 Sử dụng mask Giá trị nguyên với một số bit cụ thể có giá trị 1 sử dụng để che một số bit khi chọn các bit khác Dùng với AND 8.4 Các toán tử bit Ví dụ về mask giả sử ta muốn xem bit bên trái nhất của một số AND với mask 10000000 00000000 00000000 00000000 (mask) 10010101 10110000 10101100 00011000 (số) Nếu bit trái nhất của số cần quan tâm bằng 1 Kết quả của phép AND sẽ khác 0 Bit trái nhất của kết quả sẽ bằng 1 Mọi bit khác bị che ("masked off“) do bị AND với 0 Nếu bit trái nhất bằng 0 Kết quả phép AND sẽ bằng 0 8.4 Các toán tử bit Cách in từng bit In bit trái nhất Dịch số sang trái một vị trí Lặp lại Cách tạo mask Ví dụ, tạo mask 1000000 … 0000 số bit chứa trong unsigned sizeof(unsigned) * 8 bắt đầu bằng mask 1 (00...01), dịch trái 1 4 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 9 #include 10 11 using std::setw; 12 13 void displayBits( unsigned ); // prototype 14 15 int main() 16 { 17 unsigned inputValue; 18 19 cout > inputValue; 21 displayBits( inputValue ); 22 23 return 0; 24 25 } // end main 26 fig18_05.cpp(2 of 2)fig18_05.cppoutput (1 of 1) 27 // display bits of an unsigned integer value 28 void displayBits( unsigned value ) 29 { 30 const int SHIFT = 8 * sizeof( unsigned ) - 1; 31 const unsigned MASK = 1 > (dịch trái và dịch phải) fig18_07.cpp(1 of 4) 1 // Fig. 18.7: fig18_07.cpp 2 // Using the bitwise AND, bitwise inclusive OR, bitwise 3 // exclusive OR and bitwise complement operators. 4 #include 5 6 using std::cout; 7 using std::cin; 8 9 #include 10 11 using std::endl; 12 using std::setw; 13 14 void displayBits( unsigned ); // prototype 15 16 int main() 17 { 18 unsigned number1; 19 unsigned number2; 20 unsigned mask; 21 unsigned setBits; 22 fig18_07.cpp(2 of 4) 23 // demonstrate bitwise & 24 number1 = 2179876355; 25 mask = 1; 26 cout 4 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 9 #include 10 11 using std::setw; 12 13 void displayBits( unsigned ); // prototype 14 15 int main() 16 { 17 unsigned number1 = 960; 18 19 // demonstrate bitwise left shift 20 cout > 8 ); 32 33 return 0; 34 35 } // end main 36 37 // display bits of an unsigned integer value 38 void displayBits( unsigned value ) 39 { 40 const int SHIFT = 8 * sizeof( unsigned ) - 1; 41 const unsigned MASK = 1 << SHIFT; 42 43 cout << setw( 10 ) << value << " = "; 44 45 for ( unsigned i = 1; i <= SHIFT + 1; i++ ) { 46 cout << ( value & MASK ? '1' : '0' ); 47 value <<= 1; // shift value left by 1 48 49 if ( i % 8 == 0 ) // output a space after 8 bits 50 cout << ' '; 51 52 } // end for fig18_07.cpp(3 of 3)fig18_07.cppoutput (1 of 1) 53 54 cout << endl; 55 56 } // end function displayBits The result of left shifting 960 = 00000000 00000000 00000011 11000000 8 bit positions using the left shift operator is 245760 = 00000000 00000011 11000000 00000000 The result of right shifting 960 = 00000000 00000000 00000011 11000000 8 bit positions using the right shift operator is 3 = 00000000 00000000 00000000 00000011
File đính kèm:
- Ngôn ngữ lập trình C++ - Chương 8 Các chủ đề khác.ppt