Hằng trong C

Hằng (constant) là những tham chiếu đến những giá trị cố định, không thay đổi trong suốt quá trình thực thi. Những giá trị cố định này được gọi là literals.

Ví dụ, nếu nói PI = 3.14, trường hợp này, PI là một constant và giá trị 3.14 là một literal.

Hằng cũng như những biến bình thường, ta sử dụng chúng như sử dụng biến, chỉ khác ở giá trị không thay đổi. Chúng cũng có kiểu dữ liệu xác định.

Hằng trong C có thể là các hằng kiểu dữ liệu cơ bản, như số nguyên (integer constant), số chấm động (floating-point constant), ký tự (character constant) hoặc hằng chuỗi (string literal). Ngoài ra, cũng có các hằng dạng liệt kê (enumeration constants).

Hằng số nguyên

C hỗ trợ 3 dạng số nguyên:

  • Thập phân: bao gồm các ký số từ 0 – 9, hằng dạng này ghi bình thường, ví dụ 25, 36
  • Bát phân: bao gồm các ký số từ 0 – 7, cần ghi 0 ở đầu, ví dụ 0213, 032
  • Thập lục phân: bao gồm các ký số từ 0 – 9 và các ký tự a, b, c, d, e, f (không phân biệt hoa thường) đại diện cho các số tương ứng từ 11 đến 15 trong hệ thập phân. Hằng dạng này ghi 0x ở đầu, ví dụ 0x5b, 0x21.

00x ghi ở đầu được gọi là tiền tố (prefix). Ngoài tiền tố, bạn cũng có thể ghi thêm hậu tố (suffix). Hai hậu tố có thể dùng kèm với nhau, bao gồm:

  • u: nghĩa là unsigned, tức bạn muốn chỉ rõ đây là hằng số lưu theo kiểu không dấu
  • l: nghĩa là long, tức bạn muốn chỉ rõ hằng số này phải lưu theo dạng số nguyên dài

Hậu tố cũng không phân biệt kiểu chữ, bạn có thể ghi u hoặc U. Những hằng sau là hợp lệ trong C:

85 /* hằng thập phân*/
0213 /* hằng bát phân */
0x4b /* hằng thập lục phân */
30 /* int */
30u /* unsigned int */
30l /* long */
30UL /* unsigned long */

Hằng số chấm động

Hằng chấm động trong C ghi theo hệ thập phân. Cách ghi giống cách ghi số chấm động kiểu tiếng Anh. Ví dụ: 3.14. Hằng như 3.14 có hai phần: phần nguyên (3, integer part) và phần thập phân (.14, fractional part).

Ngoài ra, bạn cũng có thể dùng thêm e hoặc E để ghi phần mũ (exponent part). Ví dụ:

6.02e23   /* 6.02 x 10^23  */
-1.6E-19   /* -1.6 x 10^-19  */

C cũng hỗ trợ hậu tố cho hằng chấm động. Mặc định, số chấm động được lưu theo kiểu double. Bạn có thể thay đổi qua các hậu tố sau:

  • f hoặc F: hằng sẽ được lưu theo kiểu float
  • l hoặc L: hằng sẽ được lưu theo kiểu long double

Ví dụ:

15.75f

Hằng ký tự

Hằng ký tự có kiểu char, và được bao bọc quanh cặp nháy đơn (). Hằng ký tự chỉ được chứa 1 ký tự. Ví dụ:

'x'
'y'

C hỗ trợ 3 dạng hằng ký tự. Như ví dụ trên là hằng ký tự thường. Ngoài ra, còn hai dạng hằng ký tự khác là:

  • Chuỗi thoát (escape sequences, còn gọi là các hằng backslash character constants): là những ký tự đặc biệt mà ta không thể gõ bằng bàn phím, ví dụ ký tự xuống dòng,… Các hằng này bao gồm:
    • \\: ký tự \
    • \’: ký tự ‘
    • \”: ký tự “
    • \?: ký tự ?
    • \a: phát âm bip từ hệ thống
    • \b: ký tự xóa Backspace
    • \f: ký tự sang trang (form feed)
    • \n: ký tự xuống dòng (newline)
    • \r: ký tự trở về đầu dòng (carriage return)
    • \t: ký tự tab ngang (horizontal tab)
    • \v: ký tự tab đứng (vertical tab)
    • \0: ký tự null
    • \N: N là một số bát phân, đại diện một ký tự ASCII ghi theo dạng bát phân
    • \xN: N là một số thập lục phân, đại diện một ký tự ASCII ghi theo dạng thập lục phân
  • Ký tự universal (universal character): là các hằng ký tự 16 bit, dùng để lưu các ký tự như ký tự Unicode. Ví dụ:
    \U0001f34c

Hằng chuỗi

C tuy không có kiểu dữ liệu chuỗi, nhưng C có hỗ trợ hằng chuỗi. Hằng chuỗi trong C là một chuỗi ký tự (mảng ký tự) bao giữa cặp nháy đôi (). Ví dụ:

"Hello world\n"

Chuỗi bao gồm nhiều ký tự, nên các chuỗi thoát hay universal đều có thể nằm trong chuỗi.

Nếu hằng chuỗi dài, bạn không nhất thiết phải ghi nó trên một dòng. Bạn có thể ghi nó trên nhiều dòng, cuối mỗi dòng kết thúc bằng \, như:

"Hello, world! \
program"

Tương đương:

"Hello, world! program"

Bạn cũng có thể cách chuỗi với nhau bằng từng cặp , chúng sẽ được nối lại với nhau, ví dụ, chuỗi sau:

"Hello,"  " world!"
" program"

Tương đương:

"Hello, world! program"

Lưu ý là hằng chuỗi khác với hằng ký tự. Ví dụ:

'a'

Là khác với:

"A"

Hằng ký tự được lưu bằng 1 byte. Trong khi đó, hằng chuỗi, dù chỉ có 1 ký tự, được lưu bằng 2 byte. Lý do là vì C luôn đánh dấu kết thúc chuỗi bằng một ký tự null (\0), nên chuỗi "A" trên bao gồm 2 ký tự: A\0. Tổng quát, một hằng chuỗi có n ký tự sẽ được lưu trong n+1 byte.

Khai báo hằng

Trong C, để khai báo hằng, ta sử dụng một trong hai cách:

  • Sử dụng chỉ thị tiền xử lý (preprocessor) #define
  • Sử dụng từ khóa const

Khai báo hằng bằng #define

Cú pháp:

#define   tên_hằng   giá_trị

Ví dụ:

#define PI 3.14

Ví dụ dưới là một chương trình hoàn chỉnh có dùng hằng định nghĩa bằng #define:

#include <stdio.h>

#define PI 3.14
#define NEWLINE '\n'

int main() {

   float area;
   int radius = 5;

   area = PI * radius * radius;

   printf("Area : %f", area);
   printf("%c", NEWLINE);

   return 0;

}

Khai báo hằng bằng const

Cú pháp:

const    kiểu_dữ_liệu   tên_hằng   =   giá_trị;

Ví dụ:

const float PI = 3.14;

Ví dụ trên nếu viết lại bằng định nghĩa hằng const:

#include <stdio.h>

int main() {

   const float PI = 3.14;
   const char NEWLINE = '\n';

   float area;
   int radius = 5;

   area = PI * radius * radius;

   printf("Area : %f", area);
   printf("%c", NEWLINE);

   return 0;

}

So sánh #define và const

#define thực ra không phải là một câu lệnh lập trình, mà là một chỉ thị tiền xử lý. Khi dùng #define, bạn đang tạo ra một macro, mà ngay trước khi biên dịch, trình biên dịch sẽ thay thế các tên tương ứng trong mã bằng giá trị tương ứng do bạn đã định nghĩa trong #define. Ví dụ:

#include <stdio.h>

#define PI 3.14
#define NEWLINE '\n'

int main() {

   float area;
   int radius = 5;

   area = PI * radius * radius;

   printf("Area : %f", area);
   printf("%c", NEWLINE);

   return 0;

}

Sẽ được thay thế thành như sau ngay trước khi biên dịch:

#include <stdio.h>

#define PI 3.14
#define NEWLINE '\n'

int main() {

   float area;
   int radius = 5;

   area = 3.14 * radius * radius;

   printf("Area : %f", area);
   printf("%c", '\n');

   return 0;

}

Như vậy, lúc chương trình thực thi, không có biến nào được cấp phát để lưu giá trị 3.14, vì nó là một literal cố định trong mã.

Nếu bạn dùng const, sự thay thế này không xảy ra. PI định nghĩa bằng const cũng giống như một biến bình thường, lúc thực thi, hệ thống vẫn sẽ cấp phát cho nó một vùng nhớ để lưu giá trị, chỉ có điều, giá trị này là hằng, không thể thay đổi.

Ngoài ra, khi dùng #define, bạn không thể xác định kiểu dữ liệu cho hằng, còn dùng const thì bạn phải xác định kiểu cho hằng. Điều này dẫn đến tình huống sau:

Giả sử bạn định nghĩa:

#define NUM 2

và sau đó, dùng câu lệnh:

float result = 1/NUM;

Kết quả, result sẽ có giá trị 0.0, chứ không phải 0.5. Vì trường hợp này, khi biên dịch, sẽ xảy ra sự thay thế NUM trong câu lệnh bằng giá trị 2, ta được biểu thức:

float result = 1/2;

1 và 2 là số nguyên, khi chia nhau, kết quả của nó tự động sẽ được lưu theo dạng số nguyên, nên mất phần lẻ .5.

Trong khi đó, nếu định nghĩa:

const float NUM = 2;

và sau đó dùng lệnh:

float result = 1/NUM;

Thì kết quả result sẽ là 0.5. Vì trường hợp này, NUM được xác định là một số float, nên kết quả phép chia sẽ được tự động lưu theo kiểu float, và giữ được phần thập phân.

Mình có chuỗi bài về học lập trình C, bấm vào đây nếu bạn có nhu cầu.

Nguồn tham khảo:

Tags:

Bình luận

Bình luận Facebook

lời bình luận