Khai báo dữ liệu trong ngôn ngữ lập trình C

Nhiều ngôn ngữ lập trình kể cả C, biểu thị các số trong hai dạng: nguyên và thực (hay không nguyên). Sự khác nhau này hình thành từ khía cạnh kỹ thuật của các cách thức xử lý và lưu trữ các giá trị trong bộ nhớ.


Kiểu nguyên viết dưới dạng int được dùng để biểu thị các số nguyên. Kiểu nguyên có trong nhiều kích cỡ khác nhau tùy theo phân lượng bộ nhớ được dùng và độ lớn cao nhất1. Các từ khóa, có tên là các định tính, được dùng thêm vào để điều chỉnh lại kích cỡ là: short, long và long long2. Kiểu kí tự mà từ khóa của nó là char, biểu thị đơn vị nhỏ nhất có thể địa chỉ hóa được (bởi kiến trúc máy tính) thường là một byte với 8 bit.

Dạng thực được dùng để biểu thị các số thập phân hay các bộ phận hữu tỉ. Mặc dù vậy chúng không hoàn toàn chính xác mà chỉ là các biểu thị gần đúng.

Có 3 kiểu giá trị thực bao gồm: loại có độ chính xác đơn (có đặc tả là float), loại có độ chính xác kép (có đặc tả là double), và loại có độ chính xác kép mở rộng (có đặc tả là long double). Mỗi loại dùng để biểu thị các giá trị không nguyên trong một dạng khác nhau.

Các kiểu nguyên có thể hoặc là có dấu (signed) hay không dấu (unsigned). Nếu không chỉ rõ khi khai báo thì mặc định (hiểu ngầm) sẽ là loại có dấu. Một ngoại lệ là các kiểu char, signed char và unsigned char đều khác nhau, và kiểu char có thể là loại có dấu hay không có dấu.

Đối với loại có dấu, thì bit có nghĩa cao nhất được dùng để biểu thị dấu (dương hay âm). Thí dụ một số nguyên 16-bit loại có dấu thì chỉ dùng 15 bit để biểu thị giá trị (tuyệt đối) của nó còn bit có nghĩa cao nhất lại được dùng để cho biết đó là số dương hay âm. Đối với loại không dấu thì bit này lại được dùng thêm vào để biểu thị giá trị.

Các hằng số xác định các giá trị biên

Tập tin tiêu đề chuẩn limits.h sẽ xác định các giá trị nhỏ nhất và lớn nhất của các kiểu nguyên cơ bản cũng như là xác định các giới hạn khác. Tập tin tiêu đề chuẩn float.h sẽ xác định các giá trị nhỏ nhất và lớn nhất của các kiểu float, double, và long double. Nó cũng xác định các giới hạn khác liên quan tới việc xử lý các giá trị của dấu chấm động (floating-point) có độ chính xác đơn hay có độ chính xác kép như là chúng được định nghĩa trong chuẩn IEEE 754.

Bản hằng số xác định các biên của những kiểu nguyên thông thường

Đặc tả hiểu ngầmĐặc tả viết rõ raGiá trị nhỏ nhấtGiá trị lớn nhất
charviết y hệtCHAR_MINCHAR_MAX
signed charviết y hệtSCHAR_MINSCHAR_MAX
unsigned charviết y hệt0UCHAR_MAX
shortsigned short intSHRT_MINSHRT_MAX
unsigned shortunsigned short int0USHRT_MAX
không viết gì hếtsigned, hay intsigned intINT_MININT_MAX
unsignedunsigned int0UINT_MAX
longsigned long intLONG_MINLONG_MAX
unsigned longunsigned long int0ULONG_MAX
long long1signed long long int1LLONG_MIN2LLONG_MAX2
unsigned long long1unsigned long long int10ULLONG_MAX2

Các giá trị biên điển hình

Sau đây là danh sách kích cỡ và các biên điển hình của các kiểu nguyên. Các giá trị này có thể khác nhau tùy theo kiến trúc (máy và trình dịch). ISO C cung cấp tiêu đề inttypes.h, trong đó, có xác định các kiểu nguyên có dấu và không có dấu nhưng điều chắc chắn là kích cỡ đều nằm trong khoảng 8 đến 64 bit.

Kích cỡ và biên điển hình của các kiểu nguyên

Đặc tả hiểu ngầmĐặc tả viết rõ raSố bitSố byteGiá trị nhỏ nhấtGiá trị lớn nhất

charviết y hệt81-128 or 0127 hay 255
signed charviết y hệt81-128127
unsigned charviết y hệt810255
shortsigned short int162-32 76832 767
unsigned shortunsigned short int162065 535
longsigned long int324-2 147 483 6482 147 483 647
unsigned longunsigned long int32404 294 967 295
long long1signed long long int1648-9 223 372 036 854 775 8089 223 372 036 854 775 807
unsigned long long1unsigned long long int1648018 446 744 073 709 551 615

Kích cỡ và giới hạn của kiểu cơ bản int (mà không có các định tính short, long, hay long long) có thể thay đổi khác nhau (nhiều hơn các kiểu nguyên khác) tùy theo sư thiết lập (của trình dịch hay của kiến trúc máy). Cho nên, một thao tác thông thường là định nghĩa nó như là một đồng nghĩa của size_t — trong tập tin sys/types.h — và do đó, đủ sức để thích ứng với giá trị lớn nhất khả dĩ của con trỏ mà có thể được cấp phát bởi hệ thống. Đặc tả UNIX đơn chỉ ra rằng kiểu int phải có ít nhất 32 bit, mặc dù chuẩn ISO C chỉ yêu cầu có 16 bit.

Các kiểu tham chiếu

Tiền tố kí tự * được dùng để biểu thị kiểu tham chiếu, mà thường được biết đến như là một con trỏ. Nếu như đặc tả int biểu thị một kiểu nguyên cơ bản thì đặc tả int * lại biểu thị một tham chiếu kiểu nguyên, tức là một loại con trỏ. Các giá trị tham chiếu tương quan sẽ bao gồm hai phần thông tin: đó là địa chỉ của vùng nhớ và một kiểu dữ liệu. Câu lệnh sau đây khai báo một biến tham chiếu nguyên có tên là ptr:

1

int *ptr;

Sự tham chiếu


Khi một con trỏ địa phương được khai báo, thì chưa có một địa chỉ nào gán cho nó cả. Địa chỉ tương quan với một con trỏ có thể được thay đổi (hay hình thành) qua phép gán. Trong thí dụ sau, biến ptr sẽ cài cho nó chỉ tới cùng một dữ liệu như là biến nguyên cơ bản a:

1

2

3

4

int *ptr;

int a;

 

ptr = &a;

Để làm được việc này, toán tử tham chiếu (hay còn gọi là tham chiếu ngược) & đã được dùng tới. Nó trả về vị trí của biến trong bộ nhớ (tức là địa chỉ) hay là chỗ chứa thực thể theo sau (trong thí dụ thì thực thể đó là a). Toán tử này như là công việc nó làm thường đuợc gọi là toán tử “địa chỉ”.

Trong trường hợp tổng quát, cụm từ giá trị tham chiếu được dùng để nói về địa chỉ trong bộ nhớ của sự tham chiếu (hay tham chiếu ngược).

Sự tham chiếu ngược

Cùng một biểu hiện, giá trị có thể đọc về từ một giá trị tham chiếu. Trong thí dụ sau, biến nguyên cơ bản b sẽ được gán đến dữ liệu mà dữ liệu đó được tham chiếu bởi ptr:


1

2

3

4

5

6

7

8

int *ptr;

int a, b;

 

a = 10;   //gán cho a giá trị là 10

ptr = &a; //phép gán địa chỉ của a (tức là &a) lên con trỏ 'ptr'

//để ptr bây giờ chỉ đến địa chỉ có nội dung là 10

b = *ptr  //phép gán này cho b một giá trị nằm ở địa chỉ mà 'ptr'

//chỉ tới tức là giá trị của b sẽ là 10


Để hoàn tất được thao tác này, toán tử tham chiếu ngược (&) đã được dùng. Nó trả về dữ liệu cơ bản. Dữ liệu này có giá trị là một tham chiếu chỉ tới (tức là một địa chỉ). Biểu thức *ptr là một cách viết khác của giá trị 10 (gán cho b).

Việc quá tải của kí tự * có hai biểu hiện liên hệ mà có thể gây ra sự nhầm lẫn. Hiểu được sự khác nhau giữa việc dùng nó như là một tiền tố trong một khai báo (con trỏ) và việc xem nó là toán tử tham chiếu trong một biểu thức là rất quan trọng.


Sự tham chiếu tương đương và các mệnh đề cơ bản


Bảng sau đây là danh sách các mệnh đề tương đương giữa kiểu cơ bản và kiểu tham chiếu (hay tham chiếu ngược). Trong đó, biến cơ bản d và biến tham chiếu ptr được hiểu ngầm.
Các mệnh đề cơ bản và tham chiếu tương đương
Sự tham chiếu tương đương và các mệnh đề cơ bản


Bảng sau đây là danh sách các mệnh đề tương đương giữa kiểu cơ bản và kiểu tham chiếu (hay tham chiếu ngược). Trong đó, biến cơ bản d và biến tham chiếu ptr được hiểu ngầm.

Các mệnh đề cơ bản và tham chiếu tương đương


Đến một giá trị cơ bảnĐến một giá trị tham chiếuTừ một giá trị cơ bảnTừ một giá trị tham chiếu

d&d
*ptrptr

Các mảng

Khai báo mảng tĩnh

Trong C, mảng được dùng để biểu thị một cấu trúc của một dãy nhiều giá trị có cùng một kiểu được xếp thứ tự. Một mảng gọi là tĩnh nếu độ dài của dãy mảng này cố định. Sự khai báo của mảng tĩnh có cú pháp sau:

1

int array[n];

trong đó, tên của mảng là array sẽ có thể chứa được n giá trị của kiểu cơ bản int. Trong thực hành, phần bộ nhớ cho n giá trị nguyên này được để dành riêng và được gán cho mảng này (mặc dù giá trị của các phần tử trong mảng chưa được xác định). Biến array thực chất là một kiểu tham chiếu của kiểu nguyên; nó khởi thủy sẽ chỉ tới địa chỉ của giá trị đầu tiên trong mảng.
Truy cập các phần tử


Các giá trị của một mảng được gọi là các phần tử trong mảng.

* Một cách để truy cập đến các phần tử này là dùng đến cặp kí tự ngoặc vuông dạng [k]. Trong đó k là chỉ số (hay vị trí thứ tự đếm từ 0). Như vậy, phần tử thứ k trong mảng array sẽ có cú pháp

1


array[k]


Giá trị trả về của array[k] chính là giá trị mà nó chứa ở vị trí k. Thoạt nhìn thì cú pháp của việc truy cập này trông giống như cú pháp khi mảng array được khai báo nhưng về chức năng thì hoàn toàn khác nhau.

Chỉ số bắt đầu của một mảng là 0. Như vậy, chỉ số lớn nhất của một mảng bằng tổng số các phần tử trong mảng trừ đi 1. Thí dụ mảng A có 10 phần tử thì giá trị của phần tử đầu tiên của A là A[0] và của phần tử cuối dùng là A[9].

* Một cách truy cập khác là dùng con trỏ số học để tham chiếu đến giá trị của các phần tử trong mảng.

Bảng sau đây sẽ minh họa cách dùng của cả hai phương pháp:

Array Chỉ số và con trỏ số học

Phần tử vị tríKiểu dùng cơ bảnDùng con trỏ

012n
array[0]array[1]array[2]array[n]
*array*(array + 1)*(array + 2)*(array + n)

Các mảng động

C không cung cấp phương tiện để kiểm tra biên cho các mảng. Nghĩa là nó không thể bắt được các lỗi khi gán cho một mảng chỉ số âm hay chỉ số vượt quá độ đài của mảng đó. Và hơn thế nữa các chỉ số trong một mảng có thể vượt khỏi độ dài sẵn có của mảng đó.

Vì các mảng là thuần nhất, tức là nó chỉ chứa các dữ liệu có chung một kiểu nên hai thành phần thông tin cần nhớ là địa chỉ của phần tử đầu tiên và kiểu của dữ liệu.

Nhắc lại về cú pháp để khai báo một mảng tĩnh, tức là tạo ra một biến tham chiếu nguyên và cấp phát một vùng nhớ tương ứng cho nó:

1

int array[n];

Cách biểu hiện này có thể được tái lập với sự giúp đỡ của thư viện chuẩn C. Hàm calloc cung cấp một cách đơn giản để cấp phát một vùng nhớ. Hai tham số dùng đến sẽ là số lượng các phần tử và kích cỡ (độ lớn) của mỗi phần tử. Khi việc cấp phát bộ nhớ hoàn thành, calloc trả về một con trỏ chỉ tới phần tử đầu tiên và gán cho mọi phần tử giá trị khởi động là 0. Nếu sự cấp phát vùng nhớ không thành công (vì bộ nhớ không đủ chỗ trống hạn) thì calloc trả về con trỏ rỗng. Thí dụ: đoạn mã sau đây có chức năng tương đương với việc khai báo một mảng tĩnh của n phần tử có kiểu int:


1

2
int *array;

array = calloc(n, sizeof(int));


Tuy nhiên, điểm vượt trội của cách khai báo này là việc sử dụng cấp phát vùng nhớ động . Kích cỡ của mảng (tức là lượng không gian nhớ được cấp phát một cách an toàn cho mảng) lại có thể được thay đổi sau khi đã khai báo.

Một khi việc cấp phát vùng nhớ động không còn cần thiết nữa thì phần bộ nhớ đó nên được trả về cho hệ điều hành. Thao tác này có thể tiến hành bằng hàm free. Nó cần một tham số: tên của con trỏ mà trước đây đã xin cấp phát vùng nhớ. Một cách an toàn hơn là sau khi đã trả vùng nhớ về cho hệ điều hành, người lập trình cũng nên cài (hay gán) cho con trỏ liên đới giá trị NULL để hủy bỏ địa chỉ mà nó đang chỉ tới (nhằm tránh gây ra các hiệu ứng phụ do việc tham chiếu của con trỏ này có thể gây ra).

1

2

free(array);

array = NULL;


Các mảng đa chiều

C có hỗ trợ việc dùng mảng đa chiều. Việc định nghĩa chúng giống như là tạo ra mảng của các mảng , mặc dù vậy trong thực tế nó không hoàn toàn đúng. Cú pháp sau:


1


int array2D[số_hàng][số_cột];


sẽ định nghĩa một mảng hai chiều; chiều thứ nhất có số_hàng phần tử. Chiều thứ hai sẽ có số_hàng * số_cột các phần tử — một tập hợp của số_cột các phần tử mà mỗi phần tử là một chiều thứ nhất.

Các mảng đa chiều hoàn toàn có thể được xem như là dãy của các con trỏ. Trong thí dụ trên, array2D (nếu số_hàng là 1) sẽ là một tham chiếu giá trị nguyên mà nó chỉ tới một mảng của số_cột các phần tử.

Kieu char va char* trong chuoi khac nhau ntn ?

Giai thich chi tiet giup minh voi. Su dung trong cac truong hop nao ???


char a;

----> Khai báo một biến kiểu char

C Code:

char *a;
char* a;

----> Đều hợp lệ( dù nguy hiểm ) và đều có nghĩa là khai báo một biến con trỏ kiểu char
Thường hay dùng:

C Code

char* a;

vì nó tường minh hơn.
Còn nếu khai báo 2 hoặc nhiều biến con trỏ thì mới để dấu * trước tên biến
VD:

C Code:

char *a, *b;

1.  char : 1 kiểu dữ liệu cơ bản (type), được cấp bộ nhớ 1 byte (8 bits)
- unsigned char     0 <= X <= 255
- char                  -128 <= X <= 127

2.  char * a : con trỏ vô địa chỉ đầu của 1 mảng char

3.  Thí dụ:
// Char
char a0 = 'H';  // 1 byte static memory allocation

// Char *
char * a1;       // con trỏ vào địa chỉ đầu của mảng, chưa khởi động!!!
char a2[256];  // static allocation for 256 char

// Khởi động mảng dynamic:
char *a3 = new char[256]; // allocate 256 char dynamic
//a3 = "Hello"; gây memory leak, phát hiện của moderator :-)
strcpy(a3, "Hello");

// Kết quả mảng a3:
// Index:     0   1   2   3   4   5     ...  255
// Value:     'H' 'e'  'l'   'l' 'o' ''  ........

// Allocate bộ nhớ cho array of char và đồng thời khởi động mảng:
char *a4 = " C is [not] simple:-)";

// Xài hàm của C trong
strcat (a3, a4);
printf ("\n%s\n%s\n", a3, a4);

// Kết quả:
// a3 = "Hello C is [not] simple:-)"

// Dọn dẹp ngay, ai new người đó có trách nhiệm phải delete!
delete [] a3; // để ý []

4. Các hàm với mảng char trong C:
strcat (char* a, char* b);
strcmp (char *a , char *b);
int m = strncmp ( char *a , char *b , int n);
strcpy (char *a , char *b);
strncpy (char *a , char *b , int n);
int n = strlen ( char *a);
char *b = strchr (char *a , char c);
char *b = strrchr (char *a , char c);
...


 

Post a Comment

Mới hơn Cũ hơn