KIỂU DỮ LIỆU

C# là ngôn ngữ lập trình mạnh về kiểu dữ liệu, một ngôn ngữ mạnh về kiểu dữ liệu là phải khai báo kiểu của mỗi đối tượng khi tạo (kiểu số nguyên, số thực, kiểu chuỗi, kiểu điều khiển...) và trình biên dịch sẽ giúp cho người lập trình không bị lỗi khi chỉ cho phép một loại kiểu dữ liệu có thể được gán cho các kiểu dữ liệu khác. Kiểu dữ liệu của một đối tượng là một tín hiệu để trình biên dịch nhận biết kích thước của một đối tượng (kiểu int có kích thước là 4 byte) và khả năng của nó (như một đối tượng button có thể vẽ, phản ứng khi nhấn,...).

Tương tự như C++ hay Java, C# chia thành hai tập hợp kiểu dữ liệu chính: Kiểu xây dựng sẵn (built- in) mà ngôn ngữ cung cấp cho người lập trình và kiểu được người dùng định nghĩa (user-defined) do người lập trình tạo ra.

C# phân tập hợp kiểu dữ liệu này thành hai loại: Kiểu dữ liệu giá trị (value) và kiểu dữ liệu tham chiếu (reference). Việc phân chia này do sự khác nhau khi lưu kiểu dữ liệu giá trị và kiểu dữ liệu tham chiếu trong bộ nhớ. Đối với một kiểu dữ liệu giá trị thì sẽ được lưu giữ kích thước thật trong bộ nhớ đã cấp phát là stack. Trong khi đó thì địa chỉ của kiểu dữ liệu tham chiếu thì được lưu trong stack nhưng đối tượng thật sự thì lưu trong bộ nhớ heap.

Kiểu dữ liệu (data type) là một tập hợp gồm các nhóm dữ liệu có cùng đặc tính, cách lưu trữ của dữ liệu và các phép toán xử lý trên trường dữ liệu đó; nhằm mục đích phân loại các dữ liệu. Việc tìm hiểu và sử dụng đúng các kiểu dữ liệu sẽ giúp tối ưu hóa được vùng nhớ cũng như sẽ sử dụng được các hàm xử lý dữ liệu dành riêng cho mỗi kiểu dữ liệu.

@Ghi chú: Tất cả các kiểu dữ liệu xây dựng sẵn là kiểu dữ liệu giá trị ngoại trừ các đối tượng và chuỗi. Và tất cả các kiểu do người dùng định nghĩa ngoại trừ kiểu cấu trúc đều là kiểu dữ liệu tham chiếu.

Ngoài ra C# cũng hỗ trợ một kiểu con trỏ C++, nhưng hiếm khi được sử dụng, và chỉ khi nào làm việc với những đoạn mã lệnh không được quản lý (unmanaged code). Mã lệnh không được quản lý là các lệnh được viết bên ngoài nền tảng .NET, như là các đối tượng COM.

1.     Kiểu dữ liệu xây dựng sẵn

Ngôn ngữ C# đưa ra các kiểu dữ liệu xây dựng sẵn rất hữu dụng, phù hợp với một ngôn ngữ lập trình hiện đại, mỗi kiểu dữ liệu được ánh xạ đến một kiểu dữ liệu được hỗ trợ bởi hệ thống xác nhận ngôn ngữ chung (Common Language Specification: CLS) trong MS.NET. Việc ánh xạ các kiểu dữ liệu nguyên thuỷ của C# đến các kiểu dữ liệu của .NET sẽ đảm bảo các đối tượng được tạo ra trong C# có thể được sử dụng đồng thời với các đối tượng được tạo bởi bất cứ ngôn ngữ khác được biên dịch bởi .NET, như VB.NET.

Mỗi kiểu dữ liệu có một sự xác nhận và kích thước không thay đổi, không giống như C++, int trong C# luôn có kích thước là 4 byte bởi vì nó được ánh xạ từ kiểu Int32 trong . NET.

Kiểu giá trị - value type

Các biến thuộc kiểu này có thể được gán giá trị một cách trực tiếp, đây là kiểu dữ liệu được ngôn ngữ cung cấp. Nó kế thừa từ class System.ValueType.

Một biến khi khai báo kiểu dữ liệu thì hệ thống sẽ cấp phát bộ nhớ, giá trị thì vùng nhớ của biến đó sẽ chứa giá trị của dữ liệu.

Các bảng dưới đây liệt kê danh sách các kiểu dữ liệu có sẵn trong C#:

Bảng 1.3. Kiểu số nguyên

Kiểu dữ liệu

Kích thước (bytes)

Ý nghĩa

byte

1

Số nguyên dương không dấu có giá trị

từ 0 đến 255

sbyte

1

Số nguyên có dấu có giá trị

từ -128 đến 127

short

2

Số nguyên có dấu có giá trị

từ -32,768 đến 32,767

ushort

2

Số nguyên không dấu có giá trị

từ 0 đến 65,535

int

4

Số nguyên có dấu có giá trị

từ -2,147,483,647 đến  2,147,483,647

uint

4

Số nguyên không dấu có giá trị

từ 0 đến 4,294,967,295

long

8

Số nguyên có dấu có giá trị

từ -9,223,370,036,854,775,808 đến 9,223,370,036,854,775,807

ulong

8

Số nguyên không dấu có giá trị

từ 0 đến 18,446,744,073,709,551,615

Bảng 1.4. Kiểu ký tự

Kiểu dữ liệu

Kích thước (bytes)

Ý nghĩa

char

2

Chứa một ký tự Unicode

Bảng 1.5. Kiểu logic

Kiểu dữ liệu

Kích thước (bytes)

Ý nghĩa

bool

1

Chứa 1 trong 2 giá trị logic là true hoặc false

@Ghi chú: Kiểu giá trị logic chỉ có thể nhận được giá trị là true hay false mà thôi. Một giá trị nguyên không thể gán vào một biến kiểu logic trong C# và không có bất cứ chuyển đổi ngầm định nào. Điều này khác với C/C++, cho phép biến logic được gán giá trị nguyên, khi đó giá trị nguyên 0 là false và các giá trị còn lại là true.

Bảng 1.6. Kiểu số thực

Kiểu dữ liệu

Kích thước (bytes)

Ý nghĩa

float

4

Kiểu số thực dấu chấm động có giá trị dao động

từ 3.4E – 38 đến 3.4E + 38, với 7 chữ số có nghĩa

double

8

Kiểu số thực dấu chấm động có giá trị dao động

từ 1.7E – 308 đến 1.7E + 308, với 15, 16 chữ số có nghĩa

decimal

8

Có độ chính xác đến 28 con số và giá trị thập phân, được dùng trong tính toán tài chính

 

Từ khóa sizeof()

Trong trường hợp quên giá trị của từng loại kiểu dữ liệu, có thể sử dụng biểu thức sizeof(type) để lấy kích cở chính xác của một biểu thức hoặc một biến nào đó. Giá trị trả về của sizeof() là kích cỡ của đối tượng hoặc kiểu bằng giá trị byte.

Kiểu tham chiếu - reference type

Nếu biến khai báo kiểu dữ liệu tham chiếu thì vùng nhớ của nó không chứa dữ liệu thực sự được lưu trong một biến mà vùng nhớ của nó chỉ chứa địa chỉ của đối tượng dữ liệu. Hay chúng tham chiếu đến vị trí nào đó trong bộ nhớ tùy theo địa chỉ đã được lưu. Giả sử dữ liệu trong vùng nhớ thay đổi bởi một biến nào đó, thì các dữ liệu của biến khác cũng thay đổi tự động giá trị của mình.

Ta có một số kiểu dữ liệu thuộc kiểu tham chiếu có sẵn trong C# như: object, dynamic, string... Và tất cả các kiểu dữ liệu do người dùng định nghĩa đều là kiểu dữ liệu tham chiếu.

Kiểu object

Đây là kiểu dữ liệu cơ bản nhất của tất cả các kiểu dữ liệu trong .NET. Mọi kiểu dữ liệu đều được kế thừa từ System.Object nên kiểu object có thể gán mọi giá trị của các kiểu dữ liệu khác như kiểu tham chiếu, kiểu giá trị hoặc kiểu do người dùng tự định nghĩa,... Tuy nhiên trước khi gán giá trị nó cần được chuyển kiểu.

Ví dụ:

object obj;

obj = 123;

// chúng ta đã chuyển kiểu dữ liệu object sang kiểu giá trị.

Kiểu dynamic

Đây là một kiểu dữ liệu mới được đưa vào trong C# 4.0. Ta có lưu mọi kiểu giá trị trong biến kiểu dynamic. Đối tượng thuộc kiểu này sẽ không xác định được kiểu dữ liệu cho đến khi chương trình được thực thi.

Ta khai báo kiểu dynamic như khai báo biến bình thường:

Cú pháp

dynamic <tên biến>;

//hoặc

dynamic <tên biến> = <giá trị>;

Kiểu string

Đây là một kiểu dữ liệu cho phép gán chuỗi vào biến. Nó được kế thừa từ kiểu object, có hai cách gán giá trị sau:

string str = "Lap trinh C#!";

//hoặc có thể dùng cách dưới đây, với cách này thì có thể dữ nguyên chuỗi truyền vào

//kể cả chuỗi có nhiều dòng.

string str =  @"Lap trinh C#          

              voi moi nguoi!";

Kiểu con trỏ - pointer type

Biến kiểu con trỏ lưu địa chỉ bộ nhớ của kiểu khác. Con trỏ trong C# có cùng khả năng như con trỏ trong C hoặc C++. Cú pháp để khai báo biến kiểu con trỏ là: type* identifier.

Ví dụ

char* abc;

int* xyz;

Chọn kiểu dữ liệu

Thông thường để chọn một kiểu dữ liệu nguyên để sử dụng như short, int hay long thường dựa vào độ lớn của giá trị muốn sử dụng. Ví dụ, một biến ushort có thể lưu giữ giá trị từ 0 đến 65.535, trong khi biến ulong có thể lưu giữ giá trị từ 0 đến 4.294.967.295, do đó tùy vào miền giá trị của phạm vi sử dụng biến mà chọn các kiểu dữ liệu thích hợp nhất. Kiểu dữ liệu int thường được sử dụng nhiều nhất trong lập trình vì với kích thước 4 byte của nó cũng đủ để lưu các giá trị nguyên cần thiết.

Kiểu số nguyên có dấu thường được lựa chọn sử dụng nhiều nhất trong kiểu số trừ khi có lý do chính đáng để sử dụng kiểu dữ liệu không dấu.

Cách tốt nhất khi sử dụng biến không dấu là giá trị của biến luôn luôn dương, biến này thường thể hiện một thuộc tính nào đó có miền giá trị dương. Ví dụ khi cần khai báo một biến lưu giữ tuổi của một người thì ta dùng kiểu byte (số nguyên từ 0-255) vì tuổi của người không thể nào âm được.

Kiểu float, double, và decimal đưa ra nhiều mức độ khác nhau về kích thước cũng như độ chính xác.Với thao tác trên các phân số nhỏ thì kiểu float là thích hợp nhất. Tuy nhiên lưu ý rằng trình biên dịch luôn luôn hiểu bất cứ một số thực nào cũng là một số kiểu double trừ khi chúng ta khai báo rõ ràng. Để gán một số kiểu float thì số phải có ký tự f theo sau.

          float soFloat = 24f;

Kiểu dữ liệu ký tự thể hiện các ký tự Unicode, bao gồm các ký tự đơn giản, ký tự theo mã Unicode và các ký tự thoát khác được bao trong những dấu nháy đơn. Ví dụ, A là một ký tự đơn giản trong khi \u0041 là một ký tự Unicode. Ký tự thoát là những ký tự đặc biệt bao gồm hai ký tự liên tiếp trong đó ký tự dầu tiên là dấu chéo ‘\’. Ví dụ, \t là dấu tab. Bảng 3.2 trình bày các ký tự đặc biệt.

Bảng 1.7. Các ký tự đặc biệt

Ký tự

Ý nghĩa

\’

Dấu nháy đơn

\”

Dấu nháy kép

\\

Dấu chéo

\0

Ký tự null

\a

Alert

Chuyển đổi các kiểu dữ liệu

Những đối tượng của một kiểu dữ liệu này có thể được chuyển sang những đối tượng của một kiểu dữ liệu khác thông qua cơ chế chuyển đổi tường minh hay ngầm định. Chuyển đổi nhầm định được thực hiện một cách tự động, trình biên dịch sẽ thực hiện công việc này. Còn chuyển đổi tường minh diễn ra khi chúng ta gán ép một giá trị cho kiểu dữ liệu khác.

Việc chuyển đổi giá trị ngầm định được thực hiện một cách tự động và đảm bảo là không mất thông tin. Ví dụ, chúng ta có thể gán ngầm định một số kiểu short (2 byte) vào một số kiểu int (4 byte) một cách ngầm định. Sau khi gán hoàn toàn không mất dữ liệu vì bất cứ giá trị nào của short cũng thuộc về int:

      short x = 10;

      int y = x; // chuyển đổi ngầm định

Tuy nhiên, nếu chúng ta thực hiện chuyển đổi ngược lại, chắc chắn chúng ta sẽ bị mất thông tin. Nếu giá trị của số nguyên đó lớn hơn 32.767 thì nó sẽ bị cắt khi chuyển đổi. Trình biên dịch sẽ không thực hiện việc chuyển đổi ngầm định từ số kiểu int sang số kiểu short:

      short x;

      int y = 100;

      x = y;     // Không biên dịch, lỗi !!!

Để không bị lỗi chúng ta phải dùng lệnh gán tường minh, đoạn mã trên được viết lại như sau:

      short x;

      int y = 500;

      x = (short) y; // Ép kiểu tường minh, trình biên dịch không báo lỗi

2.     Kiểu dữ liệu người dùng định nghĩa

Ngoài ra còn có các kiểu dữ liệu tham chiếu do người dùng tự định nghĩa như class, interface hoặc delegate sẽ tìm hiểu sau.

3.     Một số lỗi thường gặp khi khai báo

- Không gán giá trị cho biến (trừ kiểu string). Ví dụ: int a;

- Gán giá trị nằm ngoài kiểu dữ liệu của biến. Ví dụ: int b = 6.9;

- Dùng dấu nháy đơn cho kiểu string. Ví dụ: string d = 'Q'; Vẫn phải sử dụng dấu nháy kép mặc dù giá trị gán chỉ có 1 ký tự.

- Gán giá trị null cho biến không phải kiểu string. Ví dụ: long x = null; Ta có thể sửa thành long? x = null;

- Gán giá trị của biến có kiểu dữ liệu lớn hơn cho biến có kiểu dữ liệu nhỏ hơn. Ví dụ: int x = 10; byte y = x; Mặc dù 10 vẫn nằm trong khoảng giá trị của kiểu byte nhưng vẫn không thể gán giá trị biến kiểu int cho biến kiểu byte được.