Top Banner
Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang MỤC LỤC CHƯƠNG 0.............................................................. 4 GIỚI THIỆU..........................................................4 1. LỊCH SỬ NGÔN NGỮ LẬP TRÌNH C....................................................................................... 4 2. ƯU, KHUYẾT ĐIỂM CỦA C..................................................................................................... 4 3. CÀI ĐẶT VÀ SỬ DỤNG TRÌNH SOẠN THẢO TURBO C........................................................... 5 5. THỰC HIỆN CHẠY MỘT CHƯƠNG TRÌNH C............................................................................... 5 CHƯƠNG 1.............................................................. 6 CÁC THÀNH PHẦN CƠ BẢN - CHƯƠNG TRÌNH................................6 1. TẬP KÝ TỰ CỦA C................................................................................................................... 6 2. TÊN......................................................................................................................................... 6 3. TỪ KHOÁ................................................................................................................................ 7 4. CÁC KIỂU DỮ LIỆU CHUẨN................................................................................................... 7 5. KHAI BÁO BIẾN...................................................................................................................... 8 6. KHAI BÁO HẰNG.................................................................................................................... 9 7. CÁC PHÉP TOÁN (TOÁN TỬ)................................................................................................ 10 8 CẤU TRÚC CƠ BẢN CỦA MỘT CHƯƠNG TRÌNH C............................................................. 16 8. VÀO RA................................................................................................................................. 18 BÀI TẬP.......................................................................................................................................... 25 CHƯƠNG 2............................................................. 27 CÁC CÂU LỆNH ĐIỀU KHIỂN CHƯƠNG TRÌNH...............................27 1. CÂU LỆNH ĐƠN................................................................................................................... 27 2. CÂU LỆNH GHÉP....................................................................................................................... 27 3. CÂU LỆNH if............................................................................................................................... 27 4. CÂU LỆNH WHILE...................................................................................................................... 29 5. CÂU LỆNH do…while................................................................................................................. 30 6. CÂU LỆNH FOR.......................................................................................................................... 31 7. CÂU LỆNH break VÀ continue.................................................................................................. 33 8. CÂU LỆNH switch...................................................................................................................... 35 BÀI TẬP.......................................................................................................................................... 37 CHƯƠNG 3............................................................. 40 MẢNG VÀ CON TRỎ....................................................40 1. MẢNG........................................................................................................................................ 40 2. CON TRỎ................................................................................................................................... 45 3. LIÊN HỆ GIỮA CON TRỎ VÀ MẢNG.......................................................................................... 49 4. CẤP PHÁT ĐỘNG....................................................................................................................... 52 5. TÓM TẮT.................................................................................................................................... 55 CHƯƠNG 4............................................................. 57 HÀM................................................................57 Khoa Tin học - 1 -
192

Giao Trinh C_Uyên Trang

Jun 24, 2015

Download

Documents

forever2004
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

MỤC LỤC

CHƯƠNG 0.....................................................................................................................................................4

GIỚI THIỆU...............................................................................................................................................41. LỊCH SỬ NGÔN NGỮ LẬP TRÌNH C........................................................................................42. ƯU, KHUYẾT ĐIỂM CỦA C......................................................................................................43. CÀI ĐẶT VÀ SỬ DỤNG TRÌNH SOẠN THẢO TURBO C..........................................................55. THỰC HIỆN CHẠY MỘT CHƯƠNG TRÌNH C................................................................................5

CHƯƠNG 1.....................................................................................................................................................6

CÁC THÀNH PHẦN CƠ BẢN - CHƯƠNG TRÌNH................................................................................61. TẬP KÝ TỰ CỦA C.....................................................................................................................62. TÊN..............................................................................................................................................63. TỪ KHOÁ....................................................................................................................................74. CÁC KIỂU DỮ LIỆU CHUẨN....................................................................................................75. KHAI BÁO BIẾN.........................................................................................................................86. KHAI BÁO HẰNG.......................................................................................................................97. CÁC PHÉP TOÁN (TOÁN TỬ).................................................................................................108 CẤU TRÚC CƠ BẢN CỦA MỘT CHƯƠNG TRÌNH C............................................................168. VÀO RA......................................................................................................................................18BÀI TẬP...............................................................................................................................................25

CHƯƠNG 2...................................................................................................................................................27

CÁC CÂU LỆNH ĐIỀU KHIỂN CHƯƠNG TRÌNH..............................................................................271. CÂU LỆNH ĐƠN.......................................................................................................................272. CÂU LỆNH GHÉP..........................................................................................................................273. CÂU LỆNH if...................................................................................................................................274. CÂU LỆNH WHILE.........................................................................................................................295. CÂU LỆNH do…while.....................................................................................................................306. CÂU LỆNH FOR..............................................................................................................................317. CÂU LỆNH break VÀ continue........................................................................................................338. CÂU LỆNH switch...........................................................................................................................35BÀI TẬP...............................................................................................................................................37

CHƯƠNG 3...................................................................................................................................................40

MẢNG VÀ CON TRỎ.............................................................................................................................401. MẢNG..............................................................................................................................................402. CON TRỎ.........................................................................................................................................453. LIÊN HỆ GIỮA CON TRỎ VÀ MẢNG............................................................................................494. CẤP PHÁT ĐỘNG...........................................................................................................................525. TÓM TẮT.........................................................................................................................................55

CHƯƠNG 4...................................................................................................................................................57

HÀM..........................................................................................................................................................571.GIỚI THIỆU.....................................................................................................................................572. KHAI BÁO VÀ ĐỊNH NGHĨA HÀM................................................................................................583. THAM SỐ TRONG LỜI GỌI HÀM..................................................................................................634.CẤP LƯU TRỮ VÀ PHẠM VI CỦA CÁC ĐỐI TƯỢNG..................................................................675. CON TRỎ HÀM...............................................................................................................................726. ĐỆ QUY...........................................................................................................................................74

CHƯƠNG 5...................................................................................................................................................78

Khoa Tin học - 1 -

Page 2: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CẤU TRÚC...............................................................................................................................................781. CẤU TRÚC......................................................................................................................................782. CẤU TRÚC TỰ TRỎ........................................................................................................................873. KIỂU HỢP (union)........................................................................................................................100

CHƯƠNG 6.................................................................................................................................................103

ĐỒ HOẠ.................................................................................................................................................1031. KHÁI NIỆM.............................................................................................................................1032. KHỞI ĐỘNG ĐỒ HOẠ...........................................................................................................1043. CÁC LỖI THÔNG THƯỜNG TRONG ĐỒ HOẠ....................................................................1064. CÁC MÀU VÀ MẪU...............................................................................................................1065. VẼ VÀ TÔ MÀU.......................................................................................................................1096. KIỂU ĐƯỜNG.........................................................................................................................1117. CỬA SỔ (viewport)..................................................................................................................113

VỚI VIEWPORTTYPE LÀ KIỂU CẤU TRÚC ĐƯỢC ĐỊNH NGHĨA NHƯ SAU:...................................................1138. TÔ ĐIỂM, TÔ MIỀN...............................................................................................................1159. XỬ LÝ VĂN BẢN TRÊN MÀN HÌNH ĐỒ HOẠ......................................................................11610. CẮT, DÁN, VẼ HÌNH CHUYỂN ĐỘNG.................................................................................118

BÀI TẬP.................................................................................................................................................121

CHƯƠNG 0.................................................................................................................................................121

GIỚI THIỆU...........................................................................................................................................121

CHƯƠNG 1.................................................................................................................................................121

CÁC THÀNH PHẦN CƠ BẢN CỦA C.................................................................................................121

CHƯƠNG 2.................................................................................................................................................121

CÁC KIỂU DỮ LIỆU CƠ SỞ................................................................................................................121

CHƯƠNG 3.................................................................................................................................................122

BIỂU THỨC, CÂU LỆNH VÀ CÁC PHÉP TOÁN..............................................................................122

CHƯƠNG 4.................................................................................................................................................125

CÁC CÂU LỆNH ĐIỀU KHIỂN............................................................................................................125

THAM KHẢO............................................................................................................................................126

CÁC CHỈ THỊ TIỀN XỬ LÝ..................................................................................................................1261. CHỈ THỊ #define ĐƠN GIẢN (MACRO).................................................................................1262. CHỈ THỊ #define CÓ ĐỐI (MACRO)......................................................................................1273. CHỈ THỊ BAO HÀM TỆP #include..........................................................................................1274. CÁC CHỈ THỊ BIÊN DỊCH CÓ ĐIỀU KIỆN #if.....................................................................1285. CÁC CHỈ THỊ BIÊN DỊCH CÓ ĐIỀU KIỆN #ifdef VÀ #ifndef..............................................1296. TỔ CHỨC CÁC TỆP THƯ VIỆN............................................................................................1307. CHỈ THỊ #error........................................................................................................................130

Khoa Tin học - 2 -

Page 3: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 0

GIỚI THIỆU

1. LỊCH SỬ NGÔN NGỮ LẬP TRÌNH C

Ngôn ngữ C do Brian W.Kernighan và Dennis M. Ritchie phát triển vào đầu những năm 70 tại phòng thí nghiệm BELL (Hoa Kỳ) với mục đích ban đầu là để phát triển hệ điều hành UNIX. Bối cảnh ra đời xuất phát từ nhu cầu cần phải có một ngôn ngữ lập trình hệ thống thay đổi cho hợp ngữ (Assembly) vốn nặng nề, độ tin cậy thấp và rất khó chuyển đổi giữa các hệ máy tính khác nhau.

Phần lớn những ý tưởng quan trọng nhất của C xuất phát từ ngôn ngữ trước đó là ngôn ngữ BCPL, do Martin Richard viết. Ảnh hưởng của ngôn ngữ BCPL đối với C gián tiếp thông qua ngôn ngữ B, do Ken Thompson viết năm 1970 cho hệ điều hành UNIX, chạy trên họ máy tính PDP-7.

Năm 1978, cuốn sách "The C programming languague" do chính hai tác giả Brian W.Kernighan và Dennis M. Ritchie biên soạn được xuất bản và phổ biến rộng rãi.

Ngoài việc C được dùng để viết hệ điều hành UNIX, người ta nhanh chóng nhận ra sức mạnh của C trong việc xử lý các vấn đề thực tế như: xử lý số, văn bản, cơ sở dữ liệu, …

Tuy vậy, khi độ phức tạp của các bài toán cần giải quyết trên thực tế ngày càng tăng thì các ngôn ngữ lập trình có cấu trúc đã bộc lộ những điểm yếu nhất định. Để khắc phục những hạn chế này, các nhà thiết kế phần mềm đã phát triển một ý tưởng mới, đó là lập trình hướng đối tượng.

Đến năm 1983, Bjarne Stroustrup đã phát triển ngôn ngữ C thành ngôn ngữ C++, (bổ sung mới các yếu tố hướng đối tượng (Oject). Đây là một trong những ngôn ngữ rất được ưu dùng. Vì vậy, hiện nay, ngoài Turbo C, Borland C, hầu hết các chương trình C đều được chạy trong môi trường C++.

2. ƯU, KHUYẾT ĐIỂM CỦA C

2.1 Ưu điểm

- C là một ngôn ngữ mạnh, mềm dẻo; được các nhà tin học chuyên nghiệp dùng phổ biến, nhất là để viết các phần mềm hệ thống (hệ điều hành, chương trình dịch, …)

- Có thể dùng trên nhiều loại máy khác nhau cũng như nhiều hệ điều hành khác nhau.

- Ít từ khoáKhoa Tin học - 3 -

Page 4: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

- Có cấu trúc môđun

2.2 Khuyết điểm

- Cú pháp lạ và khó học;

- Một số kí hiệu có nhiều nghĩa, ví dụ: kí hiệu * có thể là toán tử nhân, khai báo biến con trỏ,…

3. CÀI ĐẶT VÀ SỬ DỤNG TRÌNH SOẠN THẢO TURBO C

- Turbo C gồm các thư mục BGI, BIN, INCLUDE, LIB, EXAMPLE, PRJ. Ta có thể đặt thư mục TC ở một đĩa bất kì. Để vào màn hình soạn thảo Turbo C, chạy tệp tc.exe trong thư mục TC\BIN. (hoặc nhấp kép vào Shortcut của nó).

Chú ý: Nếu Turbo C không được đặt trong C:\TC thì khi vào trình soạn thảo của C, NSD cần phải thiết lập lại môi trường: vào Option / Directories / Chỉ định lại đường dẫn đến các thành phần theo yêu cầu.

* Để thoát khỏi TC:

- Cách 1: Nhấn tổ hợp Alt+x

- Cách 2: Vào File, Chọn mục Exit.

Ở 2 trường hợp trên, nếu chương trình nguồn chưa được lưu lần cuối thì Turbo C sẽ thông báo có lưu hay không.

Chú ý: Nếu đang ở menu File, nhấn phím O hoặc chọn mục Dos Shell thì máy sẽ tạm thời thoát Turob C vào DOS, nhấn EXIT đế quay lại màn hình soạn thảo Turbo C.

Như đã trình bày, ta có thể soạn thảo và chạy chương trình trong môi trường C+

+ như trong môi trường Turbo C.

5. THỰC HIỆN CHẠY MỘT CHƯƠNG TRÌNH CSau khi soạn thảo chương trình nguồn trên một hệ soạn thảo nào đó và lưu lại

trên đĩa, ta phải dịch chương trình nguồn để tạo ra tệp chương trình đích (có phần mở rộng là exe). Nếu việc dịch chương trình nguồn không có lỗi, thì ta có thể thực hiện chương trình đích từ dấu nhắc của hệ điều hành.

Trong trường hợp dùng môi trường soạn thảo của C (hoặc C++), sau khi soạn thảo chương trình nguồn, nhấn phím F2 để lưu chương trình. Để dịch và chạy chương trình, nhấn ^F9 (hoặc vào menu Run/Run).

Khoa Tin học - 4 -

Page 5: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 1

CÁC THÀNH PHẦN CƠ BẢN - CHƯƠNG TRÌNH

1. TẬP KÝ TỰ CỦA C

Mọi ngôn ngữ lập trình đều được xây dựng trên bộ kí tự nào đó. Các kí tự được nhóm lại theo nhiều cách khác nhau để tạo nên các từ. Đến lượt mình, các từ lại được liên kết với nhau theo một quy tắc (đó là cú pháp của ngôn ngữ lập trình) nào đó để tạo thành các câu lệnh. Một chương trình bao gồm nhiều câu lệnh diễn đạt một thuật toán nào đó. Ngôn ngữ lập trình C được xây dựng trên bộ kí tự sau:

26 chữ cái in hoa: A B C … Y Z 26 chữ cái in thường: a b c … y z 10 chữ số: 0 1 2 …9 Các kí hiệu toán học: + - * / = < > Các dấu ngăn cách: , ; . : space Các dấu ngoặc: ( ) [ ] { } Các kí hiệu đặc biệt: _ ? ~ ! # $ % ^ & ' \ " v.v…

2. TÊN

- Tên hay định danh (identificator) là khái niệm để xác định các đại lượng khác nhau trong một chương trình.

- Tên bao gồm một dãy các kí tự: chữ, số, dấu gạch nối '_' (underscore) và phải bắt đầu bằng một chữ hoặc dấu gạch nối '- '. Mọi tên được sử dụng trong chương trình đều phải được khai báo.

- Tên hợp lệ là dãy kí tự liền nhau, có thể gồm các chữ cái, các chữ số, kí tự gạch nối dưới. Tên phải bắt đầu bằng chữ cái hoặc dấu gạch nối dưới.

Ví dụ:

Tên hợp lệ:

LOPTT01

Loptt01

loptt01

a_1

_name

Tên không hợp lệ:

bai tap (vì có dấu space)

1_baitap (vì bắt đầu bằng số)

Khoa Tin học - 5 -

Page 6: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

name#3 (vì có dấu #)

Độ dài cực đại của tên mặc định là 32, nhưng chúng ta có thể thay đổi lại bằng một giá trị từ 1 đến 32 trong chức năng Option/Compiler/Source/Identifier Length. Dĩ nhiên, ta có thể đặt các tên có nhiều hơn 32 kí tự, nhưng trong trường hợp đó, chỉ có 32 kí tự đầu tiên có ý nghĩa.

Khác với Pascal, ngôn ngữ C phân biệt giữa chữ hoa và chữ thường. Các tên ABC, ABc, Abc, abc, AbC, aBC là khác nhau. Người ta thường dùng chữ hoa đặt tên cho các hằng, còn chữ thường được dùng để đặt tên cho các biến, hàm, cấu trúc, …

Khi đặt tên phải luôn ghi nhớ nguyên tắc là tên ít nhiều phản ánh được bản chất của đối tượng được đặt tên, ví dụ đặt tên cho biến chứa số phần tử của mảng là ArraySize sẽ dễ hiểu hơn là các tên as, hay arraysize. Việc đặt tên gợi nhớ rất có ích trong khi sửa chữa, bảo trì chương trình.

3. TỪ KHOÁ

Từ khoá là các từ dùng riêng của một ngôn ngữ lập trình, mỗi từ có một ý nghĩa và tác dụng cụ thể.

Từ khoá của C phải được dùng đúng cú pháp và không được định nghĩa lại.

Bảng 1.1 Danh sách từ khoá thông dụng của C

asm auto break case cdecl

char const continue default do

double else enum extern far

float for goto huge if

int interrupt long near pascal

register return short signed sizeof

static struct switch typedef union

unsigned void volatile while

4. CÁC KIỂU DỮ LIỆU CHUẨN

Các kiểu dữ liệu chuẩn của C được mô tả ở bảng 1.2:

TÊN Ý NGHĨA KÍCH PHẠM VI

Khoa Tin học - 6 -

Page 7: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

THƯỚC

char kí tự 1 byte -127 ÷ 128

unsigned char 1 byte 0÷ 255

int số nguyên 2 byte -32 768 ÷ 32 767

unsigned int 2 byte 0 ÷ 65 535

short int 2 byte -32768 ÷ 32 767

unsigned long int 4 byte 0 ÷ 4 294 967 295

long int 4 byte -2 147 483 648 ÷ +2 147 483 647

float số thực dấu chấm động

độ chính xác đơn4 byte 3.4E-38 ÷ 3.4E+38

double số thực dấu phẩy động

độ chính xác kép8 byte 1.7E-308 ÷ 1.7E+308

long double 10 byte 3.4E-4932 ÷ 1.1E+4932

Bảng 1.2 Các kiểu dữ liệu chuẩn của C

Một số có kiểu float có độ chính xác là 6 chữ số sau dấu chấm thập phân. Một số kiểu double có độ chính xác tới 15 chữ số sau dấu chấm thập phân.

Kiểu long int có thể viết gọn thành long; kiểu unsigned int có thể viết thành unsigned.

5. KHAI BÁO BIẾN

Tất cả các biến phải được khai báo trước khi sử dụng và kiểu của chúng phải được mô tả ngay khi khai báo để chương trình dịch cấp phát bộ nhớ cho chúng.

Các biến trong bộ nhớ ở các thời điểm khác nhau có thể cất giữ các giá trị khác nhau.

Quy tắc khai báo như sau:

<kiểu_dữ_liệu> <tên_biến>;

Có thể khai báo nhiều biến cùng kiểu trên một hàng, và khi đó, các tên biến được phân cách nhau bởi dấu phẩy.

Ví dụ:

Khoa Tin học - 7 -

Page 8: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Khai báo các biến kiểu nguyên:

int tuoiem, tuoianh;

Khai báo biến kiểu thực:

float hamf;

Khai báo biến kiểu kí tự:

char ma;

6. KHAI BÁO HẰNG

6.1 Khai bao hằngHằng là đại lượng không đổi khi thực hiện chương trình. Khai báo (định nghĩa)

một hằng về thực chất là gắn một tên (tên hằng) với một giá trị cụ thể nào đó. Về sau, khi gặp tên đó, chương trình dịch ngầm hiểu đó là giá trị tương ứng. Cú pháp khai báo như sau:

#define <tên_hằng> <giá_trị>Chỉ thị này định nghĩa một hằng có tên là tên_hằng với giá trị là giá_trị. Khi

biên dịch chương trình, chương trình dịch thay thế mọi xuất hiện của tên_hằng bằng giá_trị. Kiểu giá trị của hằng được xác định dựa theo nội dung của giá_trị.

Ví dụ:

#define MAXSIZE 100

#define DHSP "Truong Dai Hoc Su Pham"

#define NEWLINE '\n'

* Biến hằng: là các biến có giá trị không thay đổi, nghĩa là chương trình dịch sẽ cấp phát vùng nhớ tương ứng với kiểu của biến hằng và gán cho nó giá trị đã cho nhưng không thể thay đỗi giá trị của nó. Biến hằng được khai báo như sau:

const <tên_kiểu> <tên_biến_hằng> =<giá_trị>;

Ví dụ:

const int MAXSIZE = 100;

const char NEWLINE = '\n';

6.2 Cách biểu diễn hằng số6.2.1 Hằng nguyên (int):

* Miền giá trị: -32768 đến 32767* Biểu diễn trong máy: ta có thể biểu diễn hằng nguyên dưới dạng thập

phân, bát phân (cơ số 8) hay thập lục phân (cơ số 16). C quy định:- Hằng biểu diễn ở hệ bát phân: thêm số 0 ở đầu.- Hằng biểu diễn ở hệ thập lục phân: thêm 0x hặoc 0X ở đầu.

Khoa Tin học - 8 -

Page 9: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Ví dụ: Giá trị thập phân biểu diễn ở hệ bát phân biểu diễn ở hệ thập lục phân

189 0275 0xBD

6.2.1 Hằng nguyên dài (long int): được viết như hằng nguyên nhưng thêm chữ l hoặc L ở cuối giá trị. Ví dụ: 189l, 189L.

Một hằng số nguyên vượt ra ngoài phạm vi giá trị cho phép của hằng nguyên cũng được hiểu là hằng long. Ví dụ: 1234567 được tự động hiểu là 1234567L.

6.2.1 Hằng dấu phẩy động: có hai cách viết giá trị của một hằng dấu phẩy động:

Cách viết thông thường: Ví dụ: 123.456 Cách viết khoa học: hằng được viết gồm hai phần: phần định trị và phần mũ.

Phần định trị: là số thực, có dạng mmmm.nnn, m và n là các số.Phần mũ: là số nguyên

Ví dụ: 12.12E+3 có giá trị bằng 12.12*103, giá trị này có thể viết là 1.212E+4. Do đó, ta có thể tăng, giảm giá trị của phần mũ và dịch nguyển dấu chấm thập phân trong phần định trị nên số thục được viết dưới dạng này có tên gọi là số thực dấu phẩy động (hay số thực dấu chấm động), còn cách viết thông thường có tên gọi là số thực dấu phẩy tĩnh (hay số thực dấu chấm tĩnh).

6.2.1 Hằng kí tự: có thể biểu diễn: bằng kí hiệu trong bảng mã ASCII đặt giữa hai dấu nháy đơn. Ví dụ: 'A', '2', 'c', … bằng cặp bao gồm kí tự '\ ' và số thứ tự của kí tự trong bảng mã ASCII . Ví dụ:

Biểu diễn theo cách thứ nhất Biểu diễn theo cách thứ hai'A' '\65', hoặc '\0101', hoặc '\0x41'

Có một số hằng ký tự đặc biệt có thể được viết theo quy ước: dùng cặp bao gồm kí tự '\ ' và kí tự trong bảng mã ASCII, cụ thể:

Kí tự viết diễn giải' '\'' dấu nháy đơn" '\"' dấu ngáy kép\ '\\' dấu gạch chéo ngược\n '\n' kí tự xuống dòng\0 '\0' kí tự NULL

'\t' kí tự tabBảng 1.3 Cách viết một số hằng kí tự đặc biệt

Khoa Tin học - 9 -

Page 10: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

6.2.1 Hằng xâu kí tự: được đặt trong dấu nháy kép.

Ví dụ: "Truong Dai Hoc Su Pham"

7. CÁC PHÉP TOÁN (TOÁN TỬ)

7.1 Biểu thức trong CBiểu thức là kết quả ghép nối các toán tử và các toán hạng để diễn đạt một công

thức nào đó.Các toán hạng có thể là một đại lượng nào đó như: hằng, biến, lời gọi hàm…Số

ngôi của một toán tử xác định số toán hạng kết hợp với toán tử đó. Thông thường ta có các toán tử hai ngôi và một ngôi. Các toán tử được trình bày ở các mục sau của phần 7 này.

Mỗi biểu thức có một giá trị. Tuỳ theo giá trị của biểu thức mà ta có biểu thức nguyên hay biểu thức thực.

7.2 Các phép toán (toán tử) số học

Bảng 1.4 Các phép tính số học Toán tử Ý nghĩa VÍ DỤ

- đổi dấu một số thực hoặc nguyên -12, -a+ cộng hai số thực hoặc nguyên 12+12.5, x + y- trừ hai số thực hoặc nguyên 12-10, 12.5-3.4, x - y* nhân hai số thực hoặc nguyên 12*5, 12.3*2, x * y / chia hai số thực hoặc nguyên 23/3, 23.3/5, x / y

% chia lấy phần dư của phép chia hai số nguyên 12%5, x % y

Các phép toán + và - (hai ngôi) có cùng độ ưu tiên và thấp hơn độ ưu tiên của hai phép toán * và /. Bốn phép toán này lại có độ ưu tiên thấp hơn so với phép - (một ngôi).

Phép chia hai toán hạng nguyên cho kết quả là số nguyên (tương tự phép DIV trong Pascal). Ví dụ, 7/4 cho kết quả là 1, còn 7.0/4 hoặc 7/4.0 hoặc 7.0/4.0 cho kết quả là 1.75.

7.3 Các phép toán quan hệ

Toán tử Ý nghĩa Ví dụ

>so sánh lớn hơn. 3>7 có giá trị 0

a>b>= so sánh lớn hơn hoặc bằng. 7>=7 có giá trị 1

Khoa Tin học - 10 -

Page 11: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

a>=b< so sánh nhỏ hơn. 6<7 có giá trị 1

<= so sánh nhỏ hơn hoặc bằng. 10<=5 có giá trị 0 a<=b

= = so sánh bằng nhau. 7= =7 có giá trị 1 7= = 6 có giá trị 0 a = = b

!= so sánh không bằng nhau. 7!=7 có giá trị 0

a!=bBảng 1.5 Các phép toán quan hệ

Các phép toán trên áp dụng cho các toán hạng là số nguyên hay số thựcBốn phép toán đầu có cùng mực độ ưu tiên, hai phép toán sau có cùng độ

ưu tiên nhưng thấp hơn bốn phép toán đầu. Các phép toán quan hệ có độ ưu tiên thấp hơn so với các phép toán số học,

nên biểu thức i<n+1 được hiểu là i<(n+1) .

7.4 Các phép toán lôgic

Toán tử Ý nghĩa Ví dụ

&&liên kết hai biểu thức lôgic. Giá trị biểu thức kết quả bằng 1 khi cả hai toán hạng có giá trị 1.

3<7 && 8>6 có giá trị 1 ch>='0' && ch<='9'

||liên kết hai biểu thức lôgic. Giá trị biểu thức kết quả bằng 1 khi môt trong hai toán hạng có giá trị 1.

7<=3 || 8>=3 có giá trị 1 n<=0 || n>=100

! phép phủ định một ngôi !0 có giá trị 1 !12 có giá trị 0 !a

Bảng 1.6: Các phép toán lôgic

Hai phép toán && và || có độ ưu tiên thấp hơn so với các phép toán quan hệ. Tất cả các phép toán này lại có độ ưu tiên thấp hơn phép phủ định một ngôi.

7.5 Các phép toán trên bít

Phép toán Ý nghĩa

& VÀ nhị phân

| HOẶC nhị phân

Khoa Tin học - 11 -

Page 12: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

^ HOẶC LOẠI TRỪ nhị phân

<<, >> Dịch trái, dịch phải

~ BÙ 1Bảng 1.7 Các phép toán trên bít

Ta nhắc lại rằng:

1&1 = 1 1|1 = 1 1^1 = 0 ~1 = 01&0 = 0 1|0 = 1 1^0 = 1 ~0 = 1

0&1 = 0 0|1 = 1 0^1 = 1 a<<n = a*2n

0&0 = 0 0|0 = 0 0^0 = 0 a>>n = a/2n

Các phép toán này không được dùng cho số kiểu float hay double.Một số ví dụ:

1010 0001 1011 0110 & 1111 1111 = 1011 01100xa1b6 & 0xff = 0xb61010 0001 1011 0110 | 1111 1111 = 1010 0001 1111 11110xa1b6 | 0xff = 0xa1ff0xa1b6 ^ 0xff = 0xffff = 0x5e490xa1b6 << 8 =0xb6000xa1b6 >> 8 =0xa1-256 << 2 = -1024-256 >> 2 = -14~ 0xa1b6 = 0x5e49

Chú ý: rất dễ nhầm lẫn giữa các phép toán lôgic với phép toán trên bít: && với &, || với |, != với ~.

7.6 Biểu thức gánBiểu thức gán có dạng: <biến> = <biểu thức>Thực hiện một biểu thức gán thực chất là lấy giá trị của <biểu thức> gán cho

<biến>.Nếu chúng ta thêm dấu ';' vào sau biểu thức gán thì sẽ thu được một câu lệnh

gán. Biểu thức gán có thể được sử dụng trong các phép toán và các câu lệnh như các

biểu thức thông thường. Chẳng hạn, khi viết:a = b = 5;

thì điều đó có nghĩa là gán giá trị của biểu thức b = 5 cho biến a, kết quả là b = 5 và a = 5.Tương tự câu lệnh:

Khoa Tin học - 12 -

Page 13: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

x = (a = 5)*(b = 10);sé gán 5 cho a, 10 cho b và sau đó gán tiếp 50 cho x.C cho phép sử dụng một số ký pháp đặc biệt của phép toán gán sau đây:

Dạng viết thông thường Dạng viết rút gọni = i + expr i += expri = i – expr i -= expri = i * expr i *= expri = i / expr i /= expri = i % expr i %= expri = i & expr i &= expri = i | expr i |= expri = i ^ expr i ^= expri = i >> expr i >>= expri = i << expr i <<= expr

7.7 Biểu thức điều kiệnBiểu thức điều kiện có dạng: expr1?expr2:expr3

trong đó, expr1, expr2, expr3 là các biểu thức nào đó. Nếu expr1 có giá trị khác 0 thì biểu thức điều kiện có giá trị cho bởi expr2, trái lại giá trị của biểu thức expr3 được dùng làm giá trị của biểu thức điều kiện. Kiểu của biểu thức điều kiện phụ thuộc vào kiểu của hai biểu thức expr2 và expr3 và là kiểu bao trùm nhất.

Biểu thức điều kiện cũng được dùng như các biểu thức khác. Ví dụ có thể dùng câu lệnh sau để xác định giá trị lớn nhất giữa hai số a và b:

s = (a > b)? a : b;Còn câu lệnh sau đây xác định giá trị lớn nhất trong ba số a, b, c:

s = (a > b)?(a > c? a : c) : (b > c?b : c);

7.8 Phép toán tăng, giảm một đơn vịGiả định có câu lệnh: num = num +1;

thì ta có thể viết gọn hơn là: num++;Toán tử ++ có tác dụng tăng giá trị của biến num thêm 1.

Trong C có hai phép toán một ngôi để tăng, giảm giá trị các biến. Toán tử tăng ++ sẽ cộng thêm 1 vào toán hạng của nó, toán tử giảm -- sẽ trừ toán hạng đi 1. Toán hạng ở đây chỉ có thể là một biến (nguyên, thực) mà khong thể là hằng hay một biểu thức. Các dấu phép toán ++ và -- có thể đứng trước (tiền tố) đứng sau (hậu tố) toán hạng, như vậy, chúng ta có thể viết:

Toán tử Dạng tiền tố Dạng hậu tố++ ++n n++-- --n n--

Khoa Tin học - 13 -

Page 14: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Sự khác nhau giữa dạng tiền tố và hậu tố này nảy sinh khi phép toán nằm trong

một biểu thức. Điểm khác nhau ở đây là cách sử dụng giá trị của biến toán hạng khi tính toán biểu thức bao. Với dạng tiền tố, ta thay đổi giá trị của biến trước khi sử dụng giá trị biến để tính toán các biểu thức bao quanh; còn với dạng hậu tố, giá trị cũ của biến được sử dụng để tính toán biểu thức bao, rồi sau đó biến mới được thay đổi giá trị. Ta xét các ví dụ sau:Giả sử trước mỗi phép tính i = 3 và j = 15

Câu lệnh Kết quải = ++j; i = 16 và j = 16i = j++ i = 15 và j = 16i++; i = 4j = ++i + 5; i = 4 và j = 9j = i++ + 5; i = 4 và j = 8

7.9 Phép toán lấy địa chỉBộ nhớ máy tính bao gồm nhiều byte được đánh số từ 0 đến giới hạn cuối cùng

của bộ nhớ. Các số này được gọi là số thứ tự và cũng là địa chỉ của các byte bộ nhớ. Mỗi biến được khai báo sẽ chiếm giữ một vùng nào đó trong bộ nhớ và địa chỉ của nó là địa chỉ của byte đầu tiên trong vùng nhớ đó.

Để xác định dịa chỉ của biến ngời ta sử dụng phép toán mộtngôi & đặt trước tên biến: &<tên_biến>

Chẳng hạn, nếu ta khai báo: int i;thì &i cho ta địa chỉ của vùng nhớ 2 byte có tên là i.

C không sử dụng phép toán này cho các đối tượng khác như hằng, biểu thức.

7.10 Phép toán chuyển đổi kiểu bắt buộc Ta có thể chuyển đổi một kiểu bất kì sang kiểu mong muốn bằng cách dùng toán tử chuyển kiểu bắt buộc theo quy tắc sau:

(<kiểu>) <biểu_thức>Trong đó, <kiểu> có thể là tên của một kiểu dữ liệu nào đó, như là int, float, …Một số kiểu float nằm trong phạm vi số nguyên int khi chuyển sang kiểu int sẽ

bị cắt bỏ phần thập phân.

Việc chuyển kiểu ở trên diễn ra do yêu cầu của người lập trình. Trong khi thực hiên chương trình có thể có các chuyển đổi kiểu tự động trong trường hợp sau: Khi biểu thức gồm các toán hạng khác nhau về kiểu, các toán hạng có kiểu thấp hơn (phạm vi biểu diến hẹp hơn) sẽ được chuyển đổi thành kiểu dữ liệu bao trùm hơn trước khi thực hiện phép toán và kết quả thu được sẽ có kiểu dữ liệu bao trùm nhất.

char int long float double long double

Khoa Tin học - 14 -

Page 15: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

chẳng hạn với khai báo:int n; long int p; float x;

thì biểu thức x = n*p được tính như sau: Để tính n*p, máy phát hiện p kiểu cao hơn, nên n được chuyển sang kiểu long theo kiểu p. Kết qua n*p có kiểu long. Kiểu kết quả này được chuyển sang kiểu của x là float.

7.11 Toán tử danh sách (toán tử dấu phẩy)Khi có nhiều biểu thức được viết liên tiếp nhau và chúng được cách nhau bởi

dấu phẩy thì máy sẽ thực hiện lần lượt các biểu thức theo thứ tự từ trái sang phải.Ví dụ:a = 5, 6, 7a có giá trị 5a = (5,6,7)acó giá trị 7

7.12 Thứ tự ưu tiên và trật tự kết hợp của các phép toánThứ tự ưu tiên và trật tự kết hợp của các phép toán được sử dụng để xác định

cách thức kết hợp các toán hạng với các toán tử khi tính toán biểu thức:Biểu thức con trong ngoặc được tính toán trước các phép toán khác.Phép toán một ngôi đứng bên trái toán hạng được kết hợp với toán hạng đi liền

sau nó.Khi một toán hạng đứng cạnh hai toán tử, ta phân biệt hai trường hợp:

Nếu hai toán tử có độ ưu tiên khác nhau, toán tử nào có độ ưu tiên cao hơn sẽ được kết hợp với toán hạng. Trường hợp trái lại, khi các toán tử giống nhau hoặc có độ ưu tiên như nhau thì trạt tự kết hợp của các phép toán này sẽ xác định toán tử nào được kết hợp với phép toán. Các toán tử cùng độ ưu tiên sẽ tuân theo trình tự kết hợp hoặc từ trái sang phải hoặc từ phải sang trái.

Bảng 1.8 liệt kê thứ tự ưu tiên và trật tự kết hợp của các phép toán. Trong bảng có một số phép toán sẽ được giớ thiệu ở các phần sau.

Bảng 1.8 Thứ tự ưu tiên của các phép toánMức Các toán tử Trật tự kết hợp

1 ( ) [] -> . -------->

2 ! ~ ++ -- - + (type) * & sizeof <--------

3 * (nhân) / % -------->

4 + - -------->

5 << >> -------->

Khoa Tin học - 15 -

Page 16: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

6 < <= > >= -------->

7 = = != -------->

8 & -------->

9 ^ -------->

10 | -------->

11 && -------->

12 || -------->

13 ? : <--------

14 = += -= *= /= %= <<= >>= &= |= ^= <--------

8 CẤU TRÚC CƠ BẢN CỦA MỘT CHƯƠNG TRÌNH CHãy xem các thành phần cơ bản của chương trình hello.c sau đây:

Số thứ tự câu lệnh

Mã chương trình hello.c

1 /*chương trình hiện lên dòng chữ Hello trên màn hình*/2 #include <stdio.h>3 #include <conio.h>4 void main() 5 {6 printf("Hello");7 getch();8 }

Giải thích:dòng 1: chú thích,dòng 2, 3: khai báo tệp tiêu đề,dòng 4: hàm chính dòng 5, 8: dấu bắt đầu và kết thúc chương trình dòng 6, 7: thân chương trình

Cụ thể các thành phần chưa được trình bày sẽ được nêu rõ ở các mục sau:

8.1. Khái niệm hàm

Một chương trình C với bất kì kích thước nào cũng đều bao gồm một hay nhiều hàm, các hàm này sẽ xác định các thao tác tính toán thực tế cần phải thực hiện. Các hàm của C cũng tương tự như các hàm và thủ tục của ngôn ngữ Pascal.

Trong chương trình ví dụ trên, main(), prinf(), scanf() là các hàm.

Khoa Tin học - 16 -

Page 17: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Riêng hàm main() là một hàm đặc biệt, chương trình C luôn bắt đầu bằng thực hiện tại điểm đầu của hàm này. Điều này có nghĩa là mọi chương trình C đều phải có một và chỉ một hàm main() ở đâu đó trong chương trình. Hàm main() này thường gọi tới các hàm khác để thựchiện công việc của nó, một số hàm nằm trong chương trình, số khác nằm trong các thư viện của các chuẩn .

Một phương pháp truyền dữ liệu cho các hàm là thông qua danh sách các đối số; Các đối số được đặt trong dấu ngoặc đơn ( ) ngay sau tên hàm.

Hàm được gọi bởi tên của nó, theo sau là danh sách các đối số được đặt trong dấu ngoặc đơn ( ). Trong trường hợp không có đối ta vẫn phải viết dấu ngoặc này.Chẳng hạn, hàm main() ví dụ trên.Ví dụ: printf("hello"); là một lời gọi hàm có tên printf với đối ở đây là xâu ký tự "hello". printf() là một hàm có trong thư viện chuẩn được sử dụng, không phải viết lại, có chức năng đưa kết quả ra thiết bị đầu ra (thường là màn hình). Trong trường hợp này, hàm sẽ cho hiện lên màn hình dãy kí tự tạo nên đối. Xét một ví dụ khác:getch( ); là một lời gọi hàm có tên getch( ), hàm này không có đối. Hàm này có tác dụng nhận một kí tự từ bàn phím (console), không cho hiển thị lên màn hình. Hàm trả về kí tự nhận được. Do đó, đối với chương trình trên, hàm này sẽ dừng màn hình kết quả sau khi chạy chương trình nên cho phép ta xem kết quả của chương trình cho đến khi ta nhấn một phím bất kì.

8.2 Khai báo tệp tiêu đềKhi sử dụng các hàm trong các thư viện chuẩn, chúng ta phải khai báo tệp tiêu

đề (header file) chứa hàm nguyên mẫu tương ứng của hàm đó, việc khai báo tệp tieu đề được bắt đầu bằng chỉ thị #include và theo sau là tên tệp tiêu đề. Trogn ví dụ trên, chúng ta có sử dụng hàm printf() là hàm chuẩn được khai báo trong tệp stdio.h và hàm getch() được khai báo trong conio.h. Do đó, chương trình có hai dòng: #include <stdio.h>#include <conio.h>

8.3 Chú thíchTrong ví dụ trên, dòng đầu tiên:

/*chương trình hiện lên dòng chữ Hello trên màn hình*/là lời chú thích, giải thích mục đích của chương trình.

Mọi kí tự nằm giữa /* và */ đều được chương trình dịch bỏ qua. Lời chú thích có thể nằm bất kì ở đâu trong chương trình nguồn, chứa được tất cả các kí tự và có thể trải dài trên nhiều dòng khác nhau trong chương trình.

Mục đích của việc sử dụng lời chú thích là để chương trình dễ hiểu, dễ kiểm tra, sửa, bảo hành,…

Khoa Tin học - 17 -

Page 18: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Ngoài ra, trong trình soạn thảo C++, ta có thể đặt lời chú thích sau dấu //, khi đó chương trình dịch sẽ bỏ qua các kí tự sau dấu // cho đến hết dòng đó.

8. VÀO RA

Các chương trnh dịch C cung cấp cho người lập trình hai thư viện các hàm vào ra. Danh sách các hàm này có thể tìm thấy trong hai tệp tiêu đề là stdio.h và conio.h, trong đó stdio.h khai báo các hàm vào ra chuẩn (standard input output) khôn gphụ htuộc vào thiết bị và conio.h (console input output) khai báo các hàm vào ra sử dụng với các thiết bị vào ra xác định như là bàn phím, màn hình. Sau đây là các hàm vào ra hay được sử dụng:

9.1 Hàm printf()Cú pháp của hàm printf() như sau:

int printf(<format>[,<argument>, …]);trong đó, <format> là xâu "điều khiển" xác định cách thức hiển thị dữ liệu trên

thiết bị ra chuẩn (thường là màn hình). Để thực hiện chức năng này trong xâu "điều khiển" có chứa các "định dạng".

Danh sách [,<argument>,…] nếu có sẽ chứa các giá trị (hằng, biến, biểu thức) được đưa ra. Các giá trị trong danh sách phân cách nhau bởi dấu phẩy. Số lượng các giá trị trong [,<argument>,…] bằng số "định dạng" có mặt trong xâu "điều khiển" <format>. Mỗi loại dữ liệu (nguyên, thực, kí tự, xâu ký tự,…) tương ứng với một số " định dạng nhất định. Điều này có nghĩa là tương ứng với một kiểu dữ liệu có thể có nhiều hình thức trình bày khác nhau, nhưng không thể áp dụng một cách "tuỳ ý" các định dạng cho các giá trị bất kì. Chẳng hạn, một số nguyên có thể hiển thị dưới dạng thập phân hay thập lục hay hệ bát phân nhưng không thể hiểu đó là địa chỉ của bộ nhớ ; một kí tự có thể hiển thị dưới dạng nguyên thuỷ của nó là một kí hiệu trong bảng mã ASCII hay là số thứ tự của nó trong bảng mã, nhưng không thể hiển thị nó dưới dạng một xâu kí tự hay số thục. Việc sử dụng sai "định dạng" có thể đưa tới các kết quả bất thường.

Ở dạng đơn giản nhất, mỗi "định dạng" trong xâu điều khiển <format> là một cặp kí hiệu "%" và một ký hiệu mô tả định dạng. Bảng 1.8 liệt kê một số định dạng tiêu biểu:

Bảng 1.8 Một số định dạng dùng cho hàm printfĐịnh dạng

Áp dụng chokiểu dữ liệu

Ghi chú

%d int đối là một số nguyên%i int đối là một số nguyên hệ 10 có dấu%o int đối là một số nguyên hệ 8 không dấu%u int đối là một số nguyên không dấu%x int đối là một số nguyên hệ 16 không dấu (không có 0x

đứng trước)

Khoa Tin học - 18 -

Page 19: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

%X int đối là một số nguyên hệ 16 không dấu với các chữ số là A, B, C, D, E, F

%e, %E float, double đối được chuyển sang dạng thập phân dấu chấm động%f, %lf float, double đối được chuyển sang dạng thập phân dấu chấm tĩnh%g, %G float, double đối được chuyển sang dạng thập phân dấu chấm tĩnh

hay động tuỳ thuộc vào loại nào ngắn hơn%c char đối là một kí tự%s char* đối là một xâu kí tự.

Hàm trả về số kí tự được hiển thị.Ví dụ sau đây minh hoạ cách sử dụng một số "định dạng" đã được đề cập.

printf("%d", -10);sẽ hiển thị ra màn hình là: -10

printf("%o", 10);sẽ hiển thị ra màn hình là: 12

printf("%s", "Khoa Tin học");sẽ hiển thị ra màn hình là: Khoa Tin học

Để trình bày dữ liệu đẹp hơn, ta bổ sung thêm một số thuộc tính nữa trong các "định dạng" như là độ rộng (số kí tự) cần thiết để trình bày dữ liệu, căn lề dữ liệu bên phải hay bên trái,… Những thuộc tính như vậy được đặt giữa dấu % và kí tự định dạng.

Phần thuộc tính này có dạng tổng quát như sau:[-] [[0]fw] [.pp]

trong đó, [-]: nếu có dấu này thì giá trị của đối được canh trái; ngược lại, giá trị của

đối sẽ được canh phải trong khoảng quy định sự xuất hiện của kết quả cần đưa ra nếu độ rộng của đối (trường ra) nhỏ hơn độ dài quy định fw.

[[0]fw]: fw là số nguyên quy định độ rộng cho trường ra (khác độ dài thực tế của trường ra). Nếu fw bắt đầu bằng 0 thì khoảng trống còn thừa trước giá trị số được in ra sẽ bị lấp đầy bằng số 0.

[.pp]: hiển thị pp chữ số thập phân nếu trường ra có kiểu float, double. Nếu trường ra có kiểu xâu thì, nếu pp >= độ dài xâu thì cả xâu được hiển thị; còn nếu pp < độ dài xâu thì chỉ có pp kí tự đầu xâu được hiển thị.

Bảng 1.9 sẽ cung cấp một số thuộc tính định dạng thưòng sử dụng:Thuộc tính Sử dụng với

định dạngVí dụ minh hoạ Kết quả

Độ rộng tối thiểu %d printf("%6d", 10); ^^^^10

Khoa Tin học - 19 -

Page 20: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

%f printf("%6f", 3.145); 3.145000%s printf("%s","CNTT-ĐHSP"); CNTT-ĐHSP… …

Độ rộng dành cho phần thập phân

%f printf("%6.2f", 123.455); 123.46

(Ghi chú: dấu ^ đại diện cho kí tự trống)

Bên cạnh các "định dạng", trong xâu điều khiển có thể chứa: Các kí tự được thông thường, các kí tự này được hiển thị cùng với giá trị của các đối trong phần [,<argument>,…] Các kí tự điều khiển (có mã ASCII nhở hơn 32). Các kí tự này được sử dụng để gấy một số hiệu ứng đặc biệt như xuống dòng ('\n'), nhảy trang ('\f'),…Bảng 1.10 trình bày một số kí tự thường dùng.

Bảng 1.10: Bảng mã một số kí tự điều khiển thường dùng

KÍ TỰDÃY MÃ

GIÁ TRỊ TRONG BẢNG MÃ ASCII

HỆ 16 HỆ 10

Đổ chuông (BEL) \a 0x07 7

Xoá trái (backspace BS) \b 0x08 8

Nhảy cách ngang (HT) \t 0x09 9

Nhảy cách đứng (VT) \v 0x0B 11

Xuống dòng mới (LF) \n 0x0A 10

Xuống dưới (FF) \f 0x0C 12

Về đầu dòng (CR) \r 0x0D 13

Mã NULL \0 0x00 0

Tóm lại, danh sách các giá trị cần đưa ra [,<argument>,…] có thể chứa các hằng, biến hoặc biểu thức. Giá trị của đối số sẽ được chuyển dạng và in ra theo các "định dạng" tương ứng. Cần nhắc lại là mỗi "định dạng" cần có một đối tương ứng. Khi một "định dạng" không tìm thấy đối tương tứng hoặc khi kiểu dữ liệu của giá trị đưa ra không tương thích với kí tự định dạng thì máy sẽ nhầm và có thể đưa ra kết quả không như mong muốn. Số giá trị đưa ra phải bằng số "định dạng".

Chương trình printf1.c sau đây minh hoạ cách sử dụng các "định dạng" đã được đề cập:cKết quả chương trình trên như sau:10 10

Khoa Tin học - 20 -

Page 21: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

-106552612aA6512.123400 12.121.212340e+01Xau ki tu

9.2 Hàm scanf()Cú pháp của hàm scanf( ) như sau:

int scanf(<format>,{<address>, …});Hàm scanf( ) có nhiều chức năng chuyển dạng giốngnhư hàm printf( ). Hàm này đọc các kí tự từ thiết bị vào chuẩn (thông thuờng là bàn phím), biến đổi chúng theo khuôn dạng được xác định bằng các "định dạng" trong xâu điều khiển <format>, rồi lưu trữ kết quả trong các đối có địa chỉ được mô tả trong phần {<address>, …}. Số lượng "định dạng" trong <format> cũng phải bằng số phần tử trong {<address>, …}. Ví dụ, khi thực hiện các lệnh:int n;scanf("%d", &i);Máy sẽ nhập từ bàn phím một số kí tự nạp vào trong một vùng bộ nhớ trong (gọi là vùng nhớ đệm bàn phím (stdin)). Quá trình thông dịch của hàm scanf( ) được thực hiện bằng cách đọc lần lượt các kí tự số có trong stdin cho đến khi gặp một kí tự khác số, các kí tự số vừa được duyệt sẽ được sử dụng để tạok giá trị của một số nguyên và số nguyên đó sẽ được đặt vào vùng nhớ xác định bởi địa chỉ của biến n. Cần nói thêm là, nếu không tìm thấy các kí tự số trong quá trình duyệt stdin, giá trị của biến n sẽ không bị thay đổi.Hàm scanf( )

Bảng 1.11 Bảng các định dạng dùng trong hàm scanf( )Định dạng

Ý nghĩa

%d nhập vào một số nguyên; đối tương ứng phải là địa chỉ của một biến nguyên.%o nhập vào một số nguyên hệ 8; đối tương ứng phải là địa chỉ của một biến

nguyên%x nhập vào một số nguyên hệ 16; đối tương ứng phải là địa chỉ của một biến

nguyên%c nhập vào một kí tự; đối tương ứng phải là địa chỉ của một biến kiểu kí tự%s nhập vào một xâu kí tự; đối tương ứng phải là một xâu kí tự%f nhập vào một số thực float; đối tương ứng phải là địa chỉ của một biến thực

Khoa Tin học - 21 -

Page 22: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

%ld nhập vào một số nguyên long int %lf nhập vào một số thực double

Chương trình scanf1.c sau đây mô tả cách sử dụng hàm scanf( ) để nhập giá trị cho các biến:#include<stdio.h>#include<conio.h>void main(){ int n; long l; float f; double d; char c;clrscr(); /* Xoá màn hình */printf("Nhap gia tri cho bien int: ");scanf("%d", &n);printf("Nhap gia tri cho bien long int: ");scanf("%ld", &l);printf("Nhap gia tri cho bien float: ");scanf("%f", &f);printf("Nhap gia tri cho bien double: ");scanf("%lf", &d);printf("Nhap gia tri cho bien char: ");scanf("%c", &c);printf("\nHien thi lai:\nSo int:%d\nSo long int:%ld\nSo float:%f\\nSo double:%f\nKi tu:%c",n, l, f, d,c); /* dòng lệnh dài hơn một dòng, dùng \ */ getch(); } Kết quả chương trình như sau:Nhap gia tri cho bien int: 123Nhap gia tri cho bien long int: 123456Nhap gia tri cho bien float: 123.456Nhap gia tri cho bien double: 123.456Nhap gia tri cho bien char:Hien thi lai:So int:123So long int:123456So float:123.456001So double:123.456000Ki tu:

Khoa Tin học - 22 -

Page 23: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Ta có ba nhận xét về kết quả thục hiện của chương trình trên.Thứ nhất, giá trị của biến f được in ra khác tí chút so với giá trị đã nhập vào.Thứ hai, chương trình không đợi chúng ta nhập vào kí tự tương tứng với c.Thứ ba, giá trị của biến kí tự c không nhìn thấy được (nghĩa là có mã ASCII

nhỏ hơn 32).

Nhận xét thứ nhất thực ra không quan trọng lắm vì nó phụ thuộc vào cách biểu diễn số thực trong máy tính.

Nhận xét thứ hai và ba gắn liền với cách thức đọc vùng đệm bàn phím stdin của hàm scanf( ).

Quy tắc 1: Khi đọc một số, hàm scanf( ) quan niệm rằng tất cả các kí tự số, dấu chấm '.' là kí tự hợp lệ còn các dấu phân cách các số là: dấu trắng (space), dấu tab, dấu xuống dòng.

Quy tắc 2: Khi đọc kí tựu, hàm scanf( ) cho rằng mọ kí tự có trong stdin đều hợp lệ.

Quy tắc 3: Khi đọc xâu kí tự, hàm scanf( ) sử dụng dấu phân cách như trong đọc số.

Dựa vào quy tắc thứ hai ta có thể giải thích hai nhận xét liên quan đến kí tự c trong chương trình scanf11.c ở trên. Sau khi kết thúc nhập giá trị cho biến d, ta ấn phím enter nên vùng đệm stdin sẽ chỉ có kí tự xuống dòng. Kí tự này sẽ được gán cho biến c.

Theo kinh nghiệm, để nhập được đúng giá trị cho biến kí tự thì: phải sử dụng một lệnh scanf( ) riêng cho mỗi lần nhập kí tự. trước lệnh scanf( ), thực hiện lệnh sau: fflush(stdin); đế xoá sạch stdin.

Xét ví dụ sau:

#include<stdio.h> #include<conio.h> void main() { int n; long l; float f; double d; char c; clrscr(); printf("Nhap gia tri cho bien int: "); scanf("%d", &n); printf("Nhap gia tri cho bien long int: "); scanf("%ld", &l);

Khoa Tin học - 23 -

Page 24: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

printf("Nhap gia tri cho bien float: "); scanf("%f", &f); printf("Nhap gia tri cho bien double: "); scanf("%lf", &d); fflush(stdin); printf("Nhap gia tri cho bien char: "); scanf("%c", &c); printf("\nHien thi lai:\nSo int:%d\nSo long int:%ld\n\ So float:%f\nSo double:%f\nKi tu:%c",n, l, f, d,c); getch(); }Kết quả của chương trình này:Nhap gia tri cho bien int: 123Nhap gia tri cho bien long int: 123Nhap gia tri cho bien float: 123.456Nhap gia tri cho bien double: 12455.356Nhap gia tri cho bien char: c

Hien thi lai:So int:123So long int:123So float:123.456001So double:12455.356000Ki tu:cBÀI TẬP

1 Biểu thức là gì? Các thành phần của một biểu thức?2 Toán tử là gì? Nêu các loại toán tử có trong C?3 Toán hạng là gì? Mối liên hệ giữa toán hạng và toán tử?4 Nêu các loại toán tử số học trong C? Tổng quát hoá luật kết hợp giữa

các toán tử?5 Làm thế nào để chuyển một giá trị trả về bởi biểu thức sang một kiểu

dữ liệu khác? 6 Thứ tự của các toán tử có ý nghĩa như thế nào? Nêu các mối liên hệ

về thứ tự của các toán tử?7 Khi nào thì nên sử dụng dấu ngoặc trong biểu thức? 8 Các toán tử được thực hiện theo thứ tự như thế nào trong một biểu

thức có chứa các dấu đóng mở ngoặc lồng nhau?9 Cho biết tác dụng của toán tử ++, --. Các cách dùng khác nhau của

chúng?10 Cho biết số byte được cấp phát cho mỗi kiểu dữ liệu trong C? 11 Các toán tử lôgic trong C, chúng được sử dụng với các với các toán

hạng nào? Các toán tử này thuộc kiểu biểu thức nào?

Khoa Tin học - 24 -

Page 25: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

12 Toán tử trên bit?13 Công dụng của toán tử lôgic not ( ! )và toán tử not bit (~). Hai toán

tử này có giống nhau không?14 Với hai toán hạng khác kiểu thì một biểu thức gán được xác định

như thế nào? Nguyên nhân gây ra lỗi trong trường hợp này?15 Nêu các phép gán trong C? Trật tự thực hiện chúng như thế nào?16 Để sử dụng các hàm thư viện của C, phải khai báo gì trong chương

trình?17 Hãy viết mục đích của các biểu thức sau:

a) a - b d) a != bb) a*(a+b) e) (a/b)%5c) d = a*(a+b) f) –h

18 Giả sử có khai báo sau:int n = 10, p = 4;long q = 2; float x = 1.75;

Hãy cho biết kiểu và giá trị của các biếu trức sau:d) n + q g) q + 3*(n > p)e) n + x h) q&&nf) n%p + q i) (q - 2) && (n - 10)g) n < p j) x*(q = = 2)h) n >= p k) x*(q = 5)i) n > q l) (float)n/p

19 Cho biết giá trị của x nếu:int x = 5; float y = 9.0,z; z = y/z;

a) 1b) 1.8c) 2d) Không phải các giá trị trên.

20 Tìm giá trị của x:float z ;z = (int)3.0 + (int) 3.8 ;a) 6.8b) 6.0c) 7.0d) Không phải các giá trị trên.

21 Toán tử nào không phải là toán tử gán: a) = b) += c) != d)?=

26. Viết chương trình tính x mũ y.

Khoa Tin học - 25 -

Page 26: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Biểu thức (100>79) && (‘A’< ‘B’) có tương đương với 100>79&&‘A’< ‘B’?

27. Muốn biết hình dáng kí tự có mã ASCII là 222, ta làm gì?30. Nhập vào bốn số thực. Tìm giá trị lớn nhất , nhỏ nhất trong bốn số đó bằng

sử dụng các biểu thức điều kiện

Khoa Tin học - 26 -

Page 27: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 2

CÁC CÂU LỆNH ĐIỀU KHIỂN CHƯƠNG TRÌNH1. CÂU LỆNH ĐƠN

Một biểu thức lệnh (gán, tăng giảm giá trị) kết thúc bởi dấu ; là một lệnh đơn. Một lời gọi hàm đi sau bằng dấu ; cũng là một câu lệnh đơn.

Dấu ; dùng để kết thúc một câu lệnh đơn. Bản thân dấu ; là lệnh rỗng.Ví dụ:

x = 0;x++;printf("\n");bao gồm ba câu lệnh hợp lệ.2. CÂU LỆNH GHÉP

Câu lệnh ghép là tập hợp các câu lệnh được bao bởi dấu { và }. Về cú pháp, ở đâu có thể đặt một câu lệnh đơn thì ở đó cũng có thể đặt một câu lệnh ghép (khối lệnh). Phần thân của một hàm cũng là một câu lệnh ghép. Lưu ý rằng không cần đặt dấu ; sau một khối lệnh mặc dù điều đó là không sai.

Các lệnh thành phần trong lệnh ghép sẽ được thực hiện một cách tuần tựu theo thứ tự xuất hiện. Đây chính là cấu trúc lệnh tuần tự của các ngôn ngữ lẩptình cấp cao. Trong một khối lệnh có thể có các khai báo biến và hằng (khác với Pascal) nhưng phải đảm bảo rằng các khai báo phải được đặt trước tất các câu lệnh. Chẳng hạn, không thể khai báo một biến nguyên sau khi gọi hàm xoá màn hình clrscr().

3. CÂU LỆNH ifCâu lệnh if được dùng để lựa chọn quyết định. Một cách hình thức, cú pháp

của câu lệnh như sau:if (<expr>)

<lệnh_1>;[else <lệnh_2>;]

Trong đó, else là phần không bắt buộc. Biểu thức expr được tính, nếu nó đúng (có giá trị khác 0) thì <lệnh_1> được thực hiện và bỏ qua <lệnh_2>Trái lại, chỉ có <lệnh_2> sẽ được thực hiện. Trong trường hợp không có phần else thì máy sẽ bỏ qua <lệnh_2> và chuyển điều khiển sang câu lệnh ngay sau lệnh if.

Biểu thức expr luôn phải được đặt trong dấu ngoặc ( ). Phần sau if hay else có thể là một câu lẹnh đơn, lệnh ghép hay một cấu trúc điều khiển chương trình khác. Do vậy cho phép các lệnh if có thể lồng nhau. Do phần else của lệnh if là tuỳ chọn nên có thể xảy ra nhập nhằng khi một else bị bỏ qua

Khoa Tin học - 27 -

Page 28: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

trong một dãy các câu lệnh if lồng nhau. Vấn đề được giải quyết theo cách thông thuờng như sau: else được gắn với if không có else gần nhất trước đó. Chẳng hạn, trong câu lệnh sau: if (n)

if (a > b) z = a;

else z = b;

thì else sẽ luôn kết hợp với if bên trong. Để không gây nhầm lẫn, ta canh thẳng hàng else với if tương ứng, hoặc dùng dấu { } để gắn else với if tương ứng, chẳng hạn: if (n)

if (a > b) z = a;

else z = b;

hoặcif (n){

if (a > b) z = a;

else z = b;}

Chú ý: vì lệnh if chỉ kiểm tra giá trị của biểu thức nên có thể viết gọn hơn nữa theo cách:if (expr)thay choif (expr!=0)Ví dụ: chương trình if1.c cho phép nhập 2 số thực a và b, sau đó tính a/b.void main(){

float a, b;printf("Nhap a va b: ");scanf("%f%f",&a,&b);if (b) printf("a/b = %10.4f",a/b);else printf("b = 0, khong chia duoc");getch();

}

Kết quả như sau:Trường hợp 1: Nhap a va b: 0 10

Khoa Tin học - 28 -

Page 29: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

a/b = 0.0000Trường hợp 2: Nhap a va b: 10 0b = 0, khong chia duoc

4. CÂU LỆNH WHILECú pháp câu lệnh while như sau:

while (expr) <lệnh>;

Cách thức thực hiện lệnh while như sau: Đầu tiên, biểu thức expr được tính. Nếu giá trị của biểu thức này khác 0 thì

<lệnh> (thường được gọi là thân vòng lặp while) sẽ được thực hiện và biểu thức expr sẽ được tính lại. Chu trình này được tiếp tục cho đến khi biểu thức expr có giá trị 0. Khi biểu thức expr có giá trị 0 thì việc thực hiện chương trình chuyển sang câu lệnh sau thân while.

(Câu lệnh được while của C được thực hiện giống câu lệnh while …do của Pascal, hay để đễ nhớ, ta có thể tạm dịch câu lệnh này là "chừng nào biểu thức còn khác 0 thì còn thực hiện thân lệnh")

Ví dụ 1: dùng vòng lặp while để tính tổng của các số từ 1 đến 10.#include <stdio.h>#include<conio.h>main(){

int count = 0;int total = 0;while (count<=10)

total+=count++;printf("Total: %d", total);getch();

}Kết quả:Total: 55

Ví dụ 2: chương trình sau đây sử dụng vòng lặp while để nhập ba số thực dùng làm số đo ba cạnh của một tam giác. #include <stdio.h>#include<conio.h>main(){float a, b, c;printf("Nhap vao do dai ba canh cua tam giac:"); scanf("%f%f%f", &a, &b, &c);while (a+b<=c || b+c<=a || a+c<=b)

Khoa Tin học - 29 -

Page 30: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

{printf("Chua duoc!\nNhap lai:");scanf("%f%f%f", &a, &b, &c);}

printf("Duoc roi!");getch();}Chương trình cho kết quả như sau:Nhap vao do dai ba canh cua tam giac:1 2 3Chua duoc!Nhap lai:3 4 1Chua duoc!Nhap lai:3 4 5Duoc roi!

5. CÂU LỆNH do…whileCú pháp câu lệnh do…while như sau:

do { <lệnh> }while (expr);

Câu lệnh while kiểm tra điều kiện kết thúc vòng lặp ngay khi bắt đầu vòng lặp. Do đó, có thể phần thân lệnh while không được thực hiện lần nào. Trái lại, câu lệnh do…while kiểm tra tra điều kiện ở cuối chu trình, sau mỗi lần đi qua thân chu trình. Do vậy, chu trình bao giờ cũng được thực hiện ít nhất một lần.

Câu lệnh do…while của C so với câu lệnh repeat…until của Pascal có một vài điểm khác biệt. Đối với repeat…until, vòng lặp còn được thực hiện chứng nào biểu thức điều kiện đi sau từ khoá until còn chưa đúng (nghĩa là sai); ngược lại, đối với do…while của C, ta lặp lại công việc khi biểu thức điều kiện sau từ khoá while có giá trị khác 0 (vẫn còn đúng). Nói cách khác, điều kiện kết thúc trong vòng lặp repeat..until và do…while là ngược nhau. Hãy so sánh đoạn chương trình nhập vào một số nguyên dương bằng C và Pascal:

int n;…do{printf("n=");scanf("%d", &n);} while (n<=0);

n:integer;…repeatwrite('n=');readln(n);until (n>0);

Khoa Tin học - 30 -

Page 31: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Phần thân câu lệnh repeat…until có thể là một dãy các lệnh tuy nhiên không cần sử dụng hai từ khoá xác định khối, trái lại với do…while điều này lại bắt buộc ngay cả khi bên trong do…while không thực hiện bất cứ một lệnh nào.Chẳng hạn, chương trình sau đây vẫn "chạy" tốt:main(){int i=10;do{}while (i--);}

Chương trình sau đây sử dụng vòng lặp do…while để nhập ba số nguyên dùng làm độ dài ba cạnh của một tam giác.#include <stdio.h>#include<conio.h>main(){float a, b, c;clrscr();printf("Nhap vao do dai ba canh cua tam giac:");do {

scanf("%f%f%f", &a, &b, &c);if (a+b<=c || b+c<=a || a+c<=b)

printf("Chua duoc!\nNhap lai:");

}while (a+b<=c || b+c<=a || a+c<=b);printf("Duoc roi!");getch();}

Kết quả chương trình:Nhap vao do dai ba canh cua tam giac:1 2 3Chua duoc!Nhap lai:1 2 4Chua duoc!Nhap lai:3 4 5Duoc roi!

6. CÂU LỆNH FORCú pháp:

for ( [expr1]; [expr2]; [expr3]) <lệnh>

Khoa Tin học - 31 -

Page 32: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Có thể diễn đạt cú pháp này bằng vòng lặp while như sau:expr1;while (expr2) {

<lệnh>expr3;

}Trong đó, expr1, expr2, expr3 là các biểu thức. Thông thường, expr1 và expr3

là các phép gán hoặc lời gọi hàm, còn expr2 là biểu thức quan hệ hoặc lôgic.Theo cách biểu diễn tương đương bằng vòng lặp while, ta thấy rằng biểu thức

expr1 được thực hiện đầu tiên và duy nhất một lần, sau đó, vòng lặp for tính giá trị biểu thức quan hệ expr2, nếu cho giá trị khác 0, nhóm lệnh trong thân for được thực hiện, tiếp đến là thực hiện biểu thức expr3. Sau đó, biểu thức điều kiện expr2 được tính lại để kiểm tra sẽ thực hiện một vòng lặp nữa hay vòng for kết thúc.

Một cách đơn giản, có thể hiểu ý nghĩa của vòng for như như một vòng lặp với số bước xác định, trong đó:

o expr1: khởi tạo giá trị ban đầu cho các biến điều khiển,o expr2: điều kiện để tiếp tục vòng lặp,o expr3: thay đổi giá trị các biến điều khiển.#include<stdio.h>main(){int i;for (i=1;i<=100;i++)

printf("%4d", i);}

Kết quả chương trình như sau: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

Có thể thấy rằng chương trình này được chuyển đổi từ chương trình Pascal sau:program for1;var i: interger;begin

for i:=1 to 100 dowrite(i:4);

end.

Khoa Tin học - 32 -

Page 33: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Tuy nhiên, vòng for trong ngôn ngữ C có thể có nhiều biến điều khiển. Điều đó làm mất đi tính chất "có số vòng lặp xác định nhưng lại tăng thêm tính mềm dẻo cho vòng for. Trong nhiều trưòng hợp điều này còn làm cho chương trình dễ đọc hơn. Chương trình sau đây sử dụng vòng for với hai biến điều khiển để in ra từng cặp hai số trong phạm vi 100, có tổng bằng 101.#include<stdio.h>#include<conio.h>main(){int i,j;clrscr();for (i=1,j=100;i<j;i++,j--)

printf("(%d,%d)",i,j);getch();}

Kết quả chương trình như sau:(1,100)(2,99)(3,98)(4,97)(5,96)(6,95)(7,94)(8,93)(9,92)(10,91)(11,90)(12,89)(13,88)(14,87)(15,86)(16,85)(17,84)(18,83)(19,82)(20,81)(21,80)(22,79)(23,78)(24,77)(25,76)(26,75)(27,74)(28,73)(29,72)(30,71)(31,70)(32,69)(33,68)(34,67)(35,66)(36,65)(37,64)(38,63)(39,62)(40,61)(41,60)(42,59)(43,58)(44,57)(45,56)(46,55)(47,54)(48,53)(49,52)(50,51)

Về mặt cú pháp, một trong ba biểu thức thành phần của vòng for đều có thể vắng mặt, nhưng phải có dấu ; Khi biểu thức expr2 không có mặt, thì ta coi biểu thức điều kiện luôn luôn đúng. Có thể thoát khỏi vòng lặp vô hạn nhờ các câu lệnh ngắt như break hoặc return được đề cập ở các phần tiếp theo.

Cuối cùng, cũng giống như các câu lệnh có cấu trúc khác, thân của vòng for có thể chứa vòng for khác tạo thành các vòng for lồng nhau. Xét ví dụ sau:/* In ra bảng cửu chương*/#include<stdio.h>#include<conio.h>main(){int i,j;clrscr();for (i=1;i<=10;i++)

{for (j=1;j<=10;j++)

printf("%5d",i*j);printf("\n");}

getch();}Khoa Tin học - 33 -

Page 34: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Kết quả như sau:

1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100

7. CÂU LỆNH break VÀ continue

7.1 Lệnh breakTrong một số trường hợp ta cần phải thoát khỏi một chu trình mà không cần

thực hiện kiểm tra điều kiện lặp. Câu lệnh break cho phép thoát ra khỏi các vòng lặp for, while, do…while.Câu lệnh break cho phép chương trình thoát ngay lập tức ra khỏi vòng lặp trong vùng chứa nó. Sau đây là ví dụ dùng lệnh break để thoát ra khỏi vòng lặp do…while vô hạn:#include<stdio.h>#include<conio.h>main(){int n;clrscr();do {

printf("\nn= ");scanf("%d", &n);if (n>0) break;else printf("Not OK!");}while (1);

printf("So duong!");getch();}Kết quả như sau:

n= -12Not OK!n= 0

Khoa Tin học - 34 -

Page 35: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Not OK!n= 5So duong!

7.2 Lệnh continueKhi gặp câu lệnh continue chương trình không kết thúc nốt vòng lặp chứa

nó mà bỏ qua các lệnh còn lại trong thân vòng lặp để bắt đầu một lần lặp mới. Chương trình ví dụ sau minh hoạ cách sử dụng lệnh continue để in ra các số dương trong số 10 lần nhập.#include<stdio.h>#include<conio.h>main(){int i,n,s=0;clrscr();for (i=1; i<=10; i++)

{printf("\nLan nhap thu: %d, n= ",i);scanf("%d", &n);if (n<=0) continue;printf("Da nhap mot so duong %d\n", n);s+=n;}

printf("\nTong cac so duong: %d", s);getch();}Kết quả chương trình như sau:Lan nhap thu: 1, n= -10

Lan nhap thu: 2, n= -10

Lan nhap thu: 3, n= 0

Lan nhap thu: 4, n= 0

Lan nhap thu: 5, n= 0

Lan nhap thu: 6, n= 0

Lan nhap thu: 7, n= 3Da nhap mot so duong 3

Khoa Tin học - 35 -

Page 36: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Lan nhap thu: 8, n= 0

Lan nhap thu: 9, n= 10Da nhap mot so duong 10

Lan nhap thu: 10, n= 10Da nhap mot so duong 10

Tong cac so duong: 23

8. CÂU LỆNH switchCú phápcủa câu lệnh switch như sau:

switch (expr) {case <value1>: <lệnh1>; [break;]case <value2>: <lệnh2>; [break;]case <value3>: <lệnh3>; [break;]…case <valuen>: <lệnhn>; [break;][default : <lệnh> [break;]]}

Câu lệnh switch cho phép ra quyết định nhiều nhánh hoàn toàn giống như câu lệnh case of của Pascal. Nó kiểm trả xem một biểu thức expr có giá trị ứn với một trong số các giá trị hằng hay không và nhảy đến câu lệnh tương ứng khi so trúng.

Câu lệnh switch tính giá trị của biểu thức số expr trong ngoặc và so sánh giá trị này với các giá trị đứng sau case. Mỗi trường hợp đều được đánh nhãn bởi case, tiếp theo là một giá trị (nguyên, kí tự hoặc biểu thức hằng). Nếu so trúng thì sẽ thực hiện nhóm lệnh tương ứng với trường hợp đó. Nếu có nhãn default, câu lệnh tương ứng với nhãn này được thực hiện nếu không có trường hợp nào thoả mãn. Chú ý rằng default là phần tuỳ chọn; nếu không có sẽ không thực hiện gì cả. case và default có thể xuất hiện theo bất kì trật tự nào. case tương ứng với các trường hợp khác nhau.

chương trình sau đây đếm số lượng sô 1, số 50 và số 100 trong sô 10 lần nhập số nguyên.#include<stdio.h>#include<conio.h>main(){int i,n,c1=0, c50=0,c100=0;clrscr();for (i=1; i<=10; i++)

{

Khoa Tin học - 36 -

Page 37: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

printf("Lan nhap thu: %d, n= ",i);scanf("%d", &n);switch (n) {

case 1: c1++; break;case 50: c50++; break;case 100: c100++;break;}

}printf("\nSo lan nhap so 1: %d",c1);printf("\nSo lan nhap so 50: %d",c50);printf("\nSo lan nhap so 100: %d",c100);getch();}Kết quả chương trình như sau:Lan nhap thu: 1, n= 10Lan nhap thu: 2, n= 10Lan nhap thu: 3, n= 10Lan nhap thu: 4, n= -12Lan nhap thu: 5, n= -10Lan nhap thu: 6, n= 1Lan nhap thu: 7, n= 50Lan nhap thu: 8, n= 100Lan nhap thu: 9, n= 1Lan nhap thu: 10, n= 100

So lan nhap so 1: 2So lan nhap so 50: 1So lan nhap so 100

Trong thân switch, câu lệnh break tạo ra sự phân cách giữa các nhóm lệnh tương ứng với với các trường hợp khác nhau. Điều đó cho phé thoát ngay khỏi switch một khi các công việc tương ứng với một trường hợp đã thực hiện xong. Nếu muốn có nhiều nhãn case sử dụng chung một nhóm lệnh nào đó thì ta nên đặt chúng cạnh nhau, giữa các nhãn case này sẽ không đặt lệnh break, khi đo nhóm lệnh của các nhãn case đi trước sẽ bao gồm cả các lệnh của nhóm case đứng sau. Vd: Xét chương trình xác định số ngày trong một tháng dựa vào số hiệu chỉ tháng.

#include<stdio.h>#include<conio.h>main(){int thang, songay;

Khoa Tin học - 37 -

Page 38: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

clrscr();printf("Nhap vao so chi thang: ");scanf("%d", &thang);

switch (thang){case 1:case 3:case 5:case 7:case 8:case 10:case 12:printf("Thang nay co 31 ngay");break;case 4:case 6:case 9:case 11:printf("Thang nay co 30 ngay");break;case 2: printf("Thang nay co 28 hoac 29 ngay");break;}

getch();}Kết quả khi "chạy" chương trình như sau:Nhap vao so chi thang: 10Thang nay co 31 ngayVd: Với ví dụ trên kiểm tra số ngày trong tháng với trường hợp có năm nhuần và không có năm nhuần, trường hợp cuối cùng sử dụng default;

BÀI TẬP1. In ra màn hình các dấu * theo các hình dạng sau:

a)***************

b)*

*****

*********

Khoa Tin học - 38 -

Page 39: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

c) *

* * * *

d)

* * * * *

e)

** ** ** ** * * * *f)

* * *

* * * * * * * * *

1. In ra bảng mã ASCII, trong đó mỗi cột bao gồm kí tự và mã của kí tự đó dưới dạng thập phân / thập lục phân / bát phân .

2. Nhập vào ba số thực dùng làm số đo ba cạnh một tam giác, sau đó xác định xem tam giác tương ứng có tính chất gì: đều, vuông cân, cân, vuông hay thường.

3. Nhập vào một số nguyên dương, kiểm tra xem nó có phải số nguyên tố hay không.

4. Nhập vào một sồ nguyên dương n, liệt kê tất các số nguyên tố nhỏ hơn n .

Khoa Tin học - 39 -

Page 40: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

5. Nhập vào mốt số nguyên dương, kiểm tra xem số đó có phải số hoàn thiện hay không! (Số hoàn thiện là số có giá trị bằng tổng tất cả các ước số của nó không tính bản thân nó ) .

6. Nhập vào một số nguyên dương n, liệt kê các số hoàn thiện nhỏ hơn n . 7. Nhập vào các số nguyên cho đến khi gặp 0, đưa ra giá trị lớn nhất trong số

các số chia hết cho 5 vừa nhập được .8. Nhập vào một số nguyên dương, in ra số theo thự tự ngươc lại .9. Nhập vào một số nguyên dương. Đưa ra tổng các chữ số của số đó. 10. Nhập vào một nguyên dương. Cho biết số đó có bao nhiêu chữ số . 11. Nhập vào một số nguyên dương n. Nhập liên tiếp không quá n số tự nhiên.

Tính số lần nhập vào số 1, số lần nhập vào số 50, số lần nhập vào số 30.

Khoa Tin học - 40 -

Page 41: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 3

MẢNG VÀ CON TRỎ1. MẢNG

1.1 Khái niệm và định nghĩaMảng (array) là một dãy liên tiếp các phần tử trong bộ nhớ, các phần tử này

chứa dữ liệu thuộc cùng một kiểu. Một mảng số nguyên gồm các phần tử là các biến số nguyên. Một mảng số thực bao gồm các phần tử là các biến thực.

Kích thước của mảng chính là số các phần tử trong mảng. Kích thước này phải được khai báo tường minh trong phần khai báo mảng vì nó xác định vị trí và kích thước của vùng nhớ trong bộ nhớ được cấp phát cho mảng.

Mảng có thể có một hay nhiều chiều. Ngôn ngữ C không giới hạn số chiều của mảng.

1.2 Khai báo mảng1.2.1 Mảng một chiềuNguyên tắc chung để khai báo một biến mảng như sau:

<type> <array_name>[sizze];trong đó, type là kiểu của các thành phần mảng; array_name là tên mảng;

size là số phần tử trong mảng.Ví dụ: int mi[7]; /* mảng số nguyên 7 phần tử*/char ten[32]; /* mảng kí tự 32 phần tử*/

Kích thước của biến mảng được xác định dựa vào kiểu thành phần và số phần tử trong mảng. Chẳng hạn, mảng mi sẽ được cấp phát 7*2 = 14 byte trong bộ nhớ. Trong khi đó mảng ten gồm 32 kí tự, chiếm 32*1 = 32 byte.

Địa chỉ của vùng nhớ cấp phát cho biến mảng được xác định bởi tên của biến mảng. Nói cách khác, tên biến mảng cho một giá trị mô tả địa chỉ đầu của mảng. Chừng nào còn tồn tại thì tên mảng còn xác định và vùng nhớ dành cho mảng cũng cố định. Ta gọi những mảng được tạo ra bằng cách khai báo là mảng tĩnh để phân biệt với mảng được tự động tạo ra và xoá đi nhờ các công cụ cấp phát động.

1.2.2 Truy nhập vào các phần tử của mảngMỗi phần tử của mảng được truy nhập thông qua tên biến mảng và chỉ số

tương ứng, phần tử đầu tiên có chỉ số là 0. Ví dụ, với khai báo mảng mi như trên, ta có:mi[0] là phần tử đầu tiênmi[1] là phần tử thứ haimi[2] là phần tử thứ ba…

Khoa Tin học - 41 -

Page 42: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Để làm chỉ số cho mảng, ta có thể dùng các đại lượng với giá trị là số: hằng, biến, biểu thức.

Mỗi phần tử của biến mảng mi được xem như là một biến nguyên; mỗi phần tử của biến mảng ten được xem là một biến kí tự.Ví dụ:ai[1] = 5;ten[10] = 'Y';

Một điểm khác biệt giữa C là Pascal là ngôn ngữ C không đưa ra các kiểm tra về sự giới hạn chỉ số các phần tử của mảng. Chẳng hạn, với mảng mi ở trên, về nguyên tắc chỉ có các phần tử mi[0], mi[1], …, m[6] là hợp lệ. Tuy nhiên chương trình dịch C cũng không đưa ra bất kì cảnh báo nào nếu chúng ta viết mi[10] trong một biểu thức nào đó trong chương trình.

1.2.1 Mảng nhiều chiềuNgôn ngữ C coi rằng một mảng nhiều chiều là một mảng một chiều trong

đó mỗi phần tử là một mảng với số chiều không ít hơn một. Chẳng hạn, mảng hai chiều các số nguyên thực chất là mảng một chiều có kiểu thành phần là mảng một chiều các số nguyên.

Trong khai báo mảng nhiều chiều ta phải mô tả kích thước của tất cả các chiều; mỗi kích thước như thế được đặt trong dấu ngoặc vuông [ ]. Chẳng hạn, trong khai báo sau đây:float tb[10][20];ta có thể xem tb như là một mảng một chiều có 10 thành phần và mỗi thành phần là một mảng chứa 20 số thực. Như vậy, ta có thể viết:tb[i] để chỉ một mảng 20 số thực nào đó trong tbcòn tb[i][j] để xác định một số thực nào đó trong tb.

1.3 Nhập dữ liệu cho biến mảngVề nguyên tắc, có hai cách để thực hiện:(i) Sử dụng các hàm nhập (scanf( )) để nhập giá trị cho phần tử cần nhập. Ví

dụ:scanf("%d", %mi[6]);scanf("%f", &tb[2][1]);Ta gọi đây là cách nhập trực tiếp

(ii) Sử dụng một biến trung gian có cùng kiểu với các phần tử của mảng. Trước tiên ta nhập giá trị cho biến trung gian, sau đó gán giá trị của biến trung gian này cho phần tử cần nhập giá trị.Ví dụ: int temp1;float temp2;…scanf("%d", &temp1);

Khoa Tin học - 42 -

Page 43: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

mi[6] = temp1;scanf("%f", &temp2);tb[3][4] = temp2;

Theo kinh nhgiệm, cách thứ hai thường ổn định hơn, không làm treo máy, trong khi đó vì áp dụng phép toán địa chỉ lên các phần tử của mảng nên một số trường hợp khi kiểu thành phần không phải là kiểu nguyên, cách nhập trực tiếp có thể làm treo máy.

Chương trình array.c sau đây minh họa cách sử dụng mảng số nguyên để thực hiện: nhập và in ra một dãy số nguyên. Ở đây ta có thể sử dụng một trong các vòng lặp của C để thực hiện công việc. Trong chương trình này ta sử dụng vòng lặp for.#include<stdio.h>#include<conio.h>void main(){int n,i; /*n là số phần tử của dãy số */int dayso[100]; /* dãy số có tối đa 100 phần tử*/clrscr();/*nhập số phần tử của dãy số */do {

printf("\nNhap vao so phan tu: ");scanf("%d", &n);}

while (n<=0||n>=100);/* nhập giá trị cho từng phần tử*/for (i=0; i<n; i++){

printf("dayso[%d]=",i);scanf("%d", &dayso[i]);}

/*Hiển thị dãy*/for (i=0; i<n; i++)

printf("%5d", dayso[i]);/*ấn phím bất kì để kết thúc*/getch();}

Kết quả chương trình như sau:Nhap vao so phan tu: 102

Nhap vao so phan tu: -12

Nhap vao so phan tu: 5

Khoa Tin học - 43 -

Page 44: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

dayso[0]=12dayso[1]=-102dayso[2]=0dayso[3]=2dayso[4]=4 12 -102 0 2 4

1.4 Xâu kí tự1.4.1 Khai báo xâu kí tựMột biến xâu kí tự được khai báo tương tự như một mảng một chiều chứa các

kí tự. Chẳng hạn:char str[10];

Sự khác nhau giữa mảng kí tự và xâu kí tự là trong xâu kí tự có có kí tự kết thúc xâu là kí tự NULL hay '\0'. Đây là căn cứ của các thao tác trên xâu như: tính chiều dài của xâu, cộng xâu,… Do đó, một biến xâu kí tự muốn có chiều dài 9 phải được khai báo như một mảng kí tự có 10 phần tử. Hình sau đưa ra một hình ảnh về nội dung xâu kí tự str:

str[0] 'A'str[1] 'n'str[2] 'h'str[3] 'E'str[4] 'm'str[5] 'B'str[6] 'a'str[7] 'n'str[8] '\0'str[9]

Hình 3.1 Nội dung một xâu kí tự

Cần phân biệt giữa kí tự và xâu kí tự một kí tự. Ví dụ 'A' là kí tự A được mã hoá bằng 1 byte, trong khi "A" là một xâu kí tự chứa kí tự 'A' và '\0', nên được mã hoá bằng 2 byte.

Trong C không tồn tại các phép toán so sánh, gán nội dung của xâu này cho xâu khác. Để thực hiện việc này C cung cấp cho người lập trình một thư viện các hàm chuẩn là string.h.

1.4.2 Vào ra với xâu kí tự Sử dụng printf( ) và scanf()Để nhập xuất xâu ký tự với scanf( ) printf( ) ta sử dụng định dạng ''%s'' trong

xâu điều khiển, còn trong phần tham số đi sau ta sử dụng tên của biến xâu. Khi nhập xâu kí tự, để bảo đảm việc nhập đúng nên sử dụng lệnh scanf(… ) riêng cho mỗi phần nhập xâu và trước mỗi lệnh đó nên sử dụng lệnh sau:

Khoa Tin học - 44 -

Page 45: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

fflush(stdin); Sử dụng puts( ) và gets( )Hai hàm này được khai báo trong tệp tiêu đề stdio.h. Cú pháp sử dụng như sau:

gets(str);puts(str);

Hàm gets( ) đọc từ bàn phím tất cả các kí tự và điền vào xâu str. Việc đọc kết thúc khi máy gặp kí tự xuống dòng '\n' (sinh ra khi ta ấn phíp ENTER ). Kết thúc việc đọc kí tự, một kí tự '\0' được gắn thêm vào cuối xâu. Chú ý rằng nếu ta nhập vào một xâu có kích thước lớn hơn hoặc bằng với kích thước mảng khai báo để chứa xâu thì máy sẽ bị lỗi.

Khác với scanf( ) hàm gets( ) cho phép nhập cả các kí tự trắng trong nội dung của xâu. Khi lệnh gets( ) đi sau các lệnh nhập khác nên sử dụng thêm ffllush(stdin); phía trước để đảm bảo viêc nhập không bị sai .

Hàm puts( ) in nội dung xâu kí tự str ra màn hình va thay thế kí hiệu '\0 ' kết thúc xâu bằng kí hiệu xuống dòng '\n'.

Chương trình sau đây minh hoạ cách sử dụng gets( ) và puts( ) để nhập và in xâu kí tự.

#include<stdio.h>#include<conio.h>void main(){char str[81];clrscr();printf("\nNhap vao mot dong: ");gets(str);printf("\nBan da nhap vao xau: ");puts(str);getch();}

Kết quả chương trình như sau:Nhap vao mot dong: Dai hoc Da NangBan da nhap vao xau: Dai hoc Da Nang

1.5 Khai báo và khởi đầu giá trị cho mảng1.5.1 Mảng một chiềuint ai[7] = {1, 2, 3, 4, 0, -12, 1};

1.5.2 Mảng nhiều chiềuint a2i[3][4] = {{1, 2, 3, 0},

{0, 10, -90, 9}{0,0, 12, -120}};

Khoa Tin học - 45 -

Page 46: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

1.5.2 Xâu kí tự* Cách 1:char str[6] = {'a', 'b', 'c'};* Cách 2:char str[6] = "abc";

Ta có thể không cần mô tả đủ số lượng các giá trị trong danh sách. Khi đó, chỉ

những phần tử đầu tiên được gán giá trị, các phần tử còn lại sẽ lấy giá trị ngẫu nhiên.

Cũng có thể không cần mô tả số phần tử của biến mảng. Khi đó chương trình dịch tự động xác định dựa vào danh sách giá trị mô tả kèm theo. Chẳng hạn:int t[] = {1, 10, 20};tương ứng khai báo một mảng có số phần tử là 3.

2. CON TRỎ

2.1 Khái niệm và khai báo con trỏMột con trỏ là môt biến/hằng có giá trị là địa chỉ của một đối tượng khác; đối

tượng ở đây có thể là một biến hoặc một hàm mà khômg thể là một hằng. Việc sử dụng các biến con trỏ sẽ cho phép ta tham chiến đến một biến thông qua địa chỉ của nó. Khả năng đó cộng với khả năng tính toán linh hoạt trên các con trỏ của C khiến con trỏ trở thành một công cụ rất mạnh để thực hiện nhiều thao tác mà thiếu nó thì không thể làm được hoặc làm rất khó khăn .

Khai báo một biến con trỏ theo mẫu sau : <type> *<ptr_name>;

trong đó : type là kiểu dữ liệu của biến mà con trỏ chứa địa chỉ . ptr_name là tên của biến trỏ.

Nói cách khác type* chính là một kiểu dữ liệu con trỏ. Với câu lệnh khai báo trên, biến trỏ chưa chỉ đến địa chỉ nào cả và nó có giá trị bằng NULL.

NULL là một hằng số biểu thị giá trị của con trỏ khi chưa được gán địa chỉ nào.

Giống như khai báo biến, có thể khai báo nhiều biến trỏ liên tiếp nhau:int *pi, *qi;float *pf, *qf;char *pc, *qc;

Kích thước của biến con trỏ phụ thuộc vào cách biểu diễn địa chỉ bộ nhớ trong máy tính. Thường thì người ta sử dụng hai thanh ghi 16 bit để biểu thị địa chỉ bộ nhớ, do đó, kích thước biến con trỏ là 2 byte.

Khoa Tin học - 46 -

Page 47: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Ngôn ngữ C quy định tương ứng với mỗi kiểu dữ liệu sẽ có một kiểu biến con trỏ riêng biệt. Như vậy chúng ta sẽ có con trỏ kiểu nguyên int* để chứa địa chỉ của biến nguyên, con trỏ thực float* hay double* chứa địa chỉ của các biến thực, con trỏ kí tự char*,… Giống như biến nguyên và biến thực, giữa các biến con trỏ khác kiểu nhau cũng có một số điểm khác biệt, chẳng hạn, không thể gán giá trị một con trỏ nguyên cho con trỏ thực mà không thực hiện phép chuyển kiểu.

2.2 Toán tử & và *Xét các khai báo biến sau:

int m,n,p;float x,y,z;char ch1, ch2;

Toán tử một ngôi & cho ta địa chỉ của một biến, còn biến con trỏ có giá trị là địa chỉ của một biến. Như vậy lệnh:pi = &n; là hợp lệ vì nó gán địa chỉ của biến nguyên n cho con trỏ nguyên pi. Khi đó ta nói rằng pi “chỉ đến” hoặc “trỏ đến” n.

Khi pi trỏ đến n, ta sẽ có hai sách để mô tả giá trị của n, hoặc trực tiếp bằng tên của biến hoặc bằng cách áp dụng toán tử một ngôi * lên biến con trỏ. Nói cách khác: *pi tương đương với n. Như vậy, *pi=3; có tác dụng gán giá trị 3 cho biến n. Còn *pi = *pi + 10; thì thêm 10 vào n.

Các toán tử một ngôi * và & có độ ưu tiên cao hơn các toán tử số học. Vì vậy, phép gán: m = *pi +10; lấy giá trị của biến nguyên n được trỏ bởi pi, cộng thêm 10 và ghi kết quả trong m, còn: *pi+=1; tăng giá trị của đối tượng được trỏ bởi pi thêm 1. Phép toán này hoàn toàn tương đương với: ++*pi; (*pi)++; Chú ý: Dấu ngoặc ( ) cần phải có trong ví dụ cuối cùng; nếu không có, chúng ta tăng giá trị của pi chứ không phải giá trị của đối tượng mà pi trỏ tới. Điều này là do các toán tử * và ++ có cùng mức ưu tiên và được thực hiện từ phải sang trái.

2.3 Các phép toán trên con trỏ2.3.1 Phép gán trên hai con trỏ cùng kiểuChỉ có thể gán địa chỉ một biến nguyên cho một biến trỏ nguyên. Cũng vậy,

không thể gán giá trị của một con trỏ nguyên cho một con trỏ thực. Theo khai báo ở phần 2.1 và 2.2 thì các lệnh sau:pi = &n;qi = pi; sẽ cho phép cả pi và qi xác định cùng một biến (biến n). Như vậy, *pi, *qi, và n là một.Các lệnh sau đây là không hợp lệ:

Khoa Tin học - 47 -

Page 48: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

pf = &m; /*vì pf là biến trỏ thực còn m có kiểu nguyên;*/pf = pi; /*vì hai biến trỏ pf và pi không cùng kiểu.

Tuy nhiên các lệnh sau thì hợp lệ nhờ phép chuyển đổi kiểu được thực hiện trước lệnh gán:pf = (float*)&m;pf = (float*)&pi;

Chương trình sau đây minh hoạ cách dùng toán tử chuyển kiểu bắt buộc để tính byte cao và thấp của một số nguyên.

#include<stdio.h>#include<conio.h>void main(){int n;char *pc;clrscr();printf("\nNhap vao mot so nguyen: ");scanf("%d",&n);pc = (char*)&n; /* pc trỏ đến byte cao của n, pc+1 trỏ đến byte thấp */printf("Byte cao %x, byte thap %x", *pc,*pc++);getch();}

Kết quả sau khi "chạy" chương trình như sau:Nhap vao mot so nguyen: 16Byte cao 0, byte thap 10

2.3.2 Phép cộng con trỏ với số nguyênTrong phần 2.2 chúng ta biết rằng m, n, p là ba biến nguyên khai báo liên tiếp

nhau. Khi đó, m,n,p sẽ tương ứng với ba vùng nhớ 2 byte liên tiếp nhau. Chúng ta cũng biết rằng pi chỉ đến n.

Phép cộng con trỏ pi+1 sẽ cho ta địa chỉ của số nguyên m đi ngay trước n, còn pi+(-1) sẽ cho ta địa chỉ của số nguyên đi liền sau n. Điều này được minh hoạ trong chương trình contro1.c sau đây:

#include<stdio.h>#include<conio.h>void main(){int m=2,n=100, p=200;int *pi= &n;clrscr();printf("\nm=%d n=%d p=%d", m, n, p);printf("\n&n=%p &m=%p &p=%p", &n, &m, &p);

Khoa Tin học - 48 -

Page 49: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

printf("\npi =%p pi+1=%p pi-1=%p", pi, pi+1, pi-1);printf("\n*pi = %d", *pi);printf("\n*(pi+1)= %d",*(pi+1));printf("\n*(pi-1)= %d",*(pi-1));getch();}

Kết quả sau "chạy" chương trình như sau:m=2 n=100 p=200&n=FFF2 &m=FFF4 &p=FFF0pi =FFF2 pi+1=FFF4 pi-1=FFF0*pi = 100*(pi+1)= 2*(pi-1)= 200

Việc cộng trừ một con trỏ với một số nguyên có ý nghĩa trực quan như sau: nếu ta so sánh con trỏ với địa chỉ các ngôi nhà trên một con đường, mỗi ngôi nhà là một biến có địa chỉ nào đó thì các địa chỉ này được đánh số tăng dần theo một chiều nhất định.Biết địa chỉ của nhà này, ta có thể tính địa chỉ của nhà bên cạnh bằng cách cộng thêm 1 hay trừ đi 1 vào địa chỉ của căn nhà đã biết và cứ thế tiếp tục. Điều đó có nghĩa là khi cộng hoặc trừ con trỏ với một số nguyên n, ta sẽ được một địa chỉ mới chỉ đến một biến khác nằm cách biến trước đó n vị trí.

2.3.3 Phép trừ hai con trỏ cùng kiểuDo phép cộng một con trỏ với một số nguyên cho ta một con trỏ cùng kiểu,

nên phép trừ hai con trỏ cùng kiểu được coi là hợp lệ và cho kết quả là một số nguyên biểu thị “khoảng cách” giữa các biến con trỏ. Chẳng hạn, nếu:pi = &n;qi = &m;Thì hiệu:qi – pi có giá trị bằng 1.

Tuy vậy, không có phép cộng, nhân, chia hai con trỏ.

2.3.4 Các phép toán quan hệ trên các con trỏ cùng kiểu Có thể so sánh các con trỏ cùng kiểu, tiêu chuẩn so sánh dựa trên vị trí ô nhớ

tương ứng với hai con trỏ. Hằng con trỏ NULL luôn khác với mọi địa chỉ bộ nhớ và có thể đem so sánh với mọi biến con trỏ khác kiểu nhau.

Với hai con trỏ khác kiểu nhau, nếu muốn so sánh với nhau phải thực hiện phép chuyển đổi kiểu.

2.4 Con trỏ void*2.4.1 Con trỏ kiểu void

Khoa Tin học - 49 -

Page 50: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Trong ngôn ngữ C, kiểu dữ liệu void là kiểu dữ liệu rỗng (empty type). Một biến kiểu void không tương ứng với bất kì vùng nhớ nào.

2.4.2 Con trỏ kiểu void*Con trỏ kiểu void* có thể nhận địa chỉ của bất kì vùng nhớ nào dù đó là

vùng nhớ tương ứng với biến nguyên, biến thực… Các lệnh sau đây là hợp lệ:void * px, *py;int x = 1;float y = 0.1;px = &x;py = &y;

Không thể thực hiện các phép tính số học trên con trỏ kiểu void*. Điều này có thể giải thích được nhờ bản chất của các phép toán.

2.5 Con trỏ đa cấpBản thân biến con trỏ cũng có địa chỉ, do đó có thể chứa địa chỉ của nó trong

một đối tượng khác. Người ta gọi những đối tượng có giá trị là địa chỉ của một biến con trỏ là con trỏ đa cấp. Trong khai báo biến trỏ đa cấp, người ta sử dụng một số dấu * trước tên biến trỏ. Số lượng dấu * xác định cấp của con trỏ.Ví dụ: int *pi;int **ppi = &pi;

3. LIÊN HỆ GIỮA CON TRỎ VÀ MẢNG

3.1 Con trỏ và mảng một chiềuXét lệnh:

int a[10];Khai báo này sẽ xin cấp phát một vùng nhớ cho 10 số nguyên a[0], a[1], …, a[9] có địa chỉ xác định bởi a. Vậy chính a là địa chỉ của biến nguyên a[0], do vậy nếu pi là một con trỏ nguyên thì phép gán sau đây là hợp lệ:pi = a;

Sau phép gán này chúng ta có thể sử dụng pi như một tên mảng, dĩ nhiên ta có thể thực hiện các phép toán số học trên con trỏ pi. Quả thực:pi[0], pi[1], …, pi[9]tương ứng vớia[0], a[1], …, a[9] còn , a+1, a+2, …, a+9 tương ứng là địa chỉ của a[0], a[1], …, a[9].

Nói cách khác, có một liên hệ mật thiết giữa việc đánh chỉ số trên mảng với tính toán trên con trỏ.Tuy vậy, vẫn có một số điểm khác nhau giữa tên mảng và biến trỏ cùng kiểu. Tên mảng là hằng địa chỉ còn biến con trỏ là biến. Không thể thực hiện các phép toán

Khoa Tin học - 50 -

Page 51: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

tự tăng giá trị lên tên biến mảng. Cũng không thể thực hiện được phép gán trong đó tên biến mảng đứng một mình bên trái toán tử gán, chẳng hạn:int b[10];thì b = a; /* a là tên mảng khai báo ở phần trên*/là sai vì không thể gán giá trị cho một hằng.

3.1 Con trỏ đa cấp và mảng nhiều chiềuCon trỏ một cấp và mảng một chiều có nhiều điểm tương đồng. Mảng hai chiều

là mảng một chiều của các mảng một chiều. Con trỏ hai cấp là con trỏ chỉ đến con trỏ một cấp. Vì vậy có thể suy ra rằng giữa con trỏ hai chiều và mảng hai chiều cũng có nhiều điểm tương đồng. Suy luận này cũng có thể mở rộng cho các mảng và con trỏ nhiều mức hơn. Chẳng hạn với các khai báo:int **ppi;int a2[10][10]; ta có thể thực hiện phép gán:ppi = a2; và cũng có thể sử dụng ppi như tên một biến mảng.

3.1 Mảng các con trỏKhi kiểu phần tử của một biến mảng là kiểu con trỏ thì ta sẽ có một mảng các

con trỏ. Nếu các con trỏ thành phần trong mảng chỉ đến các biến nào đó, thì như vậy, chúng ta có thể truy xuất được các biến này thông qua một mảng mà không cần đến vị trí thực sự của các biến đó có liên tiếp hay không. Điều đó khiến cho chúng ta dường như đã có được một mảng đặc biệt gồm các biến mà vị trí thực sự của chúng trong bộ nhớ là bất kì. Và bằng việc đổi chỗ các con trỏ thành phần này chúng ta có thể đổi thứ tự sắp xếp của các biến trong mảng này mà không cần thay đổi thực sự vị trí của chúng. Chương trình array2.c sau đây đưa ra một cách sắp xếp các số mà không tác động lên bản thân các phần tử được sắp xếp.

#include<stdio.h>#include<conio.h>void main(){int i,j,*x;int a = 12, b = 2, c = 6, d = 10, e = 0,f = 8;int *ptr_array[6];clrscr();/*Gán các thành phần cho mảng */ptr_array[0] = &a;ptr_array[1] = &b;ptr_array[2] = &c;ptr_array[3] = &d;ptr_array[4] = &e;

Khoa Tin học - 51 -

Page 52: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

ptr_array[5] = &f;/* Sắp xếp lại dãy số theo thứ tự tăng dần*/for (i=0; i<5; i++)

for (j=i+1; j<6; j++){if(*ptr_array[i]>*ptr_array[j])

{x = ptr_array[i];ptr_array[i] = ptr_array[j];ptr_array[j] = x;}

}/* In ra kết quả*/for (i=0; i<6; i++)

printf("%3d",*ptr_array[i]);getch();}

Sau khi thực hiện, chương trình cho kết quả:0 2 6 8 10 12

Nếu các phần tử thành phần của mảng con trỏ lại được gán địa chỉ của các mảng khác thì ta sẽ được một mảng của các mảng. Không giống như các mảng hai chiều, các mảng con của chúng ta có thể nằm ở vị trí bất kì. Vì ta chỉ lưu địa chỉ của chúng nên việc sắp xếp lại thứ tự của các mảng này so với nhau thực chất chỉ là việc sắp xếp lại các địa chỉ của chúng trong mảng các con trỏ của chúng ta mà thôi. Chương trình array3.c sau đây in ra các hàng của ma trận theo thứ tự tăng dần của tổng giá trị trên từng hàng.

#include<stdio.h>#include<conio.h>void main(){int a[10][10];int *pa[10];int s[10];int *ps[10];int i, j, m, n, temp;clrscr();printf("Nhap hai kich thuoc mang: "); scanf("%d%d", &m, &n);for (i=0; i<m; i++)

{pa[i] = a[i];

Khoa Tin học - 52 -

Page 53: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

ps[i]=s+i;}

for (i=0; i<m; i++)for (j=0; j<n; j++)

{printf("a[%d][%d] = ", i,j);scanf("%d", &temp);a[i][j] = temp;}

for (i=0; i<m; i++){s[i] = 0;for (j=0; j<n; j++)

s[i]+=a[i][j];}

for (i=0; i<m-1; i++)for (j=i+1; j<m; j++) if (*ps[i]>*ps[j])

{int *t;t = ps[i];ps[i] = ps[j];ps[j] = t;t = pa[i];pa[i] = pa[j];pa[j] = t;}

printf("\nMang a:\n");for (i=0; i<m; i++)

{for (j=0; j<n; j++)printf("%4d", a[i][j]);printf("\n");}

printf("\nCac dong theo thu tu tang tong hang (pa): \n");for (i=0; i<m; i++)

{for (j=0; j<n; j++)printf("%4d", pa[i][j]);printf("\n");}

getch();}

Khoa Tin học - 53 -

Page 54: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Kết quả sau khi thực hiện chương trình:Nhap hai kich thuoc mang: 3 2a[0][0] = 70a[0][1] = 90a[1][0] = 100a[1][1] = 0a[2][0] = 10a[2][1] = 40

Mang a: 70 90 100 0 10 40

Cac dong theo thu tu tang tong hang (pa): 10 40 100 0 70 90

4. CẤP PHÁT ĐỘNGÝ nghĩa của việc cấp phát bộ nhớ động là cho phép chương trình sử dụng vừa

đúng số lượng bộ nhớ mà chương trình cần, vì khi không cần dùng tới nữa thì có thể giải phóng để cho các công việc tiếp theo có thể sử dụng được. Như vậy, cùng một vùng bộ nhớ có thể được sử dụng cho các mục đích khác nhau trong thời gian thực hiện của chương trình.

Vùng nhớ heap được sử dụng cho mục đích cấp phát động các khối nhớ có kích thước thay đối. Có nhiều cấu trúc dữ liệu sử dụng cách cấp phát động, ta có thể liệt kê một vài loại như: cây, các loại danh sách liên kết (DSLK).

4.1 Hàm malloc( )Các tệp tiêu đề liên quan: alloc.h, stdlib.hCú pháp:

malloc(size);Hàm malloc( ) dùng để xin cấp một vùng bộ nhớ có kích thước size byte từ

vùng nhớ heap. Hàm này cho phép chương trình xin cấp phát vùng bộ nhớ đúng với kích thước mà chương trình cần.

Giá trị trả về:Trong trường hợp cấp phát thành công, hàm malloc( ) trả về một con trỏ trỏ tới

khối nhớ mới được cung cấp.Trong trường hợp có lỗi (không đủ bộ nhớ để cấp phát), hàm malloc( ) trả về

trị NULL.

Khoa Tin học - 54 -

Page 55: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Nếu tham số size bằng 0, hàm này trả về trị NULL.Ví dụ:int *pi, *qi;char *pc;double *pd;double **ppd;int i;/* cấp phát vùng nhớ cho một số nguyên*/qi = (int*)malloc(sizeof(int));/* cấp phát vùng nhớ cho một mảng 12 số nguyên - mảng động*/pi = (int*)malloc(12*sizeof(int));/* cấp phát vùng nhớ cho xâu kí tự*/pc = (char*)malloc(20);pd = (double*)malloc(10*sizeof(double));/* cấp phát vùng nhớ cho ma trận*/ppd = (double**) malloc(10*sizeof(double*));for (i = 0; i<0; i++)

ppd[i] = (double*)malloc(10*sizeof(double));

Chúng ta phải sử dụng phép chuyển kiểu đối với giá trị trả về của malloc( ) để đảm bảo các yêu cầu của phép gán.

4.2. Hàm calloc( )Các tệp tin tiêu đề liên quan: alloc.h, stdlib.hCú pháp

calloc(nitems,size);

Hàm calloc( ) xin cấp một vùng bộ nhớ kích thước (nitems*size) byte và xoá trắng vùng nhớ này. Muốn xin cấp phát vùng nhớ có kích thước lớn hơn 64Kb, phải sử dụng hàm farcalloc( ) (cấp phát xa).

Giá trị trả về:Trong trường hợp cấp phát thành công, hàm calloc( ) trả về một con trỏ trỏ tới

khối nhớ mới được cấp phát.Trong trường hợp có lỗi (không đủ bộ nhớ để cấp phát hoặc một trong hai tham

số bằng 0), hàm malloc( ) trả về NULL.Ví dụ: int *pi;double *pd;double **ppd;int i;/* cấp phát vùng nhớ cho một mảng 12 số nguyên - mảng động*/pi = (int*)calloc(12,sizeof(int));

Khoa Tin học - 55 -

Page 56: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

/* cấp phát vùng nhớ cho một mảng 12 số thực- mảng động*/pd = (double*)calloc(12,sizeof(double));/* cấp phát vùng nhớ cho ma trận*/ppd = (double**)calloc(10,sizeof(double*));for (i = 0; i<0; i++)

ppd[i] = (double*)calloc(10,sizeof(double));

4.3. Hàm free()Các tệp tin tiêu đề liên quan: alloc.h, stdlib.hCú pháp

free(address);

Hàm free( ) giải phóng một khối nhớ đã được cấp phát trước đó bằng hàm malloc( ), calloc( ) hoặc realloc( ). Địa chỉ vùng nhớ được giải phóng được truyền làm tham số của hàm free( ).Ví dụ: free(qi);free(pi);free(pc);free(pd);for(i=1; i<10; i++) free(ppd[i]);free(ppd);

4.4. Ví dụChương trình free1.c sau minh hoạ cách sử dụng các hàm cấp phát động để tạo

mảng động. Bạn đọc có thể xem thêm các chương trình trong phần trợ giúp của các chương trình dịch C.

#include<stdio.h>#include<conio.h>#include<stdlib.h>#include<alloc.h>void main()

{double **ppd;int m,n;int i,j;double temp;clrscr();printf("Kich thuoc ma tran: "); scanf("%d%d", &m, &n);ppd = (double**)calloc(m,sizeof(double*));for (i=0; i<m; i++)

Khoa Tin học - 56 -

Page 57: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

ppd[i] = (double*)calloc(n, sizeof(double));for (i=0; i<m; i++)

for (j=0; j<n; j++){printf("ppd[%d][%d] = ", i, j);scanf("%lf", &temp);ppd[i][j] = temp;}

for (i=0; i<m; i++){for (j=0; j<n; j++)

printf("%6.2f",ppd[i][j]);printf("\n");

}for (i=0; i<m; i++) free(ppd[i]);free(ppd);getch();}

Kết quả sau khi thực hiện chương trình:Kich thuoc ma tran: 3 2ppd[0][0] = 10ppd[0][1] = 20ppd[1][0] = 30ppd[1][1] = 40ppd[2][0] = 0ppd[2][1] = 1 10.00 20.00 30.00 40.00 0.00 1.00

5. TÓM TẮT

Có thể lưu một danh sách các giá trị trong mảng. Một mảng là một nhóm các phần tử có cùng kiểu giá trị được lưu trữ kế tiếp nhau trong bộ nhớ. Truy nhập đến các thành phần mảng bằng tên biến mảng và chỉ số. Chỉ số các phần tử tính từ 0.

Chỉ số của phần tử mảng có thể là giá trị của một biến nguyên, một hằng nguyên hay một biểu thức có giá trị nguyên.

Một mảng kí tự có thể sử dụng để lưu trữ xâu kí tự. Xâu kí tự là một mảng đặc biệt chứa các kí tự '\0' ở cuối xâu.

Các thành phần mảng có thể được khởi tạo theo ba cách: trong khi khai báo, bằng các câu lệnh gán, bằng các câu lệnh nhập dữ liệu.

Ngôn ngữ C không tự động kiểm tra sự quá giới hạn kích thước của mảng.

Khoa Tin học - 57 -

Page 58: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Một xâu có thể được nhập bằng scanf( ) với mô tả %s hoặc bằng hàm gets( ) và có thể được hiển thị bởi hàm printf( ) với định dạng %s hoặc puts( ).

Con trỏ là biến chứa địa chỉ của các biến khác.Các con trỏ cần phải được khai báo trước khi sử dụng. Tương ứng với các kiểu

dữ liệu khác nhau, có các loại biến con trỏ khác nhau.Có ba giá trị có thể sử dụng để khởi tạo cho một biến coc trỏ: 0, NULL hoặc

một địa chỉ. Khởi tạo một con trỏ bằng 0 đồng nghĩa với khởi tạo NULL cho con trỏ.

Chỉ có một số nguyên có thể gán được cho con trỏ đó là 0.Toán tử & trả về địa chỉ của toán hạng của nó. Toán hạng ở đây là một biến

nào đó và không phải là biến kiểu thanh ghi (register).Toán tử * tham chiếu nội dung của một của đối tượng mà toán hạng của * trỏ

tới trong bộ nhớ.Các thao tác số học có thể thực hiện trên con trỏ là ++, -- (trên các con trỏ), +,

-, +=, -=, -=(giữa con trỏ và một số nguyên)Các thao tác số học trên con trỏ chỉ được thực hiện trên một phần bộ nhớ liên

tục chẳng hạn như một mảng.Khi thực hiện các thao tác số học đối với con trỏ trên mảng kí tự, kết quả thu

được giống như đối với các thao tác số học thông thường bởi vì mỗi kí tự được lưu trữ một byte trong bộ nhớ.

Có thể gán giá trị của một con trỏ cho một con trỏ khác cùng kiểu.Một con trỏ void không áp dụng được phép toán *.Các con trỏ có thể được so sánh với nhau theo các toán tử so sánh. Ý nghĩa của

các phép so sánh giống như các phép so sáng thông thường.Một con trỏ có thể được đánh chỉ số như mảng.Tên mảng là một hằng con trỏ chỉ đến phần tử đầu tiên trong mảng (phần tử

thứ tự 0).Độ lệch giữa hai con trỏ cũng giống như khái niệm chỉ số đối với mảng.Có thể khai báo một mảng các con trỏ.(i) không sử dụng mảng con trỏ(ii) có sử dụng mảng các con trỏ.

Khoa Tin học - 58 -

Page 59: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 4

HÀM

1.GIỚI THIỆU

1.1 Tổng quát về chương trình con Hầu hết chương trình máy tính giải quyết các vấn đề trong thực tế đều lớn hơn rất nhiều so với các ví dụ được giới thiệu trong cuốn sách này. Kinh nghiệm cho thấy rằng cách tốt nhất để phát triển và bảo trì các chương trình lớn là xây dựng chúng từ những mảnh nhỏ hơn hay các mô đun. Khi đó việc quản lí sẽ dễ hơn đối với chương trình ban đầu. Các chương trình con cho phép lập trình viên mô đun hoá chương trình. Nói chung, các biến định nghĩa trong chương trình con là các biến cục bộ, chúng chỉ được biết trong bản thân chương trình con tại đó chúng được định nghĩa. Hầu hết các chương trình con đều có các tham số. Các tham số cung cấp thông tin truyền giữa các chương trình con. Các tham số của chương trình con nói chung cũng được xem như các biến cục bộ. Mô đun hoá chương trình có nhiều ưu điểm. Trước tiên,''chia để trị'' giúp cho việc phát triển các ứng dụng dễ dàng hơn. Một ưu điểm nữa là tính sử dụng lại, nghĩa là khả năng dùng các chương trình con có sẵn như các khối cơ bản để xây dựng lên các chương trình mới. Ưu điểm thứ ba là tránh việc lặp đi lặp lại các đoạn mã trong chương trình. Việc gói các đoạn mã vào trong chương trình cho phép cả đoạn mã đó thực hiện ở nhiều nơi trong chương trình chỉ với một lời gọi hàm đơn giản. Chương này mô tả các đặc tính của ngôn ngữ lập trình C trong quá trình thiết kế, cài đặt, hoạt động và bảo trì các chương trình lớn.

1.2 Các mô đun chương trình trong C Các mô đun trong C được gọi là hàm. Các chương trình C nói chung được viết

bằng cách kết hợp các hàm mà lập trình viên tự viết với các hàm chuẩn trong thư viện của C. Chúng ta sẽ bàn đến cả hai loại trên trong chương này. Các hàm chuẩn trong thư viện của C cung cấp rất nhiều các thao tác tính toán, thao tác trên xâu kí tự, thao tác trên kí tự, vào/ra và nhiều thao tác thông dụng khác. Chúng làm cho công việc của các lập trình viên dễ dàng hơn vì các hàm này đưa ra rất nhiều khả năng cho các lập trình viên. Mặc dù các hàm trong thư viện chuẩn không là một phần trong ngôn ngữ lập trình C theo chuẩn ANSI C, nhưng các hàm này không thay đổi trong các chương dịch C khác nhau. Các hàm printf ( ) hay scanf( ) … mà chúng ta dùng trong các chương trình trước đây chính là các hàm thư viện chuẩn.

Khoa Tin học - 59 -

Page 60: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Lập trình viên có thể viết các hàm thao tác các công việc xác định dùng tại nhiều nơi trong chương trình. Các hàm loại này đôi khi còn được gọi là hàm người dùng định nghĩa. Các hàm được gọi thực hiện bằng các lời gọi hàm, các lời gọi hàm xác định tên của hàm và cung cấp các thông tin (hay còn gọi là các tham số) theo đúng trình tự khi khai báo hàm đó.

1.3 Giới thiệu thư viện các hàm toán học Thư viện các hàm toán học (khai báo trong tệp tiêu đề math.h) cho phép lập trình viên thực hiện các thao tác toán học cơ bản. Một vài hàm trong thư viện các hàm toán học của C được tổng kết trong bảng 4.1. Chúng ta sẽ sử dụng thư viện các hàm toán học ở đây để giới thiệu các khái niệm về hàm. Trong các phần sau, các hàm khác sẽ được đề cập tới.

Bình thường các hàm được gọi trong chương trình bằng cách viết tên hàm, có tham số hoặc danh sách tham số cách nhau bởi dấu phẩy được đặt trong cặp dấu ngoặc đơn. Ví dụ để tính và in ra căn bậc hai của 900.0 ta viết:printf("%.2f", sqrt(900.0));

Khi câu lệnh trên được thực hiện, hàm căn bậc hai sqrt( ) được gọi để tính căn bậc hai của số 900.0 trong dấu ngoặc. Số 900.0 là tham số của hàm sqrt( ). Tiếp sau câu lệnh sẽ in ra 30.00. Hàm sqrt( ) nhận các tham số kiểu double và trả lại giá trị là số kiểu double.

Các tham số của hàm có thể là các hằng, biến hay các biểu thức, ví dụ nếu cl = 13.0, d = 3.0 thì câu lệnh:printf("%.2f", sqrt(c1+d*4.0));sẽ tính và in ra căn bậc hai của 13.0 + 3.0 * 4.0 = 25.0 là 5.00

Hàm Ý nghĩa Ví dụsqrt(x) căn bậc hai của x sqrt(9.00) bằng 3exp(x) hàm ex exp(1.0) bằng 2.718282log(x) logarith tự nhiên (cơ số e) của x log(2.718282) bằng 1.0

log10(x) logarith thập phân (cơ số 10) của xlog10(1.0) bằng 0.0log10(10.0) bằng 1.0

fabs(x) giá trị tuyệt đối của xnếu x 0 thì fabs(x) bằng xnếu x 0 thì fabs(x) bằng -x

ceil(x)làm tròn thành số nguyên nhỏ nhất lớn hơn x

ceil(9.2) bằng 10.0ceil(-9.8) bằng -9

floor(x)làm tròn thành số nguyên lớn nhất nhỏ hơn x

floor(-9.8) bằng -10.0floor(9.8) bằng 9.0

pow(x,y) x mũ y (xy) pow(2,5) bằng 32fmod(x,y) phần dư của phép chia x cho y fmod(13.657, 2.333) bằng 1.992sin(x) sin của x (x tính theo radian) sin(0.0) bằng 0.0cos(x) cos của x (x tính theo radian) cos(0.0) bằng 1.0

Khoa Tin học - 60 -

Page 61: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

tan(x) tan của x (x tính theo radian) tan(0.0) bằng 0.0Bảng 4.1 Các hàm thường dùng trong thư viện chuẩn các hàm toán học

2. KHAI BÁO VÀ ĐỊNH NGHĨA HÀM

2.1 Một ví dụ Mỗi chương trình đều có một hàm chính main (). Hàm này gọi các hàm khác để thực hiện công việc của chương trình. Ta sẽ xem xét cách viết một hàm. Chương trình ví dụ funcl.c dùng hàm square() để tính bình phương các số nguyên từ 1 đến 10.

#include<stdio.h>#include<conio.h>int square(int);/* khai báo hàm nguyên mẫu */main(){

int i;clrscr();for(i=1;i<=10; i++)

printf("%d ",square(i));getch();

}/* định nghĩa hàm */int square(int x){int y;y = x*x;return y;}

Kết quả khi thực hiện chương trình: 1 4 9 16 25 36 49 64 81 100

Hàm square( ) được gọi thực hiện trong hàm main() trong câu lệnh:printf("%d ",square(i)); Nó nhận một bản sao giá trị của i trong tham số hình thức x, sau đó nó tính x*x rồi kết quả được trả lại cho hàm printf() trong hàm main(), nơi mà hàm square() được gọi, sau cùng printf( ) hiển thị kết quả ra màn hình. Quá trình này lặp đi lặp lại 10 lần trong vòng lặp for. Định nghĩa hàm square( ) cho thấy hàm này nhận một tham số nguyên x. Từ khoá int đặt trước tên hàm cho thấy hàm square( ) trả lại kết quả là hàm số nguyên. Câu lệnh return trong hàm square( ) trả kết quả tính toán được lại cho hàm gọi nó.

Khoa Tin học - 61 -

Page 62: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Dòng int square (int);chính là dòng khai báo hàm (function prototype - hay còn gọi là nguyên mẫu của hàm). Từ khoá int trong ngoặc thông báo cho trình biên dịch biết hàm square( ) nhận một tham số là số nguyên do hàm gọi nó truyền cho. Từ khoá int bên trái tên hàm thông báo cho trình biên dịch biết hàm square( ) trả lại kết quả là một số nguyên do chương trình gọi nó. Chương trình biên dich sẽ tham khảo dòng khai báo hàm để kiểm tra xem các tham số trong các lần gọi hàm square( ) có đúng như mô tả không. Khi tham số truyền cho hàm khác kiểu nguyên hoặc không tự động chuyển sang kiểu nguyên thì chương trình dịch sẽ đưa ra thông báo lỗi. Khai báo hàm sẽ nêu chi tiết hơn trong phần 2.3.

2.2 Tổng quát về định nghĩa hàm Khuôn dạng của phần cài đặt hàm có dạng:

[<kiểu_giá_trị_trả_về>] <tên_hàm>([<danh_sách_tham_số>]){

các khai báo….các câu lệnh

}Trong đó, <tên-hàm> là bất kỳ tên (identifier) hợp lệ nào, <kiểu-giá-trị-trả-về>

là kiểu dữ liệu của kết quả trả lại cho hàm gọi nó. Khác với Pascal, C chỉ không cho phép hàm trả về giá trị là một biến mảng, còn mọi kiểu dữ liệu hợp lệ khác đều có thể sử dụng để mô tả kiểu giá trị trả về cho hàm. Nếu <kiểu-giá-trị-trả-về> là void thì hàm không trả lại giá trị nào cả. Nếu không xác định <kiểu-giá-trị-trả-về> thì trình biên dịnh sẽ ngầm định coi đó là int. Hãy xem một số thí dụ định nghĩa hàm trong bảng 4.2.

Bảng 4.2 Một số ví dụ định nghĩa hàm

Khoa Tin học - 62 -

float fct1(float t[20]){…}

hàm trả về giá trị là một số thực. Tham số truyền cho hàm là một mảng 20 phần tử kiểu thực.

float ftc2(float *t){…}

hàm trả về giá trị là một số thực. Tham số truyền cho hàm là một biến mảng thực hoặc một con trỏ thực.

char ftc3(int a, int b){…}

hàm trả về giá trị là một kí tự. Tham số truyền cho hàm là hai số nguyên.

Page 63: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Các khai báo và các câu lệnh trong cặp dấu { } tạo thành phần thân của hàm. Phần thân của hàm còn gọi là khối. Một khối đơn giản là một câu lệnh ghép có cả phần khai báo trong đó. Các biến có thể khai báo trong bất kì khối nào và các khối có thể lồng nhau. Tuy nhiên, khai báo và cài đặt một hàm lại không được nằm trong một hàm khác.

Có một số cách để trả lại quyền điều khiển tại điểm mà hàm đó được gọi. Nếu hàm không trả lại giá trị thì quyền điều khiển được trả lại khi gặp dấu đóng ngoặc } cuối cùng của hàm hoặc khi thực hiện câu lệnh: return;

Nếu hàm trả lại kết quả thì câu lệnh return biểu_thức;sẽ trả lại trị tính được của biểu thức cho hàm gọi nó.

Chương trình func2.c sau đây sử dụng hàm max( ) do người sử dụng định nghĩa để trả lại số nguyên lớn nhất trong ba số nguyên. Ba số nguyên ban đầu được nhập vào từ bàn phím bằng hàm scanf( ). Sau đó chúng được truyền cho hàm max( ) để xác định số lớn nhất. Giá trị này được trả lại vào biến largest trong hàm main( ) bằng lệnh return trong hàm max( ). Giá trị trong biến largest này sau đó được in ra trong câu lệnh printf.

#include<stdio.h>#include<conio.h>int max(int, int, int);/* hàm nguyên mẫu */void main() /* hàm main*/{int a,b,c,largest;clrscr();printf("Nhap ba so nguyen: "); scanf("%d%d%d",&a, &b, &c);largest = max(a,b,c);printf("So lon nhat la: %d",largest);getch();}/* định nghĩa hàm max*/int max(int x, int y, int z){int max=x;if (y>max) max = y;if (z>max) max = z;return max;}

Kết quả khi thực hiện chương trình: Nhap ba so nguyen: -12 90 0

Khoa Tin học - 63 -

Page 64: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

So lon nhat la: 90

2.3 Hàm nguyên mẫuMộ trong những đặc trưng quan trọng của ANSI C là hàm nguyên mẫu (dạng

hàm). Hàm nguyên mẫu thông báo cho chương trình biên dịch biết kiểu dữ liệu hàm trả lại, số lượng và kiểu và thứ tự các tham số được truyền cho hàm. Chương trình biên dịch dùng hàm nguyên mẫu để kiểm tra các lời gọi hàm. Các phiên bản C trước đây không thực hiện quá trình kiểm tra này nên có thể xảy ra nhiều lỗi, khi thực hiện ứng dụng rất khó phát hiện, chẳng hạn do truyền sai thứ tự các tham số.

Hàm nguyên mẫu của hàm max( ) trong chương trình func2.c là:int max(int, int, int);

Hàm nguyên mẫu trên thông báo rằng max( ) nhận ba tham số kiểu int và trả lại kết quả kiểu int. Chú ý rằng hàm nguyên mẫu giống dòng đầu tiên trong phần định nghĩa hàm nhưng có thể không cần tên các tham số hình thức.

Khi gặp một lời gọi hàm không đúng theo hàm nguyên mẫu, trình biên dịch sẽ thông báo lỗi cú pháp. Lỗi cú pháp cũng xuất hiện khi hàm nguyên mẫu và định nghĩa hàm khác nhau.

Một đặc tính quan trọng của hàm nguyên mẫu là khả năng ép kiểu các tham số, nghĩa là chuyển các tham số truyền vào theo các kiểu thích ứng, ví dụ câu lệnh:printf("%.3f", sqrt(4));sẽ in ra giá trị 2.000. Hàm nguyên mẫu sẽ yêu cầu trình biên dịch chuyển số nguyên 4 thành số double 4.0 trước khi truyền cho hàm sqrt( ).

Nếu không có hàm nguyên mẫu, trình biên dịch sẽ tự tạo ra hàm nguyên mẫu của hàm khi gặp hàm lần đầu tiên thông qua định nghĩa hàm hoặc lời gọi hàm. Khi gặp lời gọi hàm trước tiên, hàm nguyên mẫu tương ứng sẽ có giá trị trả về kiểu nguyên int và không có một ràng buộc gì về danh sách các tham số, khi đó trình biên dịch sẽ không phát hiện được các lỗi về truyền sai tham số và sai kiểu trả về của hàm.

2.4 Các tệp tiêu đề (header file)Mỗi thư viện chuẩn tương ứng có một tệp tiêu đề chứa các khai báo của các

hàm trong thư viện này cùng với các định nghĩa các kiểu dữ liệu khác nhau, các hằng dùng trong các hàm đó. Bảng 4.3 liệt kê một số tệp tiêu đề có thể dùng trong chương trình. Khái niệm macro dùng trong bảng 4.3 sẽ được mô tả chi tiết trong phần " bộ tiền xử lí".

Lập trình viên có thể tạo các tệp tiêu đề riêng của mình. Các tệp tiêu đề này cũng thường có phần mở rộng là .h và có thể sử dụng chỉ thị #include để thêm vào chương trình. Ví dụ tệp tiêu đề square.h có thể đưa vào trong chương trình của ta bằng chỉ thị #include<square.h> ở đầu chương trình. Vấn đề này được nêu rõ ở chương "bộ tiền xử lí".

Tệp *.h Nội dung

Khoa Tin học - 64 -

Page 65: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

asset.h các macro và thông tin trợ giúp lập trình debugctype.h các hàm nguyên mẫu của các hàm kiểm tra một số thuộc tính của

các kí tự, hay chuyển kí tự giữa chữ hoa và chữ thường.errno.h các macro hiển thị các thông tin về lỗifloat.h các giới hạn của số dấu phẩy động của hệ thống.limits.h các giới hạn của hệ thốnglocale.h các hàm và các thông tin liên quan đến việc thay đối ngôn ngữ và

mã nước của chương trình đang chạymath.h các hàm nguyên mẫu của các hàm thư viện toán họcsetjmp.h các hàm nguyên mẫu của các hàm cho phép bỏ qua lời gọi hàm

thông thường và trả quyền điều khiển lại một vị trí khácsignal.h các hàm nguyên mẫu và các macro điều khiển các điều kiện khác

nhau có thể xuất hiện trong quá trình thực hiện chương trình.stdarg.h định nghĩa các macro làm việc với danh sách các tham số đối với

những hàm mà số lượng và kiểu của các tham số là không biết.stddef.h định nghĩa các kiểu chung dùng để thực hiện tính toánstdio.h các hàm nguyên mẫu và các thông tin liên quan của các hàm thư

viện vào/ra chuẩnstdlib.h các hàm nguyên mẫu và các thông tin liên quan của các hàm

chuyển kiểu, cung cấp bộ nhớ, tạo số ngẫu nhiên,…string.h các hàm nguyên mẫu của các hàm xử lí xâu.time.h các hàm nguyên mẫu và kiểu dữ liệu cho thao tác thời gian.… …

Bảng 4.3 Một số tệp tiêu đề của các thư viện chuẩn

3. THAM SỐ TRONG LỜI GỌI HÀM

3.1 Một số khái niệmTham số hình thức: khái niệm này chỉ đến các tham số được khai báo trong

phần danh sách tham số trong định nghĩa hàm. Ta gọi đây là tham số hình thức vì thực tế chúng chỉ đóng vai trò ''người đại diện'' cho các tham số thực trong các lời gọi hàm. Mỗi tham số hình thức sẽ tương ứng đại diện cho một hàm số thực. Kiểu dữ liệu của tham số hình thức sẽ quyết định kiểu giá trị cho tham số thực tương ứng. Chẳng hạn, nếu tham số hình thức được khai báo như là con trỏ thì tham số thực tương ứng phải là địa chỉ của một biến hoặc là tên của biến mảng. Tham số thực (sự) : khái niệm này chỉ đến các thông tin được truyền cho hàm trong các lời gọi hàm. Mỗi tham số thực tương ứng với một tham số hình thức. Liên quan đến việc truyền thông tin cho hàm, ta phân biệt: truyền theo trị và truyền theo tham biến. Khi tham số được truyền theo trị, một bản sao giá trị của tham số thực (có thể là giá trị của biến, hằng hay của một biểu thức) được tạo ra và gán cho các tham số hình thức của hàm. Vì vậy mọi sự thay đổi trong hàm trên bản sao sẽ không ảnh đến giá trị ban đầu của biến (nếu có) nằm trong hàm gọi. Khoa Tin học - 65 -

Page 66: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Khi một tham số được truyền theo tham biến, hàm gọi sẽ truyền trực tiếp tham số đó (bắt buộc phải là biến) cho hàm được gọi. Trong trường hợp này tham số hình thức và tham số thực là một. Như vậy, nếu bên trong hàm được gọi có thay đổi đến tham số hình thức thì những thay đổi đó cũng tác dụng trên các than số thực tương ứng. Điều này đôi khi dẫn đến hiệu ứng phụ. Truyền theo trị được dùng bất cứ khi nào hàm bị gọi không cần thiết thay đổi giá trị biến mà hàm gọi truyền vào tham số cho nó. Điều này giúp tránh được hiệu ứng phụ, lỗi mà các chương trình con thay đổi các biến toàn cục của chương trình gọi nó. Các lỗi này rất khó phát hiện. Truyền theo tham biến chỉ nên dùng khi hàm bị gọi thật sự cần thiết thay đổi giá trị của biến truyền vào cho nó.

3.2 Truyền tham số trong C Trong C, tất cả các tham số được truyền theo trị. Chương trình swap1.c sau đây là một minh chứng cho nhận xét này.

#include<stdio.h>#include<conio.h>void swap(int a, int b);void main(){int x = 10, y = 20;clrscr();printf("Truoc khi goi swap: x = %d y= %d", x, y);swap(x,y);printf("\nSau khi goi swap: x = %d y= %d", x, y);getch();}void swap(int a, int b){int temp;temp = a;a = b;b = temp;}

Kết quả thực hiện chương trình: Truoc khi goi swap: x = 10 y= 20Sau khi goi swap: x = 10 y= 20

3.3 Tham số hình thức của hàm là con trỏ Khi tham số hình thức của hàm là con trỏ thì tham số thực tương ứng phải là một địa chỉ, có thể là địa chỉ của một biến hoặc tên của một biến mảng. Khi truyền

Khoa Tin học - 66 -

Page 67: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

địa chỉ của biến cho hàm người ta có thể tuỳ thích thay đổi giá trị của biến với địa chỉ được truyền. Còn khi truyền tên của mảng thì sẽ có hai cái lợi so với việc khai báo tham số hình thức như một mảng: thứ nhất tiết kiệm được thời gian sao chép dữ liệu, thứ hai có thể thay đổi giá trị của các phần tử trong biến mảng được truyền vào. Sau đây giới thiệu một số chương trình minh hoạ. Chương trình swap2.c sau đây là một phiên bản sửa đổi từ swap1.c trong đó các tham số hình thức trong hàm swap( ) là con trỏ.

#include<stdio.h>#include<conio.h>void swap(int *a, int *b);void main(){int x = 10, y = 20;clrscr();printf("Truoc khi goi swap: x = %d y= %d", x, y);swap(&x,&y);printf("\nSau khi goi swap: x = %d y= %d", x, y);getch();}void swap(int *a, int *b){int *temp;*temp = *a;*a = *b;*b = *temp;}

Kết quả thực hiện chương trình: Truoc khi goi swap: x = 10 y= 20Sau khi goi swap: x = 20 y= 10

Chương trình func3.c sau đây thực hiện các thao tác nhập và in và sắp xếp một dãy số trong đó tên mảng được truyền như một tham số cho hàm.

#include<stdio.h>#include<conio.h>void nhapds(int *d);void inds(int *d);void sapxepds(int *d);void main(){

Khoa Tin học - 67 -

Page 68: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

int a[5];clrscr();printf("\nNhap day so:\n");nhapds(a);printf("\nIn day so: ");inds(a);sapxepds(a);printf("\nDay so sau khi sap xep:\n");inds(a);printf("An phim bat ki de ket thuc...");getch();}void nhapds(int *d){int i;int temp;for (i=0; i<5; i++)

{printf("d[%d] = ", i); scanf("%d",&temp);d[i] = temp;}

}void inds(int *d){int i;for (i=0; i<5; i++)

printf("%4d", d[i]);printf("\n");}void sapxepds(int *d){int i, j;for (i=0; i<4; i++)

for (j=i+1; j<5; j++)if(d[i]>d[j])

{int temp = d[i];d[i] = d[j];d[j] = temp;}

}

Kết quả thực hiện chương trình:

Khoa Tin học - 68 -

Page 69: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Nhap day so:d[0] = 10d[1] = -90d[2] = 2d[3] = 7d[4] = 100

In day so: 10 -90 2 7 100

Day so sau khi sap xep: -90 2 7 10 100An phim bat ki de ket thuc...

3.4 Tham số của hàm main( ) Chúng ta đề cập đến tham số của hàm main( ) khi viết chương trình có tham số dòng lệnh. Trong trường hợp này hàm main( ) sẽ được khai báo với hai tham số hình thức: void main( int argc, char *argv[]) Tham số argc mô tả số lượng các tham số dòng lệnh bao gồm cả tên chương trình. Tham số agrv là một mảng các xâu kí tự tương ứng với các tham số dòng lệnh. Chương trình sau đây minh hoạ cách viết chương trình sử dụng tham số dòng lệnh. Giá trị của agrc và nội dung của agrv được tự động xác định khi thực hiện chương trình.

#include<stdio.h>#include<conio.h>void main(int argc, char *argv[]){int i;clrscr();for (i=0; i<argc;i++)

printf("Tham so thu %d la: %s", i+1, argv[i]);getch();}

Sau khi dịch và tạo ra file d:\tc\main.exe. Tại cửa sổ lệnh nếu ta gõ: d:\tc\main.exe I love you thì chương trình được thực hiện và cho kết quả:Tham so thu 1 la: D:\TC\MAIN.EXETham so thu 2 la: ITham so thu 3 la: loveTham so thu 4 la: you

Khoa Tin học - 69 -

Page 70: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

( là kí hiệu của việc gõ phím enter)

Chú ý: Trong môi trường soan thảo của Borland C hoặc Turbo C ta có thể đưa vào các tham số dòng lệnh cho chương trình nhờ lệnh Run / Arguments…

4.CẤP LƯU TRỮ VÀ PHẠM VI CỦA CÁC ĐỐI TƯỢNG

4.1 Khái niệm Ta biết rằng mỗi đối tượng (biến hoặc hàm) sử dụng trong một chương trình cần phải có các khai báo (và định nghĩa). Các khai báo (và định nghĩa) này có thể được đặt ở ngoài tất cả các hàm hoặc ở bên trong một hàm nào đó (điều này chỉ đúng đối với các biến). Các khai báo đặt ở các vị trí khác nhau làm cho đối tượng được khai báo có các tính chất khác nhau. Sự khác nhau đó thể hiện ở phạm vi sử dụng (scope), thời gian tồn tại (lifetime) và cấp lưu trữ (storage class). Phạm vi của một đối tượng là phần chương trình mà đối tượng còn được nhìn thấy và có thể sử dụng bởi phần chương trình đó. Phạm vi có thể là trong một khối lệnh, một hàm hoặc một tệp nguồn hoặc toàn thể chương trình. Một số đối tượng có thời gian tồn tại rất ngắn, một số thì liên tục được tạo ra và xoá đi, một số khác tồn tại trong suốt thời gian thực hiện chương trình. Sau khi một số đối tượng đã được khai báo (và định nghĩa) máy tính sẽ cung cấp cho biến hay hàm vùng nhớ thường trực hay động trong thời gian thực hiện chương trình. Như vậy, một đối tượng có thể có thời gian tồn tại lâu dài hay tạm thời (so với thời gian thực hiện của chương trình). Cấp lưu trữ của đối tượng được xác định dựa theo vùng bộ nhớ được cấp phát. các đối tượng được cấp phát bộ nhớ thường trực thì có cấp lưu trữ cố định (static). Thuộc loại này bao gồm các hàm và các biến khai báo bên ngoài các hàm. Bên cạnh đó cũng có một số biến cục bộ có thuộc tính này, chúng ta sẽ xem xét vấn đề này sau. Các đối tượng (biến hoặc hàm) có cấp lưu trữ cố định tồn tại ngay khi chương trình bắt đầu thực hiện. Đối với biến, vùng nhớ được cung cấp và được khởi tạo ngay khi bắt đầu chương trình. Đối với hàm, tên của hàm cũng tồn tại ngay khi chương trình bắt đầu thược hiện. Tuy nhiên mặc dù biến và tên hàm tồn tại ngay khi bắt đầu chương trình nhưng không có nghĩa là đối tượng này có thể dùng bất cứ đâu trong chương trình. Các biến có cấp lưu trữ động được tạo ra khi vào khối mà chúng ta được khai báo, chúng tồn tại khi điều khiển còn đang trong khối đó và chúng ta sẽ bị xoá khi quyền điều khiển thoát ra khỏi khối. Phạm vi và thời gian tồn tại và cấp lưu trữ của một đối tượng được xác định bằng cách tổ hợp các từ khoá chỉ định cấp lưu trữ với các khai báo cùng vị trí đặt bên trong hay bên ngoài một hàm hay một khối lênh. Ngôn ngữ C cung cấp 4 từ khoá sau: auto, extern, static và register để chỉ định phạm vi và thời gian tồn tại của các biến. Chúng có thể chia thành hai nhóm theo

Khoa Tin học - 70 -

Page 71: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

thời gian tồn tại hay phạm vi. Các từ khoá auto và register dùng để khai báo các đối tượng có cấp lưu trữ động và phạm vi cục bộ. Các từ khoá extern và statis dùng để khai báo các đối tượng (biến hoặc hàm) có thời gian lưu trữ tĩnh và phạm vi toàn cục. Từ khoá auto Từ khoá auto được dùng để khai báo các biến cục bộ. Tuy nhiên rất ít khi người ta sử dụng nó vì các biến cục bộ đã mặc nhiên được xem là auto. Từ khoá register: Từ khoá register được đặt trước khi khai báo của các biến tự động nhằm yêu cầu chương trình biên dịnh duy trì giá trị biến đó trong thanh ghi của máy tính, do đó nâng cao tốc độ thực hiện (vì truy nhập, thao tác dữ liệu trực tiếp trên thanh ghi máy tính là nhanh nhất). Ví dụ, sau đề nghị trình biên dịch đặt biến counter vào một trong các thanh ghi của máy và khởi tạo nó giá trị ban đầu bằng 1:register int counter = 1; Chương trình biên dịch có thể bỏ qua từ khoá register khi không còn đủ thanh ghi nữa và nói chung chỉ áp dụng cho các biến kiểu int và char. Ngày nay, các khai báo register thường cũng rất ít được dùng vì bộ tối ưu của trình biên dịch thường có khả năng nhận biết được các biến thường xuyên được sử dụng và đặt chúng vào các thanh ghi nếu thấy cần thiết mà không cần thiết có các từ khoá register trong chương trình nguồn. Từ khoá static Biến tĩnh là biến có thời gian tồn tại cùng với chương trình. Biến tĩnh được tạo ra khi chương trình khởi động khởi động chương trình. Chỉ khi nào chương trình kết thúc các biến tĩnh mới được giải phóng khỏi bộ nhớ.

Theo định nghĩa, các biến khai báo bên ngoài các hàm sẽ là biến tĩnh vì tồn tại độc lập với các hàm, có thể sử dụng trong mọi hàm miễn là hàm đó không khai báo các biến cục bộ cùng tên.

Bên cạnh các biến ngoài, có thể khai báo khai báo các biến cục bộ tĩnh bằng cách đặt từ khoá static trước khai báo biến. Cú pháp như sau:static <khai báo biến>;

Ví dụ:{…….static int x; /* x là biến cục bộ tĩnh*/…}

Từ khoá static đi với biến cục bộ thông báo cho trình biên dịch biết cần cung cấp một vùng lưu trữ thường xuyên cho biến cục bộ này. Khác với biến toàn cục, các biến tĩnh cục bộ không được nhận biết ở bên ngoài hàm khai báo chúng nhưng chúng vẫn duy truỳ được giá trị của chúng giữa mỗi lần gọi hàm, đây là điểm khác

Khoa Tin học - 71 -

Page 72: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

với biến cục bộ thông thường. Các biến loại này có ứng dụng trong việc viết những hàm cần phải bảo toàn một số giá trị giữa những lần gọi.

Chương trình func4.c sau đây minh hoạ cách sử dụng biến cục bộ static:

#include<stdio.h>#include<conio.h>void fct();void main(){int i;clrscr();for (i=1; i<10;i++)

fct();getch();}void fct(){static int count = 1;printf("\nLan goi ham thu %d",count++);}

Kết quả thực hiện chương trình: Lan goi ham thu 1Lan goi ham thu 2Lan goi ham thu 3Lan goi ham thu 4Lan goi ham thu 5Lan goi ham thu 6Lan goi ham thu 7Lan goi ham thu 8Lan goi ham thu 9

Tất cả các biến có thời gian lưu trữ tĩnh, ngầm định đều được khởi tạo giá trị bằng 0 (con trỏ thì khởi tạo giá trị NULL)

Trong chương trình func4.c, nếu ta khai báo một biến toàn cục để lưu giá trị của count thì bất kì chương trình nào muốn sử dụng hàm fct( ) cũng phải xem xét việc khai báo biến đó, phải đảm bảo không có sự nhầm lẫn nào với các biến khác được khai báo và rất khó có thể đưa hàm này vào thư viện.

Chú ý rằng tất cả các hàm đều là đối tượng ngoài vì ngôn ngữ C không cho phép một hàm được định nghĩa bên trong một hàm khác.

Từ khoá extern

Khoa Tin học - 72 -

Page 73: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Các biến ngoài chỉ có thể sử dụng được trong các hàm định nghĩa trong tệp nguồn khai báo chúng. Khi phát triển các ứng dụng lớn, hiển nhiên là các chương trình nguồn phải được lưu trữ trên nhiều tệp khác nhau. Bởi vì chương trình có thể do nhiều người cùng lập, đồng thời việc phân nhỏ chương trình nguồn còn làm tăng tốc độ biên dịch. Vậy làm cách nào các hàm ở một tệp này có thể truy nhập tới các biến được khai báo hoặc gọi tới các hàm được định nghĩa ở một tệp khác? Từ khoá extern cho phép sử dụng các biến hàm trên phạm vi nhiều tệp. Nguyên tắc như sau:Chẳng hạn tệp A có chứa khai báo các biến toàn cục sau:int x, y;char ch;Tệp B muốn sử dụng các biến toàn cục ở trên tệp A thì cần khai báo:extern int x, y;extern char ch;

Từ khoá extern thông báo với trình biên dịch rừng tên và kiểu của các biến đặt sau đã được khai báo ở đâu đó rồi. Nói cách khác, extern thông báo với trình biên dịch không cần cấp phát thêm vùng nhớ cho các biến đó một lần nữa. Khi liên kết, trình liên kết sẽ tự giải quyết vấn đề tham khảo tới biến công cộng được phân phối ở ngoài tệp B.

4.2 Bốn kiểu phạm vi trong chương trình CPhạm vi của một đối tượng (biến hay hàm) là phần của chương trình mà đối

tượng đó có thể được tham chiếu tới. Ví dụ khi ta khai báo một biến cục bộ trong một khối, nó chỉ có thể được tham chiếu trong khối đó hoặc các khối lồng trong khối đó thôi. Có bốn loại phạm vi khác nhau: phạm vi trong hàm, phạm vi trong têp, phạm vi trong khối và trong hàm nguyên mẫu.

Các nhãn của chương trình (danh biểu theo sau là dấu hai chấm chư start:) chỉ có phạm vi trong hàm. Các nhãn được dùng trong lệnh switch có thể được dùng bất cứ ở đâu trong thân hàm nhưng không thể bị tham chiếu bởi câu lệnh ở ngoài thâm hàm đó.

Một đối tượng khai báo ở ngoài các hàm thì có phạm vi trong tệp đó. Các biến toàn cục, định nghãi hàm và cá hàm nguyên mẫu được đặt ở ngoài tất cả các hàm đều có phạm vi tệp.

Các danh biểu khai báo trong khối có phạm vi trong khối đó. Phạm vi của khối kết thúc khi gặp kí tự đóng ngoặc kết thúc khối }của khối đó. Các biến cục bộ được khai báo đầu hàm có phạm vi khối như các tham số của hàm đó. Bất cứ một khối nào cũng có thể kha báo các biến trong đó. Khi các khối lồng nhau, cac sbiến ở trong một khối chỉ có phạm vi là khối đó càc tất cả các khối lồng trong nó. Các biến tĩnh cục bộ cũng có phạm vi khối, mặc dù chúng tồn tại từ khi chương trình bứt đầu thực hiện. Có thể nói thuộc tính thời gian tồn tại độc lập với thuộc tính phạm vi.

Khoa Tin học - 73 -

Page 74: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Cuối cùng, các danh biểu dùng trong danh sách các tham số của hàm nguyên mẫu chỉ có phạm vi trong hàm nguyên mẫu. Các hàm nguyên mẫu không yêu cầu tên trong danh sách các tham số mà chỉ yêu cầu kiểu, nếu có tên tham số, trình biên dịch sẽ bỏ qua. Vì vậy, các danh biểu dùng trong hàm nguyên mẫu có thể dùng lại ở những nơi khác.

4.3 Tệp projectKhi viết các chương trình lớn người ta thường để các hàm trên nhiều tệp nguồn

khác nhau. Tệp project là hình thức để liên kết các tệp nguồn đó vói nhau trước khi biên dịch. Như vậy có thể hiểu rằng nội dung của một tệp project là danh sách tên các tệp dùng để tạo nên một chương trình ứng dụng. Thông thường các tệp này là chương trình nguồn, tuy nhiên có thể sử dụng các tệp đối tượng (*.OBJ) sinh ra do việc biên dịch các chương trình nguồn *.C hoặc các tệp viết trên ngôn ngữ Assembly. Mặc dù vậy vẫn chỉ có duy nhất một tệp nguồn chứa hàm main( ), các tệp thành phần khác chứa các hàm thư viện tự định nghĩa để sử dụng trong chương trình. Thứ tự các tệp thành phần trong tệp project không quan trọng. Sau đây chúng ta nói về các thao tác trên một tệp project. Mở một tệp project:

Tệp project thường có phần mở rộng là .prj. Để mở một tệp project, thực hiện lệnh Project/Open project…sau đó lựa chọn một tệp project đã có hoặc đăng kí tên cho một tệp project mới. Tại mỗi thời điểm chỉ có thể mở không quá một tệp project. Như vậy nếu đang làm việc với một tệp project việc mở project khác sẽ đóng tệp project trước đó.

Cửa sổ project:Một khi đã đăng kí tên tệp project mới hay mở một tệp project đã có, trên màn

hình xuất hiện thêm một cửa sổ tệp project. Trong cửa sổ này hiện lên các tệp thành phần.

Thêm một tệp vào tệp project:Để thêm một tệp thành phần, chọn cửa sổ project, sau đó thực hiện lệnh

Project/Add item… hay nhấn phím INS (Insert) và thực hiện các chỉ dẫn trên màn hình.

Bỏ một tệp khỏi tệp project.Để loại bỏ một tệp thành phần khỏi tệp project, trước hết chọn cửa sổ project,

sau đó lựa chọn tên tệp cần xoá, cuối cùng thực hiện lệnh Project/Delete item hoặc nhấn phím Del (Delete).

Biên dịch và thực hiện tệp project:Khi một tệp project được mở thì mọi thao tác biên dịch (F9, Alt+F9) và thực

hiện (Ctrl+F9) chỉ liên quan đến các tệp thành phần trong tệp project. Mọi tệp chương trình khác đều không được xem xét. Vì vậy nếu muốn biên dịch và thực hiện một chương trình nguồn đơn lẻ khác thì phải đóng tệp project đang mở.

Đóng tệp project đang mở:Thực hiện lênh: Project/Close project.

Khoa Tin học - 74 -

Page 75: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

5. CON TRỎ HÀM

5.1 Khái niệm Mặc dù một hàm không phải là một biến nhưng nó vẫn chiếm vị trí trong bộ nhớ và ta có thể gán vị trí của nó cho một loại biến con trỏ. Con trỏ này trỏ đến điểm xâm nhập vào hàm. Ta gọi đây là con trỏ hàm. Con trỏ hàm có thể sử dụng thay cho tên hàm và việc sử dụng con trỏ cho phép các hàm cũng được truyền như là tham số cho các hàm khác. Để hiểu được các con trỏ hàm làm việc như thế nào, ta cần hiểu một chút về cách biên dịch và gọi một hàm. Khi biên dịch hàm, trình biên dịch chuyển chương trình nguồn sang dạng mã máy và thiết lập môt điểm xâm nhập vào hàm (chính là vị trí của chỉ thị mã máy đầu tiên của hàm). Khi có lời gọi thực hiện hàm, máy tính sẽ thực hiện một chỉ thị call chuyển điều khiển đến điểm xâm nhập này. Trong trường hợp gọi hàm bằng tên hàm thì điểm xâm nhập này là trị tức thời (gần như là một hằng và không chứa trong biến nào cả), cách gọi hàm này là cách gọi hàm trực tiếp. Trái lại, khi gọi hàm gián tiếp thông qua một biến trỏ thì biến trỏ đó phải trỏ tới chỉ thị mã máy đầu tiên của hàm đó. Cách gọi hàm thông qua biến trỏ hàm gọi là cách gọi hàm gián tiếp.

5.2 Khai báo con trỏ hàmCú pháp khai báo:

[<kiểu_giá_trị>](*tên_biến_con_trỏ_hàm>)([<danh_sách_tham_sô>]);

Ý nghĩa của các thành phần trong khai báo con trỏ hàm giống như đối với con trỏ hàm.

Một con trỏ hàm chỉ có thể nhậm giá trị là tên của các hàm có cùng kiểu giá trị trả về kiểu giá trị của các tham số. Chẳng hạn con trỏ hàm: int fptr1(int);chỉ có thể lấy giá trị của các hàm một tham số kiểu int và có giá trị trả về cũng là kiểu nguyên int.trong khi con trỏ hàm:void fptr2(int,float);thì có thể nhận giá trị là tên các hàm có kiểu giá trị trả về là void và có hai tham số, trong đó tham số thứ nhất có kiểu int và tham số thứ hai có kiểu float.

5.3 Tham số hình thức của hàm là con trỏ hàmNhư đã đề cập ở phần 2.2, C không hạn chế về kiểu giá trị của tham số hình

thức. Vì vậy, có thể kha báo tham số hình thức của hàm là một con trỏ hàm. Khi đó trong lời gọi hàm chúng ta có thể sử dụng tên một hàm khác làm tham số thực. Chương trình func5.c sau đây thực hiện việc sắp xếp dãy số theo nhiều tiêu chuẩn khác nhau:

Khoa Tin học - 75 -

Page 76: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

(i) sắp xếp tăng dần theo giá trị.(ii) sắp xếp tăng dần theo trị tuyệt đốimà chỉ dùng một hàm sắp xếp.

#include<stdio.h>#include<conio.h>#include<math.h>void nhapds(int *d);void inds(int *);void sapxep(int *, int (*cmp)(int, int));int tanggt(int, int);int tangtd(int, int);void main(){int a[5];clrscr();printf("\nNhap vao day so nguyen:\n");nhapds(a);printf("\nDay so vua nhap: ");inds(a);sapxep(a,tanggt);printf("\nDay so sau khi sap xep tang:");inds(a);sapxep(a,tangtd);printf("\nDay so sau khi sap xep tang theo gia tri tuyet doi:\n");inds(a);getch();}void nhapds(int *d){int i;int temp;for (i=0; i<5; i++)

{printf("d[%d] = ", i); scanf("%d",&temp);d[i] = temp;}

}void inds(int *d){int i;for (i=0; i<5; i++)

printf("%4d", d[i]);

Khoa Tin học - 76 -

Page 77: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

printf("\n");}void sapxep(int *d, int (*cmp)(int,int)){int i, j;for (i=0; i<4; i++)

for (j=i+1; j<5; j++)if(cmp(d[i],d[j]))

{int temp = d[i];d[i] = d[j];d[j] = temp;}

}int tanggt(int a, int b){return a>b?1:0;}int tangtd(int a, int b){return abs(a)>abs(b)?1:0;}

Kết quả thực hiện chương trình: Nhap vao day so nguyen:d[0] = -12d[1] = 100d[2] = -70d[3] = 0d[4] = 56

Day so vua nhap: -12 100 -70 0 56Day so sau khi sap xep tang: -70 -12 0 56 100Day so sau khi sap xep tang theo gia tri tuyet doi: 0 -12 56 -70 100

Chú ý: (i) Các hàm có thể trỏ thành đối tượng được gọi trong hàm khác nên được khai báo trước.(ii) Khi gọi một hàm bằng con trỏ hàm f cần phải có dấu ngoặc, hàm được gọi theo kiểu f(danh sách các tham số nếu có).

Khoa Tin học - 77 -

Page 78: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

(iii) Không có sự khác biệt nào giữa việc gọi bằng tên của hàm và gọi bằng con trỏ hàm.

6. ĐỆ QUYCác chương trình đã xét đều có chung cấu trúc phân cấp giữa hàm gọi và hàm

bị gọi. Tuy nhiên trong một số bài toán, việc dùng hàm gọi ngay chính nó rất hữu dụng. Có thể định nghĩa hàm đệ quy là hàm sẽ gọi đến chính nó trực tiếp hay gián tiếp thông qua các hàm khác. Vấn đề đệ quy là một vấn đề phức tạp, vì vậy trong phần này chỉ giới thiệu một số khía cạnh cùng với những ví dụ đơn giản nhất của vấn đề đệ quy. Trước tiên ta xem xét khái niệm đệ quy, sau đó kiểm tra trên một vài chương trình có chứa các hàm đệ quy. Cách tiến hành giải một bài toán đệ quy nhìn chung có những điểm chung sau đây:

Hàm đệ quy thực ra chỉ giải bài toán trong trường hợp đơn giản nhất hay còn gọi là trường hợp cơ sở. Khi hàm đệ quy được gọi trong các trường hợp cơ sở, hàm chỉ cần trả lại kết quả. Nếu hàm được gọi trong các trường hợp phức tạp hơn, hàm đệ quy sẽ chia công việc cần giải quyết thành hai phần. Một phần hàm biết cách giải quyết như thế nào, còn phần kia vẫn không biết cách giải quyết như thế nào tuy nhiên để được gọi là có khả năng đệ quy, phần sau phải giống với bài toán ban đầu nhung đơn giản hơn hay nhỏ hơn bài toán ban đầu. Bởi vì bài toán mới giống với bài toán ban đầu nên hàm sẽ thực hiện gọi chính nó để giải quyết công việc đơn giản hơn này - đây chính là lời gọi đệ quy hay còn gọi là một bước đệ quy. Để đảm bảo việc đệ quy có kết thúc, mỗi một lần gọi đệ quy thì bài toán phải đảm bảo bảo đơn giản hơn và các bước đệ quy này còn thực hiện liên tiếp cho đến khi nào bài toán đơn giản dần, đơn giản tới mức trở trành trường hợp cơ sở. Ta nhận thấy hàm đệ quy xử lí trường hợp cơ sở để trả lại kết quả tính được cho các hàm phức tạp hơn nữa…cứ như vậy cho đến lời gọi hàm ban đầu. Trước khi đưa ra các nhận xét tiếp, ta xem xét ví dụ tính n giai thừa (n!) sau đây: Theo định nghĩa n! bằng tất cả các số tự nhiên từ 1 đến n: n! = n * (n-1) … 2 * 1Ví dụ 5! = 5 * 4 * 3 * 2 * 1 = 120, 0! được định nghĩa bằng 1.

Để tính n! có thể dùng vòng lặp như sau:kq = 1;for (i=n; i>=1; i--)

kq*= i;

Tuy nhiên cũng có thể đinh nghĩa đệ quy hàm giai thừa như sau: nếu n>0 n! = n * (n-1)! nếu n=0 n! = 0! = 1 Khi đó để tính 3! quá trình tính phải lần ngược trở về tính 0!, sau đó lấy kết quả tính 1!, lại lấy kết quả để tính 2! và cuối cùng mới tính được 3! : 3! = 3*(2!) = 3*(2*(1!) = 3*(2*(1*(0!))) 3! = 3*(2*(1*1))) = 3*(2*1)= 3*2 =6

Khoa Tin học - 78 -

Page 79: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Sau đây là chương trình tính n giai thừa sử dụng hàm đệ quy:

#include<stdio.h>#include<conio.h>long gt(long);void main(){int i;clrscr();for(i=0; i<10; i=i+3)

printf("%d! = %ld\n",i, gt(i));getch();}long gt(long num){if (num == 0)

return 1;else return (num*gt(num-1));}

Kết quả thực hiện chương trình: 0! = 13! = 66! = 7209! = 362880

Ví dụ thứ hai dùng hệ qui là tính dãy số Fibonaci. Dãy số Fibonaci gồm những số: 0 , 1, 1, 2 , 3 , 5 , 8, 13, 21,…bắt dầu từ hai số 0 và 1, tiếp sau đó các số Fibonaci sau bằng tổng của hai số Fibonaci đứng trước nó. Dãy Fibonaci gặp rất nhiều trong thực tế, đặc biệt khi mô tả các mô hình xoắn ốc. Tỉ số giữa hai số Fibonaci liên tiếp khoảng 1,618…Số này gặp rất nhiều trong thực tế và được gọi là tỉ số vàng. Các kiến trúc sư thường thiết kế kích thước cửa sổ, phòng, nhà ở có tỉ lệ chiều dài và chiều rộng bằng tỉ số vàng. Các bưu thiếp cũng thường có tỉ lệ giữa chiều dài và chiều rộng bằng tỉ số vàng. Dẫy Fibonaci có thể định nghĩa đệ quy như sau: fibonaci(0) = 0; fibonaci(1) = 1; fibonaci(n) = fibonaci(n-1) + fibonaci(n-2) n>1 Chương trình trong bài tập mẫu sau đây tính đệ quy số Fibonaci thứ i bằng cách dùng hàm fibonaci( ). Chú ý rằng dãy số Fibonaci tăng rất nhanh, vì vậy chúng ta chọn kiểu long cho tham số và giá trị trả về của hàm fibonaci( ).

Khoa Tin học - 79 -

Page 80: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

#include<stdio.h>#include<conio.h>long fibo(long);void main(){long result, number;clrscr();printf("\n Hay nap vao mot so nguyen: ");scanf("%ld", &number);result = fibo(number);printf("Fibonaci thu %ld la: %ld \n", number, result);getch();}long fibo(long n){if (n == 0||n == 1)

return n;else

return (fibo(n-1)+fibo(n-2));}Kết quả thực hiện chương trình: Hay nap vao mot so nguyen: 8 Fibonaci thu 8 la: 21

Mỗi lần hàm fibo( ) được gọi, nó kiểm tra ngay lập tức liệu đây có phải là trường hợp cơ sở, n bằng 0 hay 1 hay không. Nếu đúng thì n được trả lại, nếu không, tức là khi n>1, nó sẽ tạo ra hai lời gọi đệ qui đơn giản hơn lời gọi đến hàm fibo ban đầu. So sánh đệ quy và lặp Ta đã xét cách tính giai thừa của một số, có thể tính theo phương pháp lặp (phi đệ qui) hoặc phương pháp đệ qui. Trong phần này ta sẽ so sánh hai phương pháp này và tìm hiểu tại sao trong thực tế thường chọn phương pháp lặp hơn. Lập trình đệ quy sử dụng cấu trúc lựa chọn, còn lặp sử dụng cấu trúc lặp. Cả hai phương pháp đều liên quan đến quá trình lặp. Tuy nhiên, phương pháp lặp sử dụng vòng lặp tường minh, còn phương pháp đệ quy còn được quá trình lặp bằng cách sử dụng liên tục các lời gọi hàm. Cả hai phương đều phải kiểm tra khi nào kết thúc. Phương pháp lặp kết thúc khi điều kiện để tiếp tục vòng lặp sai, còn lời gọi đệ quy kết thúc khi gặp trường hợp cơ sở. Quá trình lặp thực hiện các tác động lên điều kiện lặp nhờ các lệnh trong thân câu lệnh lặp cho đến điều kiện lặp sai, còn lời gọi đệ qui làm cho các lời gọi hàm đơn giản dần cho đến khi đạt tới trường hợp cơ sở. Cả hai phương pháp này đều có thể dẫn đến trường hợp vô hạn không dừng, quá trình lặp sẽ không thoát ra được khi điều kiện lặp không bao giờ sai, còn lời

Khoa Tin học - 80 -

Page 81: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

gọi đệ qui không thoát ra được khi các bước đệ qui không làm cho bài toán đơn giản hơn để cuối cùng đạt tới trường hợp cơ sở.

Tuy nhiên phương pháp đệ quy không hiệu quả do nó liên tự thực hiện các lời gọi hàm, nên tốn thời gian xử lý và không gian nhớ. Mỗi lần gọi hàm, lại cần thêm một bản sao của hàm, tốn thêm bộ nhớ (lưu các biến của hàm, địa chỉ trở về của hàm,…). Vì vậy phương pháp lặp có lẽ được ưu chuộng hơn do khả năng tiết kiệm. Tuy nhiên trong nhiều trường hợp người ta vẫn dùng phương pháp đệ quy vì nó cho phép diễn đạt được tương đối trực quan và dễ hiểu quá trình giải quyết bài toán.

Khoa Tin học - 81 -

Page 82: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 5

CẤU TRÚC1. CẤU TRÚC

1.1 Khái niệm và định nghĩa cấu trúc Trong thực tế lập trình, chúng ta có thể sẽ cần đến những kiểu dữ liệu phức tạp hơn, được tạo thành từ những kiểu dữ liệu đơn giản mà đã biết. Những kiểu dữ liệu này cho ta một khả năng kết hợp một nhóm các biến để thể hiện một đối tượng chung. Chẳng hạn, để lưu trữ những thông tin liên quan đến một đối tượng nhân viên, ta có thể cần đến một biến nào đó có khả năng lưu trữ được cả tên, địa chỉ, ngày sinh lẫn mã số sinh viên, mức lương, v.v…để có thể xử lý biến này như một phần tử thống nhất, thể hiện thông tin của một nhân viên cụ thể. Ngôn ngữ C cho phép chúng ta tự xây dựng những kiểu dữ liệu phức hợp như vậy và sử dụng những kiểu dữ liệu này để khai báo cho các biến sử dụng sau đó. Chúng ta gọi những kiểu dữ liệu như vậy là cấu trúc. Cấu trúc là một kiểu dữ liệu bao gồm nhiều thành phần có thể thuộc nhiều kiểu dữ liệu khác nhau. Các thành phần được truy nhập thông tin qua tên. Khái niệm cấu trúc trong C có nhiều nét tương tự như khái niệm về bản ghi (record) trong PASCAL hay trong FOXPRO

1.2 Khai báo cấu trúc1.2.1 Khai báo kiểu dữ liệu cấu trúcCú pháp:

struct <tên_cấu_trúc> {<khai_báo_các_thành_phần>};

trong đó struct là từ khoá đứng trước một khai báo cấu trúc, <tên_cấu_trúc> là tên hợp lệ được dùng làm tên cấu trúc. <khai_báo_các_thành_phần> tựa như khai báo biến, bao gồm kiểu dữ liệu và một danh sách cá khai báo tên của các thành phần tạo nên cấu trúc này. Thông thường người ta sử dụng thuật ngữ trường để chỉ các thành phần của cấu trúc. Chúng ta xét các ví dụ sau:struct hocsinh {

char hoten[20];float diemthi;};

struct diem{float x,y;}

Khoa Tin học - 82 -

Page 83: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Khai báo thứ nhất xác định một kiểu cấu trúc có tên là hocsinh gồm hai thành phần: hoten (họ tên) và diemthi (điểm thi).

Khai báo thứ hai mô tả các toạ độ của một điểm trên mặt phẳng toạ độ thực, gồm hai thành phần là x (hoành độ) và y (tung độ). Dù rằng hai thành phần của cấu trúc giống hệt nhau và có thể sử dụng mảng để thay thế nhưng vệc sử dụng cấu trúc trong trường hợp này làm cho thông tin được mô tả tường minh hơn.

1.2.2 Khai báo kiểu dữ liệu cấu trúcCú pháp:

struct <tên_cấu_trúc> <tên_biến_cấu_trúc>;Ví dụ: struct hocsinh hs, dshs[100];struct diem p,q, dsiem[100];

1.2.3 Khai báo con trỏ cấu trúcCú pháp:

struct <tên_cấu_trúc> *<tên_biến_cấu_trúc>;Ví dụ: struct diem *ptr_p;Giống như mọi con trỏ khác, sau khi khai báo con trỏ cấu trúc ptr_p có giá trị

NULL. Con trỏ này có thể nhận giá trị là địa chỉ của các biến cấu trúc kiểm diem. Chẳng hạn:ptr_p = &p;

1.2.4 Khai báo đồng thời cấu trúc và biến cấu trúcCú pháp:

struct [<tên_cấu_trúc>] {<khai_báo_các_thành_phần>}<danh_sách_biến_cấu_trúc >;

Trong cú pháp trên ta thấy rằng phần <tên_cấu_trúc> là tuỳ chọn. Tuy vậy, nếu không có <tên_cấu_trúc> thì cấu trúc khai báo tương ứng không thể sử dụng về sau.Ví dụ:struct dagiac {

int n;struct diem dsdinh[20];} dg1, dg2;

Giống như Pascal, ngôn ngữ C cho phép khai báo trực tiếp kiểu của các thành phần là biến cấu trúc bên trong một cấu trúc lớn hơn. Chẳng hạn nếu không định nghĩa riêng cấu trúc diem, ta có thể viết lại khai báo cấu trúc dagiac như sau:struct dagiac{

itn n;

Khoa Tin học - 83 -

Page 84: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

struct {float x,y;}dsdinh[10];

}dg1, dg2;

1.3 Đặt tên kiểu dữ liệu bằng typedef1.3.1 Từ khoá typedefNgôn ngữ C cho phép thêm tên mới cho một kiểu dữ liệu bằng câu lệnh như

sau:typedef <tên_kiểu_đã_có> <tên_mới>;

trong đó <tên_kiểu_đã_có> là kiểu dữ liệu mà ta muốn thêm tên mới, <tên_mới> là tên mới mà ta muốn đặt.Xét câu lệnh sau:typedef unsigned char byte;Sau câu lệnh này byte được xem như là kiểu dữ liệu tương đương với unsigned char và có thể được sử dụng trong các khai báo biến như các kiểu dữ liệu khác. Xét chương trình struct1.c sau:

#include<stdio.h>#include<conio.h>typedef unsigned char byte;void main(){

byte ch =15, ch1;int i;clrscr();ch1 = ch;for(i=0; i<5; i++) ch>>=1;printf("\nByte %X Sau khi dich phai 5 lan la : %X", ch1, ch);getch();

}

Kết quả thực hiện chương trình: Byte F Sau khi dich phai 5 lan la : 0

typedef thường được sử dụng để định nghĩa lại các kiểu dữ liệu phức hợp thành một tên duy nhất để dễ dàng viết hơn trong khi viết.Ví dụ: typedef int * ptrint

Khoa Tin học - 84 -

Page 85: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

định nghĩa một kiểu dữ liệu con trỏ nguyên. Sau câu lệnh này để khai báo một biến con trỏ nguyên ptr_int chúng ta chỉ cần viết:ptrint ptr_int;

1.3.2 Sử dụng typedef với cấu trúc Đặc biệt đối với các kiểu dữ liệu cấu trúc, typedef cho phép đơn giản hoá cách

viết khi khai báo. Xét ví dụ sau:struct hocsinh {

char hoten[20];float diemthi;}

typedef struct hocsinh t_hocsinh;typedef struct hocsinh *ptr_hocsinh;

Với các câu lệnh này, tên mới của cấu trúc strcut hocsinh sẽ là t_hocsinh, đồng thời còn định nghĩa một kiểu con trỏ cấu trúc có tên là ptr_hocsinh.Khi đó câu lệnh khai báo biến cấu trúc viết gọn lại như sau:t_hocsinh hs, dshs[100];

Để khai báo biến con trỏ cấu trúc, ta có thể sử dụng câu lệnh sau:ptr_hocsinh ptrhs;Câu lệnh này rõ ràng ngắn gọn hơn câu lệnh struct hocsinh *ptrhs;

Có thể sử dụng typedef ngay trong khi khai báo cấu trúc như ví dụ sau:typedef struct {

char hoten[20];float diemthi;}t_hocsinh;

Trong khai báo này cần chú ý hai điểm: thứ nhất, tên cấu trúc bây giờ có thể không cần; thứ hai các tên đi sau dấu } không phải là tên biến mà là tên của kiểu dữ liệu đang định nghĩa.

1.4 Thao tác trên biến cấu trúc1.4.1 Truy nhập đến thành phần trong cấu trúcMỗi biến cấu trúc là một thể hiện của cấu trúc, nghĩa là nó mô tả cụ thể giá trị

của các thành phần được khai báo bên trong cấu trúc. Chẳng hạn, biến cấu trúc p và q sẽ có các thành phần x và y khác nhau trong cấu trúc diem. Và dĩ nhiên có thể gán cho hai thành phần x và y của cấu trúc p các giá trị khác nhau. Như vây, để xác định một thành phần bên trong biến cấu trúc người ta cần đến cả tên biến và tên thành phần. Nguyên tắc chung như sau:

<tên_biến_cấu_trúc>.<tên_thành_phần>

Khoa Tin học - 85 -

Page 86: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Dấu chấm ở đây được gọi là toán tử truy nhập thành phần cấu trúc. Nếu bản thân thành phần của biến cấu trúc là một biến cấu trúc thì vẫn áp dụng được toán tử này để xác định các thành phần "sâu hơn" trong cấu trúc thành phần. Ví dụ:p.x = 5;p.y = 10;dg1.n = 10;dg1.dsdinh[2].x = 100;dg1.dsdinh[2].y = 100;...

1.4.2 Truy nhập đến thành phần trong cấu trúc từ con trỏ cấu trúc Giả sử có con trỏ ptr_p chỉ đến p, khi đó có hai cách để xác định p:* Cách thứ nhất: áp dụng toán tử * lên con trỏ ptr_p, nghĩa là *ptr_p:(*ptr_p).x

* Cách thứ hai: sử dụng toán tử mũi tên -> (dấu trừ kết hợp với dấu lớn hơn) như sau:

ptr_p->x

1.4.3 Nhập dữ liệu cho biến cấu trúcNgôn gnữ C không đưa ra các phương tiện để nhập dữ liệu cho biến cấu trúc.

Như vậy, người lập trình phải tự mình thực hiện các thao tác nhập dữ liệu cho các thành phần trong cấu trúc.

Theo kinh nghiệm, nếu áp dụng trực tiếp hàm scanf( ) lên các thành phàn cấu trúc thì nhiều trường hợp sẽ xảy ra hiện tượng treo máy. Biện pháp được đề nghị là sử dụng biến trung gian: trước hết chúng ta nhập các số liệu cho biến trung gian rồi sau đó mới đem gán các giá trị nhập được cho các thành phần cần nhập. Điều này tương tự như việc nhập dữ liệu cho các phần tử của mảng. Đoạn chương trình sau trình bày cách nhập dữ liệu cho biến cấu trúc p:…float temp;printf("Toa do x: ");scanf("%f", &temp); p.x = temp;printf("Toa do y: ");scanf("%f", &temp); p.y = temp;…

Trong chương trình struct2.c sau đây có khai báo một mảng các cấu trúc hocsinh và thực hiện một số thao tác quen thuộc trên danh sách sinh viên:(i) vào số liệu(ii) in ra danh sách sinh viên phải thi lại

#include<stdio.h>

Khoa Tin học - 86 -

Page 87: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

#include<conio.h>#include<string.h>typedef struct {

char hoten[20];float diem;}hocsinh;

void main(){

int n = 0;/* số lượng học sinh của lớp đã được nhập*/int k;/* số lượng học sinh thi lại */int i;hocsinh dshs[100];char name[20];float d;clrscr();do {

fflush(stdin);printf("Nhap vao hoc sinh thu: %d\n", n+1);printf("Ho ten:");gets(name);if (strcmp(name,"")!=0)

{strcpy(dshs[n].hoten,name);printf("Diem: ");scanf("%f", &d);dshs[n++].diem = d; }

}while (strcmp(name,"")!=0);if(!n)

{printf("Chua nhap hoc sinh nao!\n");return;}

else{ k = 0; printf("Danh sach hoc sinh thi lai:\n"); for(i=0; i<n; i++)

if (dshs[i].diem < 5)printf("%d. %s %6.2f\n", ++k, dshs[i].hoten, dshs[i].diem);

if (!k)printf("Khong co hoc sinh thi lai!");}

Khoa Tin học - 87 -

Page 88: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

getch();}

Kết quả thực hiện chương trình: Nhap vao hoc sinh thu: 1Ho ten:ADiem: 5.6Nhap vao hoc sinh thu: 2Ho ten:TDiem: 8.9Nhap vao hoc sinh thu: 3Ho ten:FDiem: 3.4Nhap vao hoc sinh thu: 4Ho ten:MDiem: 3.9Nhap vao hoc sinh thu: 5Ho ten:MDiem: 5.1Nhap vao hoc sinh thu: 6Ho ten:Danh sach hoc sinh thi lai:1. F 3.402. M 3.90

1.4.4 Phép gán giữa các biến cấu trúcCó thể thực hiện phép gán nội dung một biến cấu trúc cho một biến cấu trúc

khác cùng kiểu. Chẳng hạn nếu p và q có cùng kiểu diem, ta có thể thực hiện lệnh gán sau đây:q = p;

Câu lệnh gán cấu trúc thực hiện gán giá trị các trường của cấu trúc bên vế phải cho các trường tương ứng trong biến cấu trúc bên vế trái. Chương trình struct3.c sau đây cụ thể hoá lời giải thích này:

#include<stdio.h>#include<conio.h>#include<string.h>#include<stdlib.h>void main(){struct {

char ht[20];

Khoa Tin học - 88 -

Page 89: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

int x,y;} a, b;

clrscr();a.x = 10; a.y = 10;strcpy(a.ht,"Nguyen van A");b = a;printf("a: {%s %d %d}\n", a.ht, a.x, a.y);printf("b: {%s %d %d}\n", b.ht, b.x, b.y);getch();}

Kết quả thực hiện chương trình: a: {Nguyen van A 10 10}b: {Nguyen van A 10 10}

1.5 Truyền biến cấu trúc cho hàm1.5.1 Truyền biến cấu trúc bằng tham trịViệc khai báo tham số hình thức của hàm là một biến cấu trúc ảnh hưởng ít

nhiều đến tốc độ thực hiện chương trình. Lý do là vì trong trường hợp này nội dung của biến cấu trúc dùng làm tham số thực sẽ được sao chép sang vùng nhớ dành cho tham số hình thức. Chúng ta biết rằng kích thước của các biến cấu trúc thường rất lớn. Vì vậy, cấu trúc sẽ chậm đi do phải tốn nhiều thời gian cho việc sao chép dữ liệu trong mỗi lời gọi hàm liên quan đến cấu trúc. Và hơn nữa nội dung của biến cấu trúc dùng làm tham số thực lại không thể thay đổi được.

Nhằm mục đích nâng cao tốc độ thực hiện cấu trúc và đôi khi cần thực hiện các thay đổi dữ liệu trên các cấu trúc thao tác, thay vì truyền nội dung cấu trúc người ta chỉ truyền cho hàm địa chỉ của cấu trúc.

1.5.2 Truyền biến cấu trúc bằng tham biến Việc truyền địa chỉ biến cấu trúc cho hàm có hai lợi ích; thứ nhất, dù kích thước biến nhường nào thì địa chỉ của biến vẫn chỉ là 2 bytes; thứ hai, ta có thể sử dụng toán tử '-> 'để thực hiện các truy xuất dữ liệu lên biến cấu cấu trúc liên quan.

Chương trình struct4.c sau đây khai báo cấu trúc mô tả một tam giác dựa trên toạ độ ba đỉnh và thực hiện một số tính toán trên tam giác.

#include<stdio.h>#include<conio.h>#include<math.h>typedef struct {

float x,y;}diem;

typedef struct

Khoa Tin học - 89 -

Page 90: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

{diem A, B, C;}tamgiac;

/* */void nhaptoado(tamgiac*);float canh(diem, diem);float canhbp(diem, diem);int loaitamgiac(tamgiac);float dientich(tamgiac);void main(){tamgiac tg;clrscr();printf("Nhap toa do cac dinh cuar tam giac:\n");nhaptoado(&tg);printf("Canh BC: %f\n", canh(tg.B, tg.C));printf("Canh CA: %f\n", canh(tg.C, tg.A));printf("Canh AB: %f\n", canh(tg.A, tg.B));switch (loaitamgiac(tg))

{case 1: printf("Tam giac deu\n"); break;case 2: printf("Tam giac vuong can\n"); break;case 3: printf("Tam giac can\n"); break;case 4: printf("Tam giac vuong\n"); break;case 5: printf("Tam giac thuong\n"); break;}

printf("Dien tich: %f\n", dientich(tg));getch();}/* */void nhaptoado(tamgiac *t){float temp;printf("Dinh A\n");printf("x = ");scanf("%f", &temp);t->A.x = temp;printf("y = ");scanf("%f", &temp);t->A.y = temp;printf("Dinh B\n");printf("x = ");scanf("%f", &temp);t->B.x = temp;printf("y = ");scanf("%f", &temp);t->B.y = temp;printf("Dinh C\n");printf("x = ");scanf("%f", &temp);t->C.x = temp;printf("y = ");scanf("%f", &temp);t->C.y = temp;

Khoa Tin học - 90 -

Page 91: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

}/* */float canh(diem p, diem q){return(sqrt(pow(p.x-q.x,2.0)+pow(p.y-q.y,2.0)));}/* */float canhbp(diem p, diem q){return(pow(p.x-q.x,2.0)+pow(p.y-q.y,2.0));}float dientich(tamgiac t){float a, b, c, p;a = canh(t.B, t.C);b = canh(t.C, t.A);c = canh(t.A, t.B);p = (a+b+c)/2;return(sqrt(p*(p-a)*(p-b)*(p-c)));}int loaitamgiac(tamgiac t){float a2, b2, c2;a2 = canhbp(t.B, t.C);b2 = canhbp(t.C, t.A);c2 = canhbp(t.A, t.B);if(a2==b2||b2==c2||c2==a2)

if(a2==b2&&b2==c2) return 1;else

if (a2==b2+c2||b2==a2+c2||c2==a2+b2) return 2;else return 3;

elseif(a2==b2+c2||b2==a2+c2||c2==a2+b2) return 4;else return 5;

}

Kết quả thực hiện chương trình: Nhap toa do cac dinh cuar tam giac:Dinh Ax = 0y = 0Dinh B

Khoa Tin học - 91 -

Page 92: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

x = 0y = 4Dinh Cx = 3y = 0Canh BC: 5.000000Canh CA: 3.000000Canh AB: 4.000000Tam giac vuongDien tich: 6.000000

2. CẤU TRÚC TỰ TRỎ

2.1 Nhắc lại khái niệmCấu trúc có ít nhất một thành phần là con trỏ chỉ đến bản thân cấu trúc được

gọi là cấu trúc tự trỏ. Dưới đây là một số ví dụ về cấu trúc tự trỏ:struct hocsinh{

char hoten[20];float diem;struct hocsinh *psau;};

Khai báo này định nghĩa một cấu trúc tự trỏ có thể dùng để quản lý danh sách các học sinh. Việc sử dụng cấu trúc tự trỏ để quản lý danh sách tỏ ra linh hoạt hơn so với cách quản lý danh sách bằng mảng tĩnh. Với cấu trúc tự trỏ, chúng ta có thể kéo dài danh sách ra vô hạn miễn là bộ nhớ máy tính có thể cấp phát được, đồng thời sẽ rất tiết kiệm bộ nhớ khi có một số lượng hạn chế đối tượng cần quản lý. Tất cả điều này có được là nhờ các phương tiện cấp phát bộ nhớ.

Hạn chế của cách quản lý này là việc quản lý danh sách trở nên phức tạp hơn so với mảng, việc duyệt danh sách phải tuân theo một số kiểu nhất định, phụ thuộc vào các con trỏ liên kết trong mỗi thành phần. Chẳng hạn, nếu mỗi phần tử chỉ có một mối nối, khi đó ta chỉ có thể duyệt danh sách theo môt chiều nhất định. Khi số mối nối tăng lên, việc duyệt danh sách sẽ linh hoạt hơn nhưng phải trả giá cho việc cập nhật danh sách cũng như chi phí bộ nhớ để duy trì các kiên kết. Sau đây là một ví dụ về cấu trúc tự trỏ để tạo các danh sách liên kết hai chiều hoặc cây nhị phân:struct hocsinh{

char hoten[20];float diem;struct hocsinh *ptruoc, *psau;}

Trong câu lệnh khai báo này, nhờ hai thành phần con trỏ trước và sau tương ứng chỉ đến địa chỉ của cấu trúc chứa thông tin về các học sinh đứng liền trước và

Khoa Tin học - 92 -

Page 93: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

liền sau trong danh sách, chúng ta có thể dễ dàng duyệt danh sách theo cả hai chiều "tiến" hoặc "lui".

Việc vài đặt các thao tác trên danh sách móc nối quen thuộc như: ngăn xếp, hàng đợi, cây nhị phân sẽ được trình bày ở các phần tiếp theo.

2.2 Ngăn xếp (stack) Ngăn xếp là kiểu danh sách đặc biệt với các thao tác (chèn, xoá, thay đổi nội dung các phần tử) được thực hiện ở một đầu của danh sách.

Cấu trúc tự trỏ quản lý ngăn xếp chỉ có một thành phần tự trỏ (thành phần con trỏ chỉ đến bản thân cấu trúc). Cú pháp khai báo như sau:

typedef struct stack {< khai báo phần dữ liệu chứa trong mỗi phần tử>struct strck *prev;}t_stack;

Ngăn xếp được quản lý thông qua một biến trỏ xác định địa chỉ của phần tử cuối cùng đưa vào danh sách. Sau mỗi thao tác lấy ra một phần tử (POP) hay đưa thêm một phần tử (PUSH) ta cần phải cập nhật lại con trỏ đầu. Để làm ví dụ minh hoạ xét chương trình xác định dạng biểu diễn nhị phân của một số nguyên dương. Cấu trúc tự trỏ trong trường hợp này được sử dụng để mô tả danh sách các chữ số nhị phân của số. Định nghĩa của cấu trúc như sau:typedef struct stack_binary {

unsigned char d;struct stack_binary *prev;}binary;

trong đó thành phần d chứa chữ số nhị phân, prev xác định địa chỉ của biến cấu trúc chứa chỉ số nhị phân hàng thấp hơn. Các chữ số nhị phân của số được chứa trong một danh sách xác định bởi con trỏ head:binary *head; Ban đầu head có giá trị NULL, sau đó cùng với quá trình lặp của thuật toán Euclit, danh sách head sẽ được kéo dài thêm nhờ hàm:binary push(binary*, unsined);trong đó tham số thứ nhất là giá trị của head hiện tại, giá trị trả về của push( ) sẽ là địa chỉ của cấu trúc mới được thêm vào với nội dung là tham số thứ hai của push(). Để in ra số nhị phân tương ứng chúng ta cần lần lượt lấy các phần tử trong danh sách cấu trúc xác định bởi head, đồng thời giải phóng vùng nhớ động cấp phát cho chúng. Việc giải phóng bộ nhớ được thực hiện bởi hàm:binary pop(binary*);

Khoa Tin học - 93 -

Page 94: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Hàm pop ( ) nhận một tham số là giá trị của head hiện tại, giải phóng vùng nhớ cấp phát cho head và trả về địa chỉ của phần tử vừa mới trở thành đỉnh mới của ngăn xếp. Chương trình được viết như sau:

#include<stdio.h>#include<conio.h>#include<alloc.h>#include<stdlib.h>typedef struct stack_binary {

unsigned char d;struct stack_binary *prev;}binary;

binary *push(binary *, unsigned char);binary *pop(binary *);

void main(){unsigned int n;binary *head = NULL;clrscr();do {

printf("\nNhap vao mot so nguyen khac 0:");scanf("%d", &n);if (n==0) break;do {

head = push(head, (unsigned char)(n%2));n/=2;} while(n!=0);

printf("Dang bieu dien nhi phan:\n");do

{printf("%1d", head->d);head = pop(head);}while (head!=NULL);

}while(1);}

binary *push(binary *head , unsigned char c){

binary *p;p = (binary*)malloc(sizeof(binary));

Khoa Tin học - 94 -

Page 95: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

p->d = c;p->prev = head;return p;

}

binary * pop(binary * head){

binary *p = head->prev;free(head);return p;

}

Nhap vao mot so nguyen khac 0:255Dang bieu dien nhi phan:11111111Nhap vao mot so nguyen khac 0:1Dang bieu dien nhi phan:1Nhap vao mot so nguyen khac 0:6Dang bieu dien nhi phan:110Nhap vao mot so nguyen khac 0:8Dang bieu dien nhi phan:1000Nhap vao mot so nguyen khac 0:2Dang bieu dien nhi phan:10Nhap vao mot so nguyen khac 0:

2.3 Hàng đợi (queue) Hàng đợi là một kiểu danh sách trong đó việc thêm các phần tử mới diễn ra ở cuối của danh sách còn việc xoá các phần tử diễn ra ở đầu của danh sách. Với cách tổ chức như vậy, phần tử được lấy ra nếu có thời gian tồn tại trong danh sách lâu nhất so với các phần tử khác. Vì vậy, cấu trúc này có tên là FIFO (Fist In First Out-Vào trước ra trước). Cấu trúc tự trỏ mô tả hàng đợi có thể khai báo giống như cấu trúc dùng cho ngăn xếp. Sự khác nhau giữa ngăn xếp và hàng đợi được thể hiện khi cài đặt cụ thể của các thao tác thêm, xoá, sửa.struct queue{

<phần thông tin cần quản lý><phần liên kết>}t_queue;

Khoa Tin học - 95 -

Page 96: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Cấu trúc hàng đợi thường được dùng làm bộ đệm lưu trữ các công việc cần giải quyết theo trình tự, ví dụ, giải quyết danh sách xếp hàng của khách hàng. Yêu cầu là khách hàng nào xếp trước được ưu tiên giải quyết trước. Một ví dụ khác rất quen thuộc với người sử dụng máy nhắn tin để ghi lại các thông báo gửi cho mình: thông báo đến trước sẽ được hiện lên đầu tiên v.v… Chương trình queue.c sau đây mô tả cách sử dụng cấu trúc tự trỏ để mô phỏng việc xếp hàng mua vé tàu tại ga Hà Nội. Việc xếp hàng được thực hiện theo nguyên tắc ai đến trước sẽ được mua vé trước. Thông tin về khách hàng là số chứng minh thư và họ tên. Ta có cấu trúc thông tin như sau:typedef struct khachhang{

long cmt;char hoten[20];struct khachhang *truoc;}t_khachhang;

Việc quản lý danh sách các khách hàng đang mua vé được thực hiện thông qua hai biến trỏ dautien và cuoicung, chỉ đến hai biến cấu trúc mô tả người đầu tiên và người cuối cùng tương ứng: Có hai tác động lên danh sách khách hàng:

(i) Thêm một khách hàng vào danh sách. Thao tác này sẽ cập nhật lại cuoicung. Công việc tương ứng được thực hiện trong hàm: t_khachhang *them(t_khachhang*, long, char*);

Hàm them( ) thực chất bổ sung thêm một cấu trúc tiếp theo sau cấu trúc xác định bởi cuoicung đồng thời cập nhật lại giá trị của cuoicung.

(ii) Gọi một khách hàng vào mua vé. Công việc này đồng nghĩa với việc loại khách hàng đầu tiên trong danh sách. Như vậy nó sẽ tác động đến dautien. Các công việc tương ứng sẽ được cài đặt trong hàm:

t_khachhang *muave(t_khachhang*); Hàm này sẽ xoá khỏi danh sách cấu trúc đang được trỏ bởi dautien và cho biến trỏ này chỉ đến phần tử tiếp theo.Chương trình minh hoạ:

#include<stdio.h>#include<conio.h>#include<alloc.h>#include<stdlib.h>#include<string.h>typedef struct queue_khachhang

{

Khoa Tin học - 96 -

Page 97: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

long cmt;char hoten[20];struct queue_khachhang *next;} t_khachhang;

t_khachhang *them(t_khachhang* ,long, char str[20]);t_khachhang *muave(t_khachhang*);void main(){

unsigned int n;long temp_cmt;char ht[20];t_khachhang *dautien = NULL, *cuoicung = NULL;clrscr();n = 0;do {

printf("\nNhap them mot khach hang:\n");printf("So chung minh thu:");scanf("%ld", &temp_cmt);if(temp_cmt==0) break;fflush(stdin);printf("Ten khach hang:");gets(ht);if(dautien==NULL)

dautien = cuoicung = them(cuoicung,temp_cmt,ht);else cuoicung = them(cuoicung, temp_cmt, ht);

}while (1);/* Gọi khách hàng mua ve vao*/

printf("Danh sach khach hang mua ve:\n");n=0;do {

printf("%2d",++n);printf("%8ld %20s\n",dautien->cmt, dautien->hoten);dautien=muave(dautien);

}while(dautien!=NULL);getch();

}

t_khachhang *them(t_khachhang *cuoi, long c, char t[20]){t_khachhang *p;p = (t_khachhang*)malloc(sizeof(t_khachhang));p->cmt = c;strcpy(p->hoten,t);

Khoa Tin học - 97 -

Page 98: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

p->next=NULL;cuoi->next = p;return p;}t_khachhang *muave(t_khachhang *dautien){

t_khachhang *d = dautien->next;return d;

}

Kết quả thực hiện chương trình: Nhap them mot khach hang:So chung minh thu:20057458Ten khach hang:Phuong

Nhap them mot khach hang:So chung minh thu:20057459Ten khach hang:Toan

Nhap them mot khach hang:So chung minh thu:200574510Ten khach hang:Thang

Nhap them mot khach hang:So chung minh thu:200579100Ten khach hang:Phap

Nhap them mot khach hang:So chung minh thu:0Danh sach khach hang mua ve: 120057458 Phuong 220057459 Toan 3200574510 Thang 4200579100 Phap

2.4 Cây nhị phân (Binary tree) * Một cách phi hình thức, cây nhị phân được định nghĩa như sau: (i) Hoặc cây rỗng không chứa thành phần nào. (ii) Hoặc cây được tạo thành bằng một gốc và hai cây con tương ứng là cây con trái là cây con phải. Mỗi nút trong cây tương ứng với một đối tượng được quản lý bao gồm;

(ii.1) Thông tin về bản thân đối tượng.

Khoa Tin học - 98 -

Page 99: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

(ii.2) Thông tin xác định các nút bên trái và bên phải nếu có của nút đang xem xét. Thông thường các thông tin này là địa chỉ của các đối tượng. Như vậy để quản lý cây nhị phân ta sử dụng cấu trúc tự trỏ có hai thành phần liên kết:

typedef struct binary_tree {<phần mô tả thông tin về đối tượng>struct binary_tree *left, *right;}t_banary_tree;

* Có ba giải thuật duyệt cây nhị phân là: (i) Duyệt theo thứ tự trước: duyệt gốc trước, rồi duyệt cây con trái, cuối cùng duyệt cây con phải. (ii) Duyệt theo thứ tự giữa: duyệt cây con trái trước, sau đó duyệt gốc; cây con phải được duyệt cuối cùng. (iii) Duyệt theo thứ tự sau: đầu tiên duyệt cây con trái, cây con phải được duyệt tiếp sau; nút gốc được duyệt cuối cùng.

Cả ba giải thuật trên là đệ qui vì rằng các cây con trái, cây con phải là một cây với kích thước nhỏ hơn. Ta xem xét cách cài đặt các thao tác này trên một lớp đặt biệt các cây nhị phân tìm kiếm.

Trong cây nhị phân tìm kiếm, mỗi nút có một trường dữ liệu đặc biệt được gọi là khoá đặc trưng của nút. Giá trị của khoá phải có kiểu dữ liệu sao cho có định nghĩa một quan hệ thứ tự giữa các giá trị. Thí dụ, kiểu dữ liệu có thể định nghĩa một quan hệ thứ tự là tập hợp các số nguyên với quan hệ lớn hơn ">" hoặc tập các xâu kí tự cũng với quan hệ ">". Trong cây nhị phân tìm kiếm thông thường ta quy định khoá của nút gốc bé hơn khoá của nút con phải và lớn hơn khoá của nút con trái. Các thao tác trên cây nhị phân tìm kiếm bao gồm:(i) Thêm một nút mới vào cây nhị phân. Nút mới này phải có khoá khác với tất cả các nút đã có trong cây. Việc thêm nút đồng nghĩa với việc xác định vị trí trong cây của nút mới.(ii) Loại bỏ nút trong cây. Thao tác này bắt đầu bao gồm xác định nút cần xoá, rồi sau đó tuỳ theo đặc điểm của nút đó để thực hiện thao tác tiếp theo. Ta phân biệt ba trường hợp:

(ii.1) Nếu nút cần xoá là nút lá, mọi chuyện dễ dàng.(ii.2) Trong trường hợp nút xoá chỉ có một nút con, việc xoá cũng dễ dàng.(ii.3) Vấn đề chỉ thực sự phức tạp khi ta phải xoá một nút có đầy đủ con trái

và con phải, trong trường hợp này vai trò của nút cần được chuyển sang cho nút con trái có khoá lớn nhất.

Chương trình tree.c sau đây cài đặt các thao tác trên cây tìm kiếm nhị phân có khoá là các số nguyên dương.

Khoa Tin học - 99 -

Page 100: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Cấu trúc của cây nhị phân tìm kiếm như sau:typedef struct tree{

unsigned int value;struct tree *left, *right;} t_tree;

Để quản lý cây nhị phân ta cần đến một biến con trỏ xác định địa chỉ của nút gốc trong cây:t_tree root;

#include<stdio.h>#include<conio.h>#include<alloc.h>#include<stdlib.h>/*Cấu trúc mô tả cây nhị phân tìm kiếm*/typedef struct tree

{unsigned int n;struct tree *left, *right;} t_tree;

/*Ham tao nut goc */t_tree *taocay(unsigned int);/* Giai phong cac vung nho cap phat cho cay*/t_tree *xoacay(t_tree *);/*Them nut moi vao cay */void themnut(t_tree *, unsigned int);/*Loai khoi cay mot nut*/t_tree *xoanut(t_tree*, unsigned int);/*Duyet cay theo thu tu giua*/void infix(t_tree *);/*Xac dinh nut cha hoac nut co kha nang lam nut chacua mot nut co khoa la mot gia tri nao do*/t_tree *nutcha(t_tree *, unsigned int, int *);/*Tim nut voi khhoa nao do*/t_tree *nut(t_tree*, unsigned int);/*Xac dinh nut con lon nhat trong nhanh ben trai*/t_tree *contrailonnhat(t_tree*);/*Ham chinh*/void main(){unsigned int n;t_tree *root = NULL;clrscr();

Khoa Tin học - 100 -

Page 101: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

/*tao cay*/do {

printf("Nhap vao mot so nguyen:");scanf("%d", &n);if (n==0) break;/*Neu la nut dau tien trong cay nhi phan thi phai tao no */if (root == NULL)root = taocay(n);/* Trai lai ta them mot nut moi vao cay*/else themnut(root, n);}while(1);

printf("Day so vua nhap duoc sap xep tang dan:\n");infix(root);do {

printf("\nNhap vao mot so nguyen can loai bo:");scanf("%d", &n);if (n ==0) break;root = xoanut(root, n);printf("Day so sau khi xoa:\n");infix(root);} while(1);

root = xoacay(root);getch();}

t_tree *taocay(unsigned int n){

t_tree *p;p = (t_tree*)malloc(sizeof(t_tree));p->left = NULL;p->right = NULL;p->n = n;return p;

}

void themnut(t_tree *r, unsigned int n)/*Viec them nut chi dien ra khi nut co khoa tuong ung chua co trong cay */{

t_tree *p, *q;int k;q = (t_tree*)malloc(sizeof(t_tree));q->left = NULL;q->right = NULL;

Khoa Tin học - 101 -

Page 102: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

q->n = n;p = nutcha(r,n,&k);switch(k){

case 1: if (p->left!=NULL)printf("Nut da co trong cay\n");

else p->left = q; break;

case 2: if (p->right!=NULL)printf("Nut da co trong cay\n");

else p->right = q; break;

}}

t_tree *xoanut(t_tree *r , unsigned int n)/*De xoa mot nut, ta phai kiem tra ba truong hop */{t_tree *p, *q, *s, *t;int k;p =nut(r,n);if (p!=NULL)

/*Neu nut can xoa la nut la, chi viec xoa nut dova cap nhat cac lien ket trong nut cha cua no */{if (p->left ==NULL && p->right ==NULL)

{if (p==r)

{free(p);return NULL;}

q = nutcha(r,n,&k);switch(k)

{case 1: q->left = NULL; free(p); break;case 2: q->right = NULL;free(p); break;}

return r;}

/*Neu nut chi co mot nut con,

Khoa Tin học - 102 -

Page 103: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

ta thay the no boi con cua no trong cay */if (p->left ==NULL)

{if (p==r)

{q = p->right;free(p);return q;}

q = nutcha(r,n,&k);switch(k)

{case 1: q->left = p->right;

free(p);break;case 2: q->right = p->right; free(p); break;}

return r; } if (p->right ==NULL)

{if (p ==r)

{q = p->left;free(p);return q;}

q = nutcha(r,n,&k); switch(k)

{case 1: q->left = p->left;free(p);

break;case 2: q->right = p->left;free(p);

break;}

return r; }

/*Truong hop kho nhat, nut can xoa co ca hai nut con Chung ta phai tim cho duoc nut con trong nhanh con ben trai */ s= contrailonnhat(p); t = nutcha(p,s->n,&k); /* Cap nhat lai cac lien ket cua bo*/

if(t!=p){

Khoa Tin học - 103 -

Page 104: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

t->right = s->left;s->left = p->left;s->right = p->right;}

elses->right= p->right;

/* s bay gio dong vai tro cua p*/ if ((q=nutcha(r,n,&k)!=NULL))

{switch(k)

{case 1: q->left = s; break;case 2: q->right = s; break;}

}if (p ==r)

{free(p);return s;}

else{free(p);return r;}

} else

{printf("Khong tim thay nut tuong ung\n");return r;}

}

void infix(t_tree* r){if(r==NULL) return;infix(r->left);printf("%4u", r->n);infix(r->right);}

t_tree *nutcha(t_tree *r, unsigned int n, int *k)

Khoa Tin học - 104 -

Page 105: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

{if (r->n ==n) return NULL;if (r->n>n)

if (r->left->n==n||r->left==NULL){*k = 1;return r;}

else return nutcha(r->left, n, k);else

if (r->right->n ==n||r->right ==NULL){*k = 2;return r;}

else return nutcha(r->right, n, k);}

t_tree *nut(t_tree *r, unsigned int n){if (r==NULL) return NULL;if (r->n ==n) return r;if (r->n >n) return nut(r->left,n);else return (r->right,n);}

t_tree *contrailonnhat(t_tree *r ){t_tree *p;p = r->left;while (p->right !=NULL)

p = p->right;return p;}

t_tree *xoacay(t_tree *r){if (r->left!=NULL)

xoacay(r->left);if (r->right!=NULL)

xoacay(r->right);free(r);

Khoa Tin học - 105 -

Page 106: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

return NULL;}Kết quả thực hiện chương trình: Nhap vao mot so nguyen:10Nhap vao mot so nguyen:4Nhap vao mot so nguyen:16Nhap vao mot so nguyen:1Nhap vao mot so nguyen:8Nhap vao mot so nguyen:3Nhap vao mot so nguyen:2Nhap vao mot so nguyen:5Nhap vao mot so nguyen:9Nhap vao mot so nguyen:12Nhap vao mot so nguyen:19Nhap vao mot so nguyen:14Nhap vao mot so nguyen:17Nhap vao mot so nguyen:0Day so vua nhap duoc sap xep tang dan: 1 2 3 4 5 8 9 10 12 14 16 17 19Nhap vao mot so nguyen can loai bo:1Day so sau khi xoa: 2 3 4 5 8 9 10 12 14 16 17 19Nhap vao mot so nguyen can loai bo:19Day so sau khi xoa: 2 3 4 5 8 9 10 12 14 16 17 19Nhap vao mot so nguyen can loai bo:03. KIỂU HỢP (union)

3.1 Đặt vấn đềMột biến kiểu union cũng bao gồm nhiều thành phần giống như một biến cấu

trúc, nhưng khác biến cấu trúc ở chỗ: các trường trong biến cấu trúc được cấp phát các vùng nhớ khác nhau, còn các trường của biến union được cấp phát chung một vùng nhớ. Như vậy union cho phép ta sử dụng một vùng nhớ cho nhiều biến khác nhau.

3.2 Khai báo kiểu dữ liệu unionKhai báo một union tương tự như khai báo một biến cấu trúc, thay cho từ kháo

struct ta dùng từ khoá union. Việc định nghĩa một biến kiểu union, mảng các union, con trỏ union cũng tương tự như đối với các cấu trúc. Một cấu trúc có thể có thành phần union và ngược lại các thành phần của union lại có thể là cấu trúc.Ta xét ví dụ khai báo sau:typedef union {

Khoa Tin học - 106 -

Page 107: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

unsigned int n;unsigned char ch[2];}val;

val a, b, x[10];Câu lệnh trong ví dụ trên định nghĩa kiểu union val gồm hai thành phần là n và mảng kí tự ch. Độ dài của val bằng độ dài của trường n và bằng 2. Tiếp đó khai báo các biến union a, b và mảng union.Phép gán:a.n = 0x1b1a;sẽ gán một số trong hệ 16 là 0x1b1a cho thành phần n của a. Do n chiếm hai byte của union nên sau câu lệnh trên ta có:a.ch[0] = 0x1avàa.ch[1] = 0x1bnhư vậy ta đã dùng khai báo union để tách ra byte cao và byte thấp của một số nguyên. Hãy xem chương trình ví dụ sau:

#include<stdio.h>#include<conio.h>typedef union {

unsigned int n;char ch[2];} u;

void main(){u a;clrscr();printf("a.n = 0x1b1a;\n");a.n = 0x1b1a;printf("Kich thuoc cua union %d\n", sizeof(a));printf("a.ch[0] = %x\n", a.ch[0]);printf("a.ch[1] = %x\n", a.ch[1]);getch();}

Kết quả thực hiện chương trình: a.n = 0x1b1a;Kich thuoc cua union 2a.ch[0] = 1aa.ch[1] = 1b

Khoa Tin học - 107 -

Page 108: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Khoa Tin học - 108 -

Page 109: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

CHƯƠNG 6ĐỒ HOẠ

1. KHÁI NIỆMTừ trước đến nay, chúng ta chủ yếu làm việc với kiểu màn hình văn bản.

Nghĩa là màn hình với 25 dòng và 80 cột, và kí tự hiển thị trên màn hình (kiểu và kích thước) cũng không thay đổi được.

Ở màn hình đồ hoạ, các các kí tự, hình vẽ, … được thể hiện và xử lí đến từng điểm nhỏ (pixel). Các điểm được bố trí theo chiều thẳng đứng từ trên xuống và ngang từ trái qua phải.

Độ phân giải của một màn hình là số điểm có thể có của màn hình đó. Độ phần giải càng cao thì số điểm ảnh trên màn hình càng lớn và chất lượng đồ hoạ càng cao.

Ví dụ: Màn hình VGA 640 x 480

Mỗi loại màn hình có cách xử lí đồ hoạ riêng, nên Turbo C cung cấp một tệp tin điều khiển đồ hoạ cho từng loại màn hình, thường là *.BGI.

Các phần cơ bản của một chương trình đồ hoạ:- Khởi động đồ hoạ- Xác định màu nền (màu ,màn hình), màu đường vẽ, màu tô, kiểu tô.- Vẽ, tô màu các hình mà ta mong muốn- Các thao tác đồ hoạ khác như hiện dòng chữ,…- Đóng hệ thống đồ hoạ để trở về mode văn bản.

Bảng 13.1: Các kiểu màn hình đồ hoạ và tệp điều khiển chúngKIỂU MÀN HÌNH TÊN TỆP TIN

ATT & T6300 (400 dòng) ATT.BGI

IBM CGA, MCGA và các máy tính tương thích CGA.BGI

IBM EGA, VGA và các máy tính tương thích EGAVGA.BGI

Hercules monochorome và các máy tính tương thích HERC.BGI

IBM 8514 các máy tính tương thích IBM8514.BGI

Khoa Tin học - 109 -

0, 0 639, 0

479, 0 639, 479

Page 110: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

IBM 3270 PC PC3270.BGI

2. KHỞI ĐỘNG ĐỒ HOẠ- Mục đích của việc khởi động hệ thống đồ hoạ là xác định thiết bị đồ hoạ

(màn hình) và mốt (mode) đồ hoạ sẽ sử dụng trong chương trình.- Để khởi động đồ hoạ, ta dùng hàm sau:

void initgraph(int *graphdriver, int *graphmode, char *driverpath);

driverpath là đường dẫn đến thư mục chứa các tệp diều khiển đồ hoạ.

graphdriver: loại màn hình graphmode: mode đồ họa

Ví dụ:#include “graphics.h”main(){

int mh = EGA, mode = EGALO;initgraph(&mh, &mode, “D:\\TC\\BGI”):…closegraph();

}

* Chú ý:- Khi lập trình, ta có thể dùng tên hằng hoặc giá trị hằng tương ứng trong

bảng 13.2- Độ phân giải phụ thuộc vào cả màn hình và mode đồ hoạ.- Nếu không biết chính xác màn hình đang dùng là gì, thì ta nên dùng hằng

DETECT (0) cho graphdriver. Khi đó, kết quả của hàm initgraph sẽ là:+ Kiểu màn hình đang sử dụng dược phát hiện, giá trị số của nó được gán

cho biến graphdriver.+ Mode đồ hoạ ở độ phân giải cao nhất ứng với màn hình đang sử dụng

cũng được phát hiện và trị số của nó được gán cho biến graphmode.

Như vậy, việc dùng hằng DETECT không những khởi động được hệ thống đồ hoạ với mode cao nhất mà còn cho ta biết kiểu màn hình đang sử dụng.

Ví dụ 1: Xác định kiểu màn hình và mode đang sử dụng bằng DETECT.#include "graphics.h"#include "stdio.h"void main()

Khoa Tin học - 110 -

Page 111: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

{int mh = 0, mode = 0;initgraph(&mh, &mode,"");printf("\nGia tri so cua man hinh la: %d", mh);printf("\nGia tri mode cua man hinh la: %d", mode);closegraph();getch();

}

Bảng 13.2: Bảng giá trị của graphdriver, graphmodeGRAPHDRIVER GRAPHMODE ĐỘ PHÂN

GIẢIHẰNG GIÁ TRỊ HẰNG GIÁ TRỊ

DETECT 0

CGA 1 CGAC0 0 320 x 200

CGAC1 1 320 x 200

CGAC2 2 320 x 200

CGAC3 3 320 x 200

CGAHi 4 640 x 200

MCGA 2 MCGA0 0 320 x 200

MCGA1 1 320 x 200

MCGA2 2 320 x 200

MCGA3 3 320 x 200

MCGAMed 4 640 x 200

MCGAHi 5 640 x 480EGA 3 EGALO 0 640 x 200

EGAHi 1 640 x 350

EGA64 4 EGA64LO 0 640 x 200

EGA64Hi 1 640 x 350

EGAMONO 5 EGAMONOHi 0 640 x 350

VGA 9 VGALO 0 640 x 200

VGAMED 1 640 x 350

VGAHI 2 640 x 480

HERCMONO 7 HERCMONOHI 720 x 348

ATT400 8 ATT400C0 0 320 x 200

Khoa Tin học - 111 -

Page 112: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

ATT400C1 1 320 x 200

ATT400C2 2 320 x 200

ATT400C3 3 320 x 200

ATT400CMED 4 640 x 400

ATT400CHI 5 640 x 400

PC3270 10 PC3270HI 0 720 x 350

IBM8514 6 IBM8514LO 0 640 x 480256 màu

IBM8514HI 1 1204 x 768256 màu

3. CÁC LỖI THÔNG THƯỜNG TRONG ĐỒ HOẠKhi khởi động hệ thống đồ hoạ, nếu máy không tìm thấy các chương trình

điều khiển đồ hoạ thì sẽ phát sinh lỗi và việc khởi động coi như không thành công. Lỗi đồ hoạ còn phát sinh khi dùng các hàm đồ hoạ. Trong mọi trường hợp, hàm graphresult() cho biết có lỗi hay không và lỗi đó là lỗi gì.

Bảng 13.3 cho biết các mã lỗi mà hàm này phát hiện được, ta có thể dùng hàm grapherrormsg() với mã lỗi do hàm graphresult()trả về để biết được đó là lỗi gì.

Bảng 13.3 Các mã lỗi của graphresult()HẰNG TRỊ LỖI PHÁT HIỆN

grOk 0 Không có lỗigrNoInitGraph -1 Chưa khởi động hệ đồ hoạgrNotDetected -2 Không có phần cứng đồ hoạgrFileNotFound -3 Không tìm thấy trình điều khiển đồ hoạgrInvalidDriver -4 Trình điều khiển không hợp lệgrNoLoadMem -5 Không đủ RAM cho đồ hoạgrNoScanMem -6 Vượt vùng RAM trong scan fillgrNoFloodMem -7 Vượt vùng RAM trong flood fillgrFontNoFound -8 Không tìm thấy tệp FontgrNoFontMem -9 Không đủ RAM để nạp FontgrInvalidMode -10 Kiểu đồ hoạ không hợp lệ cho trình điều khiểngrError -11 Lỗi đồ hoạ tổng quátgrIOerror -12 Lỗi đồ hoạ vào ra

Khoa Tin học - 112 -

Page 113: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

grInvalidFont -13 Tệp Font không hợp lệgrInvalidFontNum -14 Số hiệu Font không hợp lệ

Ví dụ: Đoạn mã sau cho biết nội dung lỗi:int maloi;maloi = grapresult();printf(“Loi do hoa la: ”, grapherror(maloi));

4. CÁC MÀU VÀ MẪU- Chọn màu nền:

void setbkcolor(int color);- Chọn màu đường vẽ:

void setcolor(int color);- Chọn kiểu tô (pattern) và màu tô:

void setfillstyle(int pattern, int color); Trong cả 3 trường hợp trên, color xác định mã màu, pattern xác định kiểu

tô.

Bảng 13.4 Bảng giá trị mặc định của colorTÊN HẰNG GIÁ TRỊ SỐ MÀU HIỂN THỊ

BLACK 0 đen

BLUE 1 xanh da trời

GREEN 2 xanh lá cây

CYAN 3 xanh lơ

RED 4 đỏ

MAGENTA 5 tím

BROWN 6 nâu

LIGHTGRAY 7 xám nhạt

DARKGRAY 8 xám sẫm

LIGHTBLUE 9 xanh da trời nhạt

LIGHTGREEN 10 xanh lá cây nhạt

LIGHTCYAN 11 xanh lơ nhạt

LIGHTRED 12 đỏ nhạt

LIGHTMAGENTA 13 tím nhạt

YELLOW 14 vàng

WHITE 15 Trắng

Khoa Tin học - 113 -

Page 114: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

- Thay đổi giải màu:Để thay đổi giải màu đã định nghĩa trong bảng 13.4, ta dùng hàm:

void setpalette(int colornum, int color);Ví dụ: setpalette(0, LIGHTCYAN);sẽ biến màu đầu tiên trong bảng thành LIGHTCYAN. Các màu khác không

bị ảnh hưởng.- Nhận giải màu:

void getpalette(struct palettetype *palette); Ở đây, palettetype là kiểu đã được Turbo C định nghĩa như sau:

# define MAXCOLORS 15struct palettetype {

unsigned char size;unsigned char colors[MAXCOLORS +1];

};

size: số lượng màu trong palettecolors: mảng chứa màu với chỉ số mảng từ 0 đến size-1

Bảng 13.5 Bảng giá trị mặc định của patternTÊN HẰNG GIÁ TRỊ SỐ MÀU HIỂN THỊ

EMPTY_FILL 0 tô bằng màu nền

SOLID_FILL 1 tô bằng đường nét liền

LINE_FILL 2 tô bằng ---

LTSLASH_FILL 3 tô bằng ///

SLASH_FILL 4 tô bằng /// in đậm

BKSLASH_FILL 5 tô bằng \\\ in đậm

LTBKSLASK_FILL 6 tô bằng \\\

HATCH_FILL 7 tô bằng đường gạnh bóng nhạt

XHATCH_FILL 8 tô bằng đường gạnh bóng chữ thập

INTERLEAVE_FILL 9 tô bằng đường đứt quảng

WIDE_DOT_FILL 10 tô bằng dấu chấm thưa

CLOSE_DOT_FILL 11 tô bằng dấu chấm dày

Hàm getcolor() trả về màu đã xác định trước đó.Khoa Tin học - 114 -

Page 115: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Hàm getbkcolor() trả về màu nền đã xác định trước đó.Hàm getmaxcolor() trả về số lượng màu cực đại thuộc giải màu hiện đang

có hiệu lực.Ví dụ 2: Hiển thị chữ “CONG NGHE THONG TIN” màu đỏ trên nền xanh

da trời.#include "graphics.h"#include "stdio.h"svoid main(){

int mh = 0, mode = 0;initgraph(&mh, &mode,"d:\\tc\\bgi");setbkcolor(1);setcolor(4);outtext("CONG NGHE THONG TIN");getch();closegraph();

}5. VẼ VÀ TÔ MÀU

Ta tìm hiểu các hàm vẽ và tô màu qua bốn nhóm chính sau:a. Đường tròn và hình tròn

- Vẽ cung tròn. void arc(int x, int y, int gd, int gc, int r);(x,y) : toạ độ của tâm cung tròn.gd : góc đầu, gc : góc cuốir : bán kính

Chú ý: Góc trong tất cả các hàm ở phần này được tính bằng đơn vị độ và nằm trong [0,360].

- Vẽ đường trònvoid circle(int x, int y, int r);(x,y) : toạ độ của tâm cung tròn.r : bán kính

- Vẽ cung ellipsevoid ellipse(int x, int y, int gd, int gc, int xr, int yr);(x,y) : toạ độ của tâm ellipsegd : góc đầugc : góc cuốixr, yr : bán kính trục ngang là trục đứng

- Hình quạtvoid pieslice(int x, int y, int gd, int gc, int r);(x,y) : toạ độ của tâm hình quạtgd : góc đầugc : góc cuối

Khoa Tin học - 115 -

Page 116: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

r : bán kính b. Đường gấp khúc và hình đa giác

Để vẽ hình gấp khúc đi qua n điểm (x1,y1), (x2,y2),…(xn,yn), thì trước hết, ta phải đưa n điểm vào một mảng a kiểu int một chiều: a[0]=x1, a[1]=y1, a[2]=x2, a[3]=y2,…

- Vẽ đường gấp khúc khép kín:drawpoly(n,a);n : số đỉnha : mảng một chiều chứa giá trị vị trí của đỉnh

Khi điểm cuối (xn,yn) trùng với (x1, y1) thì ta có đường gấp khúc khép kín.- Vẽ và tô màu hình đa giác

fillpoly(n, a);n : số đỉnha : mảng một chiều chứa giá trị vị trí của đỉnh

Ví dụ 3: Vẽ đường gấp khúc và hình tam giác.#include "graphics.h"#include "stdio.h"void main(){

int mh = 0, mode = 0;// Xay dung cac toa do dinhint poly1[]={5,5,5,20,30,30,40,50};int poly2[]={100,100,390,5,300,100,100,100};initgraph(&mh, &mode,"d:\\tc\\bgi");setbkcolor(1);setfillstyle(2, YELLOW);drawpoly(4, poly1);fillpoly(4, poly2);getch();closegraph();

}

c. Đường thẳng- Vẽ đường thẳng nối 2 điểm

void line(int x1, int y1, int x2, int y2);(x1, y1): toạ độ điểm thứ nhất(x1, y1): toạ độ điểm thứ hai

Chú ý: Vị trí con trỏ không thay đổi sau khi vẽ.

- Vẽ đường thẳng từ điểm hiện tại đến điểm có toạ độ được chỉ địnhvoid lineto(int x, int y);(x, y) : toạ độ điểm.

Sau khi vẽ, con trỏ chuyển đến điểm (x, y)

Khoa Tin học - 116 -

Page 117: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

- Vẽ đường thẳng từ điểm hiện tại đến điểm có toạ độ hơn kém một lượng (dx,dy)

void linerel(int dx, int dy);Đường thẳng được vẽ từ vị trí hiện tại (x, y) đến điểm (x + dx, y + dy). Con

trỏ di chuyển đến vị trí mới.

- Di chuyển con trỏ đến vị trí chỉ địnhvoid moveto(int dx, int dy);

d. Hình chữ nhật (HCN)- Vẽ HCN có các cạnh song song với các cạnh màn hình

void rectangle(int x1, int y1, int x2, int y2)(x1, y1), (x2, y2): toạ độ đỉnh bên trên trái và bên dưới phải của HCN.

- Vẽ và tô màu HCNvoid bar(int x1, int y1, int x2, int y2);

(x1, y1), (x2, y2): toạ độ đỉnh bên trên trái và bên dưới phải của HCN.

- Vẽ khối hộp CNvoid bar3d(int x1, int y1, int x2, int y2, int depth, int top);

(x1, y1), (x2, y2): toạ độ đỉnh bên trên trái và bên dưới phải của HCN.HCN này được tô màu, tham số depth xác định số điểm ảnh trên bề sâu của

khối 3 chiều. Tham số top có thể nhận trị 0 (không hiển thị mặt trên của khối CN), hay 1 (có mặt trên).

6. KIỂU ĐƯỜNG- Hàm void setlinestyle(int linestyle, int pattern, int thickness);Tác động đến nét vẽ của các thủ tục line, lineto, rectangle, drawpoly,

cirrcle,… Hàm này cho phép ta ấn dịnh 3 yếu tố của đường thẳng là dạng, kiểu, bề dày.

+ Dạng: do tham số linestyle quy định. Sau đây là một số dạng mặc định.

Khoa Tin học - 117 -

TopON TopOFF

Page 118: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

DẠNG GIÁ TRỊ Ý NGHĨA

SOLID_LINE 0 nét liền

DOTTED_LINE 1 nét chấm

CENTER_LINE 2 nét chấm gạch

DASHED_LINE 3 nét gạch

USERBIT_LINE4 mẫu tự tạo

+ Bề dày: do tham số thickness quy định. Sau đây là một số dạng mặc định:NORM_WIDTH = 1 bề dày bình thườngTHICK_WIDTH = 3 bề dày gấp 3

+ Kiểu: nếu tham số là USERBIT_LINE thì ta có thể tạo ra mẫu đường thẳng bằng tham số pattern.

Ví dụ:int pettern = 0x1010;setlinestyle(USERBIT_LINE, pattern, NORM_WIDTH);line(0,0, 100, 100);

- Hàm void getlinesettings(struct linesettingstype *lineinfo);Cho phép nhận các giá trị của 3 yếu tố ở mục 1, với linesettingstype được

định nghĩa như sau:struct linesettingstype {

int linestyle;unsigned int upattern;int thickness;

}

Ví dụ 4: Minh hoạ hàm setlinestyle và getlinesettings.#include "graphics.h"#include "conio.h"void main(){

int mh= 0, mode=0;struct linesettingstype kieucu;initgraph(&mh, &mode,"d:\\tc\\bgi");setbkcolor(WHITE);setcolor(RED);line(0,0, 100, 100);getch();//Luu lai kieu cugetlinesettings(&kieucu);//Thiet lap kieu moisetlinestyle(DOTTED_LINE, 0, THICK_WIDTH);

Khoa Tin học - 118 -

Page 119: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

line(50,0, 150, 100);getch();//Phuc hoi kieu cusetlinestyle(kieucu.linestyle, kieucu.upattern,

kieucu.thickness);line(50, 100,150,200); getch();closegraph();

}- Hàm void setwritemode(int writemode);Thiết lập kiểu thể hiện đường thẳng cho các hàm line, drwpoly, linerel,

lineto, rectangle. Kiểu thể hiện do tham số writemode xác dịnh.+ Nếu writemode = COPY_PUT (= 0): đường thẳng được vẽ đè lên dòng

hiện hành.+ Nếu writemode = XOR_PUT (= 1): màu của đường thẳng sẽ kết hợp với

màu của từng chấm điểm của đường hiện hành trên màn hình theo phép XOR để tạo nên một đường thẳng mới.

Ứng dụng: Khi thiết lập kiểu writemode bằng XOR_PUT rồi vẽ lại đường thẳng cùng màu thì sẽ xoá đường thẳng cũ và trả về màn hình nguyên thuỷ.

Ví dụ 5: minh hoạ cách dùng hàm setwritemode. Khi thực hiện, ta sẽ thấy hình chữ nhật thu nhỏ dần vào tâm màn hình.

#include "graphics.h"#include "dos.h"#include "stdio.h"void main(){

int mh = 0, mode = 0;int x1, x2, y1, y2;initgraph(&mh, &mode,"d:\\tc\\bgi");setbkcolor(CYAN);setcolor(YELLOW);setfillstyle(CLOSE_DOT_FILL,BLUE);x1=0;y1=0;x2=getmaxx();y2=getmaxy();setwritemode(XOR_PUT);tt: rectangle(x1, y1, x2, y2);delay(200);

if( (x1+1)<(x2-1)&&(y1+1)<(y2-1)){

rectangle(x1, y1,x2, y2);//Xoa HCNx1++;y1++ ;x2--;y2--;goto tt;

}setwritemode(COPY_PUT);//tro ve overrite mode

Khoa Tin học - 119 -

Page 120: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

getch();closegraph();

}7. CỬA SỔ (viewport)

a. Viewport: là một vùng chữ nhật trên màn hình đồ hoạ tựa như window trong textmode.

- Để thiết lập viewport ta dùng hàm sau:void setviewport(int x1, int y1, int x2, int y2, int clip);

(x1, y1), (x2, y2): toạ độ đỉnh bên trên trái và dưới phải của cửa sổ.clip: cho phép vẽ ra ngoài cửa sổ mới hay không, tuỳ thuộc vào giá trị sau:Nếu clip=1: không cho phép Nếu clip=0: cho phép Ví dụ:setviewport(100, 100, 200, 200, 1);sẽ thiết lập một cửa sổ mà góc phải của nó có toạ độ là (0,0) và không cho

phép vẽ ra cửa sổ mới này.- Nhận viewport hiện hành, ta dùng hàm sau:

void getviewsettings(struct viewporttype *vp);với viewporttype là kiểu cấu trúc được định nghĩa như sau:

struct viewporttype {int left, top, right, bottom;int clip;

}- Xoá viewport hiện hành, ta dùng hàm sau:

void clearviewport(void);- Xoá mọi thứ trên màn hình và đưa con trỏ về toạ độ (0, 0) của màn hình,

ta dùng hàm sau: void cleardevice(void);

b. Toạ độ âm dương: Nhờ sử dụng viewport ta có thể viết các chương trình theo tọa độ âm dương. Muốn vậy, ta thiết lập viewport sao cho tâm tuyệt đối của màn hình là góc trái trên của viewport, và tham số clip = 0 để có thể vẽ ra ngoài viewport.

Ví dụ: Đoạn mã thực hiện việc nàyint xmid, ymid;xmid=getmaxx()/2;ymid=getmaxy()/2;setviewport(xmid, ymid, getmaxx(), getmaxy(), 0);

Như thế, màn hình sẽ được chia thàmh 4 phần với toạ độ âm dương như sau:

Khoa Tin học - 120 -

Page 121: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Ví dụ 6: Vẽ đồ thị hàm sin trong hệ trục toạ độ âm dương.#include "graphics.h"#include "stdio.h"#include "math.h"void main(){

double scaleX=20;double scaleY=50 ;int mh = 0, mode = 0;int xmid,ymid, x, y, i;initgraph(&mh, &mode,"d:\\tc\\bgi");xmid=getmaxx()/2;ymid=getmaxy()/2;setviewport(xmid,ymid,getmaxx(),getmaxy(),0);//Ke he truc toa toa dosetcolor(BLUE);line(0, -ymid, 0, ymid);line(-xmid, 0, xmid, 0);settextjustify(1,1);setcolor(RED);outtext("(0,0)");for(i= -400;i <= 400; i++){

x = floor(2*M_PI*i*scaleX/200);y = floor(sin(2*M_PI*i/200)*scaleY);putpixel(x, y, YELLOW);

}getch();closegraph();

}

8. TÔ ĐIỂM, TÔ MIỀN- Tô điểm (x, y) theo màu xác định:

void putpixel( int x, int y);- Lấy số hiệu màu ở điểm ảnh (x, y)

Khoa Tin học - 121 -

x âmy âm

x dươngy âm

x dươngy dương

x âmy dương

Page 122: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

unsigned getpixel(int x, int y);Nếu điểm này chưa được tô màu bởi các hàm vẽ hoặc putpixel, mà chỉ mới

thiết lập setbkcolor thôi thì hàm trả về giá trị 0. Vì vậy, có thể xác định các nét vẽ trên màn hình đồ hoạ và vẽ ra giấy bằng đoạn sau:

if (getpixel(x, y)!=0){// điểm (x, y) được vẽ;}

- Tô miềnvoid floodfill(int x, int y, int border);

(x, y) toạ độ của một điểm trong miền cần tô (điểm gieo)border: chứa mã của một màuSự hoạt động của hàm này phục thuộc vào giá trị x, y, border và trạng thái

màn hình. Cụ thể:- Khi trên màn hình có đường (cong, gấp khúc) khép kín mà mã màu của nó

(màu viền) bằng giá trị border (màu tô) thì:+ Miền giới hạn bởi đường kín sẽ được tô màu nếu diểm gieo nằm trong

miền.+ Trong trường hợp trái lại, phần ngoài hình bao bởi đường sẽ được tô.

- Khi trên màn hình không có một đường cong như vậy, thì cả màn hình được tô màu.

Ví dụ 7: Vẽ một dường tròn đỏ trên nền xanh. Toạ độ điểm gieo (x,y) được nhập từ bàn phím. Tuỳ thuộc vào (x,y) mà chương trình sẽ cho các kết quả khác nhau.

#include "graphics.h"#include "conio.h"void main(){

int mh= 0, mode=0;int x, y;initgraph(&mh, &mode,"d:\\tc\\bgi");setbkcolor(CYAN);setcolor(RED);setfillstyle(11,BLUE);circle(320,100,50);moveto(1,150);outtext("Toa do diem gieo: ");scanf("%d%d", &x, &y);floodfill(x, y, RED);getch();closegraph();

}

Khoa Tin học - 122 -

Page 123: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

9. XỬ LÝ VĂN BẢN TRÊN MÀN HÌNH ĐỒ HOẠa. Xử lí văn bản trên màn hình đồ hoạ

- Hàm void outtext(char *str); hiển thị chuỗi str tại vị trí hiện hành của con trỏ.

- Hàm void outtextxy(int x, int y, char *str); hiển thị chuỗi str tại vị trí (x, y)

Ví dụ:2 đoạn mã sau đây tương đương về tác dụng:outtextxy(100, 100, “Cong Nghe Thong Tin”);

vàmoveto(100, 100);outtext( “Cong Nghe Thong Tin”);

Chú ý: Trong màn hình đồ hoạ, các hàm getch(), scanf(), kbhit() vẫn tác dụng như màn hình text.

b. Font: Các tệp tin font chữ có phần mở rộng là .CHR trên đĩa. Các font này cho các kích thước và kiểu chữ khác nhau, sẽ hiển thị trên màn hình đồ hoạ bằng outtext hoặc outtextxy. Để chọn và nạp font, ta dùng hàm sau:

void settextstyle(int font, int direction, int chasize);(Hàm này chỉ có tác dụng khi tệp .CHR tồn tại trên đĩa)Trong đó:- font: xác định kiểu chữ và nhận một trong các hằng sau:

DEFAULT_FONT=0TRILEX_FONT=1SMALL_FONT=2SANS_SERIF_FONT=3GOTHIC_FONT=4

- direction: là một trong 2 hằng số: HORIZ_DIR = 0: văn bản sẽ hiển thị theo hướng nằm ngangVERT_DIR = 1: văn bản sẽ hiển thị theo hướng đứng từ dưới lên.

- chasize: là hệ số phóng to kí tự và có giá trị trong (1, 10)Nếu chasize =1: font được thể hiện trong hình chữ nhật 8*8 pixel.Nếu chasize =2: font được thể hiện trong hình chữ nhật 16*16 pixel.…Nếu chasize =10: font được thể hiện trong hình chữ nhật 80*80

pixel.Chú ý: các giá trị do settextstyle thiết lập sẽ giữ nguyên cho đến khi gọi

một settextstyle mới.c. Vị trí hiển thị

void settextjustify(int horiz, int vert);

Khoa Tin học - 123 -

Page 124: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Trong đó: - horiz là một trong các hằng sau:

LEFT-TEXT=0: văn bản xuất hiện bên phải con trỏCENTER_TEXT=1: con trỏ ở tâm văn bản RIGHT_TEXT=2: văn bản xuất hiện bên trái con trỏBOTTOM_TEXT=0: văn bản xuất hiện phía trên con trỏCENTER_TEXT=1:con trỏ ở tâm văn bản TOP_TEXT=2: văn bản xuất hiện phía dưới con trỏ

Ví dụ: settextjustify(1,1)outtextxy(100, 100, “PHNG”);

d. Bề rộng và bề cao của văn bản- Hàm void textheight(char *str);

trả lại chiều cao của chuỗi mà str trỏ tới. Ví dụ:Với 8*8 bitmap font và hệ số khuếch đại là 1 thì textheight(“H”)=8;

Ví dụ 8: Cho hiện 5 dòng chữ với định dạng khác nhau#include "graphics.h"#include "conio.h"void main(){

int mh= 0, mode=0, y, size;initgraph(&mh, &mode,"d:\\tc\\bgi");y =10;settextjustify(0,0);for (size = 1; size <= 5; size++){

settextstyle(0,0,size);outtextxy(0, y, "TIN HOC");y+= textheight("TIN HOC")+10;

}getch();closegraph();

}

- Hàm void textwidth(char *str); trả lại bề rộng của chuỗi do str trỏ tới dựa vào chiều dài chuỗi, kích thước

font, hệ số khuếch đại.

Khoa Tin học - 124 -

Page 125: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

10. CẮT, DÁN, VẼ HÌNH CHUYỂN ĐỘNGa. Lấy số byte cần thiết để lưu ảnh trong phạm vi HCN:

unsigned imagesize(int x1, int y1, int x2, int y2);b. Tạo con trỏ trỏ tới một vùng nhớ mới được cấp phát

#include “alloc.h”void *malloc(unsigned n);n: số byte của vùng nhớ.

c. Chép điểm ảnhvoid getimage(int x1, int y1, int x2, int y2, void *bitmap);

Chép các điểm ảnh của HCN (x1, y1, x2, y2) và các thông tin về bề rộng, cao của nó vào vùng nhớ do bitmap trỏ tới. Vùng nhớ và biến bitmap cho bởi hàm malloc. Độ lớn của vùng nhớ được xác định bằng hàm imagesize.

d. Sao lưu ảnhvoid putimage(int x, int y, void *bitmap, int copymode);

Sao lưu ảnh trong vùng nhớ bitmap ra màn hình tại vị trí (x, y). Tham số copymode xác định kiểu sao chép ảnh:

COPY_PUT=0: sao chép nguyên xiXOR_PUT=1:các điểm ảnh trong bitmap kết hợp với các điểm ảnh trên

màn hình theo phép XOR.OR_PUT=2: các điểm ảnh trong bitmap kết hợp với các điểm ảnh trên màn

hình theo phép OR.AND_PUT =3: các điểm ảnh trong bitmap kết hợp với các điểm ảnh trên

màn hình theo phép AND.NOT_PUT =4: ảnh xuất hiện trên màn hình theo dạng đảo ngược ( phép

NOT) với ảnh trong bitmap.

Chú ý: Nếu dùng XOR_PUT đẽ chép hình, sau đó lặp lại đúng câu lệnh đó thì hình sẽ bị xoá và màn hình trỏ lại như cũ. Kỹ thuật này được dùng để tạo các hình ảnh chuyển động.

Ví dụ 9: Ví dụ minh hoạ cách dùng imagesize, malloc, getimage, putimage.#include "graphics.h"#include "alloc.h"void main(){

int mh= 0, mode=0;char *p;unsigned size;initgraph(&mh, &mode,"d:\\tc\\bgi");bar(0,0, getmaxx()/2, getmaxy()/2);size = imagesize(10, 20, 30, 40);p = (char*)malloc(size);

Khoa Tin học - 125 -

Page 126: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

getimage(10, 20, 30, 40, p);getch();cleardevice();putimage(100, 100, p, COPY_PUT);getch();closegraph();

}

e. Tạo ảnh di độngNguyên tắc tạo ảnh di động là:1/ Vẽ một hình (trong chuỗi hình di động)2/ Delay3/ Xoá hình đó4/ Vẽ hình kế tiếp5/ Delay…

1/ Vẽ một hình (trong chuỗi hình di động)- Cách 1: vẽ lại một ảnh nhưng ở các vị trí khác nhau- Cách 2: Lưu ảnh vào vùng nhớ rồi đưa ảnh ra màn hình ở các vị trí khác

nhau.3/ Xoá hình- Cách 1: Dùng hàm cleardevice- Cách 2: Dùng hàm putimage (mode XOR_PUT) để xếp chồng lên ảnh cần

xoá.- Cách 3: Lưu trạng thái màn hình vào một chỗ nào đó. Vẽ một hình ảnh.

Đưa trạng thái cũ màn hình ra xếp đè lên ảnh vừa vẽ.

Ví dụ 10: Bắn pháo hoa trên bầu trời đầy sao. #include "graphics.h"#include "alloc.h"#include"stdlib.h"void main(){

int mh= 0, mode=0, i, n;int x[101], y[101];char *p[101];initgraph(&mh, &mode,"d:\\tc\\bgi");setcolor(RED);//Ve bau troi day saofor(i = 1; i <= 100; ++i)

putpixel(random(getmaxx()), random(getmaxy()),random(getmaxcolor()));

Khoa Tin học - 126 -

Page 127: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

//Luu hien trng 100 hinh chu nhat tren man hinh de khoi phuc for (i = 1; i<= 100; ++i)

{x[i] = random(getmaxx()) -10;y[i] = random(getmaxy()) -10;if (x[i] < 0) x[i] = 0;if (y[i] < 0) y[i] = 0;n = imagesize(x[i], y[i], x[i]+10, y[i]+10);p[i] = (char*)malloc(n);getimage(x[i], y[i], x[i]+10, y[i]+10, p[i]);

}//Chu trinh ban phao hoado{

//Dua 100 qua phao len man hinh tai cac vi tri quy dinh for (i = 1; i <= 100; ++i){

setfillstyle(SOLID_FILL,i%15+1);pieslice(x[i]+5, y[i]+5, 0, 360, 5);

}delay(500);//Xoa chum phao vua ban bawng cach khoi phuc man hinhfor (i = 100; i >= 1; i--)

putimage(x[i], y[i],p[i], COPY_PUT);delay(500);

} while(!kbhit());getch(); getch();closegraph();

}

Khoa Tin học - 127 -

Page 128: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

BÀI TẬP

CHƯƠNG 0GIỚI THIỆU

1. Máy tính cá nhân là gì? 2. Byte, bit, word, nodle, là gì? 3. Cho biết tên của một số thiết bị nhớ phụ điển hình. Các loại bộ nhớ này

khác bộ nhớ chính ở chỗ nào?4. Ngôn ngữ máy là gì? Sự khác nhau giữ ngôn ngữ mày và ngôn ngữ bậc

cao?5. Cho biết một số ngôn ngữ bậc cao thông dụng. Thuận lợi khi sử dụng ngôn

ngữ bậc cao?6. Ý nghĩa của quá trình biên dịch? Ý nghĩa của quá trình thông dịch? Hai quá

trình này khác nhau như thế nào?7. Chương trình nguồn là gì? Chương trình đích là gì? 8. Hãy nêu nguồn gốc phát triển và tác giả của ngôn ngữ C.

CHƯƠNG 1CÁC THÀNH PHẦN CƠ BẢN CỦA C

1. Hãy nêu các thành phần chính của một chương trình C. Ý nghĩa và tầm quan trọng của từ “main”.

2. Thế nào là lời chú giải trong C? Các chú thích thường được đặt ở đâu? Đặt như thế nào?

3. Vì sao phải biết các từ khóa của C?4. Tên là gì? Dùng để làm gì? 5. Có phải các chương trình C phải được viết bằng chữ thường không? Các kí

tự viết hoa có được sử dụng trong chương trình C không? Giải thích.6. Tại sao khi viết mã nguồn, ta thường phải thụt vào?7. Hãy tổng quát hoá ý nghĩa của các đặc tính sau: tính toàn vẹn, tính trong

sáng, tính đơn giản, tính hiệu quả, tính phân phối và tính tổng quát. Tại sao các đặc tính này lại quan trọng?

8. Có nên dùng tiếng Việt trong chương trình C?

CHƯƠNG 2CÁC KIỂU DỮ LIỆU CƠ SỞ

2. Hãy kể ra các kí tự trong C.

Khoa Tin học - 128 -

Page 129: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

3. Hãy tổng quát hoá các luật xác định cách đặt tên trong C? Các kí tự viết thường có tương đương với viết hoa khống? Một tên được định nghĩa có thể bao gồm các số không? Có thể bao gồm cả kí tự đặc biệt không?

4. Hãy nêu các từ khoá trong C. 5. Hãy cho biết tên và diễn giải bốn kiểu dữ liệu cơ bản trong C.6. Khi viết số nguyên, làm thế nào để phân biệt là trong hệ 10, hệ 8 hay hệ

16? 7. Thông thường thì miền giá trị lớn nhất của hằng nguyên là bao nhiêu? Hãy

viết các giá trị đó trong các hệ trên.8. Hằng số nguyên không dấu (unsigned integer) là gì? Thế nào là một hằng

số nguyên dài? Làm thế nào để phân biệt các hằng số nguyên này?9. Hãy mô tả 2 cách viết khác nhau của hằng dấu chấm đọng? Các quy luật

nào được áp dụng cho mỗi trường hợp?10. Mục đích của số mũ trong hằng dấu chấm động là gì?11. Hằng kí tự là gì? Nêu sự khác nhau giữa hằng kí tự với các hằng số. Mỗi

hừng kí tự đại diện cho một giá trị số phải không?12. Bảng mã ASCII là gì? Cụ thể về các nhóm kí tự được mã hoá trong bảng

mã?13. Hằng chuỗi kí tự là gì? Có khác với hằng kí tự không?14. Thế nào là một biến? Nêu các đặc điểm của biến? 15. Mục đích của khai báo kiểu dữ liệu? Nêu cú pháp của một khai báo?16. Trong C, có phải các biến phải được khai báo trước khi được sử dụng?17. Cho biết cách gán giá trị cho biến khi khai báo?18. Xác định các tên đúng: record, $tax, name_address, 123_ten, name, return,

nam address, file_3, void.19. Xác định các trị số đúng của các hằng sau:

a) 0.5 b) 12,12 c) 9e12 d) 1e-12 f) 29374g) 0.3E12 h) 0x8273ef k)01234 l) 01122 m)0xefef

20. Xác định các hằng kí tự trong số các hằng sau:a) ‘a’ b) ‘\\’ c) ‘\0’ d) ‘xy’ e) ‘\032’f) ‘&’ g) ‘\n’ h) ‘\a’ k) ‘T’ l) ‘\0xef’

21. Xác định các hằng chuỗi kí tự trong số các hằng sau: a) ‘abn’ d) “11:12:97”b) “Red, white, blue” e) “Please contact me …”c) “Ten: f) “Chuong 3”

22. Viết chương trình nhập vào: tên học sinh, diểm toán, điểm lý, điểm hoá. Tính điểm trung bình và hiển thị theo dạng sau:Ho ten: Nguyen Van AĐiem toan: 8.00Diem ly: 9.00Diem hoa: 8.50Diem tb: 8.50

Khoa Tin học - 129 -

Page 130: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

23. Viết chương trình hiển thị một mẫu đơn giản tự chọn. 24. Viết chương trình nhập vào hai đáy và chiều cao hình thang. Tính diện tích

của nó.

CHƯƠNG 3BIỂU THỨC, CÂU LỆNH VÀ CÁC PHÉP TOÁN

22 Biểu thức là gì? Các thành phần của một biểu thức?23 Toán tử là gì? Nêu các loại toán tử có trong C?24 Toán hạng là gì? Mối liên hệ giữa toán hạng và toán tử?25 Nêu 5 loại toán tử số học trong C? Tổng quát hoá luật kết hợp giữa

các toán tử?26 Làm thế nào để chuyển một giá trị trả về bởi biểu thức sang một kiểu

dữ liệu khác? Gọi quá trình chuyển đổi này là gì?27 Thứ tự của các toán tử có ý nghĩa như thế nào? Nêu các mối liên hệ

về thứ tự của các toán tử?28 Khi nào thì nên sử dụng dấu ngoặc trong biểu thức? Khi nào nên

tránh sử dụng chúng?29 Các toán tử được thực hiện theo thứ tự như thế nào trong một biểu

thức có chứa các dấu đóng mở ngoặc lồng nhau?30 Cho biết tác dụng của toán tử ++, --. Các cách dùng khác nhau của

chúng?31 Cho biết số byte được cấp phát cho mỗi kiểu dữ liệu trong C? 32 Các toán tử lôgic trong C, chúng được sử dụng với các với các toán

hạnh nào? Các toán tử này thuộc kiểu biểu thức nào?33 Toán tử trên bit?34 Công dụng của toán tử lôgic not ( ! )và toán tử not bit (~). Hai toán

tử này có giống nhau không?35 Với hai toán hạn khác kiểu thì một biểu thức gán được xác định như

thế nào? Nguyên nhân gây ra lỗi trong trường hợp này?36 Nêu các phép gán trong C? Trật tự thực hiện chúng như thế nào?37 Có phải các hàm thư viện của C cũng là một phần của ngôn ngữ C?

Giải thích?38 Đối số là gì? Cách viết đối số? Cách gọi một hàm thư viện trong C?

Cách gọi hàm không có đối?39 Để sử dụng các hàm thư viện của C, phải khai báo gì trong chương

trình?40 Cách sử dụng của phát biểu #include, #define?41 Hãy viết mục đích của các biểu thức sau:

a) a - b d) a != bb) a*(a+b) e) (a/b)%5c) d = a*(a+b) f) –h

Khoa Tin học - 130 -

Page 131: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

42 Giả sử có khai báo sau:int n = 10, p = 4;long q = 2; float x = 1.75;

Hãy cho biết kiểu và giá trị của các biếu thức sau:d) n + q g) q + 3*(n > p)e) n + x h) q&&nf) n%p + q i) (q - 2) && (n - 10)g) n < p j) x*(q = = 2)h) n >= p k) x*(q = 5)i) n > q l) (float)n/p

43 Cho biết giá trị của x nếu:int x = 5; float y = 9.0,z; z = y/z;

e) 1f) 1.8g) 2h) Không phải các giá trị trên.

44 Tìm giá trị của x:float z ;z = (int)3.0 + (int) 3.8 ;a) 6.8b) 6.0c) 7.0d) Không phải các giá trị trên.

45 Tìm câu sai:a) Sử dụng chỉ dẫn #define nói chung làm giảm kích thước chương

trình.b) Chỉ dẫn #define có thể được sử dụng đề cho chương trình dễ đọc, dễ

hiểu.c) Chỉ dẫn #define không có dấu ; kết thúc.

46 Toán tử nào không phải là toán tử gán: a) = b) += c) != d)?=

26. Viết chương trình tính x mũ y. Biểu thức (100>79) && (‘A’< ‘B’) có tương đương với 100>79&&‘A’<

‘B’? 27. Viết chương trình tính

Các biểu thức sau có sử dụng các hàm thư viện. Hãy cho biết ý nghĩa, mục đích của chúng:a) abs(i-2*j) l) sqrt(x*x+y*y)

Khoa Tin học - 131 -

Page 132: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

b) fabs(x+y) m) isalnum(10*i)c) isprint(c) n) isascii(20*i)d) isdigit(c) o) isalpha(10*i)e) toupper(d) p) toascii(10*i)f) ceil(x) q) fmod(x,y)g) floor(x+y) r)tolower(80)h) islower(c) s) pow(x,y)i) issupper(j) t) sin(x+y)j) exp(x) u) srlen(‘‘the word’’)k) log(g) v) strpos(‘‘the word’’, ‘r’) ;

28. Hãy xác định giá trị trả về của các biểu thức sau theo khai báo:int i = 8, j = 10;char c = ’c’ ; d = 'd' ;double x = 0.005, y = -0.01 ;a) abs(i-2*j) l) sqrt(x*x+y*y)b) fabs(x+y) m) isalnum(10*i)c) isprint(c) n) isascii(20*i)d) isdigit(c) o) isalpha(10*i)e) toupper(d) p) toascii(10*i)f) ceil(x) q) fmod(x,y)g) floor(x+y) r) tolower(80)h) islower(c) s) pow(x,y)i) issupper(j) t) sin(x+y)j) exp(x) u) srlen(‘‘the word’’)k) log(g) v) strpos(‘‘the word’’, ‘r’) ;

29. Muốn biết hình dáng kí tự có mã ASCII là 222, ta làm gì?30. Lập chương trình hiển thị một chữ cái ngẫu nhiên.

CHƯƠNG 4CÁC CÂU LỆNH ĐIỀU KHIỂN

1. Ý nghĩa của cấu trúc rẽ nhánh?2. Ý nghĩa của cấu trúc lựa chọn?3. Hằng kiểu kí tự và biến kiểu kí tự được dịch như thế nào khi chúng được sử

dụng như là các toán hạng trong biểu thức quan hệ?4. Mục đích sử dụng toán tử if… else. 5. So sánh toán tử điều kiện?: với lệnh if…else.6. Các toán tử if else lồng nhau đợc dịch như thế nào? Hãy cho biết cách dịch

phát biểu sau: if e1 if e1 S1else S2

7. Lập chương trình:a) Đầu tiên in ra dòng chữ: Ngon ngu lap trinh

Khoa Tin học - 132 -

Page 133: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

b) Nếu không bấm phím hoặc bấm một phím khác C và P thì dòng chữ tiếp tục hiện ra.

c) Nếu bấm phím C thì máy hiện ra: Turbo Cd) Nếu bấm P thi hiện ra: Pascal8. Lập chương trình giải phương trình bậc nhất một ẩn, bặc hai một ẩn, hệ

phương trình bậc một hai ẩn.

Khoa Tin học - 133 -

Page 134: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

THAM KHẢOCÁC CHỈ THỊ TIỀN XỬ LÝ

1. CHỈ THỊ #define ĐƠN GIẢN (MACRO)a. Cách viết: #define tên dãy_kí_tự

- Trong chương trình, khi máy gặp tên, máy sẽ thay thế tên bằng dãy_kí_tự. Điều này có tác dụng tránh sự lặp lại những câu lệnh, biểu thức,…

- Phạm vi của tên được định nghĩa là từ lúc nó được định nghĩa, cho tới cuối tệp gốc.

- Có thể định nghĩa lại tên (đã định nghĩa), và từ thời điểm này trở đi nó có ý nghĩa mới. Tuy nhiên, trước khi định nghĩa lại, ta nên giải phóng nó bằng chỉ thị:

#undef tên(Nếu không giải phóng, máy sẽ cảnh báo khi chạy chương trình)Ví dụ 1: Cho biết kết quả chương trình sau: #include "stdlib.h" #define in printf#define N 100void main(){

int M=200;in("\nN=%d M=%d", N, M);#define M 300in("\nN=%d M=%d", N, M);#undef M#define M (N+400)in("\nN=%d M=%d", N, M);getch();

}b. Một số chú ý

- Phép thế không thực hiện cho các hằng chuỗi kí tự (được đặt trong dấu “ ”). Chẳng hạn, nếu Delta là một tên đã định nghĩa, thì sẽ không có việc thay thế nào trong câu lệnh sau:

printf(“\nDelta”);- Cuối dòng #define không có dấu “;”- Khi định nghĩa một biểu thức, ta nên đặt nó trong các dấu ngoặc đơn.Ví dụ:#define length 3+5thì size = length; (=3+5)nhưng doublesize = 2*length sẽ là 2*3+5Do đó, ta nên viết là: #define length (3+5)

Khoa Tin học - 134 -

Page 135: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

- Khi định nghĩa một đoạn chương trình gồm nhiều lệnh, ta nên đặt nó trong cặp dấu {}

Ví dụ:#define Ketthuc {printf(“\nKet thuc”); exit(1);}

thì nếu trong chương trình ta sử dụng lệnh:f (x > max) Ketthuc //(1)

sẽ tương đương với:if (x > max) {printf(“\nKet thuc”); exit(1);}

nhưng nếu ta không dùng cặp {} ở định nghĩa thì (1) sẽ tương đương với:if (x > max) printf(“\nKet thuc”); exit(1);

nghĩa là chương trình sẽ kết thúc với mọi giá trị của x!- Một số định nghĩa thông dụng:#define PI 3.14159#define begin {#define end }#define TRUE 1#define FALSE 0#define ESC 0x1b

2. CHỈ THỊ #define CÓ ĐỐI (MACRO)a. Có thể dùng #define để định nghĩa các macro có đối tương tự như hàm. Khi

đó văn bản thay thế sẽ phụ thuộc vào cách gọi tới macro. Ví dụ:#define max(A, B) (A)>(B)?(A):(B)Khi đó, lệnh x = max(p + q, r + s);sẽ được thay bằng: x = (p + q) > ( r + s)?(p + q):(r + s);- Như vậy, nhờ các macro có đối, ta có thể xây dựng được các hàm đơn

giản để sử dụng trong chương trình. Đối của macro không có kiểu nhất định. Cho nên, macro max(A, B) ở trên có thể dùng cho kiểu nguyên hay thực … đều được.

b. Một số chú ý- Giữa tên macro và dấu mở ngoặc “(” không có dấu cách.Ví dụ:Khai báo: #define max (A, B) (A)>(B)?(A):(B) là sai.- Khi viết biểu thức thay thế, các đối hình thức cần được bao quanh bởi các

dấu ngoặc đơn “(…)”.Ví dụ:#define cube(y) (y*y*y)thì khi viết : cube(a + b)

sẽ nhận được kết quả là: cube(a+b*a+b*a+b)chứ không phải là cube(a+b)*(a+b)*(a+b)

3. CHỈ THỊ BAO HÀM TỆP #includea. Chỉ thị #include chỉ đường cho bộ tiền xử lí nhận nội dung của tệp khác và

đặt chèn vào tệp chương trình nguồn đang xét.b. Các cách viết

Khoa Tin học - 135 -

Page 136: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

- Cách 1: #include <[dường dẫn]tên_tệp>- Cách 2: #include “[dường dẫn]tên_tệp”* Tác dụng: Trước khi dịch, chương trình sẽ tìm tệp theo tên và đường dẫn

ghi trong chỉ thị. Nếu tìm thấy thì nội dung của tệp này được chèn vào tệp nguồn đang xét tại đúng vị trí của #include. Nếu không tìm thấy thì chương trình sẽ báo lỗi.

Cách thức làm việc của 2 dạng #include trên chỉ khác nhau khi không có thông tin đường dẫn. Khi đó, ở cách 1, #include sẽ tìm tệp trong thư mục INCLUDE của Turbo C. Còn #include ở cách 2 thì trước tiên, tìm trong thư mục hiện hành, nếu không thấy thì tiếp tục tìm trong thư mục INCLUDE.

Ví dụ:#include <d:\TurboC\hamtoan.c>#include “d:\TurboC\hamtoan.c”#include “hamtoan.c”#include <hamtoan.c>

c. Các #include lồng nhau:C cho phép các #include lồng nhau. Chẳng hạn, tệp gốc main.c có chứa chỉ

thị #include “tệp1”. Trong tệp1 lại có chứa chỉ thị #include “tệp1”, thì trước khi dịch tệp main.c, cả hai tệp tệp1 và tệp2 đều được ghép vào tệp main.c

d. Công dụng của #include - Sử dụng được các tệp có sẵn của TC.- Tổ chức chương trình trên nhiều tệp: một chương trình lớn thường gồm

nhiều hàm. Mỗi hàm do một nhóm người xây dựng, nên các hàm thường được chứa trên các tệp khác nhau. Chỉ thị #include cho phép ghép các hàm trên các tệp khác nhau vào tệp gốc để tạo thành một chương trình hoàn chỉnh. Chương trình này được dịch và thực hiện theo các quy tắc như đối với một chương trình viết trên một tệp. Như vậy , trên thực tế thì chương trình chứa trên nhiều tệp nhưng nhờ #include ta vẫn có thể xem như một tệp.

- Tổ chức thư viện: trong quá trình làm việc ta thường xây dựng được các hàm hữu ích. Chẳng hạn, các hàm xử lí ma trận được lưu ở tệp matran.c, các hàm đồ hoạ được lưu ở dohoa.c, … Nếu trong một chương trình nào đó ta cần đến các hàm trong các tệp trên, thì ta chỉ cần đưa các tệp trên vào chương trình bằng chỉ thị #include.

Ví dụ:#include <e:\thuvien\ matran.c>

4. CÁC CHỈ THỊ BIÊN DỊCH CÓ ĐIỀU KIỆN #ifGiả sử chương trình của ta cần đến các tệp module_a và module_b:

#include “module_a “ #include “module_b”

Và trong cả 2 tệp này đều có chứa chỉ thị #include “tệp x” thì có thể gây ra lỗi khi dịch, vì có những đối tượng được khai báo lại. Để tránh lỗi này khi dịch, ta dùng các chỉ thị #if.

Khoa Tin học - 136 -

Page 137: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

Các dạng chỉ thị #if- Dạng 1:

#if biểu_thức_hằngđoạn chương trình

#endifTác dụng: Nếu biểu_thức_hằng là đứng thì TC sẽ biên dịch đoạn chương

trình giữa #if và #endif. Trái lại, đoạn này sẽ bị bỏ qua. biểu_thức_hằng là biểu thức là các toán hạng của nó đều là các hàng. Các tên đã được định nghĩa bằng #define cũng được xem là các hằng.

- Dạng 2:#if biểu_thức_hằng

đoạn chương trình 1#else

đoạn chương trình 2 #endifTác dụng: nếu biểu_thức_hằng là đúng thì Turbo C sẽ biên dịch đoạn

chương trình 1, ngược lại sẽ biên dịch đoạn chương trình 2.

5. CÁC CHỈ THỊ BIÊN DỊCH CÓ ĐIỀU KIỆN #ifdef VÀ #ifndefMột phương pháp biên dịch có điều kiên khác là dùng chỉ thị #ifdef và

#ifndef, chúng có nghĩa là: “nếu đã định nghĩa” và “nếu chưa định nghĩa”. Các chỉ thị này có thể dùng theo các dạng sau:

- Dạng 1: #ifdef tên_macrođoạn chương trình

#endifÝ nghĩa: nếu tên_macro đã định nghĩa (bởi #define) thì trình biên dịch

Turbo C sẽ dịch đoạn chương trình nằm giữa #ifdef và #endif. Trái lại, đoạn này bị bỏ qua.

- Dạng 2:#ifdef tên_macro

đoạn chương trình 1#else

đoạn chương trình 2#endif

Ý nghĩa: nếu tên_macro đã định nghĩa (bởi #define) thì trình biên dịch Turbo C sẽ dịch đoạn chương trình 1, trái lại dịnh đoạn chương trình 2.

- Dạng 3:#ifndef tên_macro

đoạn chương trình #endif

Ý nghĩa: nếu tên_macro chưa được định nghĩa (bởi #define) thì trình biên dịch Turbo C sẽ dịch đoạn chương trình nằm giữa #ifndef và #endif. Trái lại, đoạn này bị bỏ qua.

Khoa Tin học - 137 -

Page 138: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

- Dạng 4:#ifndef tên_macro

đoạn chương trình 1#else

đoạn chương trình 2#endif

Ý nghĩa: nếu tên_macro chưa được định nghĩa (bởi #define) thì trình biên dịch Turbo C sẽ dịch đoạn chương trình 1. Trái lại, sẽ dịch đoạn chương trình 2.

Chú ý: Các đoạn chương trình trong #ifdef và #ifndef có thể chứa các chỉ thị tiền xử lí. Nên chúng ta có thể sử dụng các phương án lồng nhau dối với #ifdef và #ifndef như thể đã làm với #if.

Ví dụ 4: Cho biết kết quả chạy chương trình sau:#define ted 10;main(){

#ifdef maxprintf("\nmax");

#elseprintf("no max");

#endif#ifndef min

printf("min");#endif

}

6. TỔ CHỨC CÁC TỆP THƯ VIỆNĐể các tệp tiêu đề (tệp thư viện) có thể được kết nối nhiều lần vào một

chương trình nguồn mà không gây lỗi, khi lập trình ta nên tổ chức chúng như sau (giống như cách tổ chức các tệp tiêu đề của C):

Ví dụ tệp beta .h:#ifndef _BETA_H_

#define_BETA_H_//Nội dung thực sự của tệp beta .h

#endifNhư vậy, mặc dù tệp beta.h được chèn vào tệp nguồn tại nhiều chỗ, nhưng

nó chỉ được biên dịch lần đầu (lúc đó _BETA_H_ còn chưa xác định). Các phiên bản sau của beta.h đều bị bỏ qua vì _BETA_H_ đã được định nghĩa trong lần đầu gặp tệp beta.h)

7. CHỈ THỊ #errorChỉ thị #error sẽ dừng biên dịch chương trình và in ra một thông báo lỗi.

Chỉ thị này có thể sử dụng để kiểm tra tính nhất quán của các hằng.Ví dụ:#if(soluong>soluongmax)||(chiso>soluongmax)

Khoa Tin học - 138 -

Page 139: Giao Trinh C_Uyên Trang

Giáo trình Ngôn ngữ lập trình C Trần Uyên Trang

#error Soluongmax qua nho, can so luong lon hon!!!#endif

Khoa Tin học - 139 -