Lập trình cơ bản: Ngôn ngữ lập trình C

Đỗ Thị Mai Hường Bộ môn Hệ thống thông tin Khoa Công nghệ thông tin

Mảng, con trỏ và xâu ký tự

Tài liệu tham khảo • Kỹ thuật lập trình C: cơ sở và nâng cao, Phạm

Văn Ất, Nhà xuất bản KHKT – Chương 6

• The C programming language 2nd Edition,

Brian Kernighan and Dennis Ritchie, Prentice

Hall Software Series – Chương 4

• The C programming language 2nd Edition,

Brian Kernighan and Dennis Ritchie, Prentice

Hall Software Series – Chương 5

Chương 7 - Phần 1

3

Nội dung

• Mảng một chiều

• Mảng hai chiều

• Con trỏ và phép toán trên con trỏ

– Khai báo con trỏ

– Phép gán con trỏ

– Truy xuất giá trị qua con trỏ

• Con trỏ và mảng

• Cấp phát vùng nhớ cho con trỏ

– Khái niệm

– Khởi tạo

– Các thao tác trên xâu ký tự

4

Chương 7 - Phần 1

• Xâu ký tự

PHẦN 1. MẢNG MỘT CHIỀU VÀ NHIỀU CHIỀU

5

Mảng một chiều

• Khái niệm

• Khai báo

• Truy xuất dữ liệu

6

Dữ liệu kiểu mảng

• Khái niệm

– Là một kiểu dữ liệu có cấu trúc do người lập

trình định nghĩa.

– Biểu diễn một dãy các biến có cùng kiểu. Ví dụ:

dãy các số nguyên, dãy các ký tự…

– Kích thước được xác định ngay khi khai báo

và không bao giờ thay đổi.

– NNLT C luôn chỉ định một khối nhớ liên tục

cho một biến kiểu mảng.

7

Khai báo biến mảng (tường minh)

• Tường minh

[]; [][]…[];

, …, : số lượng phần tử của mỗi chiều.

• Lưu ý

– Phải xác định cụ thể (hằng) khi khai

báo.

– Mảng nhiều chiều: =

N1*N2*…*Nn

8

– Bộ nhớ sử dụng = *sizeof(

Khai báo biến mảng (tường minh)

• Ví dụ

int Mang1Chieu[10];

0 1 2 3 4 5 6 7 8 9

Mang1Chieu

int Mang2Chieu[3][4];

0 1 2 3 4 5 6 7 8 9 10 11

0

Mang2Chieu

1

9

2

Khai báo biến mảng (không tường minh)

• Cú pháp

– Không tường minh (thông qua khai báo kiểu) typedef []; typedef []…[];

;

typedef int Mang1Chieu[10]; typedef int Mang2Chieu[3][4];

• Ví dụ

Mang1Chieu m1, m2, m3; Mang2Chieu m4, m5;

10

Chương 7 - Phần 1

Số phần tử của mảng

• Phải xác định cụ thể số phần tử ngay lúc

khai báo, không được sử dụng biến hoặc int n1 = 10; int a[n1]; hằng thường const int n2 = 20; int b[n2];

#define n1 10 #define n2 20 int a[n1]; int b[n1][n2];

//  int a[10]; //  int b[10][20];

11

• Nên sử dụng chỉ thị tiền xử lý #define để

Khởi tạo giá trị cho mảng lúc khai báo

• Gồm các cách sau

– Khởi tạo giá trị cho mọi phần tử của mảng

int a[4] = {2912, 1706, 1506, 1904};

0 1 2 3

29122912

17061706

15061506

19041904

a

int a[4] = {2912, 1706};

– Khởi tạo giá trị cho một số phần tử đầu mảng

1 0 2 3

29122912

17061706

00

00

12

a

Khởi tạo giá trị cho mảng lúc khai báo

• Gồm các cách sau

– Khởi tạo giá trị 0 cho mọi phần tử của mảng

int a[4] = {0};

0 1 2 3

00

00

00

00

a

– Tự động xác định số lượng phần tử int a[] = {2912, 1706, 1506, 1904};

0 1 2 3

29122912

17061706

15061506

19041904

13

a

Truy xuất đến một phần tử

• Thông qua chỉ số

[][]…[]

• Ví dụ

0 1 2 3

– Cho mảng như sau int a[4];

– Các truy xuất

• Hợp lệ: a[0], a[1], a[2], a[3]

• Không hợp lệ: a[-1], a[4], a[5], …

=> Cho kết thường không như mong muốn!

14

Gán dữ liệu kiểu mảng • Không được sử dụng phép gán thông

thường mà phải gán trực tiếp giữa các phần tử tương ứng

• Ví dụ

#define MAX 3 typedef int MangSo[MAX]; MangSo a = {1, 2, 3}, b;

// Sai

b = a; for (int i = 0; i < 3; i++) b[i] = a[i];

15

Chương 7 - Phần 1

Ví dụ

tử của mảng

#include #define MAX 1000 void main() {

int ary[MAX]; int i,n; printf(“Nhap n = ”); scanf(“%d”,&n); for(i=0; i

printf(“\n Enter value: %d : ”, i+1); scanf(“%d”,&ary[i]);

} for(i=1; i<10; i++) printf(“a[%d]=%d\n“,i, ary[i])

}

16

1. • Nhập mảng có n phần tử kiểu nguyên, in ra các phần 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.

Ví dụ

#include #define MAX 1000 void main() mảng tổng {

int ary1[MAX], ary2[MAX], sum[MAX]; int i,n; printf(“Nhap n = ”);scanf(“%d”,&n); for(i=0; i

printf(“\n Enter value: %d : ”, i+1); scanf(“%d”,&ary1[i]);

} for(i=0; i

printf(“\n Enter value: %d : ”, i+1); scanf(“%d”,&ary2[i]);

1. • Nhập 2 mảng có n phần tử kiểu nguyên, tính và in ra 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.

} for(i=1; i<10; i++)sum[i]=ary1[i]+ary2[i]; for(i=1; i<10; i++) printf(“a[%d]=%d\n“,i, sum[i]) }

17

Mảng hai chiều

• Khái niệm

• Khai báo

• Truy xuất dữ liệu

18

Ma Trận

0 1 … n-1 0 … n-1

0 0

An Am,n

19

n-1 m-1

Ma Trận

0 … n-1 0 … n-1 0 … n-1

0 0 0

An

n-1 n-1 n-1

dòng > cột dòng < cột dòng = cột

0 … n-1 0 … n-1 0 … n-1

0 0 0

An

n-1 n-1 n-1

20

dòng + cột > n-1 dòng + cột < n-1 dòng + cột = n-1

Khai báo biến mảng 2 chiều

• Cú pháp

– Tường minh [][];

typedef [][]; – Không tường minh (thông qua kiểu)

; , ;

21

Khai báo biến mảng 2 chiều

• Ví dụ

– Tường minh

int a[10][20], b[10][20]; int c[5][10]; int d[10][20];

– Không tường minh (thông qua kiểu) typedef int MaTran10x20[10][20]; typedef int MaTran5x10[5][10];

MaTran10x20 a, b; MaTran11x11 c; MaTran10x20 d;

22

Truy xuất đến một phần tử

• Thông qua chỉ số

[][]

0 1 2 3

• Ví dụ

0

1

int a[3][4]; – Cho mảng 2 chiều như sau

– Các truy xuất

• Hợp lệ: a[0][0], a[0][1], …, a[2][2], a[2][3]

23

• Không hợp lệ: a[-1][0], a[2][4], a[3][3]

2

Gán dữ liệu kiểu mảng • Không được sử dụng phép gán thông thường

mà phải gán trực tiếp giữa các phần tử

int a[5][10], b[5][10];

// Sai

• Ví dụ b = a; int i, j; for (i = 0; i < 5; i++)

for (j = 0; j < 10; j++) b[i][j] = a[i][j];

24

Ví dụ

• Nhập mảng có n dòng, m cột các phần tử kiểu nguyên, in các phần tử

của mảng ra màn hình

#include #define MAX 1000 void main() {

int a[MAX][MAX]; int i,n,m; //Nhap mang printf(“Nhap n = ”);scanf(“%d”,&n); printf(“Nhap m = ”);scanf(“%d”,&m); for (i=0; i

for (j=0; j

printf(“Nhap a[%d][%d]: ”, i, j); scanf(“%d”, &a[i][j]);

25

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. }

Ví dụ

• Nhập mảng có n dòng, m cột các phần tử kiểu nguyên, in các phần tử của

mảng ra màn hình (tiếp)

// In cac phan tu cua mang for (i=0; i

for (j=0; j

26

16. 17. 18. 19. 20. 21. }

Ví dụ

• Nhập 2 mảng A, B có n dòng, m cột các phần tử kiểu nguyên, tính và in

#include #define MAX 1000 void main(){

int a[MAX][MAX], b[MAX][MAX], c[MAX][MAX];int i,n,m; //Nhap 2 mang printf(“Nhap n = ”);scanf(“%d”,&n); printf(“Nhap m = ”);scanf(“%d”,&m); for (i=0; i

for (j=0; j

printf(“Nhap a[%d][%d]: ”, i, j); scanf(“%d”, &a[i][j]); }

for (i=0; i

for (j=0; j

printf(“Nhap a[%d][%d]: ”, i, j); scanf(“%d”, &b[i][j]); }

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 27

các phần tử của mảng C = A + B

Ví dụ

// Tinh cac phan tu cua mang C tử của mảng C = A + B (tiếp) for (i=0; i

for (j=0; j

c[i][j] = a[i][j] + b[i][j];

// In cac phan tu cua mang C for (i=0; i

for (j=0; j

28

• Nhập 2 mảng A, B có n, m cột các phần tử kiểu nguyên, tính và in các phần 16. 17. 18. 19. 20. 21. 22. 23. 24. }

Bài tập thảo luận trên lớp 1. Nhập mảng có n phần tử kiểu nguyên, tìm phần tử

lớn nhất, nhỏ nhất của mảng.

2. Nhập mảng có n dòng, m cột phần tử kiểu nguyên,

tìm phần tử lớn nhất, nhỏ nhất của mảng

3. Nhập mảng có n phần tử kiểu nguyên, nhập giá trị x,

tìm xem x có trong mảng không, xác định vị trí xuất

hiện đầu tiên.

4. Nhập mảng có n dòng, m cột phần tử kiểu nguyên,

nhập giá trị x, tìm xem x có xuất hiện trong mảng

29

không, xác định các vị trí xuất hiện

Bài tập 5. Nhập 2 vector có n phần tử kiểu nguyên, kiểm tra 2

vector đó có vuông góc với nhau không?

6. Sắp xếp mảng một chiều bằng các phương pháp

• Phương pháp chọn

• Phương pháp nổi bọt

7. Nhập vào một dãy, tìm phần tử lớn nhất, nhỏ nhất và

các vị trí có phần tử đạt giá trị lớn nhất, nhỏ nhất.

8. Tính và in ra tích hai ma trận vuông cấp n x n.

9. Nhập ma trận A (n x m) và kiểm tra xem có hai cột

30

đứng cạnh nhau có tổng bằng nhau hay không?

Bài tập 10.Nhập 2 mảng A(n,m), B(m,n) phần tử kiểu số

thực, tính và in mảng C=A*B

11.Nhập 2 mảng A(n,m), B(m,n) phần tử kiểu số

thực, kiểm tra A có là chuyển vị của B hay

không

12.Nhập A(n,n) với n không giới hạn trước, kiểm

tra A có là ma trận đơn vị không?

31

13.Xây dụng ma trận A(n,m), sao cho các phần

tử có giá trị theo dạng xoắn ốc (n, m không

PHẦN 2. CON TRỎ VÀ CÁC PHÉP TOÁN

32

KhaiKhai bbááoo bibiếếnn con

con trtrỏỏ..

• Cú pháp: *

• Ý nghĩa: Khai báo một biến có tên là Tên con trỏ dùng để chứa địa chỉ của

các biến có kiểu Kiểu dữ liệu.

• Ví dụ 1: Khai báo 2 biến a,b có kiểu int và 2 biến pa, pb là 2 biến con trỏ kiểu

int.

int a, b, *pa, *pb;

• Ví dụ 2: Khai báo biến f kiểu float và biến pf là con trỏ float

float f, *pf;

• Chú ý: Nếu chưa muốn khai báo kiểu dữ liệu mà con trỏ ptr đang chỉ đến, ta

sử dụng:

void *ptr;

Sau đó, nếu ta muốn con trỏ ptr chỉ đến kiểu dữ liệu gì cũng được.

33

Tác dụng của khai báo này là chỉ dành ra 2 bytes trong bộ nhớ để cấp phát

cho biến con trỏ ptr.

CCáác thao t

c thao táác trên con tr

c trên con trỏỏ ..

Gán địa chỉ của biến cho biến con trỏ:

• Toán tử & dùng để định vị con trỏ đến địa chỉ của một biến

đang làm việc.

• Cú pháp: =& • Giải thích: Ta gán địa chỉ của biến Tên biến cho con trỏ

Tên biến con trỏ.

Ví dụ: Gán địa chỉ của biến a cho con trỏ pa, gán địa chỉ của

biến b cho con trỏ pb.

pa=&a; pb=&b; • Lúc này, hình ảnh của các biến trong bộ nhớ được mô tả

34

CCáác thao t

c thao táác trên con tr

c trên con trỏỏ (t)(t)

Lưu ý: • Khi gán địa chỉ của biến tĩnh cho con trỏ cần phải lưu ý

kiểu dữ liệu của chúng.

• Ví dụ sau đây không đúng do không tương thích kiểu:

int Bien_Nguyen; float *Con_Tro_Thuc; ... Con_Tro_Thuc=&Bien_Nguyen;

• Phép gán ở đây là sai vì Con_Tro_Thuc là một con trỏ

kiểu float (nó chỉ có thể chứa được địa chỉ của biến kiểu float); trong khi đó, Bien_Nguyen có kiểu int

35

CCáác thao t

c thao táác trên con tr

c trên con trỏỏ ..

Nội dung của ô nhớ con trỏ chỉ tới.

• Để truy cập đến nội dung của ô nhớ mà con trỏ chỉ tới, ta sử dụng cú pháp:

*

• Với cách truy cập này thì * có thể coi là một biến có kiểu được mô tả

trong phần khai báo biến con trỏ.

Ví dụ: Ví dụ sau đây cho phép khai báo, gán địa chỉ cũng như lấy nội dung vùng nhớ của

biến con trỏ: int x=100; int *ptr;

ptr=&x;

Lưu ý: Khi gán địa chỉ của một biến cho một biến con trỏ, mọi sự thay đổi trên nội dung ô

nhớ con trỏ chỉ tới sẽ làm giá trị của biến thay đổi theo (thực chất nội dung ô nhớ và biến

36

chỉ là một)

int y= *ptr;

#include “stdio.h” #include “conio.h” void main() {

VVíí ddụụ vvềề con con trtrỏỏ

,a,b);

int a,b,*pa,*pb; a=2; b=8; printf("\nGia tri cua bien a=%d \nGia tri cua bien b=%d ",a,b); pa=&a; pb=&b; printf("\nNoi dung cua o nho con tro pa tro toi=%d",*pa); printf("\nNoi dung cua o nho con tro pb tro toi=%d ",*pb); *pa=20; /* thay doi gia tri cua *pa*/ *pb=25; /* thay doi gia tri cua *pb*/ printf("\nGia tri moi cua bien a=%d \nGia tri moi cua bien b=%d “ /* a, b thay doi theo*/ getch();

37 }

Con Con trtrỏỏ vvàà mmảảngng mmộộtt chichiềềuu

• Trong C có mối quan hệ chặt chẽ giữa con trỏ và mảng: các phần tử của

mảng có thể được xác định nhờ chỉ số hoặc thông qua con trỏ.

Phép toán lấy địa chỉ:

• Giả sử ta có khai báo: double b[20];

 phép toán: &b[9] sẽ cho địa chỉ của phần tử b[9].

Tên mảng là một hằng địa chỉ:

• Khi chúng ta khai báo: float a[10]; máy sẽ bố trí bố trí cho mảng a mười

khoảng nhớ liên tiếp.

• Mỗi khoảng nhớ là 4 byte.

• Như vậy, nếu biết địa chỉ của một phần tử nào đó của mảng a, thì ta có thể

dễ dàng suy ra địa chỉ của các phần tử khác của mảng.

 • Trong C ta có: a &a[0]

38

 a+i &a[i]

*(a+i)  a[i]

Con Con trtrỏỏ trtrỏỏ ttớớii ccáácc phphầầnn ttửử ccủủaa mmảảngng mmộột chichiềềuu::

• Khi con trỏ pa trỏ tới phần tử a[k] của mảng a thì:

 pa+i trỏ tới phần tử thứ i sau a[k], có nghĩa là nó trỏ tới a[k+i].

 pa-i trỏ tới phần tử thứ i trước a[k], có nghĩa là nó trỏ tới a[k-i].

 *(pa+i) tương đương với pa[i].

• Ví dụ: sau hai câu lệnh:

float a[20],*pa;

pa=&a; // pa=&a[0];

• Khi đó, 4 cách viết sau có tác dụng như nhau và cùng truy cập đến

phần tử thứ i của mảng a:

a[i]

*(a+i) pa[i]

*(pa+i)

39

VVíí ddụụ::

Cách 1:

#include "stdio.h" void main() {

Vào số liệu cho các phần tử của một mảng và tính tổng các phần tử của chúng.

float a[4],tong; int i; for (i=0;i<4;++i) {

printf("\n a[%d]=",i); scanf("%f",(a+i));

40

} tong=0; for (i=0;i<4;++i) tong+=a[i];

VVíí ddụụ::

Cách 2:

#include "stdio.h" void main() {

Vào số liệu cho các phần tử của một mảng và tính tổng các phần tử của chúng.

float a[4],tong, *troa; int i; troa=a; for (i=0;i<4;++i) {

printf("\n a[%d]=",i); scanf("%f",troa+i);

} tong=0; for (i=0;i<4;++i) tong+=a[i];

printf("\n Tong cac phan tu mang la:%8.2f ",tong);

} 41

VVíí ddụụ::

Cách 3:

#include "stdio.h" void main() {

Vào số liệu cho các phần tử của một mảng và tính tổng các phần tử của chúng.

float a[4],tong,*troa; int i; troa=a; for (i=0;i<4;++i) {

printf("\n a[%d]=",i); scanf("%f",troa+i);

} tong=0; for (i=0;i<4;++i)

tong+=*(troa+i);

printf("\n Tong cac phan tu mang la:%8.2f ",tong);

} 42

Con Con trtrỏỏ vvàà mmảảngng nhinhiềềuu chichiềềuu

Phép cộng địa chỉ trong mảng hai chiều:

• Giả sử ta có mảng hai chiều a[2][3] có 6 phần tử ứng với sáu địa chỉ liên tiếp trong bộ

nhớ được xếp theo thứ tự sau:

a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]

1

2

3

4

5

6

Phần tử Địa chỉ

Tên mảng a biểu thị địa chỉ đầu tiên của mảng.

• Phép cộng địa chỉ : C coi mảng hai chiều là mảng (một chiều) của mảng, như vậy khai

báo

float a[2][3]; thì a là mảng mà mỗi phần tử của nó là một dãy 3 số thực (một hàng

của mảng).

• Vì vậy: a trỏ phần tử thứ nhất của mảng: phần tử a[0][0]

a+1 trỏ phần tử đầu hàng thứ hai của mảng: phần tử a[0][1],

...

43

a+i trỏ phần tử đầu hàng thứ i của mảng: phần tử a[0][i].

Con Con trtrỏỏ vvàà mmảảngng nhinhiềềuu chichiềềuu (t)(t)

Con trỏ và mảng hai chiều:Để lần lượt duyệt trên các phần tử của mảng

hai chiều ta có thể dùng con trỏ như minh hoạ ở ví dụ sau:

float *pa,a[2][3];

pa=(float*)a;

Khi đó:

– pa

trỏ tới

a[0][0]

– pa+1

trỏ tới

a[0][1]

– pa+2

trỏ tới

a[0][2]

– pa+3

trỏ tới

a[1][0]

– pa+4

trỏ tới

a[1][1]

– pa+5

trỏ tới

a[1][2]

44

VVíí ddụụ::

Cách 1: #include "stdio.h" void main() {

Dùn g con trỏ để vào số liệu cho mảng hai chiều

float a[2][3],*pa; int i; pa=(float*)a; for (i=0;i<6;++i)

scanf("%f",pa+i);

}

Cách 2: #include "stdio.h" void main() {

45

float a[2][3],*pa; int i; for (i=0;i<6;++i)

scanf("%f",(float*)a+i);

KiKiểểuu con

con con trtrỏỏ, , kikiểểuu đđịịaa chchỉỉ, , ccáácc phphéépp totoáánn trêntrên con

trtrỏỏ

Kiểu con trỏ và kiểu địa chỉ:

• Con trỏ dùng để lưu địa chỉ của biến. Mỗi kiểu địa chỉ của biến cần có kiểu con trỏ

tương ứng. Phép gán địa chỉ cho con trỏ chỉ có thể thực hiện được khi kiểu địa chỉ phù

hợp với kiểu con trỏ.

Theo khai báo:

Ta có:

• a là mảng 2 chiều, có 600 phần tử kiểu float. a là địa chỉ kiểu float[30].

• pa là con trỏ float.

• pn là mảng 30 con trỏ kiểu float.

• pm là con trỏ kiểu float [30].

• Như vậy, phép gán:

float a[20][30], *pa,*pn[30],(*pm)[30];

pa=a; là không hợp lệ (tuy nhiên sẽ có quá trình chuyển kiểu tự động).

• Nhưng phép gán: 46

pm=a; là hợp lệ.

#include "conio.h" #include "stdio.h"

void main() {

VVíí ddụụ vvềề ssửử ddụụngng con con trtrỏỏ

float a[2][3]={{1.0,2.0,3.0},{4.0,5.0,6.0}}, *p, (*pm3)[3]; float *b; int i; p=(float *)a; pm3=a; for (i=0;i<6;i++)

printf("%f", *(p+i));

printf("\n"); b=(float*)pm3; for (i=0;i<3;i++)

printf("%f", b[i]);

printf("\n"); b=(float*)(pm3+1); for (i=0;i<3;i++)

printf("%f", b[i]);

getch();

47 }

CCáác phc phéép top toáán trên con tr

n trên con trỏỏ

Có 4 phép toán liên quan đến con trỏ và đại chỉ là:

 Phép gán.  Phép tăng giảm địa chỉ.  Phép truy cập bộ nhớ.  Phép so sánh.

• Phép gán: Phép gán chỉ thực hiện với các con trỏ cùng kiểu. Muốn gán các

con trỏ khác kiểu phải dùng phép ép kiểu.

int x; char *pc; pc=(char*)(&x);

• Phép tăng giảm địa chỉ: Sử dụng phép toán này để di chuyển giữa các ô

nhớ:

• Ví dụ: Các câu lệnh:

float x[30],*px; px=&x[10];

48

• Cho con trỏ px là con trỏ float trỏ tới phần tử x[10]. Ta có:

px+i trỏ tới phần tử x[10+i] px-i trỏ tới phần tử x[10-i]

CCáác phc phéép top toáán trên con tr

n trên con trỏỏ (t)(t)

• Phép truy cập bộ nhớ:

Con trỏ float trỏ tới địa chỉ dài 4 byte, con trỏ int trỏ tới địa chỉ dài 2 byte, con trỏ char

trỏ tới địa chỉ dài 1 byte.

• Ví dụ: khai báo:

float *pf;

int *pi;

char *pc;

• Khi đó ta có các nhận xét sau:

Nếu trỏ pf trỏ đến byte thứ 100 thì *pf biểu thị vùng nhớ 4 byte liên tiếp từ byte 100 đến 103.

Nếu trỏ pi trỏ đến byte thứ 100 thì *pi biểu thị vùng nhớ 2 byte liên tiếp từ byte 100 đến 101.

Nếu trỏ pc trỏ đến byte thứ 100 thì *pc biểu thị vùng nhớ 1 byte chính là byte 100.

• Phép so sánh: Cho phép so sánh các con trỏ cùng kiểu.

• Ví dụ nếu p1 và p2 là các con trỏ cùng kiểu thì nếu:

p1

49

p1==p2 nếu địa chỉ p1 trỏ tới cũng là địa chỉ p2 trỏ tới.

p1>p2 nếu địa chỉ p1 trỏ tới cao hơn địa chỉ p2 trỏ tới.

Ví dụ 1:

Đoạn chương trình tính tổng các số thực dùng phép so sánh con trỏ:

float a[100],*p,*pcuoi,tong=0.0;

pcuoi=(float*)(a+99);

/* Địa chỉ cuối dãy*/

VVíí ddụụ

for (p=(float*)a;p<=pcuoi;++p)

tong+=*p;

Ví dụ 2:

Dùng con trỏ char để tách các byte của một biến nguyên, ta làm như sau:

Giả sử ta có biến nguyên n được khai báo như sau:

unsigned int n=0xABCD; /* Số nguyên hệ 16*/

char *pc;

pc=(char*)(&n);

Khi đó:

*pc=0xAB (byte thứ nhất của n)

*(pc+1)=0xCD (byte thứ hai của n)

50

u void Con trỏỏ kikiểểu void Con tr void *tên_con_trỏ;

• Cú pháp:

• Đây là con trỏ đặc biệt, con trỏ không kiểu, nó có thể nhận địa chỉ

kiểu bất kỳ.

• Ví dụ: Câu lệnh sau là hợp lệ:

void *pa;

float a[20][30];

pa=a;

• Con trỏ void thường dùng làm tham số hình thức để nhận bất kỳ địa

chỉ kiểu nào từ tham số thực. Trong thân hàm phải dùng phép chuyển

đổi kiểu để chuyển sang dạng địa chỉ cần xử lý.

• Chú ý: Các phép toán tăng giảm địa chỉ, so sánh và truy cập bộ

nhớ không dùng được trên con trỏ void.

51

MMảảng con tr

ng con trỏỏ

• Mảng con trỏ: là một mảng mà mỗi phần tử của nó là một con trỏ.

• Cú pháp:

*[N];

• Khi gặp khai báo trên, máy sẽ cấp phát N khoảng nhớ liên tiếp cho N phần

tử của mảng.

• Ví dụ:

double *pa[100];

Khai báo một mảng con trỏ kiểu double gồm 100 phần tử. Mỗi phần tử

pa[i] có thể dùng để lưu trữ một địa chỉ kiểu double.

• Chú ý :

• Bản thân các mảng con trỏ không dùng để lưu trữ số liệu.

• Trước khi sử dụng một mảng con trỏ ta cần gán cho mỗi phần tử của nó

52

một địa chỉ.

CCấấp php pháát bt bộộ nhnhớớ cho bi

cho biếến con tr

n con trỏỏ

Trước khi sử dụng biến con trỏ, ta phải cấp phát vùng nhớ cho biến con trỏ này quản lý địa chỉ. Việc cấp phát được thực hiện nhờ các hàm malloc(), calloc(), realloc() trong thư viện alloc.h, hoặc stdlib.h.

• Cú pháp các hàm:

void *malloc(size_t size); Cấp phát vùng nhớ có kích thước là size byte.

void *calloc(size_t nitems, size_t size); Cấp phát vùng nhớ có kích thước

là nitems*size byte.

void *realloc( void *memblock, size_t size ); Cấp phát lại ô nhớ cho con

trỏ memblock size byte.

• Ví dụ: Giả sử ta có khai báo: int a, *pa, *pb; pa = (int*)malloc(sizeof(int)); /* Cấp phát vùng nhớ có kích thước bằng với

kích thước của một số nguyên */

pb= (int*)calloc(10, sizeof(int)); /* Cấp phát vùng nhớ có thể chứa được 10

số nguyên*/

pa= (int*) realloc(pa, 10*sizeof(int)); /* Cấp phát lại vùng nhớ có thể chứa

được 10 số nguyên*/

53

GiGiảảii phphóóngng vvùùngng nhnhớớ do do bibiếếnn con

con trtrỏỏ ququảảnn

lýlý

• Một vùng nhớ đã cấp phát cho biến con trỏ, khi không cần

sử dụng nữa, ta sẽ thu hồi lại vùng nhớ này nhờ hàm

free().

• Cú pháp: void free(void *block)

• Ý nghĩa: Giải phóng vùng nhớ được quản lý bởi con trỏ

block.

• Ví dụ: Ở ví dụ trên, sau khi thực hiện xong, ta giải phóng

vùng nhớ cho 2 biến con trỏ pa & pb:

free(pa);

54

free(pb);

Ví dụ Cấp phát động mảng 1 chiều, nhập và in mảng sử dụng

#include #include void main() {

int *p,n,i,j,temp; printf(“Nhap n = ”);scanf(“%d”,&n); p = (int *)malloc(n*sizeof(int)); for(i=0; i

printf(“\n Enter value: %d : ”, i+1); scanf(“%d”,p+i);

} for(i=1; i<10; i++) printf(“%d\n“,i, *(p+i))

con trỏ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.

}

55

Hàm free() • Hàm free() được sử dụng để giải phóng bộ

nhớ khi nó không cần dùng nữa.

• Cú pháp:

void free(void*ptr);

• Hàm này giải phóng không gian được trỏ bởi

ptr, để dùng cho tương lai.

• ptr phải được dùng trước đó với lời gọi hàm

56

malloc(), calloc(), hoặc realloc().

Hàm calloc()

• calloc tương tự như malloc, nhưng điểm khác biệt

chính là mặc nhiên giá trị 0 được lưu vào không gian bộ nhớ vừa cấp phát.

• calloc yêu cầu hai tham số

 Tham số thứ nhất là số lượng các biến cần cấp phát bộ

nhớ

 Tham số thứ hai là kích thước của mỗi biến

• Cú pháp:

void *calloc( size_t num, size_t size ); float *calloc1, *calloc2; calloc1 = (float *)calloc(3,sizeof(float));

• Ví dụ

57

Bài tập thảo luận trên lớp

• Bài 1: Cho đoạn chương trình sau:

float pay;

float *ptr_pay;

pay=2313.54;

ptr_pay = &pay;

• Hãy cho biết giá trị của:

a. pay

b. *ptr_pay

c. *pay

58

d. &pay

Bài tập thảo luận trên lớp

• Bài 2: Tìm lỗi

#include #include

void main() {

int *x, y = 2;

*x = y; *x += y++;

printf("%d %d",*x,y); getch();

}

59

Câu hỏi ôn tập lý thuyết 1.Toán tử nào dùng để xác định địa chỉ của một

biến?

2.Toán tử nào dùng để xác định giá trị của biến do

con trỏ trỏ đến?

3.Phép lấy giá trị gián tiếp là gì? 4.Các phần tử trong mảng được sắp xếp trong bộ nhớ

như thế nào?

5.Cho mảng một chiều data. Trình bày 2 cách lấy địa

chỉ phần tử đầu tiên của mảng này.

6.Nếu ta truyền cho hàm đối số là mảng một chiều. Trình bày hai cách nhận biết phần tử cuối của mảng?

7.Trình bày 6 phép toán có thể thực hiện trên con 60

trỏ?

8.Cho con trỏ p1 trỏ đến phần tử thứ 3 còn con trỏ p2

Bài tập về nhà Sử dụng con trỏ viết cac chương trình:

1.Nhập vào một dãy số thực, tìm dãy con tăng có nhiều phần tử nhất.

2.Nhập vào một dãy và kiểm tra xem dãy đã cho là tăng hay không, nếu không hãy sắp xếp lại dãy theo chiều tăng dần

3.Nhập vào một dãy số nguyên, kiểm tra xem dãy là dãy giảm hay không? Nếu không hãy sắp xếp lại dãy

61

4.Nhập dãy số nguyên dương. Xét xem trong dãy có số nguyên tố hay không? Nếu có, hãy in ra giá

PHẦN 3. XÂU KÝ TỰ

62

Khái niệm

• Khái niệm

– Kiểu char chỉ chứa được một ký tự. Để lưu trữ một xâu ký tự (nhiều ký tự) ta sử dụng mảng (một chiều) các ký tự.

– Xâu ký tự kết thúc bằng ký tự ‘\0’ (null)

 Độ dài xâu ký tự = kích thước mảng – 1

• Ví dụ

// Dài 29 ký tự

char hoten[30]; char ngaysinh[9]; // Dài 8 ký tự

63

Khởi tạo

• Khởi tạo như mảng thông thường

– Độ dài cụ thể char s[10] = {‘T’, ‘H’, ‘C’, ‘S’, ‘A’, ‘ ’, ‘\0’}; char s[10] = “THCS A”; 4

// Tự động thêm ‘\0’ 8 5

‘T’ ‘H’ ‘T’

‘H’ ‘C’

‘S’ ‘C’ ‘S’

‘ ’‘ ’

‘A’ ‘A’

‘\0’ ‘\0’

7 0 2 3 6 9 1

char s[] = {‘T’, ‘H’, ‘C’, ‘S’, ‘ ’, ‘A’, ‘\0’}; char s[] = “THCS A”; – Tự xác định độ dài 4 0

// Tự động thêm ‘\0’ 5

‘T’ ‘H’ ‘T’

‘H’ ‘C’

‘S’ ‘C’ ‘S’

‘ ’‘ ’

‘A’ ‘A’

‘\0’ ‘\0’

64

6 3 2 1

Xuất xâu ký tự

• Sử dụng hàm printf với đặc tả “%s” char monhoc[50] = “Tin hoc co so A”; printf(“%s”, monhoc);

// Không xuống dòng

Tin hoc co so A

_

// Tự động xuống dòng

• Sử dụng hàm puts

char monhoc[50] = “Tin hoc co so A”; puts(monhoc);  printf(“%s\n”, monhoc);

Tin hoc co so A _

65

Nhập xâu ký tự • Sử dụng hàm scanf với đặc tả “%s”

– Chỉ nhận các ký tự từ bàn phím đến khi gặp ký tự

khoảng trắng hoặc ký tự xuống dòng.

– Xâu nhận được không bao gồm ký tự khoảng char monhoc[50]; trắng và xuống dòng. printf(“Nhap mot chuoi: “); scanf(“%s”, monhoc); printf(“Chuoi nhan duoc la: %s”, monhoc);

Nhap mot chuoi: Tin hoc co so A Chuoi nhan duoc la: Tin_

66

Nhập xâu ký tự

• Sử dụng hàm gets

– Nhận các ký tự từ bàn phím đến khi gặp ký tự

xuống dòng.

– Xâu nhận được là những gì người dùng nhập char monhoc[50]; printf(“Nhap mot chuoi: “); (trừ ký tự xuống dòng). gets(monhoc); printf(“Chuoi nhan duoc la: %s”, monhoc);

Nhap mot chuoi: Tin hoc co so A Chuoi nhan duoc la: Tin hoc co so A _

67

Một số hàm thao tác trên xâu ký tự

• Thuộc thư viện

– strcpy

– strdup

– strlwr/strupr

– strrev

– strcmp/stricmp

– strcat

– strlen

68

– strstr

Hàm sao chép xâu ký tự

Sao chép xâu ký tự src sang xâu ký tự dest, dừng khi ký tự kết thúc xâu ký tự ‘\0’ vừa được chép. ! dest phải đủ lớn để chứa src

Địa chỉ xâu ký tự dest

char s[100]; s = “Tin hoc co so A”; strcpy(s, “Tin hoc co so A”);

// sai // đúng

69

char *strcpy(char dest[], const char src[]) char *strcpy(char dest[], const char src[])

Hàm tạo bản sao

Tạo bản sao của một xâu ký tự s cho trước. Hàm sẽ tự tạo vùng nhớ đủ chứa xâu ký tự s.

Thành công: Địa chỉ xâu ký tự kết quả Thất bài: null

char *s; s = strdup(“Tin hoc co so A”);

70

char *strdup(const char s[]) char *strdup(const char s[])

Hàm chuyển xâu ký tự thành chữ thường

Chuyển xâu ký tự s thành xâu ký tự thường (‘A’ thành ‘a’, ‘B’ thành ‘b’, …, ‘Z’ thành ‘z’)

Địa chỉ xâu ký tự s

char s[] = “Tin hoc co so A!!!”; strlwr(s); puts(s);

// tin hoc co so a!!!

71

char *strlwr(char *s) char *strlwr(char *s)

Hàm chuyển xâu ký tự thành chữ IN

Chuyển xâu ký tự s thành xâu ký tự in (‘a’ thành ‘A’, ‘b’ thành ‘B’, …, ‘z’ thành ‘Z’)

Địa chỉ xâu ký tự s

char s[] = “Tin hoc co so A!!!”; strupr(s); puts(s);

// TIN HOC CO SO A!!!

72

char *strupr(char *s) char *strupr(char *s)

Hàm đảo ngược xâu ký tự

Đảo ngược thứ tự các ký tự trong xâu ký tự (trừ ký tự kết thúc xâu ký tự)

Địa chỉ xâu ký tự kết quả

char s[] = “Tin hoc co so A!!!”; strrev(s); puts(s);

// !!!A os oc coh niT

73

char *strrev(char *s) char *strrev(char *s)

Hàm so sánh hai xâu ký tự

So sánh hai xâu ký tự s1 và s2 (phân biệt hoa thường)

< 0 nếu s1 < s2 == 0 nếu s1 == s2 >0 nếu s1 > s2

char s1[] = “tin hoc co so A!!!”; char s2[] = “hoc tin co so A!!!”; int kq = strcmp(s1, s2); // => kq > 0

74

int strcmp(const char *s1, const char *s2) int strcmp(const char *s1, const char *s2)

Hàm so sánh hai xâu ký tự

So sánh hai xâu ký tự s1 và s2 (không phân biệt hoa thường)

< 0 nếu s1 < s2 == 0 nếu s1 == s2 >0 nếu s1 > s2

char s1[] = “tin hoc co so A!!!”; char s2[] = “TIN HOC CO SO A!!!”; int kq = stricmp(s1, s2);// => kq == 0

75

int stricmp(const char *s1, const char *s2) int stricmp(const char *s1, const char *s2)

Hàm nối hai xâu ký tự

Nối xâu ký tự src vào sau xâu ký tự dest. ! Xâu dest phải đủ chứa kết quả

Địa chỉ của xâu ký tự được nối

char s1[100] = “Tin hoc”; char s2[] = “co so A!!!”; strcat(s1, “ ”); strcat(s1, s2);

// => “Tin hoc ” // => “Tin hoc co so A!!!”

76

char* strcat(char *dest, const char *src) char* strcat(char *dest, const char *src)

Hàm tính độ dài xâu ký tự

unsigned

Tính độ dài xâu ký tự s size_t (trong cho thay ) dùng để đo các đại lượng không dấu.

Độ dài xâu ký tự s

char s[] = “Tin hoc co so A!!!”; int len = strlen(s);

// => 18

77

size_t* strlen(const char *s) size_t* strlen(const char *s)

Hàm tìm xâu ký tự trong xâu ký tự

Tìm vị trí xuất hiện đầu tiên của s2 trong s1

Thành công: trả về con trỏ đến vị trí xuất

hiện đầu tiên của s2 trong s1.

Thất bại: trả về null

char s1[] = “Tin hoc co so A!!!”; char s2[] = “hoc”; if (strstr(s1, s2) != null)

printf(“Tim thay!”);

78

char* strstr(const char *s1, const char *s2) char* strstr(const char *s1, const char *s2)

Bài tập

1. Xem thêm một số hàm khác như

atoi, atol, atof : đổi xâu ký tự thành số

itoa, ltoa, ultoa: đổi số thành xâu ký tự

2. Xóa tất cả các khoảng trắng của xâu ký tự s

3. Đếm xem có bao nhiêu từ trong xâu s. Xuất các từ trên các

dòng liên tiếp.

4. Tìm từ có chiều dài dài nhất và in ra.

5. Trích ra n ký tự đầu tiên/cuối cùng/bắt đầu tại vị trí pos.

6. Tìm kiếm và thay thế xâu con trong xâu ký tự lớn

7. Viết chương trình nhập vào một xâu ký tự bất kỳ và xoá k ký

79

tự của xâu ký tự bắt đầu từ vị trí thứ n.

strtok

Bài tập 8. Nhập xâu họ tên (không quá 40 kí tự), chuẩn

hoá xâu đó (kí tự đầu từ viết hoa, các kí tự

khác viết thường, các từ cách nhau 1 dấu

cách)

9. Nhập 3 xâu s1, s2, s3 (không quá 40 kí tự),

thay xâu s2 bằng s3 trong s1

10.Nhập xâu kí tự. Đưa xâu đó về dạng chuẩn

80

(các từ cách nhau bởi 01 dấu cách, chữ cái