Mời các bạn cùng tham khảo Bài giảng Lập trình cơ bản: Ngôn ngữ lập trình C để nắm chi tiết các kiến thức về mảng, con trỏ và xâu ký tự; mảng một chiều và nhiều chiều; con trỏ và các phép toán; xâu ký tự.
Trang 1Lậ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
Trang 2Mảng, con trỏ và xâu ký
tự
Trang 3Tài liệu tham khảo
Chương 7 - Phần 1 3
• 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
Trang 4Nộ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ỏ
Trang 5PHẦN 1 MẢNG MỘT CHIỀU VÀ
NHIỀU CHIỀU
5
Trang 7– Kích thước được xác định ngay khi khai báo
– 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
Trang 8Khai báo biến mảng (tường
– Bộ nhớ sử dụng = <tổng số phần tử>* sizeof (<kiểu cơ
<ki ểu cơ sở> <tên biến mảng> [ <s ố phần tử> ] ;
<ki ểu cơ sở> <tên biến mảng> [ <N1> ][ <N2> ] … [ <Nn> ] ;
8
Trang 90 1 2
Khai báo biến mảng (tường
Trang 10Khai 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)
• Ví dụ
typedef <ki ểu cơ sở> <tên kiểu mảng> [ <s ố phần tử> ] ;
typedef <ki ểu cơ sở> <tên kiểu mảng> [ <N1> ] … [ <Nn> ] ;
<tên ki ểu mảng> <tên biến mảng>;
typedef int Mang1Chieu [ 10 ] ;
typedef int Mang2Chieu [ 3 ][ 4 ] ;
Mang1Chieu m1, m2, m3;
Mang2Chieu m4, m5;
Trang 11Số 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 hằng thường
• Nên sử dụng chỉ thị tiền xử lý #define để
Trang 12Khở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
– Khởi tạo giá trị cho một số phần tử đầu mảng
Trang 13Khởi tạo giá trị cho mảng lúc
Trang 14• 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!
<tên bi ến mảng> [ <gt cs1> ][ <gt cs2> ] … [ <gt csn> ]
int a[4];
0 1 2 3
Trang 15Gá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
Trang 16for(i=0; i<n; 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])
Trang 17Ví dụ
• Nhập 2 mảng có n phần tử kiểu nguyên, tính và in ra mảng tổng
for(i=0; i<n; i++) {
printf(“\n Enter value: %d : ”, i+1);
scanf(“%d”,&ary1[i]);
} for(i=0; i<n; i++) {
printf(“\n Enter value: %d : ”, i+1);
scanf(“%d”,&ary2[i]);
} 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]) }
Trang 21Khai báo biến mảng 2 chiều
• Cú pháp
– Tường minh
– Không tường minh (thông qua kiểu)
<ki ểu cơ sở> <tên biến>[<N1>][<N2>];
typedef <ki ểu cơ sở> <tên ki ểu> [<N1>][<N2>];
<tên ki ểu> <tên bi ến>;
<tên ki ểu> <tên bi ến 1>, <tên biến 2>;
21
Trang 22Khai báo biến mảng 2 chiều
typedef int MaTran10x20[10][20];
typedef int MaTran5x10[5][10];
MaTran10x20 a, b;
MaTran11x11 c;
MaTran10x20 d;
Trang 23• Hợp lệ : a[0][0], a[0][1], …, a[2][2], a[2][3]
• Không hợp lệ : a[-1][0], a[2][4], a[3][3]
<tên bi ến mảng> [ <giá tr ị cs1> ][ <giá tr ị cs2> ]
int a[3][4];
0 1 2
23
Trang 24Gá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ử
Trang 26Ví 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<m; i++)
Trang 28Ví dụ
• 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
tử của mảng C = A + B (tiếp)// Tinh cac phan tu cua mang C
for (i=0; i<m; i++)
for (j=0; j<n; j++)
c[i][j] = a[i][j] + b[i][j];
// In cac phan tu cua mang C
for (i=0; i<m; i++)
Trang 29Bà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
không, xác định các vị trí xuất hiện
29
Trang 30Bà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?
9 Nhập ma trận A (n x m) và kiểm tra xem có hai cột
đứng cạnh nhau có tổng bằng nhau hay không?
30
Trang 3112.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?
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
31
Trang 32PHẦN 2 CON TRỎ VÀ CÁC
PHÉP TOÁN
Trang 33Khai báo biến con trỏ.
33
• Cú pháp: <Kiểu dữ liệu> * <Tên con trỏ>
• Ý 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
Sau đó, nếu ta muốn con trỏ ptr chỉ đến kiểu dữ liệu gì cũng được
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
Trang 34Các thao tá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: <Tên biến con trỏ>=&<Tên biến>
• 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ả
Trang 35Các thao tác trên con trỏ (t)
Trang 36Các thao tác trên con trỏ
36
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:
*<Tên biến con trỏ>
• Với cách truy cập này thì *<Tên biến con trỏ> 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;
int y= *ptr;
• 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
Trang 37Ví dụ về con trỏ
*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);
/* a, b thay doi theo*/
getch();
}
Trang 38Con trỏ và mảng một chiều
38
• 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]
a+i &a[i]
Trang 39Con trỏ trỏ tới các phần tử của mảng mộ ột
chiều:
39
• 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:
Trang 40Ví dụ:
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.
Trang 41Ví dụ:
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.
Trang 42Ví dụ:
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.
Trang 43Con trỏ và mảng nhiều chiều
43
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:
• 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],
Trang 44Con trỏ và mảng nhiều chiều (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]
Trang 45Ví dụ:
Dùn
g con trỏ
để vào số liệu cho mảng hai chiều
Trang 46Kiểu con trỏ, kiểu địa chỉ, các phép toán trên con
trỏ
46
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:
float a[20][30], *pa ,*pn[30], (*pm)[30] ;
• 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:
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:
Trang 47Ví dụ
dụng con trỏ
Trang 48Các phép toán trên con trỏ
• 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]
Trang 49Các phép toán trên con trỏ (t)
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<p2 nếu địa chỉ p1 trỏ tới thấp hơn địa chỉ p2 trỏ tới.
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.
Trang 50Dù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)
Trang 51Con trỏ kiểu void
• 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.
Trang 52Mả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:
<Ki ểu dữ liệu> *<Tên_mảng_con_trỏ>[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ó
Trang 53Cấp phát bộ nhớ cho biến con trỏ
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
Trang 54Giải phóng vùng nhớ do biến con trỏ quản
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);
free(pb);
Trang 55Ví dụ Cấp phát động mảng 1 chiều, nhập và in mảng sử dụng con trỏ
for(i=0; i<n; i++) {
printf(“\n Enter value: %d : ”, i+1);
Trang 56• ptr phải được dùng trước đó với lời gọi hàm
malloc(), calloc(), hoặc realloc()
Trang 57Hà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 );
• Ví dụ
57
float *calloc1, *calloc2;
calloc1 = (float *)calloc(3,sizeof(float));
Trang 58Bài tập thảo luận trên lớp
• Bài 1: Cho đoạn chương trình sau:
Trang 59Bài tập thảo luận trên lớp
Trang 60Câ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
Trang 61Bà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
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á
61
Trang 62PHẦN 3 XÂU KÝ TỰ
Trang 63Khá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ụchar hoten[30]; // Dài 29 ký t ự
char ngaysinh[9]; // Dài 8 ký t ự
63
Trang 64char s[] = {‘T’, ‘H’, ‘C’, ‘S’, ‘ ’, ‘A’, ‘\0’}; char s[] = “THCS A”; // T ự động thêm ‘\0’
Trang 65Xuất xâu ký tự
• Sử dụng hàm printf với đặc tả “%s”
• Sử dụng hàm puts
char monhoc[50] = “Tin hoc co so A”;
printf(“%s”, monhoc); // Không xu ống dòng
char monhoc[50] = “Tin hoc co so A”;
Trang 66Nhậ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
trắng và xuống dòng.
char monhoc[50];
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_
Trang 67Nhậ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 (trừ ký tự xuống dòng).
char monhoc[50];
printf(“Nhap mot chuoi: “);
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
Trang 68Một số hàm thao tác trên xâu ký
Trang 69Hà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”; // saistrcpy(s, “Tin hoc co so A”); // đúng
char *strcpy(char dest[], const char src[])
69
Trang 70Hà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”);
char *strdup(const char s[])
Trang 73Hà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!!!”;
Trang 74char s1[] = “tin hoc co so A!!!”;
char s2[] = “hoc tin co so A!!!”;
int kq = strcmp(s1, s2); // => kq > 0
int strcmp(const char *s1, const char *s2)
int strcmp (const char * s1 , const char * s2 )
Trang 75char s1[] = “tin hoc co so A!!!”;
char s2[] = “TIN HOC CO SO A!!!”;
int kq = stricmp(s1, s2);// => kq == 0
int stricmp(const char *s1, const char *s2)
int stricmp (const char * s1 , const char * s2 )
75