1. Trang chủ
  2. » Luận Văn - Báo Cáo

Bài báo cáo kết thúc học phần môn cơ sở lập trình Đề tài the c programming language

94 4 0
Tài liệu được quét OCR, nội dung có thể không chính xác
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Bài Báo Cáo Kết Thúc Học Phần Môn: Cơ Sở Lập Trình The C Programming Language
Tác giả Phạm Hồng Nhi, Đỗ Anh Thơ, Lưu Thị Cam Tiễn, Cao Huỳnh Ngọc Hân, Phạm Nguyễn Tường Vi
Người hướng dẫn GVHD: Nguyễn Lạc
Trường học Trường Đại Học Lao Động - Xã Hội (Cơ Sở II)
Chuyên ngành Hệ Thống Thông Tin Quản Lý
Thể loại bài báo cáo
Năm xuất bản 2024
Thành phố TP. Hồ Chí Minh
Định dạng
Số trang 94
Dung lượng 27,24 MB

Nội dung

Con trỏ được sử dụng nhiều trong C, một phần vì đôi khi chúng là cách duy nhất để biểu diễn phép tính và một phần vì chúng thường dẫn đến mã sọn nhẹ và hiệu quả hơn những cách có thé thu

Trang 1

TRƯỜNG ĐẠI HỌC LAO ĐỘNG - XÃ HỘI (CƠ SỞ II)

KHOA PHÁP LUẬT ĐẠI CƯƠNG

BÀI BAO CAO KET THUC HOC PHAN MON: CO SO LAP TRINH The C Programming Language Chapter 5: Pointers and Arrays Chapter 7: Input and Output

GVHD: NGUYEN LAC Nhóm sinh viên thực hiện:

223404050182-Phạm Hồng Nhi(NT) 223404050195-Đỗ Anh Thơ 223404050199-Luu Thi Cam Tién 223404050160-Cao Huynh Ngoc Han 223404050208-Phạm Nguyễn Tường Vi Ngành: Hệ Thống Thông Tin Quản Lí

TP Hồ Chí Minh, ngày thing năm 2024

Trang 2

NHAN XET CUA GIANG VIEN

Trang 3

MUC LUC

Trang 5

87

Trang 6

MỞ ĐẦU

Ngôn ngữ lập trình C là một trong những ngôn ngữ cơ bản và phố biến nhất hiện nay Trong môi trường công nghệ ngày nay, sự phát triển của các trình cài đặt ngôn ngữ mới như Python, Java hay Ruby đã không làm mắt sức hấp dẫn của

C Ngược lại, ngôn ngữ C đã khắng định vai trò của mình trong việc phát triển các ứng dụng phần mềm và hệ thống Trong quá trình học và làm việc với ngôn noữ nảy, việc nam vững và hiểu rõ về cách sử dụng con trỏ (pointers) và mảng (arrays), cùng với kỹ năng xử lý đữ liệu đầu vào và đầu ra là rất quan trọng

Đề tài "Pointers and Arrays, Input and Oufput" tập trung vào hai khái niệm quan trọng trong C: Pointers and Arrays va Input and Output

Sử dụng con trỏ và mảng cho phép chúng ta truy cập và thao tác với dữ liệu một cách linh hoạt và hiệu quả Con trỏ giúp chúng ta thay đôi và truy cập trực tiếp vào vị trí bộ nhớ, mang lại sự linh hoạt và tiết kiệm tài nguyên khi làm việc với dữ liệu Máng cung cấp một cách tiện lợi đề lưu trữ và quản lý một tập hợp các giá trị liên tiếp

Ngôn ngữ C cũng cung cấp các phương thức để tương tác với người dùng và

xử lý dữ liệu đầu vào và đầu ra Hiểu rõ các khái niệm và kỹ thuật liên quan đến Input and Output giúp chúng ta tạo ra các ứng dụng linh hoạt vả tương tác tốt với người dùng, từ việc nhập dữ liệu tử bản phím tới shi dữ liệu vào tệp tin

Qua đề tài nảy, chúng ta sẽ hiểu rõ hơn về cách sử dụng con trỏ và mảng trong

C, từ việc khai báo đến thao tác với dữ liệu Chúng ta cũng sẽ nắm vững các kỹ thuật đọc và ghi đữ liệu từ và vào tệp tin, xử lý dữ liệu nhập va xuất cho người dùng

Với sự hiểu biết sâu sắc và kỹ năng xử lý Pointers and Arrays, Input and Output trong ng6n ngir C, chung ta sẽ trở thành những lập trình viên chuyên

nghiệp, có khả năng phát triển các ứng dụng mạnh mẽ và tin cậy

Trang 7

NOI DUNG BAI BAO CAO Chương 5: Con Tré Va Mang Con trỏ là một biến chứa địa chỉ thứ của biến Con trỏ được sử dụng nhiều trong C, một phần vì đôi khi chúng là cách duy nhất để biểu diễn phép tính và một phần vì chúng thường dẫn đến mã sọn nhẹ và hiệu quả hơn những cách có thé thu được bằng các cách khác Con trỏ và mảng có liên quan chặt chẽ với nhau; chương

nảy cũng khám phá mối quan hệ nảy và chỉ ra cách khai thác nó

Con trỏ được gộp chung với câu lệnh goto như một cách tuyệt vời để tạo ra những chương trình khó hiểu Điều này chắc chắn đúng khi chúng được sử dụng một cách bất cân và rất đễ tạo ra các con trỏ trỏ đến một nơi nào đó không mong đợi Tuy nhiên, với didcipline, con trỏ cũng có thê được sử dụng đề đạt được sự rõ ràng và đơn giản Đây là khía cạnh mà chúng tôi sẽ cố gắng minh họa

Thay đổi chính trong ANSI C là làm rõ các quy tắc về cách con trỏ có thể được điều khiển chính xác, trên thực tế bắt buộc những gì mà các lập trình viên giỏi đã thực hành và các trình biên dịch giỏi đã thực thị Ngoài ra, kiểu void * (con trỏ tới

void) thay thế char * làm kiểu thích hợp cho con trỏ chung

5.1 Con trỏ và địa chỉ

Chúng ta hãy bắt đầu bằng một bức tranh đơn giản về cách tô chức trí nhớ Một máy thông thường có một dãy các ô nhớ được đánh số hoặc đánh địa chỉ liên tục, có

thê được thao tác riêng lẻ hoặc theo nhóm liền kề Một tình huống phô biến là bat ky

byte nào cũng có thể là ký tự, một cặp ô một byte có thé được coi là số nguyên ngắn

và bốn byte liền kề tạo thành một số đài Con trỏ là một nhóm ô (thường là hai hoặc bốn) có thê chứa một địa chỉ Vì vậy, nếu c là một ký tự và p là một con trỏ trỏ tới

nó, chúng ta có thê biểu diễn tình huông theo cách nay:

Toán tử một ngôi và cung cấp địa chỉ của một đối tượng, do đó câu lệnh p = &c;

p gán địa chỉ của c cho biến p, và p được gọi là "trỏ tới" c Toán tử chỉ áp dụng cho

Trang 8

các đối tượng trong bộ nhớ: biến và phần tử mảng Không thê áp dụng cho biểu thức, hằng hoặc biến đăng ký

Toán tử một ngôi là toán tử gián tiếp hoặc hội thảo; khi áp dụng cho một con trỏ,

nó sẽ truy cập vào đối tượng mà con trỏ trỏ tới Giả sử rằng x và y là số nguyên vả ïp

là con trỏ tới int Trình tự nhân tạo này hướng dẫn cách khai báo một con trỏ và cách

sử dụng:

int x s 1, y = 2, z[19]§

int *ip; /* ip is a pointer to */

ip = &x; /* ip now points to x */

y = *ip; /* y is now 1 * /

*ip = 0; /* x is now @ */

ip =&z[@]; /* ip now points to z[@] */

Các khai báo x, y và z là những gì chúng ta đã thay tir lau Khai bao con tro ip,

Bạn cũng nên lưu ý hàm ý rằng một con trỏ bị hạn chế trỏ đến một loại đối tượng

cụ thê: mỗi con trỏ trỏ đến một kiểu dữ liệu cụ thẻ (Có một ngoại lệ: một "con trỏ tới void" được sử dụng để giữ bất kỳ loại con trỏ nào nhưng bản thân nó không thê bị hủy tham chiếu Chúng ta sẽ quay lai van dé nay trong Phan 1.11)

Néu ip tro dén sé nguyén x thi «ip cé thé xuat hién trong bat ky ngir canh nao ma

x có thê xảy ra, vi vay ting «ip 1én 10

*ip = *ip + 10;

Các 1 và & liên kết chặt chẽ hơn các toán tử số học, do đó phép gan lay bat ky diém ip nao trỏ tới, cộng 1 và gán kết quả cho y, trong khi

Trang 9

tang sé ip trỏ tới, làm giỗng như

++*ip (*ip)++

Dấu ngoặc đơn là cần thiết trong ví dụ cuối cùng này; không có chúng, biểu thức sẽ tang ip thay vi s6 no trỏ tới, vì các toán tử một ngôi như + và ++ liên kết từ

phải sang trái

Cuối củng, vì con trỏ là các biến nên chúng có thể được sử dụng mà không cần

trong đó hàm hoán đổi được định nghĩa là

Do gọi theo giá trị, hoán đổi không thể ảnh hưởng đến các đối số a và b trong quy

trình gọi nó Hàm ở trên chỉ hoán đổi cac bản sao của a vả b

Cách để đạt được hiệu quả mong muốn là chương trình gọi chuyển con trỏ tới các ø1á trị cần thay đôi:

Trang 10

swap(&a, &b);

Vi toan tir & tạo ra địa chỉ của một biến, &a là một con trỏ tới a Trong bản thân

trao đối, các tham số được khai báo là con trỏ và toán hạng được truy cập gián tiếp

void swap(int *px, int *py) /* interchange *px and *py */ {

không còn đầu vào Các giá trị này phải được truyền trở lại bằng các đường dẫn riêng biệt, vì bat ké giá trị nào được sử đụng cho EOE, đó cũng có thê là giá trị của một số nguyên đầu vào

Một giải pháp là yêu cầu getint trả về trạng thái cuối tệp làm giá trị hàm của nó, đồng thời sử dụng đối số con trỏ để lưu lại số nguyên đã chuyến đổi trong hàm gọi Đây cũng là sơ đồ được scanf sử dụng: xem Phần 7.4

Trang 11

Vòng lặp sau sẽ diễn vào một mảng các số nguyên băng cách gọi hàm getint: int n, array[SIZE], getint(int *);

for (n = 0; n < SIZE && getint (&array[n]) I= EOF; n++)

Mỗi cuộc gọi đặt mảng [n] thành số nguyên tiếp theo được tìm thấy trong đầu vào

và tăng dần n Lưu ý rằng cần phải chuyến địa chỉ của mảng [n] tới getint Nếu không thì không có cách nào để getint truyền lại số nguyên đã chuyên đổi cho người

void ungetch( int);

/* getint: get next integer from input into *pn */

int getint (int *pn)

{

int c, sign, EOF;

while (isspace (c = getch() )) /* skip white space */ ,

if (!isdigit (c) && c != EOF && c!='+'&& c!='-'){

return 0;

} Sign = (c ss '°-°) ? =1 : 4s

return c;

}

Trong getint, «pn duoc str dung nhu mét bién int thông thường Chúng tôi cũng đã sử dung getch va ungetch (duoc m6 ta trong Phan 4.3) để một ký tự phụ cần đọc có thể được đây trở lại đầu vào

Bài tập 5-1 Như đã viết, getint coi dâu + hoặc - không có chữ số theo sau là biểu thị hợp lệ của số 0 Hãy sửa nó đề đây ký tự đó trở lại dau vao

Trang 12

Bai tập 5-2 Viết getfloat, dạng dấu phây động tương tự của getint getfloat trả về kiêu giá trị hàm nào?

5.3 Con trỏ và mảng

Trong C, có một mỗi quan hệ chặt chẽ siữa con trỏ và mảng, đủ mạnh để con trỏ

và mảng được thảo luận đồng thoi Bat ky thao tac nào có thé dat duoc bang cach đăng ký mảng cũng có thê được thực hiện bằng con trỏ Phiên bản con trỏ nói chung

sẽ nhanh hơn nhưng ít nhất là đối với những người chưa quen, hơi khó hiểu hơn int a [10];

định nghĩa một mảng a có kích thước 10, tức là một khối gồm 10 đối tượng liên tiếp có tên a[0], a[1] , a[9]

dat pa dé tro tới phần tử 0 của a; nghĩa là pa chứa địa chỉ của a[0]

Bây giờ bài tập:

Trang 13

sẽ sao chép nội dung cua a[0] vao x

Néu pa tro đến một phần tử cụ thể của một mang thì theo định nghĩa pa+1 trỏ đến phan tir tiép theo, pati tro dén phần tử ¡ sau pa va pa-i trỏ đến phần tử ¡ trước Do đó, nếu pa trỏ tới a [0]

Những nhận xét này đúng bắt kế loại hay kích thước của các biến trong mảng a

Y nghĩa của việc "thêm 1 vao m6t con trỏ” và nói rộng ra, tất cả số học con trỏ là pa+1 trỏ đến đối tượng tiếp theo, và pa+i trỏ đến đối tượng thứ ¡ ngoài pa

Sự tương ứng giữa việc lập chỉ mục và số học con trỏ rất chặt chẽ Theo định nghĩa, giá trị của một biến hoặc biểu thức kiểu mảng là địa chỉ của phần tử 0 của mảng Như vậy sau khi gan

pa = &a[@];

pa và a có giá trị giống nhau Vì tên của một mảng là từ đồng nghĩa với vị trí

paza;

của phần tử ban đầu nên phép gán pa=&al0] cũng có thể được viết là

Đáng ngạc nhiên hơn, ít nhất là ngay từ cái nhìn đầu tiên, là việc tham chiếu đến a[i] cũng có thể được viết là *(a+ï) Khi đánh giá a[i], C chuyên đổi nó thành *(a+i) ngay lập tức; hai hình thức là tương đương Áp dụng toán tử & cho cả hai phần của

sự tương đương này, thì &a[i] và a+i cũng giống nhau: a+i là địa chỉ của phần tử thứ

1 ngoài a Mặt khác của đồng xu nay, nêu pa la một con trỏ, các biêu thức có thê sử

Trang 14

dụng nó với chỉ số dưới; pa[i] giéng hét * (pati) Noi tom lai, một biểu thức mảng và chỉ mục tương đương với biểu thức được viết đưới dạng con trỏ và offset

Có một điểm khác biệt giữa tên mảng và con trỏ cần được ghi nhớ Con trỏ là một biến, vỉ vậy pa=a và pa++ là hợp lệ Nhưng tên mảng không phải là một biến; các công trình xây dựng như a=pa và a++ là bất hợp pháp

Khi một tên mảng được truyền cho một hàm, vị trí của phần tử ban đầu sẽ được truyền vào Trong hàm được goi, đối số nảy là một biến cục bộ và do đó tham số tên mảng là một con trỏ, tức là một biến chứa địa chỉ Chúng ta có thé str dung thực té nay để viết một phiên bản khác của strlen, tính toán độ dài của một chuỗi

/* strlen: return Length of string s */

int strlen(char *s)

{

int n;

for (n @; *s I= ‘'\@: s++) n++;

return n;

}

Vi s là một con trỏ nên việc tắng nó là hoàn toàn hợp pháp; s++ không có tác

dụng øì đối với chuỗi ký tự trong hàm được gọi là strlen mà chỉ tăng bản sao riêng

của con trỏ strlen Là tham số hình thức trong định nghĩa hàm, tương đương: chúng

tôi thích cái sau hơn vì nó nói rõ ràng hơn rằng tham số là một con trỏ

strlen("hello, world"); /* string constant */

Trang 15

Nó thậm chí có thê sử dụng cả hai ký hiệu nêu thay phù hợp và rõ ràng

Có thê truyền một phân của mảng cho hàm băng cách truyền một con trỏ đến đâu mang con Ví dụ: nêu a là một mảng,

f(&a[2])

{(a+2)

đều truyền cho hàm f địa chỉ của mang con bat dau tại a[2] Trong phạm vi f,

phần khai báo tham số có thê đọc

hoặc

t(SwœEgdrpoE 1Ì 4 c }

Vi vay, đối với f, việc tham số đề cập đến một phần của mảng lớn hơn không có ý nghĩa gì

Nếu chắc chắn rằng các phân tử tồn tai thì cũng có thê lập chỉ mục ngược trong một mảng:: p[-l], pl-2], v.v về mặt cú pháp là hợp pháp và đề cập đến các phần tử noay trước p[0] Tất nhiên, việc đề cập đến các đối tượng không nằm trong giới hạn mảng là bất hợp pháp

5.4 Phép toán số học trên con trồ

Nếu p là một con trỏ tới một phần tử nào đó của một mảng, thì p++ sẽ tăng p để trỏ đến phần tử tiếp theo và p+ = ¡ sẽ tăng nó đề trỏ đến các phần tử ¡ ngoài vị trí hiện tại Những cấu trúc này và những cấu trúc tương tự là những dạng đơn giản nhất của

số học con trỏ hoặc địa chỉ

C nhất quán và thường xuyên trong cách tiếp cận giải quyết số học; sự tích hợp của con trỏ, mảng và số học địa chỉ là một trone những điểm mạnh của ngôn ngữ Hay dé chung tôi minh họa bằng cách viết một bộ cấp phát lưu trữ thô sơ Có hai thói quen Đâu tiên, alloc(n), trả về một con trỏ p tới n vị trí ký tự liên tiếp, con trỏ này

có thê được người gọi alloc sử dụng để lưu trữ các ký tự Thứ hai, miễn phí (p), giải phóng bộ nhớ đã thu được đề có thể sử dụng lại sau này Các thủ tục còn thô so" vi các cuộc gọi tới affee phải được thực hiện theo thử tự ngược lại với các cuộc gọi

Trang 16

được thực hiện trên alloc Tức là bộ lưu trữ được quản lý bởi alloc và afree là một ngăn xếp hoặc danh sách vảo sau, ra trước Thư viện chuẩn cung cấp các hàm tương

tự được gọi là malloc và free không có hạn chế nào như vậy; (trong Phần 8.7 chúng tôi sẽ chỉ ra cách thực hiện chúng)

Cách triển khai đễ dàng nhất là phân bố các phần của mảng ký tự lớn mà chúng ta

sẽ goi la allocbuf Mang nay là riêng tư để phân bổ và miễn phí Vì chúng xử lý các

con trỏ chứ không phải các chỉ mục mảng nên không có thủ tục nào khác cần biết tên

của mảng, tên này có thê được khai báo tĩnh trong tệp nguồn chứa phân bổ và afree,

và do đó vô hình bên ngoải nó Trong triển khai thực tế, mảng thậm chí có thể không

có tên; thay vào đó, nó có thể có được bằng cách gọi malloc hoặc bằng cách yêu cầu

hệ điều hành cung cấp một con trỏ tới một khối lưu trữ không tên nào đó

Thông tin cần thiết khác là lượng allocbuf đã được sử dụng Chúng tôi sử dụng một con trỏ, được gọi là allocp, trỏ đến phần tử miễn phí tiếp theo Khi cấp phát được yêu cầu n ký tự, nó sẽ kiểm tra xem liệu còn đủ chỗ trong cấp phát £ hay không Nếu vậy, al1lÚc trả về giá trị hiện tại của allocp (tức là phần đầu của khối trống), sau đó

before call to alloc:

allocp: \ allocbuf: | | | | | |

in use free ——e

after call to alloc:

allocp: `

allocbuf: II | | | | |

q—— in use = free ——e

tăng nó lên n để trỏ đến vùng trồng tiếp theo Nếu không còn chỗ trống, alloc tra về

0 afree(p) chi đặt allocp thành p nếu p nằm trong allocbuf

Trang 17

static char allocbuf[ ALLOCSIZE]; /*storage for alloc */ static char *allocp = allocbuf; /* next free position */ char *alloc(int n) /* return pointer to n characters */

{

if (allocbuf + ALLOCSIZE - allocp >=) { /* it fits */ allocp += n;

return allocp - n; /* old p */

định nghĩa allocp là một con trỏ ký tự và khởi tạo nó để trỏ đến điểm bắt đầu của allocbuf, đây là vị trí trống tiếp theo khi chương trình bắt đầu Điều này cũng có thể được viết

static char *allocp = &allocbuf [9 ];

vì tên mảng là địa chỉ của phần tử thứ 0

if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fit */

kiểm tra xem có đủ chỗ đề đáp ứng yêu cầu cho n ký tự hay không Nếu có, giá trị mới của allocp sẽ cao nhất là một giá trị nằm ngoài phân cuối của allocbuf Nếu yêu cầu có thê được thỏa mãn, alloc trả về một con trỏ về đầu khối ký tự (chú ý phần khai báo của chính hàm đó) Nếu không thì phân bổ phải trả lại một số tín hiệu cho thấy không còn khoảng trống C đảm bảo rằng số 0 không bao giờ là địa chỉ hợp lệ cho đữ

Trang 18

liệu, do đó giá trị trả về bằng 0 có thê được sử dụng để báo hiệu một sự kiện bất thường, trong trường hợp này là không có khoảng trang

Con trỏ và số nguyên không thế thay thế cho nhau Số 0 là ngoại lệ duy nhất: hằng số 0 có thể được sán cho một con trỏ và một con trỏ có thể được so sánh với hằng số 0 Hằng số ký hiệu NULL thường được sử dụng thay cho số 0, như một cách ghi nhớ để chỉ rõ hơn rằng đây là một giá trị đặc biệt cho một con trỏ NULL Ï được xác định trong <stdio.h> Chúng tôi sẽ sử dụng NULL từ đó trở di

Các phép thử như

if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fit /

va

if (p >= allocbuf && p < allocbuf + ALLOCSIZE)

hiển thị một số khía cạnh quan trọng của số học con trỏ Đầu tiên, con trỏ có thể được so sánh trong những trường hợp nhất định Nếu p và q trỏ đến các thành viên của củng một mảng thi các quan hệ như =, !=, <, >z, v.v., sẽ hoạt động bình thường p<q

Vi du,

đúng nếu p trỏ tới một thành viên sớm hơn q Bất kỳ con trỏ nào cũng có thể được

so sánh một cách có ý nghĩa về đăng thức hoặc bất đẳng thức bằng 0 Nhưng hành vi không được xác định đối với số học hoặc so sánh với các con trỏ không trỏ đến các thành viên của củng một mảng (Có một ngoại lệ: địa chỉ của phần tử đầu tiên sau cudi mảng có thể được sử dụng trong số học con trỏ.)

Thứ hai, chúng ta đã quan sát thấy rằng một con trỏ và một số nguyên có thể được cộng hoặc trừ Công trình xây dựng

p+n

có nghĩa là địa chỉ của đối tượng thứ n ngoài đối tượng mà p hiện trỏ tới Điều

này đúng bất kê loại đối tượng p trỏ tới; n được chia tý lệ theo kích thước của đối

tượng p trỏ tới, được xác định bởi khai báo của p Ví dụ: nếu một int là bốn byte thì 1nt sẽ được chia tỷ lệ thành bốn

Phép trừ con trỏ cũng hợp lệ: nếu p và q trỏ đến các phần tử của cùng một mảng

Trang 19

và p-s cho biết số lượng ký tự được nâng cao, tức là độ dài chuỗi (Số lượng ký tự trong chuỗi có thể quá lớn để lưu trữ trong một int Tiêu đề <stádefh xác định một loại ptrdiff t đủ lớn để chứa sự khác biệt có dấu của hai giá trị con trỏ Tuy nhiên, nếu chúng ta rất thận trong , chung téi sé str dung size_t cho kiéu tra về của strlen, dé khớp với phiên bản thư viện chuẩn size_t là kiểu số nguyên không dấu được trả về boi toan tir sizeof.)

Số học con trỏ là nhất quán: nếu chúng ta đang xử lý £loats, cái mà chiếm nhiều dung lượng hơn ký tự và nếu p là một con trỏ nỗi, p++ sẽ chuyên sang #loat tiếp theo Do đó, chúng ta có thể viết một phiên bản khác của alloc duy trì floats thay vi

ký tự, chỉ bằng cách thay đôi char thành float trong toàn bộ alloc và afree All các thao tác con trỏ sẽ tự động tính đến kích thước của đối tượng được trỏ tới

Các phép toán con trỏ hợp lệ là phép gán các con trỏ cùng loại, cộng hoặc trừ một con trỏ và một số nguyên, trừ hoặc so sánh hai con trỏ với các thành phần của cùng một mảng và gán hoặc so sánh với 0 Tất cả các số học con trỏ khác là bất hợp pháp Sẽ không hợp lý khi cộng hai con trỏ, hoặc nhân hoặc chia, dịch chuyền hoặc che dấu chúng, hoặc thêm float hoặc đouble cho chúng, hoặc thậm chí, ngoại trừ void

#, oán một con trỏ cùng loại cho một con trỏ có kiểu một loại khác không có diễn viên

Trang 20

Có lẽ sự xuất hiện phổ biến nhất của hằng chuỗi là làm đối số cho hàm, như trong printf( “hello, world\n" ) ;

Khi một chuỗi ký tự như thế này xuất hiện trong một chương trình, việc truy cập vào nó phải thông qua một con trỏ ký tự; printf nhận một con trỏ tới đầu mang ky tự Nghĩa là, một hằng chuỗi được con trỏ truy cập tới phần tử đầu tiên của nó Các hằng

chuỗi không nhất thiết phải là đối số của hàm Nếu pmessage được khai báo là

char *pmessage ;

thì câu lệnh

pmessage = “now is the time " ;

gán để nhắn tin cho một con trỏ tới mảng ký tự Đây không phải là một bản sao chuỗi; chỉ có con trỏ có liên quan C không cung cấp bất ky toán tử nào để xử lý toàn

bộ chuỗi ký tự dưới dạng một đơn vị

Có một sự khác biệt quan trọng g1ữa các định nghĩa này:

char amessage[] = “now this the time"; /* an aray */ char *pmessage = “now this the time"; /* a pointer*/

một con trỏ tin nhắn là một mảng, chỉ đủ lớn để chứa chuỗi ký tự và '\ 0 khởi tạo

nó Các ký tự riêng lé trone mảng có thê được thay đổi nhưng tin nhắn sẽ luôn đề cập đến củng một bộ lưu trữ Mặt khác, pmessase là một con trỏ, được khởi tạo để trỏ tới một hằng chuỗi; con trỏ sau đó có thể được sửa đổi để trỏ đến nơi khác, nhưng kết quả sẽ không được xác định nêu bạn cô sửa đối nội dung chuối

- ~

pmessage: now is the time\0

amessage: | now is the time\0

Trang 21

Chung ta sé minh hoa nhiéu khia canh hon vé con tro va mang bang cach nghién

cứu các phiên ban cua hai hàm hữu ích được điều chỉnh từ thư viện chuẩn Hàm đầu tiên là strepy(s , t), sao chép chuỗi t thành chuỗi s Sẽ thật tuyệt nếu chỉ nói nhưng điều này sao chép con trỏ chứ không phải ký tự Để sao chép các ký tự, chúng ta cần một vòng lặp Phiên bản mảng là đầu tiên:

/* strcpy: copy t to s; array subscript version */

void strcpy(char *s, char *t)

Ngược lại, đây là phiên bản của strcpy có con trỏ:

/* strcpy: copy t to s ; pointer version 1*/

void strcpy(char *s, char *t)

{ while ((*s = *t) != '\@') {

St+;

t ++;

} }

Bởi vì các đối số được truyền theo 214 tri, strepy có thé str dung cac tham SỐ s và t theo bất ky cach nao nd muốn Ở đây, chúng là các con trỏ được khởi tạo một cách thuận tiện, được tuần tự dọc theo mảng theo từng ký tự, cho đến khi 'XO' kết thúc t được sao chép vào s

Trong thực tế, strcpy sẽ không được viết như chúng tôi đã trình bảy ở trên Các

lập trình viên C có kinh nghiệm sẽ thích

Trang 22

/* strcpy: copy t to s ; pointer version 2*/

void strcpy(char *s, char *t)

dấu kết thúc "0°

Là chữ viết tắt cuối cùng, hãy lưu ý rằng việc so sánh với ^0' là không cần thiết,

vì câu hỏi chỉ là liệu biểu thức có bằng 0 hay không Vì vậy, hàm này có thể được viết là

/* strcpy: copy t to s ; pointer version 3*/

void strcpy(char *s, char *t)

while ((*s ++= *t++) ,

Mặc dù điều nay thoạt nhìn có vẻ khó hiểu, nhưng sự thuận tiện về mặt ký hiệu là rất đáng kê và bạn nên nắm vững thành ngữ này vì bạn sẽ thấy nó thường xuyên trong các chương trinh C

strepy trong thư viện chuẩn (<string.h> trả về chuỗi đích làm giá trị hàm của nó Thủ tục thứ hai mà chúng ta sẽ kiểm tra la stremp(s , t), so sánh các chuỗi ký tự s

và t và trả về âm, 0 hoặc dương nếu s nhỏ hơn, bằng hoặc lớn hơn t về mặt từ điển Giá trị thu được bằng cách trừ các ký tự ở vị trí đầu tiên trong đó s và t không đồng

ý

Trang 23

return s[i] - t[i];

return *s - *t;

Vi + và là toán tử tiền tố hoặc hậu tố nên các kết hợp khác của * và ++ và xảy

ra, mặc dù ít thường xuyên hơn Ví dụ,

* p

giảm p trước khi lay ký tự mà p trỏ tới Trên thực tế, cặp biểu thức

*p++ = val; /* push val onto stack */

val = * p; /* pop top of stack into val */

là những thành ngữ tiêu chuân đề chỉ việc đây và bật một ngăn xếp; xem Phần

Bài tập 5-4 Viết hàm strtrend(s, t), trả về 1 nêu chuỗi t xuất hiện ở cuối chuỗi s

và trả về 0 nếu ngược lại

Bài tập 5-5 Viết các phiên bản của các hàm thư viện strncpy, strncat và strncmp,

Trang 24

hoạt động trên tối đan ký tự đầu tiên của chuỗi đối số của chúng Ví dụ, strncpy(s, t, n) sao chép tôi đa n ký tự của t vào s Mô tả đầy đủ có tại Phụ lục B O

Bài tập 5-6 Viết lại các chương trình và bài tập phù hợp từ các chương trước và bài tập bằng con trỏ thay vì lập chỉ mục mảng Các khả năng tốt bao gồm getline (Chương 1 và 4), atoi, itoa và các biến thể của chúng (Chương 2, 3 và 4), đảo ngược (Chương 3), strindex và øetop (Chương 4)

5.6 Máng con trổ; Con trô tới con trổ

Vì bản thân con trỏ là các biến nên chúng có thể được lưu trữ trong mảng giống

như các biến khác Hãy để chúng tôi minh họa bằng cách viết một chương trình sắp

xếp một tập hợp các dòng văn bản theo thứ tự bảng chữ cái, một phiên bản rút gọn của cách sắp xếp chương trình UNIX

Trong Chương 3, chúng tôi đã trình bảy hàm sắp xếp Shell để sắp xếp một mang các số nguyên và trong Chương 4, chúng tôi đã cải thiện chức năng này bằng tính năng sắp xếp nhanh Các thuật toán tương tự sẽ hoạt động, ngoại trừ việc bây giờ chúng ta phải xử lý các dòng văn bản có độ dài khác nhau và không giống như số nguyên, không thế so sánh hoặc đi chuyền trong một thao tác Chúng ta cần một cách trinh bày dữ liệu có thể xử lý hiệu quả và thuận tiện với các dòng văn bản có độ dài

thay đối

Đây là nơi mảng con trỏ đi vào Nếu các dòng cần sắp xếp được lưu trữ nối tiếp nhau trong một mảng ký tự dài thì mỗi dòng có thế được truy cập bằng một con trỏ tới ký tự đầu tiên của nó Bản thân các con trỏ có thể được lưu trữ trong một mảng

Hai dòng có thế được so sánh bằng cách chuyên con trỏ của chúng tới stremp Khi

hai dòng không theo thứ tự phải được trao đổi, các con trỏ trong mảng con trỏ sẽ được trao đôi chứ không phải các dòng văn bản

Trang 25

ra

Thủ tục nhập phải thu thập và lưu các ký tự của mỗi dòng và xây dựng một mang con trỏ tới các dòng Nó cũng sẽ phải đếm số dòng đầu vào, vi thong tin đó cần thiết

cho việc sắp xếp và in ấn Vì hàm đầu vào chỉ có thể xử lý một số dòng đầu vào hữu

hạn nên nó có thé trả về một số đòng không hợp lệ như -1 nếu có quá nhiều đầu vào Thủ tục đầu ra chỉ phải ín các dòng theo thứ tự chúng xuất hiện trong mảng con

trỏ

#include <string.h>

#define MAXLINES 5909 /* max #Lines to be sorted */

char *lineptr[MAXLINES]; /* pointers to text Lines */

int readlines(char *lineptr[], int nlines);

void writelines(char *lineptr[], int nlines);

void gsort(char lineptr[], int left, int right);

/* sort input Lines*/

main()

{

int nlines; /* number of input Lines read */

if ((nlines = readlines(lineptr, MAXLINES)) >= 9 ){

qsort(lineptr, @ , nlines-1);

writelines(lineptr, nlines);

return 0;

} else { printf ("error: input too big to sort\n");

return 1;

Trang 26

#define MAXLEN 1000 /* max Length of any input Line */

int getline(char *, int);

char *alloc(int);

/* readlines: read input Lines */

int readlines(char *lineptr[], int maxline)

{

int len, nlines;

char *p, line[MAXLEN];

nlines = 9;

while ((len = getline(line, MAXLEN)) >@)

if (nlines >= maxlines || (p = alloc(len)) == NULL)

return -1;

else { line[len-1] = '\@'*; /* delete newline */

strcpy(p, line);

lineptr[nlines++] = p;

return nlines;

/* writelines: write output Lines */

void writelines(char *lineptr[], int nlines)

{

int i;

for (i = @; ix nlines; i++) printf ("%s\n", lineptr[i]);

Ham getline lay tir Phan 1.9

Điểm mới chính là phần khai báo cho lineptr:

char *lineptr [MAXLINES]

nói rằng lineptr là một mảng gồm các phần tử MAXLINE§, moi phan tir trong đó là một con trỏ tới một ký tự Nghĩa là,

lineptr[i] là một con trỏ ký tự và là ký tự mà nó trỏ tới, ký tự đầu

tiên của dòng văn bản được lưu thứ nhất

Vi lineptr ban thân nó là tên của một mảng nên nó có thê được

coi như một con trỏ theo cách tương tự như trong các ví dụ trước

của chúng ta và thay vào đó, các dòng ghi có thể được viết dưới

Trang 27

dang

/* writelines: write output Lines */

void writelines(char *lineptr[], int nlines)

while (nlines > @) printf("%s\n", *lineptr++);

Ban dau *lineptr tro đến dòng đầu tiên; mỗi bước tăng nó tới con trỏ dòng tiếp theo trong khi n dòng được đếm ngược

Với đầu vào và đầu ra được kiểm soát, chúng ta có thé tiến hành sắp xếp Sắp xếp nhanh trong Chương 4 cần có những thay đổi nhỏ: các khai báo phải được sửa đôi và

thao tác so sánh phải được thực hiện bằng cách gọi stremp Thuật toán vẫn giữ nguyên, điều này giúp chúng tôi tin tưởng rằng nó vẫn sẽ hoạt động

/* qsort: sort v[left] v[right] into increasing order */

void qsort(char *v[], int left, int right)

{

int i, last;

void swap(char *v[], int i, int j);

if (left >= right) /* do nothing if array contains*/

return; /* fewer than two elements*/

swap(v, left, (left + right)/2);

last = left;

for (i = left+1; i <= right; i++)

if (strcmp(v[i], v[left]) <0)

swap(v, ++last, i);

swap(v, left, last);

qsort(v, left, last-1);

qsort(v, last+1, right);

}

Tương tự, quy trình trao đôi chỉ cần những thay đổi nhỏ:

/* swap: interchange v[i] and v[j] */

void swap(char *v[], int i, int j)

Trang 28

Vì bất kỳ phần tử riêng lẻ nào của v (bí danh lineptr) déu 1a mét con trỏ ký tự, nên temp cũng phải như vậy, đo đó phần tử này có thế được sao chép sang phần tử khác Bài tập 5-7 Viết lại các dòng đọc để lưu trữ các dòng trong một mảng do main cung cấp, thay vì gọi alloc để duy trì việc lưu trữ Chương trình nhanh hơn bao

nhiêu?

5.7 Mảng đa chiều

C cung cấp các mảng đa chiều hình chữ nhật, mặc dù trong thực tế chúng ít được

sử dụng hơn nhiều so với mảng con trỏ Trong phần này, chúng tôi sẽ hiến thị một số tính chất của chúng

Xét bài toán chuyền đổi ngày, từ ngày trong tháng sang ngày trong năm và ngược lại Ví dụ: Ngày 1 tháng 3 là ngày thứ 60 của năm không nhuận và là ngày thử 61 của năm nhuận Chúng ta hãy xác định hai hàm để thực hiện chuyên đổi: day_ của _ năm chuyển đổi tháng và ngày thành ngày trong năm và tháng ngày chuyên đổi ngày

trong năm thành tháng và ngày Vì hàm sau nảy tính toán hai giá trị nên các đối số

tháng và ngày sẽ là con trỏ:

month_day( 1988, 60,&m, &d)

đặt m thành 2 và d thành 29 (29 tháng 2)

Cả hai hàm này đều cần thông tin giống nhau, một bảng số ngày trong mỗi tháng

("ba mươi ngày có tháng 9 ") Vì số ngày mỗi tháng khác nhau giữa năm nhuận và năm không nhuận nên việc tách chúng thành hai hàng của mảng hai chiều sẽ đễ dàng hơn so với việc theo đối những øì xảy ra với tháng Hai trong quá trình tính toán Mảng và các hàm đề thực hiện các phép biên đôi như sau:

Trang 29

24

static char daytab[2][13] = {

{@, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {9, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

};

/* day_of_year: set day of year from month & day */

int day_of_year(int year, int month, int day)

{

int i, leap;

leap = year%4 == 0 && year%100 != @ || year%400 == @;

for (i =1; i< month; i++)

day += daytab[leap][i];

return day;

}

/*month_day: set month, day from day of year */

void month_day(int year, int yearday, int *pmonth, int *pday)

{

int i, leap;

leap = year%4 == 0 && year%100 != 9 || year%400 == 0;

for (i = 1; yearday > daytab[leap][i]; i++)

Mang daytab phai nam ngoài cả ngày trong năm và tháng _ ngày để cả hai đều

có thể sử dụng Chúng tôi tạo nó thành char để minh họa cách sử dụng char hợp pháp

để lưu trữ các số nguyên nhỏ không phải ký tự

daytab là mảng hai chiều đầu tiên chúng ta xử lý Trong C, mảng hai chiều thực chất là mảng một chiều, mỗi phần tử của nó là một mảng Do đó, chỉ số dưới được viết dưới dạng

daytab[{ i][ j ] /* [row][col] */

daytab[ i, j ] /* WRONG*/

thay vi

Ngoài sự khác biệt về ký hiệu này, mảng hai chiêu có thê được xử ly theo cách

Trang 30

tương tự như trong các ngôn ngữ khác Các phần tử được lưu trữ theo hàng, vì vậy chỉ số đưới củng bên phải hoặc cột sẽ thay đổi nhanh nhất khi các phần tử được truy cập theo thứ tự lưu trữ

Một mảng được khởi tạo bằng danh sách các giá trị khởi tạo trone dấu ngoặc nhọn; mỗi hàng của mảng hai chiều được khởi tạo bởi một danh sách con tương ứng Chúng tôi đã bắt đầu bảng ngày của mảng với một cột bằng 0 để các số tháng có thé chạy tử I đến 12 tự nhiên thay vì 0 đến II Vì không gian ở đây không được ưu tiên lắm nên điều này rõ ràng hơn việc điều chỉnh các chỉ số

Nếu muốn truyền mảng hai chiều vào hàm thì khai báo tham số trong hàm phải bao gồm số cột; số lượng hàng không liên quan, vì những øì được truyền, như trước

đây, là một con trỏ tới một mảng các hàng, trong đó mỗi hàng là một mảng gồm 13

số nguyên Trong trường hợp cụ thé nay, nó là một con trỏ tới các đối tượng là mảng

13 int Vì vậy, nếu bảng ngày của mảng được chuyền cho hàm f thì khai báo của f sẽ

là một mảng gồm 13 con trỏ tới số nguyên Tông quát hơn, chỉ có chiều thứ nhất (chỉ

số dưới) của một mảng là miễn phí; tat cả những thứ khác phải được chỉ định Mục 5.12 thảo luận sâu hơn về các khai báo phức tạp

Bài tập 5-8 Không có lỗi kiếm tra ngày _ của _ năm hoặc tháng ngày Hãy khắc phục khiếm khuyết này

Trang 31

5.8 Khởi tạo mảng con trỏ

Hãy xem xét vấn đề viết hàm month_name (n), hàm này trả về một con trỏ tới

một chuỗi ký tự chứa tên của tháng thứ n Đây là một ứng dụng lý tưởng cho mảng

tĩnh bên trong Tên tháng _ chứa một mảng chuỗi ký tự riêng va trả về một con trỏ tới chuỗi ký tự thích hợp khi được gọi Phần này cho thấy mảng tên đó được khởi tạo như thế nào

Cú pháp tương tự như các lần khởi tạo trước đó:

/* month name: return name of n-th month */

char *month_name(int n)

{

static char *name[] = {

"Illegal month",

"January", “February”, "March",

"April", "May", “June”,

"July", "August", "September",

"October", "November", "December"

chúng được lưu trong tên|I] Vì kích thước của tên mảng không được chỉ định, trình

biên dịch sẽ đêm các phân khởi tạo và điện vào số chính xác

5.9 Con trỏ so với mảng đa chiều

Những người mới sử dụng C đôi khi bị nhằm lẫn về sự khác biệt gitra mang hai chiéu va mang con tro, chang han nhw tén trong vi du trên Đưa ra các định nghĩa int a[10][20];

int *b[10];

sau đó a[3][4] và b[3][4] đều là các tham chiếu hợp pháp về mặt cú pháp cho một int duy nhất Nhưng a là một mảng hai chiều thực sự: 200 vị trí có kích thước int di được đặt sang một bên và phép tính chỉ số dưới hình chữ nhật thông thường

Trang 32

20xrow+col được sử dụng để tìm phần tử a[row][col] Tuy nhiên, đối với b, định nghia chỉ cấp phát 10 con trỏ và không khởi tạo chúng: việc khởi tạo phải được thực hiện một cách rõ ràng, tĩnh hoặc bằng mã Giả sử rằng mỗi phần tử của b chỉ đến một mảng 20 phần tử thì sẽ có 200 số nguyên được đặt sang một bên, cộng với 10 ô dành cho con trỏ Ưu điểm quan trọng của mảng con trỏ là các hàng của mảng có thể

có độ dài khác nhau Nghĩa là, mỗi phần tử của b không cần trỏ đến một vecto hai mươi phần tử; một số có thê trỏ đến hai phần tử, một số đến năm mươi và một số không có gì cả

Mặc dù chúng tôi đã diễn đạt cuộc thảo luận này về các số nguyên, cho đến nay việc sử dụng thường xuyên nhất các mảng con tró là lưu trữ các chuỗi ký tự có độ dài

đa dạng, như trone hàm month_name So sánh khai báo và hình ảnh cho một mảng con trỏ:

char *name[] = { “Illegal month", "Jan", "Feb", "Mar" };

vẻ

| —-any]

-} te)

với những thứ đó cho một mảng hai chiều:

char aname[][15] = { “Illegal month", "Jan", "Feb", "Mar" };

Trang 33

hai đối số Đối số đầu tiên (thường được goi la argc, để đếm đối số) là số lượng đối

số dòng lệnh mà chương trình được gọi; thứ hai (argv, cho vectơ đối số) là một con trỏ tới một mảng các chuỗi ký tự chứa các đối số, mỗi chuỗi một đối số Chúng tôi xin- thường sử dụng nhiều cấp độ con trỏ để thao tác các chuỗi ký tự này

Minh họa đơn giản nhất là chương trình echo, lặp lại các đối số dòng lệnh của nó trên một dòng duy nhất, cách nhau bằng khoảng trắng Đó là lệnh

echo hello, world

hello, world

in dau ra

Theo quy ước, argv [0] là tên mà chương trình được gọi, vì vậy arge ít nhat la 1 Néu arge là 1, không có đối số dòng lệnh nảo sau tên chương trình Trong ví dụ trên, argc la 3, và arev[0], arev [1] và argv [2] là "echo", "hello,", và "world" Đối số tùy chọn đầu tiên là arev[I] và đối số cuối cùng là arev[argc-l]; ngoài ra, tiêu chuẩn yêu cầu argv[arec] phải là con tro trong

Phiên bản đâu tiên của echo coi arev là một mảng các con trỏ ký tự:

/* echo command-line arguments; Ist version */

main(int argc, char *argv[])

{

int i;

for (i = 1; i < argc; i++)

printf("%s%s", argv[i], (i < argc-1) ? ""; " "); printf("\n");

return 0;

}

Vi arev là một con trỏ tới một mảng các con trỏ, nên chúng ta có thê thao tác với con trỏ thay vì lập chỉ mục cho mảng Biên thê tiếp theo này dựa trên việc tăng dân arev, là một con trỏ tới con trỏ tới char, trong khi arev được đếm ngược:

Trang 34

#include <stdio.h>

/* echo command-line arguments; 2nd version */

main(int argc, char *argv[])

đó Đồng thời, arge được giảm đề cập đến; khi nó trở thành 0, không còn đối số nào

dé in

Ngoài ra, chúng ta có thê viết câu lệnh printf đưới dạng

printf((argc › 1) ? "%s ": "Xs", *#++argv);

Điều này cho thấy đối số định dạng của printf cũng có thế là một biểu thức

Vi dụ thứ hai, chúng ta hãy thực hiện một số cải tiền cho chương trình tìm mẫu từ Phần 4.1 Nếu bạn nhớ lại, chúng tôi đã đưa mô hình tìm kiếm vào sâu trong chương trình, một sự sắp xếp rõ ràng là không thỏa đáng Theo hướng dẫn của grep chương trình UNIX, chúng ta hãy thay đôi chương trình để mẫu phù hợp được chỉ định bởi

đôi sô đầu tiên trên dòng lệnh

Trang 35

#include < stdio.h>

#include <string.h>

#define MAXLINE 1000

int getline(char *line, int max);

/* find: print Lines that match pattern from 1st arg */

main(int argc, char *argv[])

{

char line[MAXLINE];

int found = 0;

if (argc !=2) printf("Usage: find pattern\n");

else while (getline(line, MAXLINE) > @)

if (strstr(line, argv[1]) != NULL){

Một quy ước chung cho các chương trình C trên hệ thống UNIX là một đối số bắt đầu bằng dấu trừ sẽ giới thiệu một cờ hoặc tham số tủy chọn Nếu chúng ta chọn -x (for "except") để báo hiệu đảo ngược và -n ("number") để yêu cầu đánh số dòng, thi

fine -x-n pattern

lệnh

sẽ in từng dòng không khớp với mẫu, trước số dòng của nó

Các đối số tùy chọn phải được cho phép theo bất kỳ thứ tự nào và phần còn lại của chương trình phải độc lập với sô lượng đôi sô có mặt Hơn nữa, sẽ thuận tiện cho

fine -nx pattern

Trang 36

người dùng nếu các đối số tủy chọn có thé được kết hợp, như là

Đây là chương trình:

#include <stdio.h>

#include <string.h>

#define MAXLINE 1998

int getline(char *line, int max);

/* find: print Lines that match pattern from ist arg */

main(int argc, char *argv[])

char line[MAXLINE];

long lineno = @;

int c, except = @, number = @, found = @;

while ( argr >@ && (*++argv)[@] == ‘'-')

while (c - *++argv[@]})

switch (c){

case '' noe except = 1;

else while (getline(line, MAXLINE) > @) { lineno++;

if ((strstr(line, *argv) !=NULL) != except){

if (number) printf("%ld:", lineno);

arec được giảm đi và arev được tăng lên trước mỗi đối số tùy chọn Ở cuối vòng

lặp, nếu không có lỗi, argv sẽ cho biết có bao nhiêu đối số vẫn chưa được xử lý vả

argv trỏ đến đối số đầu tiên trong số này Như vậy argc phải là l và *argv sẽ trỏ vào mau Luu y rang *++argv la mét con trỏ tới một chuỗi đối số, vì vay (*++argv)[0] la

ký tự đầu tiên của nó (Một đạng hợp lệ thay thé sé la **++arev.) Vi [] lién két chặt

Trang 37

chẽ hơn * và++ nên cần có dấu ngoặc đơn; không có chúng, biểu thức sẽ được coi là

*++(argv[0]) Trén thực tế, đó là những øì chúng tôi đã sử dụng trong vòng lặp bên trong, trong đó nhiệm vụ là duyệt dọc theo một chuỗi đối số cụ thế Ở vòng lặp bên trong, biểu thức #++arev[0] tăng con trỏ arev{[0]!

Hiếm khi người ta sử dụng các biểu thức con trỏ phức tạp hơn những biểu thức

này; trong những trường hợp như vậy, chia chúng thành hai hoặc ba bước sẽ trực

quan hơn

Bài tập 5-10 Viết chương trình expr, đánh giá một biểu thức Ba Lan đảo ngược từ dòng lệnh, trong đó mỗi toán tử hoặc toán hạng là một đối số riêng biệt

expr 2 3 4 - * evaluates 2 x (3+4)

Bài tập 5-11 Sửa đôi chương trình entab và detab (được viết đưới dạng bài tap trong Chương 1) để chấp nhận danh sách các điểm dừng tab làm đối số Sử dụng cài đặt tab mặc định nếu không có đối số

Bài tập 5-12 Mở rộng entab và detab để chấp nhận tốc ký

tail -n

in n dong cuéi cing Chuong trinh phải hoạt động hợp lý bất kế thế nào đầu vào hoặc giá trị của n không hợp lý Hãy viết chương trình để nó thực hiện sử dụng tốt nhất dung lượng lưu trữ sẵn có; các dòng nên được lưu trữ như trong chương trình sắp xếp của Mục 5.6, không phải trong mảng hai chiều có kích thước có định

5.11 Con trồ đến hàm

Trong C, bản thân hàm không phải là một biến nhưng có thể định nghĩa các con

trỏ tới các hàm có thể được gan, duoc dat trong mảng, được truyén cho cac ham, được trả về theo chức năng, v.v Chúng tôi sẽ minh họa dieu nay bang cach sua doi

Trang 38

quy trình sắp xếp quy trình được viết trước đó trong chương này để nếu đối số tùy chọn -n là nhất định, nó sẽ sắp xếp các đòng đầu vao theo sé thay vi theo tir điền Việc sắp xếp thường bao gồm ba phần - phần so sánh xác định thứ tự của bất kỳ cặp đối tượng nào, một trao đổi đảo ngược thứ tự của chúng và một thuật toán sắp xếp thực hiện so sánh và trao đối cho đến khi các đối tượng theo đúng thứ tự Thuật toán sắp xếp độc lập với các phép toán so sánh và trao đổi nên bằng cách chuyên các ham so sánh và trao đổi khác nhau cho nó, chúng ta có thể sắp xếp để sắp xếp theo các tiêu chí khác nhau Đây là cách tiếp cận được thực hiện theo cách mới của chúng

tÔI

So sánh từ điển của hai đòng được thực hiện bởi stremp, như trước đây; chúng ta cũng sẽ cần một hàm numecmp thường lệ để so sánh hai dòng trên cơ sở giá trị số và trả về củng loại chỉ báo điều kiện như stremp Các hàm này được khai báo trước hàm main và một con trỏ tới hàm thích hợp sẽ được chuyến tới qsort Chúng tôi đã lược

bo việc xử lý lỗi trong các tranh luận đề tập trung vào các vân đề chính

Trang 39

Trong lệnh gọi qsort, stremp va numemp 1a dia chi cua ham Vi chung duoc biét

đến là các hàm, toán tử & không cần thiết, theo cách tương tự rằng nó không cần thiết trước tên mảng

Chúng tôi đã viết qsort dé nó có thể xử lý bất kỳ loại dữ liệu nào, không chỉ ký tự dây Như được chỉ ra bởi nguyên hàm, qsort mong đợi được mảng các con trỏ, hai số nguyên và một hàm có hai đối số con trỏ Kiểu con trỏ chung void * được sử dụng cho các đối số con trỏ Bất kỳ con trỏ nào cũng có thê được chuyền thành void * và quay lại mà không mất thông tin, vì vậy chúng ta có thê gọi qsort bằng cách chuyển đổi các đối số thành void* Việc chuyền đối số phức tạp của hàm sẽ tạo ra các đối số của hàm so sánh Những điều này nói chung sẽ không có tác dụng gì đối với việc biểu diễn thực tế Nhưng đảm bảo với trình biên dịch rằng tất cả đều ôn

Trang 40

void qsort(void *v[], int left, inr right,

int (*comp)(void *, void *))

{

int i, last;

void swap(void *v[], int, int);

if (left >=sright) /* do nothing if array contains */

return; /* fewer than two elements */

swap(v, left, (left + right)/2);

last = left;

for (i = left+1; i <= right; i++)

if ((*comp)(v[i], v[left]) <@) swap(v, ++last, i);

swap(v, left, last);

qsort(v, left, last-1, comp);

qsort(v, last+1, right,comp);

Các tuyên bô nên được nghiên cứu một cách cân thận Tham sô thứ tự của qsort

trong đó nói rắng comp là một con trỏ tới một hàm có hai đôi sô void #* và trả về một

int

int (*comp)(void *, void *)

Viéc su dung comp trong dong

if ((*comp)(v[i], v[left] < @)

la nhat quan với khai báo: comp là một con tré dén ham, *comp là hàm, và

(*comp) (v[i], v[left])

là lời kêu gọi của nó Cân có dâu ngoặc đơn đề các thành phân được chính xác có

liên quan; không có họ,

int *comp(void *, void*) /*wrong*/

nói răng comp là một hàm trả về một con trỏ đên một int, diéu nay rat khac — s61 noi Chúng tôi đã hiện thị stremp, so sánh hai chuôi Đây là numecmp, so sánh hai chuỗi trên một giá trị số đứng đầu, được tính bằng gọi atof:

Ngày đăng: 26/12/2024, 17:19