Ngôn ngữ lập trình C/Khai báo

From Wikiversity

Ngôn ngữ lập trình C có một hệ thống mở rộng cho việc khai báo các biến của các kiểu khác nhau. Những quy tắc dành cho các kiểu phức tạp có thể gây nhầm lẫn tùy theo các kiểu thiết kế của chúng. Bài này nói về các khai báo biến, bắt đầu từ các kiểu đơn giản, và dẫn tới các kiểu phức tạp hơn.

Kiểu hợp nhất[edit]

Kiểu hợp nhất có tên từ khóa là union kiểu đặc biệt này cho phép nó chứa dữ liệu mà có thể có kiểu khác nhau trong cùng một phần bộ nhớ (mà nó có thể được cấp phát khi khai báo biến):

union folder
{ 
  int number;
  double real;
  char letter;
}; //lưu ý dấu ";" cần dùng để kết thúc câu lệnh

Để khai báo biến, có thể dùng cách thông thường, tạo mảng các union hay cách tham chiếu:

union folder matter;
union folder listtype[100];
union folder *matterptr;

Để gán hay truy cập giá trị cho một biến union, có thể dùng toán tử "." Theo hàng khai báo đầu tiên của thí dụ trên ta có thể viết một trong các phép gán:

matter.real = 3.1416;

hay là:

matter.letter = 't';

hay là:

matter.number = 1;

Lưu ý:

  • Việc gán giá trị cho một biến kiểu union đòi hỏi kiểu của dữ liệu đó phải có mặt trong khai báo ban đầu của nó. Theo thí dụ trên thì kiểu folder chỉ chấp nhận chứa một đơn vị dữ liệu của một trong ba kiểu int, double,char.
  • Một khi giá trị có kiểu đúng nào đó được gán cho một biến kiểu union thì nó sẽ xóa bỏ hẳn giá trị cũ (nếu có) mà biến này đã chứa trước đó.
  • Việc truy cập một giá trị từ một biến kiểu union cần lưu ý đến kiểu hiện tại của dữ liệu đang được chứa của biến này nếu không, có thể gây ra lỗi dùng sai kiểu.
  • Điểm khác nhau quan trọng giữa unionstructunion chỉ có được một thành phần (nhưng thành phần này phải có kiểu tùy theo khai báo của người lập trình) trong khi struct bao gồm nhiều thành phần (và mỗi thành phần có thể có kiểu khác nhau).
  • Tương tự như struct, union cho phép khai báo nhiều union lồng nhau.

Dùng #define để định nghĩa hằng và kiểu[edit]

Một cách tổng quát thì từ khóa tiền xử lý #define đùng để định nghĩa tên của một kiểu (đối tượng) nào đó. Thực ra, câu lệnh #define chỉ là một loại câu lệnh macro. Có hai ứng dụng chính như sau:

Định nghĩa tên hằng[edit]

Có thể dùng câu lệnh tiền xử lý #define để định nghĩa một hằng:

#define PI 3.14159 //định nghĩa tên một hằng số PI
#define STANDARD "ANSI C" //định nghĩa tên một hằng dãy ký tự
#define ESC '\033' //định nghĩa tên một hằng ký tự mã ASCII của phím Esc.

Lưu ý: so với cách định nghĩa dùng từ khóa const thì cách dùng này không được uyển chuyển bằng nhưng nó thường cho hiệu quả thực thi nhanh hơn vì đây chỉ là các macro.

Định nghĩa tên của kiểu dữ liệu[edit]

Có thể dùng #define để định nghĩa tên của một kiểu dữ lieu

#define real float //định nghĩa tên kiẻu real cho dữ liệu có kiểu float

Việc khai báo các biến không có gì khác lạ ngoại trừ tên mới được dùng:

real x, y[3], *z;

Lưu ý: Việc sử dụng #define có thể có các hiệu ứng phụ không ngờ nếu dùng nó kết hợp với nhiều định tính và có thể dẫn đến những lỗi khó tìm khi viết mã:

#define STRING char *

Trong lúc định nghĩa biến người lập có thể muốn định nghĩa hai con trỏ char như sau:

STRING name, job;

Tuy nhiên, điều ước muốn sẽ không xảy ra vì #define là macro nên trình dịch sẽ diễn giải thành (nó chỉ thay thế tên STRING bằng char *):

char * name, job;

Và như vậy, người lập trình sẽ không nhận được hai biến con trỏ như dự tính mà chỉ có một biến name là con trỏ mà thôi.

Dùng typedef để định nghĩa kiểu[edit]

Một cách khác để đặt tên riêng cho kiểu dữ liệu là dùng câu lệnh với từ khóa typedef:

typedef float real;

Nếu so sánh cách viết trong thí dụ trên với việc dùng từ khoá #define để định nghĩa thì chúng hoàn toàn tương đương (chỉ khác nhau về thứ tự các chữ floatreal). Tuy nhiên, cách viết này là một sự thay thế thế tên "đúng nghĩa" chứ không phải là một macro đơn thuần. Trở lại thí dụ:

  typedef char* string;

Câu lệnh trên cho phép đặt tên string như là một kiểu mới (mà nội dung của nó là kiểu con trỏ char). Bây giờ hãy xét đến câu lệnh khai báo biến:

  string name, job;

Trường hợp này sẽ được trình dịch diễn dịch đúng theo mong muốn thành:

  char *name, *job;

Dùng cho struct[edit]

Một thí dụ khác liên quan đến việc đặt tên cho struct là việc kết hợp cả hai khai báo và đặt tên lại trong cùng một câu lệnh:

  typedef struct
  { 
    char * name;
    int * age;
  } person;

Như vậy khi khai báo biến chỉ cần viết là:

  person Trung;

Ứng dụng[edit]

Một ứng dụng đáng lưu ý của typedef là việc làm cho mã C trở nên linh hoạt hơn trong nhiều môi trường khác nhau. Thí dụ: khi muốn xác định dùng đúng 4 byte cho một kiểu nguyên nhưng trên một số hệ máy thì nó ứng với kiểu int, trong khi trên một số hệ máy khác nó lại ứng với kiểu long int. Để giải quyết việc dùng chính xác 4 byte cho kiểu nguyên mà người lập trình muốn, thì có thể dùng giải pháp là: thêm vào một tập tin bao gồm trong đó có chứa định nghĩa:

  typedef int FOURBYTE; //dùng cho các máy lấy int là 4 byte

hay định nghĩa:

  typedef long int FOURBYTE; //dùng cho các máy lấy long int là 4 byte

và chỉ cần thêm vào trong mã nguồn câu lệnh #include <Tên_tập_tin_bao_gồm> như vậy chỉ cần thay nội dung của tập tin bao gồm thì toàn bộ mã vẫn hoạt động đúng.

Kiểu enum[edit]

Kiểu enum là một kiểu dữ liệu đặc biệt được dùng để định nghĩa một quan hệ thứ tự cho một tập họp hữu hạn các tên. (Trong thực tế thì enum có kiểu là int Theo trang 553 trong cuốn "New C Primer Plus"—xem thêm phần tham khảo)

enum Wiki {Arisa, Bluesman, VietBio, Trung, Quang, Minh};
Để khai báo biến member có kiểu enum dùng câu lệnh:
enum member;

Các giá trị (hiểu ngầm) của cáo ký hiệu Arisa, Bluesman, VietBio, Trung, Quang, Minh theo mặc định sẽ tương ứng với 0, 1, 2, 3, 4, 5. các cách viết câu lệnh sau đây là có hiệu lực

member = Minh;
if(member == VietBio)
{ 
  //do_some_commands 
}
for(member = Arisa; member <= Trung; member++)
{
  //do_some_commands
}
Như vậy, theo mặc đinh. các tên của một enum được xem là các hằng số từ 0 tăng dần cho đến phần tử cuối cùng trong đó.

Tuy nhiên, C không loại trừ khả năng đặt lại giá trị của một phần tử trong enum theo cách riêng:

enum reordert = {duck, cat = 10, mouse = 50, elephant = 1000, lion, virus};

Trong ví dụ trên thì duck có giá trị tương ứng là 0, cat10,..., elephant1000, còn lion sẽ tương ứng là 1001virus1002.

Một trong những ứng dụng chính của kiểu này là để tăng cường khả năng đọc mã được dễ hiểu hay phù hơn với con người.

Kiểu FILE[edit]

Kiểu FILE là kiểu dữ liệu dùng để xử lý các tập tin. Theo ANSI thì có hai phương thức để truy cập là nhị phân (binary) và văn bản (text). Người ta dùng một biến con trỏ để khai bảo:

 FILE *fp;

Thủ tục quan trọng cần làm tiếp theo là việc mở tập tin. Hàm thường được dùng để mở một tập tin là fopen

 fp = fopen ("Dung.txt", "r");

Trong dòng lệnh trên thì tập tin có tên Dung.txt sẽ được mở trong chế độ đọc r. Các chế độ truy cập cơ bản bao gồm:

  • r - đọc
  • w - viết
  • a - viết tiếp vào cuối tập tin và tạo tập tin mới nếu chưa có
  • r+ - đọc và viết
  • w+ - đọc và viết nhưng cắt bỏ nội dung cũ của tập tin nếu có, tạo tập tin mới nếu chưa có
  • a+ - Mở file đã tồn tại với mục đích đọc và ghi. Nó tạo file mới nếu không tồn tại. Việc đọc file sẽ bắt đầu đọc từ đầu nhưng ghi file sẽ chỉ ghi vào cuối file.
  • rb wb ab rb+ r+b wb+ w+b ab+ a+b giống như các trường hợp trên nhưng chỉ dùng cho tập tin nhị phân.

Để tiếp tục việc xở lý thì có thể dùng tới các hàm trong thư viện chuẩn như: getc(), putc(), fprintf(), fscanf(), fget(), fgets(), fputs(), fseek(), ftell() và hàm fclose().

Lưu ý về biến được khai báo static[edit]

Các biến có được xác định bởi định tính static đặt trước tên kiểu biến khi khai báo sẽ cho biến đó một tính năng đặc biệt, đó là, giá trị của nó sẽ được lưu giữ không bị mất đi mặc dù khối mã chứa nó đã được xử lý xong. Trường hợp. Đặc biệt nếu một biến được khai báo có định tính static trong một hàm và được cài đặt giá trị nào đó thì sau khi hàm đó được gọi, giá trị của biến static đó vẫn còn giữ nguyên giữa mỗi lần gọi (cho tới khi nó được gán giá trị khác trong lần gọi tới của hàm). Thi dụ sau đây khai báo biến my_static có định tính static trong một hàm:

#include<stdio.h>
int static_func(int init)
{
    static int my_static_var;
    my_static_var += init;
    return my_static_var;
}

int main(void)
{
   printf("call the 1st time (init=0), my_static_var = %d\n", static_func(0));
   printf("call the 2nd time (init=1), my_static_var = %d\n", static_func(1));
   printf("call the 3rd time (init=2), my_static_var = %d\n", static_func(2));
}

sau khi dịch và chạy mã này sẽ cho kết quả:

call the 1st time (init=0), my_static_var = 0   //0 +0 =0
call the 2nd time (init=1), my_static_var = 1   //0 +1 =1
call the 3rd time (init=2), my_static_var = 3   //1 +2 =3

Lưu ý: Mọi biến toàn cục đều có định tính static một cách tự động.