Giáo trình C# và ứng dụng (Phần 1) - Nguyễn hoàng hà

pdf 51 trang ngocly 1280
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình C# và ứng dụng (Phần 1) - Nguyễn hoàng hà", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfgiao_trinh_c_va_ung_dung_phan_1_nguyen_hoang_ha.pdf

Nội dung text: Giáo trình C# và ứng dụng (Phần 1) - Nguyễn hoàng hà

  1. ĐẠI HỌC HUẾ ĐẠI HỌC KHOA HỌC KHOA CÔNG NGHỆ THÔNG TIN GIÁO TRÌNH C# VÀ ỨNG DỤNG NGUYỄN HOÀNG HÀ – NGUYỄN VĂN TRUNG HUẾ - 2008 Giáo trình Visual Studio .NET 1
  2. CHƯƠNG 1 TỔNG QUAN VỀ .NET FRAMEWORK 1.1 Tổng quan về kiến trúc của .NET Framework .NET Framework được thiết kế như là môi trường tích hợp để đơn giản hóa việc phát triển và thực thi các ứng dụng trên Internet, trên desktop dưới dạng Windows Forms, hoặc thậm chí là trên cả các thiết bị di động (với Compact Framework). Các mục tiêu chính mà .NET framework hướng đến là: - Cung cấp một môi trường hướng đối tượng nhất quán cho nhiều loại ứng dụng - Cung cấp một môi trường giảm tối thiểu sự xung đột phiên bản (“DLL Hell” – Địa ngục DLL) từng làm điêu đứng các lập trình viên Windows (COM), và đơn giản hóa quá trình triển khai/cài đặt. - Cung cấp một môi trường linh động, dựa trên các chuẩn đã được chứng nhận để có thể chứa trên bất cứ hệ điều hành nào. C# và một phần chính của môi trường thực thi .NET, CLI (Common Language Infrastructure – Hạ tầng ngôn ngữ chung) đã được chuẩn hóa bởi ECMA. - Để cung cấp một môi trường quản lý được, trong đó mã được dễ dàng xác thực để thực thi an toàn. Kiến trúc của .NET Framework được thiết kế thành 2 phần: CLR (Common Language Runtime – Khối thức thi ngôn ngữ chung) và FCL (Framework Class Library – Thư viện lớp khung) như hình dưới Hình 1.1 – Kiến trúc .NET Framework Giáo trình Visual Studio .NET 2
  3. CLR, phần cài đặt CLI của Microsoft, làm nhiệm vụ quản lý sự thực thi mã lệnh và tất cả các tác vụ liên quan đến nó: biên dịch, quản lý bộ nhớ, bảo mật, quản lý tuyến đoạn, và thực thi an toàn kiểu. Mã lệnh thực thi trong CLR được gọi là mã được quản lý (managed code), phân biệt với mã không được quản lý (unmanaged code), là mã lệnh không cài đặt những yêu cầu để thực thi trong CLR – chẳng hạn như COM hoặc các thành phần dựa trên Windows API. FCL là thư viện kiểu dữ liệu có thể tái sử dụng (gồm các class, structure, ) dành cho các ứng dụng thực thi trong .NET. Tất cả các ngôn ngữ hỗ trợ .NET Framework đều sử dụng thư viện lớp dùng chung này. 1.2 Môi trường thực thi ngôn ngữ chung CLR (Common Language Runtime) CLR (Common Languge Runtime – Môi trường thực thi ngôn ngữ chung) quản lý toàn bộ vòng đời của một ứng dụng: nó nạp các lớp có liên quan, quản lý sự thực thi của các lớp, và đảm bảo quản lý bộ nhớ một cách tự động. Ngoài ra, CLR còn hỗ trợ tích hợp giữa các ngôn ngữ để cho phép mã lệnh được sinh ra bởi các ngôn ngữ khác nhau có thể tương tác với nhau một cách liền mạch. 1.2.1 Biên dịch mã lệnh .NET Trình biên dịch tương thích với CLR sẽ sinh mã thực thi cho môi trường thực thi chứ không phải là mã thực thi cho CPU cụ thể. Mã thực thi này được biết đến qua tên gọi CIL (Common Intermediate Language – Ngôn ngữ trung gian chung), hay MSIL (Microsoft Intermediate Language – Ngôn ngữ trung gian của Microsoft); đó là ngôn ngữ kiểu assembler được đóng gói trong các file EXE hoặc DLL. Các file này không phải thuộc dạng file có thể thực thi như thông thường, chúng cần trình biên dịch JIT (Just-in- Time) của môi trường thực thi để chuyển đối IL chứa trong nó sang dạng mã lệnh cụ thể của máy khi ứng dụng thực sự thực thi. Quá trình biên dịch, thực thi một chương trình trong .NET framework có thể tóm tắt như sau: - Chương trình nguồn trước hết sẽ được biên dịch và đóng gói thành một khối gọi là assembly. Khối này sẽ chứa các mã lệnh ngôn ngữ trung gian và các metadata mô tả thông tin cần thiết cho sự hoạt động của khối. - Mỗi khi có yêu cầu thực thi assembly nói trên, CLR sẽ chuyển đối mã lệnh ngôn ngữ trung gian trong assembly thành mã lệnh tương thích với CPU cụ thể trước Giáo trình Visual Studio .NET 3
  4. khi có thể thực thi. Hình 1.2 – Chức năng của CLR Như vậy, lập trình viên có thể sử dụng bất cứ ngôn ngữ nào để phát triển ứng dụng trên .NET framework, miễn là ngôn ngữ đó có hỗ trợ .NET framework. Điều đặc biệt là, do sử dụng chung hệ thống kiểu dữ liệu, nên tính năng liên thông giữa các ngôn ngữ trên .NET framework là rất cao. 1.2.2 Hệ thống kiểu dữ liệu chung CTS (Common Type System) CTS cung cấp một tập cơ sở các kiểu dữ liệu cho mỗi ngôn ngữ hoạt động trên .NET platform. Ngoài ra, nó đặc tả cách khai báo và tạo các kiểu dữ liệu tùy biến, cách quản lý vòng đời của một thể hiện của những kiểu dữ liệu này. Hình dưới đây mô tả cách tổ chức CTS của .NET Giáo trình Visual Studio .NET 4
  5. Hình 1.3 – Các kiểu dữ liệu cơ sở của CTS Mọi kiểu dữ liệu trong .NET đều được kế thừa từ kiểu dữ liệu System.Object. Các kiểu dữ liệu được chia làm hai loại: kiểu tham chiếu và kiểu giá trị. Kiểu dữ liệu tham chiếu được xử lý trong một vùng nhớ đặc biệt gọi là heap thông qua các con trỏ. Kiểu dữ liệu giá trị được tham chiếu trực tiếp trong stack của chương trình. 1.2.3 Assemblies Tất cả các mã được quản lý thực thi trong .NET đều phải được chứa trong một assembly. Một assembly được xem như là một file EXE hoặc DLL. Một asembly có thể chứa một tập hợp gồm một hay nhiều file chứa phần mã lệnh hoặc tài nguyên (như ảnh hoặc dữ liệu XML). Một assembly được tạo ra khi trình biên dịch tương thích với .NET chuyển một file chứa mã nguồn thành một file DLL hoặc EXE. Như minh họa trong hình 1.4, một assembly chứa một manifest, metadata, và ngôn ngữ trung gian sinh bởi trình biên dịch cụ thể. Manifest: Mỗi assembly phải có một file chứa một manifest. Manifest này là một tập hợp các bảng chứa các metadata trong đó liệt kê tên của tất cả các file trong assembly, tham chiếu đến các assembly bên ngoài, và các thông tin như tên, phiên bản để định danh assembly đó. Một số assembly còn có cả chữ ký điện tử duy nhất (unique digital signature). Khi một assembly được nạp, nhiệm vụ đầu tiên của CLR là mở file chứa manifest để có thể định danh các thành viên có trong assembly. Metadata: Ngoài các bảng trong manifest vừa định nghĩa, trình biên dịch C# còn sinh ra các bảng định nghĩa và bảng tham chiếu. Bảng định nghĩa cung cấp một ghi chú đầy đủ Giáo trình Visual Studio .NET 5
  6. về các kiểu chứa trong IL. Ví dụ, có các bảng định nghĩa kiểu, phương thức, trường dữ liệu, tham số, và thuộc tính. Bảng tham chiếu chứa các thông tin về tất cả các tham chiếu về kiểu và các assembly khác. Trình biên dịch JIT phụ thuộc vào các bảng này để chuyển IL sang mã máy. IL: Vai trò của IL đã được đề cập trước đây. Trước khi CLS có thể sử dụng IL, nó phải được đóng gói vào trong một assembly dạng DLL hoặc EXE. Assembly dạng EXE phải có một điểm nhập (entry point) để nó có thể thực thi. Ngược lại, Assembly dạng DLL, được thiết kế để hoạt động như là một thư viện mã lệnh nắm giữ các định nghĩa kiểu. Hình 1.4 – Assembly chỉ gồm 1 file Assembly không chỉ là cách logic để đóng gói các mã thực thi. Nó quy định mô hình chủ yếu của .NET để triển khai mã lệnh, quản lý phiên bản, và bảo mật. - Tất cả các mã được quản lý, cho dù là một chương trình đơn, một điều khiển, hay một thư viện DLL chứa các kiểu dữ liệu tái sử dụng, đều được đóng gói vào một assembly. Đây là khối cơ bản nhất có thể triển khai trên hệ thống. Khi một ứng dụng được bắt đầu, chỉ những assembly được yêu cầu cho việc khởi tạo mới cần hiện diện. Các assembly khác sẽ được nạp khi có yêu cầu. Các nhà phát triển có thể phân ứng dụng thành các assembly dựa theo mức độ thường xuyên sử dụng. - Trong thế giới .NET, một assembly quy định một biên giới phiên bản. Trường Version Number trong manifest áp dụng cho tất cả các kiểu và tài nguyên trong assembly. Vì vậy, mọi file tạo nên assembly được xem như là một đơn vị đơn nhất có cùng phiên bản. - Một assembly cũng thiết lập một biên giới bảo mật để định ra quyền hạn truy xuất. Giáo trình Visual Studio .NET 6
  7. C# sử dụng các bổ từ truy cập để điều khiển cách mà các kiểu và thành phần kiểu trong một assembly được truy xuất. Hai trong số này được sử dụng trong assembly, đó là public – cho phép truy xuất tùy ý từ assembly bất kỳ ; và internal – giới hạn truy xuất đến các kiểu và thành viên bên trong assembly. Như đã đề cập ở trên, một assembly có thể chứa nhiều file. Những file này không giới hạn là các module mã lệnh mà có thể là các file tài nguyên như file hình ảnh hoặc văn bản. Một cách sử dụng tính chất này trong thực tế đó là chúng ta có thể tạo ra ứng dụng đa ngôn ngữ, trong đó ứng dụng sẽ cùng sử dụng chung các module logic, phần giao diện hoặc các tài nguyên khác có thể được triển khai riêng thành các file độc lập. Không có giới hạn về số lượng file trong một assembly. Hình 1.5 minh họa bố cục của một assembly chứa nhiều file. Hình 1.5 - Assembly chứa nhiều file Trong minh họa assembly chứa nhiều file, manifest của assembly chứa thông tin để định danh mọi file được sử dụng trong assembly. Mặc dù hầu hết các assembly đều chứa một file duy nhất. Sau đây là các thuận lợi của assembly chứa nhiều file: - Có thể tổ hợp các module được tạo ra từ nhiều ngôn ngữ lập trình khác nhau. - Các module mã lệnh có thể được phân ra để tối ưu cách mà mã lệnh được nạp vào trong CLR. Các mã lệnh có liên quan và được sử dụng thường xuyên nên được đặt vào trong cùng một module; những mã lệnh ít khi được sử dụng sẽ được đặt vào trong module khác. CLR không nạp các module nào khi chưa thực sự cần thiết. Giáo trình Visual Studio .NET 7
  8. - Các file tài nguyên có thể được đặt vào trong module của riêng nó, qua đó cho phép nhiều ứng dụng có thể chia sẻ tài nguyên dùng chung. 1.2.4 Private Assembly và Shared Assembly Các assembly có thể được triển khai theo hai dạng: private assembly và global assembly. Private assembly là assembly được đặt trong thư mục của ứng dụng hoặc thư mục con của nó. Quá trình cài đặt và cập nhật private assembly chỉ đơn giản là chép assembly vào trong thư mục cần thiết, không cần thiết lập thông tin trong registry. Đôi khi, có thể dùng thêm một file cấu hình ứng dụng có thể ghi đè một số thiết lập trong manifest của ứng dụng. Shared assembly là assembly được cài đặt vào vị trí toàn cục, gọi là Global Assembly Cache (GAC), là nơi có thể truy xuất được từ nhiều ứng dụng. Điểm quan trọng nhất của GAC đó là nó cho phép nhiều phiên bản của một assembly có thể được thực thi. Để hỗ trợ điều này, .NET khắc phục vấn đề xung đột tên bằng cách sử dụng 4 thuộc tính để định danh 1 assembly, bao gồm: Assembly Name (tên assembly), Culture Identity (định danh văn hóa), Version (phiên bản), và Public Key Token (dấu hiệu mã khóa công khai). Các shared assembly thường được đặt trong thư mục assembly ở dưới thư mục hệ thống của hệ điều hành (WINNT\ trong Windows 2000, WINDOWS\ trong Windows XP). Như mô tả ở hình 1.6, các assembly được liệt kê theo định dạng đặc biệt để hiển thị 4 thuộc tính của chúng (.NET Framework bao gồm một file DLL để mở rộng Windows Explorer cho phép nó có thể hiển thị nội dung GAC). - Assembly Name: còn được gọi là tên thường gọi, là tên file của assembly không chứa phần mở rộng. - Version: Mỗi assembly có một số hiệu phiên bản để dùng cho tất cả các file trong assembly. Nó chứa 4 số theo định dạng: . . . Thông thường các số và được cập nhật cho những lần thay đổi mang tính phá vỡ tính tương thích ngược. Một số hiệu phiên bản có thể được gán cho một assembly bằng cách đính thuộc tính AssemblyVersion trong phần mã nguồn của assembly. - Culture Setting: Nội dung của một assembly có thể được kết hợp với một văn hóa hay ngôn ngữ cụ thể. Thiết lập này được chỉ định bằng mã hai ký tự kiểu như “en” cho English, “vi” cho Vietnam, và có thể được gán với thuộc tính Giáo trình Visual Studio .NET 8
  9. AssemblyCulture đặt trong mã nguồn của assembly [assembly: AssemblyCulture ("fr-CA")] - Public Key Token: Để đảm bảo một shared assembly là duy nhất và đáng tin cậy, .NET yêu cầu người tạo ra assembly phải đánh dấu bằng một định danh mạnh. Quá trình này được gọi là ký, yêu cầu sử dụng cặp khóa công khai/riêng tư. Khi trình biên dịch xây dựng assembly, nó sẽ sử dụng khóa riêng tư để sinh ra một định danh mạnh. Token được sinh ra ở đây là 8 byte cuối cùng của phép băm (hashing) khóa công khai. Token này sẽ được đặt trong manifest của bất kỳ assembly client nào có tham chiếu đến shared assembly và sử dụng nó để định danh assembly trong quá trình thực thi. Một assembly được gán một cặp khóa công khai/riêng thì được gọi là một assembly định danh mạnh. Mọi assembly đều phải có định danh mạnh. Giáo trình Visual Studio .NET 9
  10. Hình 1.6 – Thư mục Global Assembly trong một hệ thống Windows XP 1.2.5 Tiền biên dịch một Assembly Sau khi một assembly được nạp vào CLR, IL phải được biên dịch sang thành mã máy trước khi thực sự được thực thi. .NET Framework có cung cập một công cụ gọi là Ngen (Native Image Generator), dùng để biên dịch một assembly thành một “native image” được lưu trong native image cache – một vùng dành riêng của GAC. Mỗi khi CLR nạp một assembly, nó sẽ kiểm tra trong cache xem đã có native image tương ứng chưa; nếu có nó sẽ nạp mã đã biên dịch đó chứ không cần biên dịch thêm lần nữa. Đây là tính năng mà nếu được khai thác hợp lý thì có thể tận dụng để cải thiện hiệu năng 1.2.6 Kiểm chứng mã lệnh (Code Verification) Như là một phần của quá trình biên dịch JIT, CLR thực hiện hai loại kiểm chứng: kiểm Giáo trình Visual Studio .NET 10
  11. chứng IL và hợp lệ hóa metadata để bảo đảm mã lệnh được an toàn kiểu. Trong thực tế, điều này có nghĩa là các tham số trong lời gọi và phương thức được gọi phải được kiểm tra để đảm bảo chúng có cùng kiểu dữ liệu, hoặc là một phương thức chỉ trả về đúng kiểu được đặc tả trong khai báo trả về. Nói ngắn gọn, CLR sẽ xem xét trong IL và metadata để đảm bảo mọi giá trị được gán cho một biến là tương thích kiểu; nếu không sẽ có một ngoại lệ xuất hiện. Thuận lợi của mã lệnh được kiểm chứng đó là CLR có thể chắc chắn mã lệnh sẽ không ảnh hưởng đến ứng dụng khác theo kiểu truy xuất đến vùng nhớ ngoài vùng cho phép của nó. Do đó CLR tự do thực thi nhiều ứng dụng trong cùng một tiến trình hay không gian địa chỉ. Giáo trình Visual Studio .NET 11
  12. CHƯƠNG 2 NGÔN NGỮ LẬP TRÌNH C# 2.1 Chương trình đầu tiên Chúng ta sẽ làm quen với ngôn ngữ lập trình C# và môi trường tích hợp phát triển (IDE – Integrated Development Environment) Visual Studio .NET bằng cách xây dựng một ứng dụng đầu tiên, ứng dụng firstApp. Ứng dụng này cho phép người sử dụng nhập vào 2 số, sau đó in ra màn hình tổng, tích và thương của hai số vừa nhập. Trình tự thực hiện như sau: 1. Khởi động Microsoft Visual Studio 2005. Nhấn Ctrl + Shift + N hoặc chọn menu tương ứng là File New Project để tạo mới một project 2. Chọn loại ứng dụng cần phát triển là Visual C# Windows Console Application. Sau đó, chọn thư mục chứa project và đặt tên cho project như minh họa ở hình trên. Chú ý, ở đây, chúng ta bỏ chọn ở hộp kiểm “Create directory for solution”. Chú thích: Giáo trình Visual Studio .NET 12
  13. Visual Studio .NET coi một “bài toán” cần giải quyết là một solution. Một solution có thể bao gồm một hoặc nhiều project. Một solution, nếu có nhiều project thì nên được tạo ra trong một thư mục riêng để có thể chứa các project trong nó. Ở đây, solution chỉ có duy nhất một project, thế nên không cần thiết phải tạo ra một thư mục cho solution. 3. Sau khi nhấn nút OK, hãy khảo sát xem cấu trúc của thư mục chứa solution của chúng ta. Bạn phải luôn nắm chắc về sự tồn tại, ý nghĩa của các tập tin, thư mục được tạo ra trong quá trình làm việc! 4. Gõ mã lệnh như minh họa vào trong phần mã nguồn của tập tin Program.cs Giáo trình Visual Studio .NET 13
  14. 5. Bạn có thể sử dụng MSDN để tra cứu các thông tin bạn chưa biết về: a. Lớp Console và các phương thức ReadLine(), WriteLine() của nó b. Cách chuyển đổi kiểu chuỗi thành số, ví dụ như int.Parse() 6. Nhấn Ctrl + F5 để thực hiện biên dịch và chạy chương trình. Sau đó quan sát cấu trúc thư mục của solution, cho biết sự thay đổi của nó so với khi mới được tạo ra ở bước 3 (xem thư mục bin và thư mục obj của project). 7. Thử thay đổi kết câu lệnh float thuong = (float)x / y; thành float thuong = x / y; rồi chạy chương trình, quan sát kết quả và rút ra kết luận. 8. Sử dụng thêm các cấu trúc lệnh khác để tinh chỉnh hoạt động của chương trình (xử lý phép chia cho 0, ) Rõ ràng, đoạn chương trình đơn giản trên không phải là quá phức tạp đối với người đã Giáo trình Visual Studio .NET 14
  15. từng làm quen với các ngôn ngữ lập trình bậc cao. 2.2 Biến dữ liệu Biến trong C# được khai báo theo cú pháp như sau: datatype identifier; Ví dụ: int i; Câu lệnh này khai báo một số int tên là i. Trình biên dịch thực sự chưa cho phép sử dụng biến này cho đến khi chúng ta khởi tạo nó bằng một giá trị. Lệnh khai báo này chỉ làm nhiệm vụ cấp phát một vùng nhớ (4 bytes) cho biến i. Sau khi khai báo, chúng ta có thể gán một giá trị cho biến bằng toán tử gán =, như sau: i = 10; Chúng ta cũng có thể vừa khai báo, vừa khởi tạo giá trị cho biến cùng lúc: int i = 10; // khai bao va khoi tao gia tri cho bien int double x = 10.25, y = 20; // khai bao va khoi tao hai bien double 2.2.1 Tầm hoạt động của biến Tầm hoạt động của một biến là vùng mã lệnh mà trong đó biến có thể truy xuất. Nói chung, tầm hoạt động của biến được xác định theo các quy tắc sau: Một trường dữ liệu (field), còn được gọi là một biến thành phần của một lớp đối tượng sẽ có tầm hoạt động trong phạm vi lớp chứa nó. Một biến cục bộ sẽ có tầm hoạt động trong khối khai báo nó (trong cặp dấu ngoặc nhọn { }) Một biến cục bộ được khai báo trong các lệnh lặp for, while, sẽ có tầm hoạt động trong thân vòng lặp Tất nhiên, trong cùng phạm vi hoạt động, không được có hai biến có trùng tên. 2.2.2 Hằng dữ liệu Hằng dữ liệu là biến có giá trị không được phép thay đổi trong suốt thời gian tồn tại của nó. Cách khai báo của hằng dữ liệu là tương tự như đối với biến dữ liệu, chỉ khác là được thêm từ khóa const ở đầu. Giáo trình Visual Studio .NET 15
  16. const int a = 100; // Gia tri nay khong duoc thay doi Hằng dữ liệu có các đặc tính sau: Phải được khởi tạo ngay khi nó được khai báo, sau đó không được phép thay đổi giá trị của hằng. Giá trị của hằng dữ liệu phải được tính toán trong thời điểm biên dịch. Vì vậy, chúng ta không thể khởi tạo một hằng số có giá trị được lấy từ một biến dữ liệu. Nếu cần điều này, chúng ta sử dụng trường dữ liệu kiểu read-only. 2.3 Các kiểu dữ liệu định nghĩa sẵn của C# C# phân kiểu dữ liệu thành hai loại (tương tự như cách phân loại chung trong CTS): kiểu dữ liệu giá trị và kiểu dữ liệu tham chiếu. Về mặt khái niệm, điểm khác biệt giữa hai kiểu dữ liệu này đó là, biến kiểu dữ liệu giá trị lưu giữ trực tiếp một giá trị, trong khi đó, biến kiểu tham chiếu lưu giữ tham chiếu đến một giá trị dữ liệu. Về mặt lưu trữ vật lý, biến của hai kiểu dữ liệu này được lưu vào hai vùng nhớ khác nhau của chương trình, đó là vùng nhớ stack (cho biến dữ liệu kiểu giá trị) và vùng nhớ heap (cho biến dữ liệu kiểu tham chiếu). Bạn cần đặc biệt lưu ý hiệu ứng của các phép gán đối với kiểu dữ liệu kiểu tham chiếu. 2.3.1 Kiểu dữ liệu giá trị được định nghĩa sẵn Các kiểu dữ liệu giá trị được định nghĩa sẵn bao gồm số nguyên, số dấu chấm phẩy động, ký tự và boolean. 2.3.1.1 Các kiểu số nguyên C# hỗ trợ sẵn 8 kiểu số nguyên: Tên Kiểu trong CTS Mô tả Vùng biểu diễn (min:max) sbyte System.SByte Số nguyên có dấu 8-bit -27:27-1 short System.Int16 Số nguyên có dấu 16-bit -215:215-1 int System.Int32 Số nguyên có dấu 32-bit -231:231-1 long System.Int64 Số nguyên có dấu 64-bit -263:263-1 byte System.Byte Số nguyên không dấu 8- 0:28-1 bit ushort System.UInt16 Số nguyên không dấu 16- 0:216-1 bit uint System.UInt32 Số nguyên không dấu 32- 0:232-1 bit Giáo trình Visual Studio .NET 16
  17. ulong System.UInt64 Số nguyên không dấu 64- 0:264-1 bit 2.3.1.2 Các kiểu số dấu chấm động Các kiểu số thực dấu chấm động được hỗ trợ sẵn của C# bao gồm: Tên Kiểu trong CTS Số chữ số có nghĩa Vùng biểu diễn tương đối (khoảng) float System.Single 7 ±1.5 × 10-45 to ±3.4 × 1038 double System.Double 15/16 ±5.0 × 10-324 to ±1.7 × 10308 2.3.1.3 Kiểu số thập phân Để biểu diễn số thập phân với độ chính xác cao hơn số thực dấu chấm động, C# hỗ trợ kiểu dữ liệu số thập phân: Tên Kiểu trong CTS Số chữ số có nghĩa Vùng biểu diễn decimal System.Decimal 28 ±1.0 × 10-28 to ±7.9 × 1028 2.3.1.3 Kiểu boolean Tương ứng với System.Boolean trong CTS, C# có kiểu dữ liệu bool, có thể nhận một trong hai giá trị true hoặc false. Có một điều lưu ý, kiểu dữ liệu bool không được nhận các giá trị nguyên như một số ngôn ngữ (C, C++) 2.3.1.4 Kiểu ký tự Để lưu trữ giá trị của một ký tự đơn, C# hỗ trợ dữ liệu kiểu ký tự Tên Kiểu trong CTS Giá trị char System.Char Biểu diễn 1 ký tự 16-bit (Unicode) Các hằng kiểu ký tự được gán bằng cách đóng trong cặp dấu nháy đơn, ví dụ 'A'. Cũng có thể biểu thị hằng ký tự dưới dạng số thập lục phân, kiểu như ‘\u0041’, hoặc ép kiểu như (char)65. Ngoài ra có thể sử dụng một số ký tự escape sau: Ký tự escape Ký tự tương ứng \' Dấu nháy đon \" Dấu nháy đôi \\ Ký tự \ \0 Null \a Ký tự Alert \b Ký tự Backspace \f Ký tự Form feed \n Ký tự xuống dòng \r Ký tự Carriage return Giáo trình Visual Studio .NET 17
  18. \t Ký tự Tab \v Ký tự Vertical tab 2.3.2 Kiễu dữ liệu tham chiếu được định nghĩa sẵn C# hỗ trợ sẵn hai kiểu dữ liệu tham chiếu: Tên Kiểu CTS Mô tả object System.Object Kiểu dữ liệu gốc, mọi kiểu dữ liệu khác trong CTS đều kế thừa từ đây (kể cả các kiểu dữ liệu giá trị) string System.String Chuỗi ký tự Unicode 2.3.2.1 Kiểu dữ liệu object object là kiểu dữ liệu gốc, cơ bản nhất mà từ đó, tất cả các kiểu dữ liệu khác đều phải kế thừa (trực tiếp hoặc gián tiếp). Các thuận lợi chúng ta có được từ kiểu dữ liệu object là: - Chúng ta có thể sử dụng tham chiếu đối tượng để gắn kết với một đối tượng của bất kỳ kiểu dữ liệu con nào. Tham chiếu đối tượng cũng được sử dụng trong những trường hợp mà mã lệnh phải truy xuất đến những đối tượng chưa rõ kiểu dữ liệu (tương tự như vai trò con trỏ void ở C++) - Kiểu object có cài đặt một số phương thức cơ bản, dùng chung, bao gồm: Equals(), GetHashCode(). GetType(), và ToString(). Các lớp do người sử dụng tự định nghĩa có thể cài đặt lại các phương thức này theo kỹ thuật gọi là overriding (ghi đè) trong lập trình hướng đối tượng. 2.3.2.2 Kiểu dữ liệu string Kiểu dữ liệu string được cung cấp sẵn trong C# với nhiều phép toán và cách thức hoạt động thuận tiện là một trong những kiểu dữ liệu được sử dụng nhiều nhất khi lập trình. Đối tượng string được cấp phát trong vùng nhớ heap, và khi gán một biến string cho một biến khác, chúng ta sẽ có hai tham chiếu đến cùng một chuỗi trong bộ nhớ. Tuy nhiên, khi thay đổi nội dung của một trong các chuỗi này, chuỗi thay đổi sẽ được tạo mới hoàn toàn, không ảnh hưởng đến các chuỗi khác. Hãy xem hiệu ứng này trong đoạn chương trình dưới đây: using System; class MinhHoaString { public static int Main() { string s1 = "a string"; string s2 = s1; Giáo trình Visual Studio .NET 18
  19. Console.WriteLine("s1 is " + s1); Console.WriteLine("s2 is " + s2); s1 = "another string"; Console.WriteLine("s1 is now " + s1); Console.WriteLine("s2 is now " + s2); return 0; } } Kết quả của đoạn chương trình trên là: s1 is a string s2 is a string s1 is now another string s2 is now a string Nói cách khác, việc thay đổi giá trị của s1 không ảnh hưởng gì đến s2, ngược với những gì chúng ta trông đợi ở kiểu dữ liệu tham chiếu. Hằng kiểu chuỗi được bao trong cặp dấu nháy kép (“ ”). Trong chuỗi có thể chứa các dãy ký tự escape như đối với kiểu dữ liệu ký tự. Do dãy ký tự escape được bắt đầu bằng ký tự \ nên ký tự \ phải được lặp đôi: string filepath = "C:\\CSharp\\MinhHoaString.cs"; Có một giải pháp khác để biểu diễn ký tự \ trong chuỗi, đó là dùng cú pháp @: string filepath = @"C:\CSharp\MinhHoaString.cs"; Cú pháp này còn cho phép chúng ta ngắt dòng trong hằng chuỗi, như sau: string st = @"'Day la dong thu nhat Day la dong thu hai."; Khi đó, giá trị của chuỗi st sẽ là: 'Day la dong thu nhat Day la dong thu hai. 2.4 Luồng điều khiển chương trình 2.4.1 Câu lệnh điều kiện Các câu lệnh điều kiện cho phép phân nhánh mã lệnh theo các điều kiện cụ thể. C# có hai cấu trúc phân nhánh if và switch. Giáo trình Visual Studio .NET 19
  20. 2.4.1.1 Câu lệnh if Câu lệnh if của C# được kế thừa từ cấu trúc if của C và C++. Cú pháp của nó là: if (condition) statement1(s) [else statement2(s)] Nếu có nhiều hơn một câu lệnh được thực thi tương ứng với một trong hai giá trị của biểu thức logic condition, chúng ta có thể gộp các lệnh này trong cặp dấu ngoặc nhọn ({ }) (điều này cũng được áp dụng cho nhiều cấu trúc lệnh khác mà chúng ta sẽ đề cập sau này): bool isZero; if (i == 0) { isZero = true; Console.WriteLine("i is Zero"); } else { isZero = false; Console.WriteLine("i is Non-zero"); } Điều đáng lưu ý nhất khi sử dụng câu lệnh if đó là condition nhất thiết phải là một biểu thức logic (chứ không thể là một số như ở C/C++). 2.4.1.2 Câu lệnh switch Câu lệnh switch là một câu lệnh điều khiển quản lý nhiều lựa chọn và liệt kê bằng cách chuyển điều khiển đến một trong những câu lệnh case trong thân của nó: switch (expression) { case const_1: statement_1; break; case const_2: statement_2; break; case const_n: statement_n; break; [default: statement_n+1; break;] Giáo trình Visual Studio .NET 20
  21. } Chú ý rằng, điều khiển được chuyển đến nhánh rẽ tương ứng với giá trị của biểu thức. Câu lệnh switch có thể chứa nhiều nhánh rẽ nhưng không có hai nhánh rẽ nào được có cùng giá trị. Việc thực thi thân câu lệnh được bắt đầu tại nhánh được lựa chọn và tiếp tục cho đến khi được chuyển ra ngoài qua lệnh break. Câu lệnh nhảy break là bắt buộc đối với mỗi nhánh rẽ, ngay cả khi đó là nhánh rẽ cuối cùng hoặc là nhánh rẽ default. Nếu biểu thức không ứng với nhánh nào của lệnh switch thì điều khiển sẽ được chuyển đến các câu lệnh sau nhãn default (nếu có). Nếu không có nhãn default, điều khiển được chuyển ra bên ngoài câu lệnh switch. Ví dụ: switch (integerA) { case 1: Console.WriteLine("integerA =1"); break; case 2: Console.WriteLine("integerA =2"); break; case 3: Console.WriteLine("integerA =3"); break; default: Console.WriteLine("integerA is not 1,2, or 3"); break; } 2.4.2 Câu lệnh lặp C# cung cấp bốn loại lệnh lặp (for, while, do while, và foreach) cho phép lập trình viên có thể thực thi một khối lệnh liên tiếp cho đến khi một điều kiện xác định nào đó được thỏa mãn. 2.4.2.1 Câu lệnh lặp for Cú pháp của câu lệnh lặp for có cú pháp như sau: for (initializer; condition; iterator) statement(s) trong đó: Initializer:biểu thức được ước lượng trước khi lần lặp đầu tiên được thực thi (đây Giáo trình Visual Studio .NET 21
  22. thường là nơi khởi tạo một biến cục bộ như là một “biến đếm”). Condition: là biểu thức kiểm tra trước khi mỗi vòng lặp được thực thi. Iterator: biểu thức được ước lượng sau mỗi vòng lặp (thường dùng để tăng “biến đếm”). Các vòng lặp sẽ kết thúc khi condition được ước lượng là false. Ví dụ dưới đây in ra 100 số tự nhiên đầu tiên (0, 1, 2, , 99), mỗi số trên một dòng: for (int i = 0; i < 100; i = i+1) { Console.WriteLine(i); } Tất nhiên, chúng ta có thể sử dụng các vòng lặp for lồng nhau, như ví dụ in ra tam giác hình sao dưới đây. for (int i = 0; i < 5; i++) { for (int j = 0; j <= i; j++) Console.Write(“*”); Console.WriteLine(i); } 2.4.2.2 Câu lệnh lặp while Câu lệnh lặp while, còn được gọi là câu lệnh lặp kiểm tra điều kiện trước, có cú pháp như sau: while(condition) statements; Ví dụ: Đoạn chương trình sau minh họa việc kiểm tra nhập vào một chuỗi từ dòng lệnh, sẽ dừng khi chuỗi nhập vào là “abc”: string correctPwd = “abc”, st = “”; while (st != correctPwd) { Console.Write(“Password = “); st = Console.ReadLine(); } 2.4.2.3 Câu lệnh lặp do while Câu lệnh lặp do while được coi là phiên bản kiểm tra điều kiện sau của câu lẹnh while, có cú pháp như sau: do { Giáo trình Visual Studio .NET 22
  23. statements; } while(condition) Ví dụ: Đoạn chương trình minh họa việc kiểm tra nhập vào một chuỗi từ dòng lệnh, sẽ dừng khi chuỗi nhập vào là “abc”, được viết lại theo kiểu câu lệnh lặp do while như sau: string correctPwd = “abc”, st; //để ý rằng st không cần khởi tạo là “” do { Console.Write(“Password = “); st = Console.ReadLine(); } while (st != correctPwd) 2.4.2.4 Câu lệnh lặp foreach Câu lệnh lặp foreach cho phép duyệt qua mỗi phần tử có trong một tập phần tử. Kiểu dữ liệu tập hợp phần tử (collection) sẽ được trình bày trong các phần tiếp theo. Xét ví dụ dưới đây: foreach (int temp in arrayOfInts) { Console.WriteLine(temp); } Ví dụ này sẽ in ra tất cả các phần tử có trong tập hợp arrayOfInts. Có một điều đáng lưu ý, chúng ta không được thay đổi giá trị của biến phần tử lặp. Chẳng hạn, đoạn chương trình dưới đây sẽ bị báo lỗi: foreach (int temp in arrayOfInts) { temp++; //không được thay đổi giá trị temp!!! Console.WriteLine(temp); } 2.4.3 Câu lệnh nhảy C# cung cấp một số câu lệnh nhảy cho phép chuyển điều khiển đến dòng lệnh khác trong chương trình. 2.4.3.1 Câu lệnh goto Lệnh goto cho phép nhảy trực tiếp đến một dòng cụ thể trong chương trình, được xác định bằng một nhãn (label): goto Label1; Console.WriteLine("Dòng lệnh này sẽ không được thực hiện"); Label1: Giáo trình Visual Studio .NET 23
  24. Console.WriteLine("Continuing execution from here"); 2.4.3.2 Câu lệnh break Chúng ta đã sử dụng câu lệnh break trong phần câu lệnh rẽ nhánh switch. Có một cách sử dụng khác của câu lệnh này, đó là dùng để nhảy ra khỏi điểu khiển của lệnh lặp trực tiếp chứa nó (for, foreach, while, do while). Ví dụ sau đây là một phiên bản khác của đoạn lệnh kiểm tra mật khẩu ở trên: string correctPwd = “abc”; while (true) { Console.Write(“Password = “); string st = Console.ReadLine(); if (st == correctPwd) break; } 2.4.3.3 Câu lệnh continue Lệnh continue cũng tương tự như câu lệnh break, phải được sử dụng trong thân câu lệnh for, foreach, while, hay do while. Tuy nhiên, nó chỉ thoát từ lần lặp hiện tại của vòng lặp để bắt đầu lần lặp mới. Ví dụ, đoạn chương trình dưới đây for (int i = 0; i < 4; i++) { Console.WriteLine(“ “); if (i%2 == 0) continue; Console.WriteLine(“i = {0}”, i); } sẽ in ra kết quả như thế này trong cửa sổ Console: i = 1 i = 3 2.4.3.4 Câu lệnh return Lệnh return được sử dụng để thoát khỏi phương thức của một lớp, trả điều khiển trở về nơi gọi phương thức. Tùy theo kiểu dữ liệu trả về của phương thức là void hoặc có một Giáo trình Visual Studio .NET 24
  25. kiểu dữ liệu cụ thể, lệnh return phải tương ứng không trả về kiểu dữ liệu gì, hoặc là trả về một giá trị có kiểu dữ liệu thích hợp. 2.5 Cấu trúc chương trình Trong phần đầu của chương này, chúng ta đã viết một chương trình C# đơn giản đầu tiên. Tại thời điểm đó, chúng ta chỉ quan tâm đến cách thức quản lý, biên dịch solution, project của Visual Studio. Sau khi đã nắm vững được cấu trúc điều khiển cũng như một số đặc điểm cụ thể của ngôn ngữ, giờ là lúc chúng ta xem xét cấu trúc của một chương trình viết bằng C#. 2.5.1 Lớp đối tượng Lớp đối tượng đóng vai trò rất lớn trong các chương trình C#. Nói một cách nôm na, lớp đối tượng là khuôn đúc ra các đối tượng cụ thể (gọi là instance), định nghĩa các thành phần dữ liệu và chức năng có thể có cho mỗi đối tượng cụ thể. Thành viên của lớp đối tượng là các dữ liệu và các hàm bên trong lớp đối tượng nó, gọi là dữ liệu thành phần và hàm thành phần. Các thành viên của lớp đối tượng có thể được khai báo là public (có thể được truy xuất trực tiếp từ bên ngoài lớp đối tượng), hoặc private (chỉ được nhìn thấy ở trong chính khai báo lớp đối tượng), protected (chỉ được truy xuất từ bên trong chính lớp đối tượng hoặc các lớp đối tượng khác kế thừa từ nó). Dữ liệu thành phần là các thành phần bên trong lớp chứa dữ liệu cho class – đó có thể là các trường dữ liệu (field), hằng số (constant) hoặc là các sự kiện (event). Trường dữ liệu là các biến được khai báo ở mức lớp đối tượng. Ví dụ dưới đây định nghĩa một lớp đối tượng có tên là PhoneCustomer với 3 trường dữ liệu CustomerID, FirstName và LastName. Lớp này cũng định nghĩa một hằng ở mức lớp là DayOfSendingBill. class PhoneCustomer{ public const int DayOfSendingBill = 1; public int CustomerID; public string FirstName; public string LastName; } Khi tạo ra một đối tượng của lớp đối tượng PhoneCustomer, chúng ta có thể truy xuất các trường dữ liệu này theo dạng đốiTượng.TrườngDữLiệu, như ví dụ dưới đây: PhoneCustomer Customer1 = new PhoneCustomer(); Giáo trình Visual Studio .NET 25
  26. Customer1.FirstName = "Burton"; Hàm thành phần là các thành phần cung cấp chức năng xử lý dữ liệu cho lớp đối tượng. Chúng có thể là các phương thức (method), thuộc tính (property), hàm khởi dựng (constructor), hàm hủy bỏ (destructor), hoặc indexer. Phương thức (method) là các hàm được khai báo trong lớp đối tượng. Chúng có thể là phương thức làm việc với thể hiện cụ thể của lớp, hoặc là phương thức chỉ hoạt động ở mức lớp (phương thức tĩnh – static method). Thuộc tính (property) là tập các hàm có thể được truy xuất theo cách giống như trường dữ liệu public của lớp đối tượng. C# cung cấp các cú pháp đặc biệt để định nghĩa các thuộc tính chỉ đọc, chỉ ghi hay được truy xuất tự do. Hàm khởi dựng (constructor) là các hàm đặc biệt, được gọi mỗi khi đối tượng của lớp được tạo mới. Các hàm khởi dụng phải có trùng tên với tên lớp đối tượng. Hàm hủy bỏ (destructor) là các hàm được gọi khi đối tượng bị hủy. Các hàm này có tên của lớp và được bắt đầu bằng ký tự ~ (dấu ngã). Danh sách đầy đủ các bổ từ truy cập các hàm thành phần của lớp được cho ở bảng dưới đây: Bổ từ truy cập Mô tả new Ẩn phương thức có cùng khai báo được kế thừa từ lớp cha public Phương thức có thể được truy cập từ mọi nơi protected Phương thức có thể được truy cấp bên trong lớp khai báo nó hoặc từ một kiểu dữ liệu khác được dẫn xuất từ lớp khai báo nó. internal Phương thức có thể được truy xuất trong phạm vi cùng assembly. private Phương thức chỉ có thể truy xuất trong lớp khai báo nó. static Phương thức hoạt động ở mức lớp, không hoạt động với một đối tượng cụ thể. virtual Phương thức có thể được ghi đè (override) trong lớp dẫn xuất. abstract Phương thức chỉ đóng vai trò định nghĩa cú pháp, không cài đặt. override Phương thức ghi đè phương thức được định nghĩa là virtual hoặc abstract ở lớp cha. sealed Phương thức ghi đè phương thức virtual, nhưng không thể được ghi đè bởi bất cứ lớp nào khi dẫn xuất thêm. extern Phương thức được cài đặt từ bên ngoài, có thể là bằng ngôn ngữ khác. Giáo trình Visual Studio .NET 26
  27. 2.5.2 Kiểu dữ liệu cấu trúc – struct Cú pháp khai báo kiểu dữ liệu cấu trúc trong C# hoàn toàn tương tự như khai báo lớp đối tượng, chỉ thay từ khóa class bằng từ khóa struct. Chẳng hạn, đây là khai báo kiểu cấu trúc PhoneCustomer: struct PhoneCustomer{ public const int DayOfSendingBill = 1; public int CustomerID; public string FirstName; public string LastName; } Điểm khác biệt giữa kiểu cấu trúc và lớp đối tượng đó là cách mà chúng được lưu trữ và truy xuất. Lớp đối tượng là kiểu dữ liệu tham chiếu, được lưu trữ trong vùng nhớ heap, trong khi đó, cấu trúc là kiểu dữ liệu giá trị, được lưu trữ trong vùng nhớ stack. Ngoài ra, cấu trúc không thể được kế thừa như ở lớp đối tượng. 2.6 Phương thức 2.6.1 Khai báo phương thức Cú pháp định nghĩa một phương thức trong C# tương tự như ở C++. Điểm khác biệt đó là, trong C#, mỗi phương thức được đều được khai báo tầm truy xuất của nó (public, private, protected) và định nghĩa luôn phần thân phương thức. Nghĩa là, không được sử dụng từ khóa “public:” để gộp nhóm nhiều định nghĩa phương thức public. Dưới đây là cú pháp định nghĩa một phương thức: [modifiers] return_type MethodName([parameters]) { // Method body } Ví dụ, đoạn code dưới đây định nghĩa hai phương thức IsSquare() và Move(): public bool IsSquare(Rectangle rect) { return (rect.Height == rect.Width); } protected void Move(int dX, int dY) { this.x += dX; this.y += dY; Giáo trình Visual Studio .NET 27
  28. } 2.6.2 Truyền tham số cho phương thức Các đối số có thể được truyền cho phương thức theo tham chiếu hoặc giá trị. Biến được truyền theo tham chiếu đến một phương thức thì sẽ bị ảnh hưởng bởi mọi thay đổi nếu có trong thân phương thức, trong khi đó, biến được truyền theo giá trị thì không bị ảnh hưởng bởi những thay đổi diễn ra trong thân phương thức. Dưới đây là minh họa việc sử dụng các đối số trong phương thức: using System; namespace MinhHoaDoiSo { class ParameterTest { static void SomeFunction(int[] ints, int i) { ints[0] = 100; i = 100; } public static int Main() { int i = 0; int[] ints = { 0, 1, 2, 4, 8 }; // Hien thi danh sach gia tri truoc khi goi phuong thuc Console.WriteLine("i = " + i); Console.WriteLine("ints[0] = " + ints[0]); Console.WriteLine("Thuc hien goi phuong thuc SomeFunction() "); // Sau khi goi phuong thuc, gia tri trong mang ints duoc thay doi, // nhung gia tri cua i thi khong! SomeFunction(ints, i); Console.WriteLine("i = " + i); Console.WriteLine("ints[0] = " + ints[0]); return 0; } } } Một minh họa kết quả ở console là như sau: i = 0 ints[0] = 0 Thuc hien goi phuong thuc SomeFunction() i = 0 ints[0] = 100 Giáo trình Visual Studio .NET 28
  29. Để thay đổi giá trị của đối số i, chúng ta phải truyền nó như là một đối số kiểu tham chiếu. Việc định nghĩa một đối số là kiểu tham chiếu được thực hiện bằng cách thêm từ khóa ref vào đầu định nghĩa đối số, như ví dụ sau: static void SomeFunction(int[] ints, ref int i) { ints[0] = 100; i = 100; } Và khi thực hiện lời gọi phương thức, từ khóa ref cũng phải được thêm vào trước biến truyền cho phương thức: SomeFunction(ints, ref i); Một điểm cần lưu ý đó là, biến được sử dụng để truyền cho phương thức phải được khởi tạo trước khi thực hiện lời gọi phương thức. Để truyền một đối số làm nhiệm vụ chứa giá trị đầu ra của một phương thức, chúng ta sử dụng từ khóa out. Biến được truyền theo kiểu như thế này thì không nhất thiết phải được khởi tạo trước khi thực hiện lời gọi phương thức, tuy nhiên, nếu trong thân hàm đối số không được gán một giá trị nào thì trình biên dịch sẽ báo lỗi. Cách sử dụng đối số kiểu out là như ví dụ dưới đây: static void SomeFunction(out int i) { i = 100; //phai thuc hien thay doi gia tri cua doi so i trong than phuong thuc!!! } public static int Main() { int i; // chi khai bao bien i, chua can KHOI TAO GIA TRI cho no SomeFunction(out i); Console.WriteLine(i); return 0; } 2.7 Dữ liệu kiểu array 2.7.1 Cú pháp khai báo array Array trong C# được khai báo bằng cách gắn cặp dấu ngoặc vuông vào sau kiểu dữ liệu cơ sở, theo cú pháp dưới. Giáo trình Visual Studio .NET 29
  30. type[] arrayName; Ví dụ: int[] daySo; //khai bao daySo la mot array (co the chua cac so int) Để khởi tạo array, chúng ta sử dụng từ khóa new, sau đó chỉ định cụ thể kích thước trong cặp dấu ngoặc vuông. Sau khi khởi tạo, mỗi phần tử trong array được truy xuất thông qua tên array cùng với số hiệu của nó (được đánh số từ 0 trở đi) // Khoi tao mot array voi 32 phan tu du lieu kieu int int kichThuoc = 10 + 20; int[] daySo = new int[kichThuoc]; // luu y co the dung BIEN de xac dinh kich thuoc!!! daySo[0] = 35; // gan gia tri cho phan tu dau tien trong daySo daySo[31] = 432; // gan gia tri cho phan tu thu 32 trong daySo Lưu ý rằng, chúng ta có thể sử dụng giá trị của biến khi định nghĩa kích thước cho array. Và, một khi đã khởi tạo xong array với kích thước cụ thể, chúng ta không thể thay đổi kích thước của array đó. Để làm được điều này, chúng ta cần một kiểu dữ liệu khác – ArrayList. Chúng ta cũng có thể khai báo và định nghĩa một array theo cách chỉ ra các phần tử cụ thể của nó như sau: string[] myArray = {"first element", "second element", "third element"}; 2.7.2 Làm việc với array Để lấy kích thước của array, chúng ta sử dụng thuộc tính Length của nó. Một số phương thức thường dùng đối với dữ liệu kiểu array là: - Array.Sort(arr): hàm tĩnh, sử dụng để sắp xếp array arr; arr là array của các phần tử có kiểu được định nghĩa sẵn trong C#. - Array.Reverse(arr): hàm tĩnh, sử dụng để đảo ngược vị trí của các phần tử có trong array arr. Ví dụ sau minh họa việc khai báo một array gồm các string, sắp xếp theo thứ tự ABC, đảo ngược các phần tử, rồi in các phần tử ra Console: string[] hoTen = {"Nguyen Van Trung", "Nguyen Hoang Ha", "Tran Nguyen Phong"}; // in danh sach cac phan tu trong array for (int i = 0; i < hoTen.Length; i++) Giáo trình Visual Studio .NET 30
  31. Console.WriteLine(hoTen[i]); Array.Sort(hoTen); Array.Reverse(hoTen); // in danh sach cac phan tu trong array su dung cu phap foreach foreach (string stHoTen in hoTen) Console.WriteLine(stHoTen); 2.7.3 Array nhiều chiều C# hỗ trợ hai kiểu array nhiều chiều: kiểu ma trận (chẳng hạn như một ma trận hai chiều là một array trong đó mỗi dòng sẽ có cùng số cột) và array kiểu răng cưa. Ví dụ dưới đây minh họa cách khai báo và khởi tạo một array kiểu ma trận hai chiều: string[,] beatleName = { {"Lennon","John"}, {"McCartney","Paul"}, {"Harrison","George"}, {"Starkey","Richard"} }; Lưu ý rằng chúng ta sử dụng dấu phẩy để phân cách các chiều trong khai báo array, ngay cả khi chúng ta chưa thực sự xác định kích thước của mỗi chiều. Để khai báo một array 3 chiều gồm các string, chúng ta làm như sau: string[,,] my3DArray; Array, sau khi khai báo có thể tiến hành khởi tạo giá trị cho các phần tử bên trong, như sau: double [, ] matrix = new double[5, 10]; for (int i = 0; i < 5; i++) { for (int j=0; j < 10; j++) matrix[i, j] = i*j; } Sau khi khởi tạo array, chúng ta có thể xác định kích thước của từng chiều bằng phương thức GetLength(). Chẳng hạn, double [, ] matrix = new double[5, 10]; Console.WriteLine(“Kich thuoc chieu thu nhat: {0}”, matrix.GetLength(0)); // 5 Console.WriteLine(“Kich thuoc chieu thu nhat: {0}”, matrix.GetLength(1)); // 10 Kiểu array nhiều chiều thứ hai là array kiểu răng cưa, trong đó kích thước của mỗi chiều là có thể khác nhau. Chính xác, đây là kiểu dữ liệu array của các array. Đoạn code dưới Giáo trình Visual Studio .NET 31
  32. đây minh họa cách định nghĩa một array kiểu răng cưa: int[][] a = new int[3][]; a[0] = new int[4]; a[1] = new int[3]; a[2] = new int[1]; Từng phần tử của array kiểu răng cưa sẽ được truy xuất như là một array một chiều như thông thường. Chương trình dưới đây cho thấy rõ hơn cách thức sử dụng của array kiểu răng cưa. Chương trình này có nhiệm vụ nhận vào một số nguyên n, sau đó thành lập tam giác Pascal rồi in tam giác này ra màn hình. using System; namespace MinhHoaArray { class TamGiacPascal { Console.Write("n = "); int n = int.Parse(Console.ReadLine()); long[][] C = new long[n+1][]; // khoi tao C la 1 array gom n+1 phan tu for (int i = 0; i <= n; i++) { C[i] = new long[i+1]; // phan tu thu i cua C la 1 array gom (i+1) phantu C[i][0] = C[i][i] = 1; for (int j = 1; j < i; j++) C[i][j] = C[i-1][j] + C[i-1][j-1]; } for (int i = 0; i < C.Length; i++) { for (int j = 0; j < C[i].Length; j++) Console.Write("{0, 5}", C[i][j]); // in so voi do rong la 5 Console.WriteLine(); } Console.ReadLine(); } } Một minh họa kết quả in ra của chương trình là: n = 5 1 1 1 1 2 1 1 3 3 1 Giáo trình Visual Studio .NET 32
  33. 1 4 6 4 1 1 5 10 10 5 1 2.8 Các toán tử C# hỗ trợ các kiểu toán tử sau đây: Loại toán tử Ký hiệu Số học + - * / % Logic & | ^ ~ && || ! Cộng chuỗi + Tăng và giảm ++ Dịch bit > So sánh == != = Phép gán = += -= *= /= %= &= |= ^= >= Truy xuất thành phần (cho object và struct) . Indexing (cho array và các indexers) [] Ép kiểu () Điều kiện ?: Tạo đối tượng new Thông tin về kiểu sizeof is typeof as Điều khiển Overflow exception checked unchecked Truy xuất địa chỉ và gián tiếp * -> & [] Cũng giống như các ngôn ngữ tựa C, chúng ta cần phân biệt phép gán với phép so sánh. Chẳng hạn x = 3; // gán giá trị 3 cho x trong khi đó, muốn so sánh x với 3, chúng ta phải ghi như thế này if (x == 3) 2.8.1 Các toán tử tắt Dưới đây là các toán tử viết tắt của C# Toán tử tắt Tương đương với x++, ++x x = x + 1 x , x x = x - 1 x += y x = x + y Giáo trình Visual Studio .NET 33
  34. Toán tử tắt Tương đương với x -= y x = x – y x *= y x = x * y x /= y x = x / y x %= y x = x % y x >>= y x = x >> y x <<= y x = x << y x &= y x = x & y x |= y x = x | y x ^= y x = x ^ y 2.8.2 Toán tử tam nguyên (ternary operator) Cũng như các ngôn ngữ tựa C, C# cũng cung cấp toán tử tam nguyên: condition ? true_value : false_value 2.8.3 Chỉ dẫn checked và unchecked Xem xét đoạn mã lệnh sau byte b = 255; b++; Console.WriteLine(b.ToString()); Dữ liệu kiểu byte chỉ có thể nắm giữ được các giá trị trong vùng từ 0 đến 255, vì thế, việc tăng giá trị của b sẽ làm xuất hiện lỗi overflow. Việc quản lý các lỗi kiểu như thế này rất quan trọng, vì thế C# cung cấp một cú pháp cho phép phát hiện ra kiểu lỗi này. Xem đoạn mã lệnh byte b = 255; checked { b++; } Console.WriteLine(b.ToString()); Khi thực thi đoạn mã lệnh này, chúng ta sẽ nhận được thông báo lỗi dạng Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow. at MinhHoa.Main(String[] args) Nếu muốn bỏ qua lỗi overflow, chúng ta đánh dấu đoạn mã lệnh tương ứng là unchecked Giáo trình Visual Studio .NET 34
  35. byte b = 255; unchecked { b++; } Console.WriteLine(b.ToString()); Trong trường hợp này, không có ngoại lệ nào được phát sinh, nhưng chúng ta sẽ bị mất dữ liệu! Lưu ý rằng, unchecked là trạng thái mặc định! 2.8.4 Toán tử is Toán tử is cho phép kiểm tra một object có “tương thích” với một kiểu dữ liệu nào đó hay không. Trong phần lập trình hướng đối tượng, chúng ta sẽ có dịp sử dụng toán tử này. 2.8.5 Toán tử sizeof Toán tử sizeof cho phép xác định kích thước (tính bằng byte) được yêu cầu bởi một kiểu dữ liệu giá trị trên vùng nhớ stack . Xem ví dụ minh họa sau: string s = "A string"; unsafe { Console.WriteLine(sizeof(int)); // in ra kich thuoc cua 1 int, la 4 } Lưu ý rằng, toán tử sizeof chỉ được sử dụng với mã lệnh không an toàn (unsafe code). 2.9 Enumerations – Kiểu liệt kê Một kiểu liệt kê là một kiểu dữ liệu nguyên do người sử dụng tự định nghĩa. Khi khai báo một kiểu liệt kê, chúng ta xác định một tập các giá trị có thể nhận được thông qua các tên gọi có tính gợi nhớ. Chúng ta có thể định nghĩa một kiểu liệt kê như ví dụ sau: public enum TimeOfDay { Morning = 0, Afternoon = 1, Evening = 2 } Việc sử dụng kiểu dữ liệu liệt kê sẽ cho phép viết các đoạn mã lệnh dễ đọc, dễ quản lý lỗi logic hơn class EnumExample { Giáo trình Visual Studio .NET 35
  36. public static int Main() { WriteGreeting(TimeOfDay.Morning); return 0; } static void WriteGreeting(TimeOfDay timeOfDay) { switch(timeOfDay) { case TimeOfDay.Morning: Console.WriteLine("Good morning!"); break; case TimeOfDay.Afternoon: Console.WriteLine("Good afternoon!"); break; case TimeOfDay.Evening: Console.WriteLine("Good evening!"); break; default: Console.WriteLine("Hello!"); break; } } } Một đặc tính đặc biệt của kiểu liệt kê cần lưu ý đó là khả năng chuyển đổi qua lại giữa nó với kiểu dữ liệu string. //enum string TimeOfDay time = TimeOfDay.Afternoon; Console.WriteLine(time.ToString()); // chuoi in ra la “Afternoon” //string enum TimeOfDay time2 = (TimeOfDay) Enum.Parse(typeof(TimeOfDay), "afternoon", true); Console.WriteLine((int)time2); Việc lấy chuỗi tương ứng của một biến giá trị liệt kê có thể tiến hành đơn giản bằng cách sử dụng phương thức ToString() của biến. Trong khi đó, để lấy giá trị kiểu liệt kê từ một chuỗi tương ứng thì phức tạp hơn, chúng ta phải sử dụng phương thức tĩnh Parse của lớp Enum. Còn có nhiều phương thức khác của lớp Enum; chi tiết tham khảo bạn xem ở tài liệu MSDN. 2.10 Namespace Namespace là đơn vị gộp nhóm mang tính logic. Khi định nghĩa một lớp đối tượng trong một file C#, chúng ta có thể đưa nó vào trong một namespace nào đó. Sau đó, khi định Giáo trình Visual Studio .NET 36
  37. nghĩa lớp khác có chức năng liên quan với lớp đối tượng trước đó, chúng ta có thể gộp nó vào trong cùng namespace, qua đó tạo ra một nhóm logic, cho các nhà phát triển biết rằng các lớp đối tượng trong cùng namespace là có liên quan với nhau. namespace BusinessLayer { public struct Student { // Ma lenh dinh nghia struct Student } } Kiểu dữ liệu thuộc về một namespace sẽ có tên đầy đủ được xác định bằng namespace.tenKieuDuLieu Như ở ví dụ trên, tên đầy đủ của lớp Student sẽ là BusinessLayer.Student Student còn được gọi là tên ngắn gọn của kiểu dữ liệu. Chúng ta cũng có thể lồng các namespace bên trong các namespace khác, qua đó tạo ra cấu trúc phân lớp cho các kiểu dữ liệu. namespace HueUni { namespace COSciences { namespace DoIT { class Student { // Ma lenh dinh nghia lop doi tuong Student } } } } Tên namespace bao gồm các tên namespace chứa nó, phân cách nhau bằng dấu chấm, bắt đầu bằng namespace ngoài cùng nhất, và kết thúc bằng chính tên ngắn gọn của nó. Ví dụ, tên đầy đủ của namespace DoIT là HueUni.COSciences.DoIT Chúng ta cũng có thể dùng cú pháp như sau để tổ chức các namespace: namespace HueUni.COSciences.DoIT Giáo trình Visual Studio .NET 37
  38. { class Student { // Ma lenh dinh nghia lop doi tuong Student } } Namespace không tương ứng với assembly. Hoàn toàn có thể có các namespace khác nhau trong cùng một assembly, hay ngược lại, có thể định nghĩa các kiểu dữ liệu cho cùng một namespace ở nhiều assembly khác nhau. 2.10.1 Khai báo sử dụng namespace Việc sử dụng kiểu dữ liệu với tên đầy đủ rõ ràng là không thuận tiện lắm, đặc biệt là khi kiểu dữ liệu được định nghĩa trong một namespace ở mức quá sâu. C# cho phép sử dụng tên ngắn để xác định kiểu dữ liệu bằng cách xác định trước namespace của kiểu dữ liệu này với từ khóa using ở đầu file mã nguồn. Chẳng hạn, nếu có khai báo using HueUni.COSciences.DoIT ở đầu file mã nguồn thì trong file đó, chúng ta có thể sử dụng tên ngắn của lớp đối tượng Student thay cho tên đầy đủ của nó là HueUni.COSciences.DoIT.Student Trong trường hợp tên ngắn của hai kiểu dữ liệu thuộc về hai namespace cùng tham khảo là trùng nhau, khi sử dụng kiểu dữ liệu, chúng ta phải chỉ rõ với namespace cụ thể. Chẳng hạn, giả sử lớp đối tượng có tên Student được định nghĩa trong cả hai namespace HueUni.COSciences.DoiT và HueUni.COSciences.DoP; khi đó chúng ta cần xác định lớp Student bằng một cái tên dài hơn, DoIT.Student hoặc DoP.Student tùy theo từng tình huống. using HueUni.COSciences.DoIT; using HueUni.COSciences.DoP; class Test { public static int Main() { DoIT.Student nvtrung = new DoIT.Student(); DoP.Student nhha = new DoP.Student(); return 0; } } 2.10.2 Bí danh cho Namespace Một cách sử dụng khác của từ khóa using đó là gán bí danh cho các lớp đối tượng và các Giáo trình Visual Studio .NET 38
  39. namespace. Cú pháp của cách sử dụng này là như sau: using alias = NamespaceName; Ví dụ sau đây minh họa cách thức sử dụng bí danh namespace: using System; using KhoaCNTT = HueUni.COSciences.DoIT; class Test { public static int Main() { KhoaCNTT.Student nvtrung = new KhoaCNTT.Student(); Console.WriteLine(nvtrung.GetNamespace()); return 0; } } namespace HueUni.COSciences.DoIT { class Student { public string GetNamespace() { return this.GetType().Namespace; } } } 2.11 Bài thực hành Bài thực hành 1.1. Chương trình đầu tiên Tóm tắt Bài thực hành này giúp bạn làm quen với môi trường Visual Studio 2005 và các thao tác nhập xuất cơ bản thông qua giao diện bàn phím. Cụ thể, chương trình yêu cầu người sử dụng nhập hai số, sau đó in ra màn hình tổng, tích và thương của hai số này. Kỹ thuật được trình bày - Làm quen với môi trường Visual Studio 2005. Cấu trúc một solution, project và các tài nguyên có liên quan - Cách thức sử dụng thư viện MSDN để tra cứu, hướng dẫn - Sử dụng thao tác nhập xuất cơ bản Giáo trình Visual Studio .NET 39
  40. Trình tự thực hiện 1. Khởi động Microsoft Visual Studio 2005. Nhấn Ctrl + Shift + N hoặc chọn menu tương ứng là File New Project để tạo mới một project 2. Chọn loại ứng dụng cần phát triển là Visual C# Console Application. Chọn thư mục chứa project và đặt tên cho project. Về mặt thực chất, Visual Studio coi project thuộc về một solution nào đó, và một solution có thể chứa nhiều project. Tuy nhiên, trong nhiều “bài toán” đơn giản (như ví dụ của chúng ta chẳng hạn), một solution chỉ có 1 project. 3. Đặt tên cho project của chúng ta thành firstApp. Sau khi nhấn nút OK, hãy khảo sát xem cấu trúc của thư mục chứa solution của chúng ta. Bạn phải luôn nắm chắc về ý nghĩa của các tập tin, thư mục được tạo ra trong quá trình làm việc. 4. Gõ mã lệnh như minh họa vào trong phần mã nguồn của tập tin Program.cs Giáo trình Visual Studio .NET 40
  41. 5. Sử dụng MSDN để tra cứu các thông tin bạn chưa biết về: a. Console và các phương thức ReadLine(), WriteLine() của nó b. Cách chuyển đổi kiểu chuỗi thành số, ví dụ như int.Parse() 6. Nhấn Ctrl + F5 để thực hiện chạy chương trình. Sau đó quan sát cấu trúc thư mục của solution, cho biết sự thay đổi của nó so với khi mới được tạo ra ở bước 3. 7. Thử thay đổi kết câu lệnh thành float thuong = (float)x / y; float thuong = x / y; rồi chạy chương trình, quan sát kết quả và rút ra kết luận. 8. Sử dụng thêm các cấu trúc lệnh khác để tinh chỉnh hoạt động của chương trình (xử lý phép chia cho 0, ) Giáo trình Visual Studio .NET 41
  42. Bài thực hành 1.2. Module hóa chương trình Tóm tắt Viết chương trình nhập vào một số nguyên N từ bàn phím. Sau đó a. In ra màn hình giá trị N!. K b. Nhập thêm một số nguyên K từ bàn phím. Sau đó in ra C N = N!/(K!*(N-K)!) Kỹ thuật được trình bày - Cấu trúc, cách quản lý logic và vật lý, cách làm việc của solution và project - Thực hiện chia nhỏ ứng dụng thành để chuyên môn hóa các phần - Cơ bản về các kiểu phương thức trong một lớp Trình tự thực hiện 1. Mở solution đã làm ở Bài thực hành 1.1. Chỉnh sửa tên của solution từ “firstApp” thành “day1” cho có ý nghĩa. Xem cấu trúc thư mục của solution sau khi thay đổi. 2. Thêm một project vào solution này bằng menu lệnh File Add New project . Tương tự như cách tạo mới project ở bài thực hành trước, chọn thể loại project là Console Application. Đặt tên cho project mới là “modular”. Giáo trình Visual Studio .NET 42
  43. 3. Quan sát cấu trúc cây thư mục của solution trong cửa sổ Solution Explorer và cả trong Windows Explorer. Để ý rằng, trong cửa sổ Solution Explorer, project firstApp được tô đậm. Điều này có nghĩa, firstApp đóng vai trò là “Startup project”. Khi nhấn Ctrl + F5 thì project này sẽ được gọi thực thi chứ không phải là project modular mà ta mới tạo ra. Trong cửa sổ Solution Explorer, nhắp phải chuột lên “modular”. Trong menu hiện ra, chọn menu lệnh “Set as Startup project” để thiết lập lại startup project cho solution. 4. Việc nhập n, tính n! rồi in kết quả bạn hoàn toàn có thể thực hiện được bằng các câu lệnh đơn giản. Tuy nhiên, để tăng tính rõ ràng và tái sử dụng, bạn nên tạo ra một phương thức để hỗ trợ việc tính toán n!. Xem mã lệnh bên dưới 5. Chạy thử chương trình để xem kết quả. Hãy để ý rằng, khai báo phương thức giaiThua là static long giaiThua(int n). Thử xóa static trong khai báo này rồi chạy lại chương trình.  Lỗi nhận được cho biết chỉ các phương thức static mới được triệu gọi, sử dụng lẫn nhau 6. Bằng cách tạo ra phương thức long giaiThua() như trên, chúng ta có thể giải quyết Giáo trình Visual Studio .NET 43
  44. k k được vấn đề tính C n một cách dễ dàng. Lời gọi để tính C n như sau: GiaiThua(n)/(GiaiThua(n-k)*GiaiThua(k)) 7. Hãy tạo ra một phương thức để tính tổ hợp chập k của n phần tử (bạn tự quyết định các tham số và kiểu dữ liệu trả về). Giáo trình Visual Studio .NET 44
  45. Bài thực hành 1.3. Tạo thư viện sử dụng chung Tóm tắt Trong thực tế, một ứng dụng có thể là có khả năng thực thi (executable) hoặc chỉ đơn thuần là thư viện để chứa các chức năng, lớp đối tượng. Bài thực hành này hướng dẫn bạn tạo thư viện chứa các phương thức thường dùng. Với mục đích minh họa, thư viện này chỉ chứa 2 hàm tiện ích giúp tính giai thừa và tổ hợp chập. Sau khi biên dịch, bạn sẽ có được một file nhị với phần mở rộng là DLL. Thư viện này, khi cần, sẽ được tham chiếu đến trong các ứng dụng khác. Kỹ thuật được trình bày - Tạo loại ứng dụng loại thư viện Trình tự thực hiện 1. Tạo mới một project, đặt tên là commonUtils (common utilities - các tiện ích dùng chung). Chú ý chọn loại ứng dụng cần tạo là Class Library 2. Mặc định Visual Studio 2005 sẽ tạo ra trong namespace CommonUtils một lớp tên là Class1. Đổi tên lớp này lại thành Math. Sau đó cài đặt các phương thức như sau: 3. Rõ ràng, đây không phải là một chương trình để chạy như các ứng dụng bạn đã viết Giáo trình Visual Studio .NET 45
  46. trước đó - class Math không có phương thức static public Main() – tức là bạn không thể nhấn Ctrl + F5 để chạy chương trình. Biên dịch project này bằng menu lệnh Build Build commonUtils. Kết quả, bạn sẽ có một thư viện commonUtils.dll trong thư mục bin\Release hoặc bin\Debug của project tùy theo cách chọn chế độ biên dịch. Thư viện này sẽ được dùng để tham chiếu đến trong các ứng dụng cần nó. Mở rộng Bổ sung các phương thức thường dùng khác vào thư viện, chẳng hạn như phương thức xác định xem một số có phải là nguyên tố hay không, phương thức hoán đổi giá trị của hai số cho trước, Giáo trình Visual Studio .NET 46
  47. Bài thực hành 1.4. Tam giác Pascal Tóm tắt Viết chương trình nhập một số nguyên N từ bàn phím, sau đó in ra màn hình N dòng đầu tiên của tam giác Pascal. Kỹ thuật được trình bày - Sử dụng thư viện có sẵn Trình tự thực hiện 1. Tạo mới một ứng dụng kiểu Console Application. Đặt tên project là pascalTriangle1 2. Thực hiện bổ sung tham khảo đến thư viện commonUtils bằng cách: - Nhắp phải chuột vào project pascalTriangle1 trong cửa sổ Solution Explorer - Trong menu hiện ra, chọn Add Reference Trong tab Browse của hộp thoại Add Reference, tìm đến thư viện Giáo trình Visual Studio .NET 47
  48. commonUtils.dll đã tạo ra trước đó. Dễ thấy rằng thư viện được tham khảo đến không chỉ có dạng DLL mà có thể có các dạng khác, bao gồm EXE, OCX, 3. Hoàn thiện phần mã nguồn có sử dụng tham chiếu đến thư viện vừa bổ sung như hình dưới: Mở rộng Hãy tự rút ra những ghi chú cần thiết về việc: - Khai báo phương thức C(int n, int k) trong commonUtils là public static long C(int n, int k) static, public ở đây có ý nghĩa gì, có thể thay thế hoặc bỏ đi? - Tương tự cho phương thức giaiThua(int n); Tại sao trong quá trình sử dụng phương thức C() lại phải ghi đầy đủ là Giáo trình Visual Studio .NET 48
  49. commonUtils.Math.C()? Chỉ cần ghi Math.C() có được không? Giáo trình Visual Studio .NET 49
  50. Bài thực hành 1.5. Tam giác Pascal – array version Tóm tắt Sử dụng array để xây dựng tam giác Pascal như Bài thực hành 1.4. Kỹ thuật được trình bày - Sử dụng array Trình tự thực hiện 1. Tạo mới một project kiểu Console Application với tên là pascalTriangle2 0 k k k-1 k 2. Sử dụng các tính chất C0 = Ck = 1, Cn = Cn-1 + Cn-1 , ta sẽ xây dựng một jagged array từ nhỏ đến lớn. Chi tiết như phần mã nguồn phía dưới: Giáo trình Visual Studio .NET 50
  51. Mở rộng Có thể dùng array nhiều chiều trong trường hợp này không? Nếu có thì có sự khác nhau nào so với dùng jagged array? Giáo trình Visual Studio .NET 51