24/05/2018, 19:21

Namespace và các chỉ dẫn biên dịch

Việc sử dụng namespace trong khi lập trình là một thói quen tốt, bởi vì công việc này chính là cách lưu các mã nguồn để sử dụng về sau. Ngoài thư viện namespace do MS.NET và các hãng thứ ba cung cấp, ta có thể tạo riêng cho mình các ...

Việc sử dụng namespace trong khi lập trình là một thói quen tốt, bởi vì công việc này chính là cách lưu các mã nguồn để sử dụng về sau. Ngoài thư viện namespace do MS.NET và các hãng thứ ba cung cấp, ta có thể tạo riêng cho mình các namespace. C# đưa ra từ khóa using đề khai báo sử dụng namespace trong chương trình:

using < Tên namespace >

Để tạo một namespace dùng cú pháp sau:

namespace <Tên namespace>
    {
    < Định nghĩa lớp A>
    
    < Định nghĩa lớp B >
    .....
    }
    

Đoạn ví dụ sau minh họa việc tạo một namespace.

Tạo một namespace.

-----------------------------------------------------------------------------

namespace MyLib

{

using System;

public class Tester

{

public static int Main()

{

for (int i =0; i < 10; i++)

{

Console.WriteLine( "i: {0}", i);

}

return 0;

}

}

}

-----------------------------------------------------------------------------

Ví dụ trên tạo ra một namespace có tên là MyLib, bên trong namespace này chứa một lớp có tên là Tester. C# cho phép trong một namespace có thể tạo một namespace khác lồng bên trong và không giới hạn mức độ phân cấp này, việc phân cấp này được minh họa trong ví dụ dưới đây.

Tạo các namespace lồng nhau.

-----------------------------------------------------------------------------

namespace MyLib

{

namespace Demo

{

using System;

public class Tester

{

public static int Main()

{

for (int i =0; i < 10; i++)

{

Console.WriteLine( "i: {0}", i);

}

return 0;

}

}

}

}

-----------------------------------------------------------------------------

Lớp Tester trong ví dụ được đặt trong namespace Demo do đó có thể tạo một lớp Tester khác bên ngoài namespace Demo hay bên ngoài namespace MyLib mà không có bất cứ sự tranh cấp hay xung đột nào. Để truy cập lớp Tester dùng cú pháp sau:

MyLib.Demo.Tester

Trong một namespace một lớp có thể gọi một lớp khác thuộc các cấp namespace khác nhau, ví dụ tiếp sau minh họa việc gọi một hàm thuộc một lớp trong namespace khác.

Gọi một namespace thành viên.

-----------------------------------------------------------------------------

using System;

namespace MyLib

{

namespace Demo1

{

class Example1

{

public static void Show1()

{

Console.WriteLine("Lop Example1");

}

}

}

namespace Demo2

{

public class Tester

{

public static int Main()

{

Demo1.Example1.Show1(); Demo1.Example2.Show2(); return 0;

}

}

}

}

// Lớp Example2 có cùng namespace MyLib.Demo1 với

//lớp Example1 nhưng hai khai báo không cùng một khối.

namespace MyLib.Demo1

{

class Example2

{

public static void Show2()

{

Console.WriteLine("Lop Example2");

}

}

}

-----------------------------------------------------------------------------

Kết quả:

Lop Exemple1

Lop Exemple2

-----------------------------------------------------------------------------

Ví dụ trên có hai điểm cần lưu ý là cách gọi một namespace thành viên và cách khai báo các namspace. Như chúng ta thấy trong namespace MyLib có hai namespace con cùng cấp là Demo1 và Demo2, hàm Main của Demo2 sẽ được chương trình thực hiện, và trong hàm Main này có gọi hai hàm thành viên tĩnh của hai lớp Example1 và Example2 của namespace Demo1.

Ví dụ trên cũng đưa ra cách khai báo khác các lớp trong namespace. Hai lớp Example1 và Example2 điều cùng thuộc một namespace MyLib.Demo1, tuy nhiên Example2 được khai báo một khối riêng lẻ bằng cách sử dụng khai báo:

namespace MyLib.Demo1
    {
    class Example2
    {
    ....
    }
    }
    

Việc khai báo riêng lẻ này có thể cho phép trên nhiều tập tin nguồn khác nhau, miễn sao đảm bảo khai báo đúng tên namspace thì chúng vẫn thuộc về cùng một namespace.

Đối với các ví dụ minh họa trong các phần trước, khi biên dịch thì toàn bộ chương trình sẽ được biên dịch. Tuy nhiên, có yêu cầu thực tế là chúng ta chỉ muốn một phần trong chương trình được biên dịch độc lập, ví dụ như khi debug chương trình hoặc xây dựng các ứng dụng...

Trước khi một mã nguồn được biên dịch, một chương trình khác được gọi là chương trình tiền xử lý sẽ thực hiện trước và chuẩn bị các đoạn mã nguồn để biên dịch. Chương trình tiền xử lý này sẽ tìm trong mã nguồn các kí hiệu chỉ dẫn biên dịch đặc biệt, tất cả các chỉ dẫn biên dịch này đều được bắt đầu với dấu rào (#). Các chỉ dẫn cho phép chúng ta định nghĩa các định danh và kiểm tra các sự tồn tại của các định danh đó.

Định nghĩa định danh

Câu lệnh tiền xử lý sau:

#define DEBUG

Lệnh trên định nghĩa một định danh tiền xử lý có tên là DEBUG. Mặc dù những chỉ thị tiền xử lý khác có thể được đặt bất cứ ở đâu trong chương trình, nhưng với chỉ thị định nghĩa định danh thì phải đặt trước tất cả các lệnh khác, bao gồm cả câu lệnh using.

Để kiểm tra một định danh đã được định nghĩa thì ta dùng cú pháp #if <định danh>. Do đó ta có thể viết như sau:

#define DEBUG

//...Các đoạn mã nguồn bình thường, không bị tác động bởi trình tiền xử lý

...

#if DEBUG

// Các đoạn mã nguồn trong khối if debug được biên dịch

#else

// Các đoạn mã nguồn không định nghĩa debug và không được biên dịch

#endif

//...Các đoạn mã nguồn bình thường, không bị tác động bởi trình tiền xử lý

Khi chương trình tiền xử lý thực hiện, chúng sẽ tìm thấy câu lệnh #define DEBUG và lưu lại định danh DEBUG này. Tiếp theo trình tiền xử lý này sẽ bỏ qua tất cả các đoạn mã bình thường khác của C# và tìm các khối #if, #else, và #endif.

Câu lệnh #if sẽ kiểm tra định danh DEBUG, do định danh này đã được định nghĩa, nên đoạn mã nguồn giữa khối #if đến #else sẽ được biên dịch vào chương trình. Còn đoạn mã nguồn giữa #else và #endif sẽ không được biên dịch. Tức là đoạn mã nguồn này sẽ không được thực hiện hay xuất hiện bên trong mã hợp ngữ của chương trình.

Trường hợp câu lệnh #if sai tức là không có định nghĩa một định danh DEBUG trong chương trình, khi đó đoạn mã nguồn ở giữa khối #if và #else sẽ không được đưa vào chương trình để biên dịch mà ngược lại đoạn mã nguồn ở giữa khối #else và #endif sẽ được biên dịch.

Tất cả các đoạn mã nguồn bên ngoài #if và #endif thì không bị tác động bởi trình tiền xử lý và tất cả các mã này đều được đưa vào để biên dịch.

Không định nghĩa định danh

Sử dụng chỉ thị tiền xử lý #undef để xác định trạng thái của một định danh là không được định nghĩa. Như chúng ta đã biết trình tiền xử lý sẽ thực hiện từ trên xuống dưới, do vậy một định danh đã được khai báo bên trên với chỉ thị #define sẽ có hiệu quả đến khi một gọi câu lệnh #undef định danh đó hay đến cuối chương trình:

#define DEBUG
    #if DEBUG
    // Đoạn code này được biên dịch
    #endif
    ....
    #undef DEBUG
    ....
    #if DEBUG
    // Đoạn code này không được biên dịch 
    #endif
    .....
    

#if đầu tiên đúng do DEBUG được định nghĩa, còn #if thứ hai sai không được biên dịch vì DEBUG đã được định nghĩa lại là #undef.

Ngoài ra còn có chỉ thị #elif và #else cung cấp các chỉ dẫn phức tạp hơn. Chỉ dẫn #elif cho phép sử dụng logic “else-if”. Ta có thể diễn giải một chỉ dẫn như sau: “Nếu DEBUG thì làm công việc 1, ngược lại nếu TEST thì làm công việc 2, nếu sai tất cả thì làm trường hợp 3”:

....
    #if DEBUG
    // Đoạn code này được biên dịch nếu DEBUG được định nghĩa
    #elif TEST
    //Đoạn code này được biên dịch nếu DEBUG không được định nghĩa
    // và TEST được định nghĩa
    #else
    //Đoạn code này được biên dịch nếu cả DEBUG và
    //TEST không được định nghĩa.
    #endif
    ....
    

Trong ví dụ trên thì chỉ thị tiền xử lý #if đầu tiên sẽ kiểm tra định danh DEBUG, nếu định danh DEBUG đã được định nghĩa thì đoạn mã nguồn ở giữa #if và #elif sẽ được biên dịch, và tất cả các phần còn lại cho đến chỉ thị #endif đều không được biên dịch. Nếu DEBUG không được định nghĩa thì #elif sẽ kiểm tra định danh TEST, đoạn mã ở giữa #elif và #else sẽ được thực thi khi TEST được định nghĩa. Cuối cùng nếu cả hai DEBUG và TEST đều không được định nghĩa thì các đoạn mã nguồn giữa #else và #endif sẽ được biên dịch.

Câu hỏi và trả lời

Sự khác nhau giữa dựa trên thành phần (Component-Based) và hướng đối tượng (Object- Oriented)?

Phát triển dựa trên thành phần có thể được xem như là mở rộng của lập trình hướng đối tượng. Một thành phần là một khối mã nguồn riêng có thể thực hiện một nhiệm vụ đặc biệt. Lập trình dựa trên thành phần bao gồm việc tạo nhiều các thành phần tự hoạt động có thể được dùng lại. Sau đó chúng ta có thể liên kết chúng lại để xây dựng các ứng dụng.

Những ngôn ngữ nào khác được xem như là hướng đối tượng?

Các ngôn ngữ như là C++, Java, SmallTalk, Visual Basic.NET cũng có thể được sử dụng cho lập trình hướng đối tượng. Còn rất nhiều những ngôn ngữ khác nhưng không được phổ biến lắm.

Tại sao trong kiểu số không nên khai báo kiểu dữ liệu lớn thay vì dùng kiểu dữ liệu nhỏ hơn?

Mặc dù điều có thể xem là khá hợp lý, nhưng thật sự không hiệu quả lắm. Chúng ta không nên sử dụng nhiều tài nguyên bộ nhớ hơn mức cần thiết. Khi đó vừa lãng phí bộ nhớ lại vừa hạn chế tốc độ của chương trình.

Chuyện gì xảy ra nếu ta gán giá trị âm vào biến kiểu không dấu?

Chúng ta sẽ nhận được lỗi của trình biên dịch nói rằng không thể gán giá trị âm cho biến không dấu trong trường hợp ta gán giá trị hằng âm. Còn nếu trong trường hợp kết quả là âm đựơc tính trong biểu thức khi chạy chương trình thì chúng ta sẽ nhận được lỗi dữ liệu. Việc kiểm tra và xử lý lỗi dữ liệu sẽ đựơc trình bày trong các phần sau.

Những ngôn ngữ nào khác hỗ trở Common Type System (CTS) trong Common Language Runtime (CLR)?

Microsoft Visual Basic (Version 7), Visual C++.NET cùng hỗ trợ CTS. Thêm vào đó là một số phiên bản của ngôn ngữ khác cũng được chuyển vào CTS. Bao gồm Python, COBOL, Perl, Java. Chúng ta có thể xem trên trang web của Microsoft để biết thêm chi tiết.

Có phải còn những câu lệnh điều khiển khác?

Đúng, các câu lệnh này như sau: throw, try, catch và finally. Chúng ta sẽ được học trong chương xử lý ngoại lệ.

Có thể sử dụng chuỗi với câu lệnh switch?

Hoàn toàn được, chúng ta sử dụng biến giá trị chuỗi trong switch rồi sau đó dùng giá trị chuỗi trong câu lệnh case. Lưu ý là chuỗi là những ký tự đơn giản nằm giữa hai dấu ngoặc nháy.

Câu hỏi thêm

Có bao nhiêu cách khai báo comment trong ngôn ngữ C#, cho biết chi tiết?

Những từ theo sau từ nào là từ khóa trong C#: field, cast, as, object, throw, football, do, get, set, basketball.

Những khái niệm chính của ngôn ngữ lập trình hướng đối tượng? Câuhỏi 4: Sự khác nhau giữa hai lệnh Write và WriteLine?

C# chia làm mấy kiểu dữ liệu chính? Nếu ta tạo một lớp tên myClass thì lớp này được xếp vào kiểu dữ liệu nào?

Kiểu chuỗi trong C# là kiểu dữ liệu nào?

Dữ liệu của biến kiểu dữ liệu tham chiếu được lưu ở đâu trong bộ nhớ?

Sự khác nhau giữa lớp và cấu trúc trong C#? Khi nào thì dùng cấu trúc tốt hơn là dùng class?

Sự khác nhau giữa kiểu unsigned và signed trong kiểu số nguyên?

Kiểu dữ liệu nào nhỏ nhất có thể lưu trữ được giá trị 45?

Số lớn nhất, và nhỏ nhất của kiểu int là số nào?

Có bao nhiêu bit trong một byte?

Kiểu dữ liệu nào trong .NET tương ứng với kiểu int trong C#?

Những từ khóa nào làm thay đổi luồng của chương trình?

Kết quả của 15%4 là bao nhiêu?

Sự khác nhau giữa chuyển đổi tường minh và chuyển đổi ngầm định?

Có thể chuyển từ một giá trị long sang giá trị int hay không?

Số lần tối thiểu các lệnh trong while được thực hiện?

Số lần tối thiểu các lệnh trong do while được thực hiện?

Lệnh nào dùng để thoát ra khỏi vòng lặp?

Lệnh nào dùng để qua vòng lặp kế tiếp?

Khi nào dùng biến và khi nào dùng hằng?

Cho biết giá trị CanhCut trong kiểu liệt kê sau:

enum LoaiChim
    {
    HaiAu, BoiCa,
    DaiBang = 50, CanhCut
    }
    

Cho biết các lệnh phân nhánh trong C#?

Bài tập

Nhập vào, biên dịch và chạy chương trình. Hãy cho biết chương trình làm điều gì?

class BaiTap3_1
    {
           public static void Main()
    {
         int x = 0;
            for(x = 1; x < 10; x++)
           {
            System.Console.Write("{0:03}", x);
           }
           }
    }
    

-----------------------------------------------------------------------------

Tìm lỗi của chương trình sau? sửa lỗi và biên dịch chương trình.

-----------------------------------------------------------------------------

class BaiTap3_2
    {
    public static void Main()
    {
    for(int i=0; i < 10 ; i++) System.Console.WriteLine("so :{1}", i);
    }
    }
    

-----------------------------------------------------------------------------

Tìm lỗi của chương trình sau. Sửa lỗi và biên dịch lại chương trình.

-----------------------------------------------------------------------------

using System;
    class BaiTap3_3
    {
    public static void Main()
    {
    double myDouble; decimal myDecimal; 
    myDouble = 3.14; 
    myDecimal = 3.14;
    Console.WriteLine("My Double: {0}", myDouble); 
    Console.WriteLine("My Decimal: {0}", myDecimal);
    }
    }
    

-----------------------------------------------------------------------------

Tìm lỗi của chương trình sau. Sửa lỗi và biên dịch lại chương trình.

-----------------------------------------------------------------------------

class BaiTap3_4
    {
    static void Main()
    {
    int value;
    if (value > 100);
    System.Console.WriteLine("Number is greater than 100");
    }
    }
    

-----------------------------------------------------------------------------

Viết chương trình hiển thị ra màn hình 3 kiểu sau:

Viết chương trình hiển ra trên màn hình

Viết chương trình in ký tự số (0..9) và ký tự chữ (a..z) với mã ký tự tương ứng của từng ký tự

‘0’ : 48

‘1’ : 49

....

Viết chương trình giải phương trình bậc nhất, cho phép người dùng nhập vào giá trị a, b.

Viết chương trình giải phương trình bậc hai, cho phép người dùng nhập vào giá trị a, b, c.

Viết chương trình tính chu vi và diện tích của các hình sau: đường tròn, hình chữ nhật, hình thang, tam giác.

0