Download as pdf or txt
Download as pdf or txt
You are on page 1of 35

Ngôn ngữ lập trình C

Cấu trúc dữ liệu mảng và con trỏ

1
Các nội dung chính
1. Kiểu mảng
2. Kiểu con trỏ
3. Mối quan hệ giữa hai kiểu DL
4. Cấp phát bộ nhớ động
5. Con trỏ hàm

2
1. Kiểu mảng
1.1. Giới thiệu
Một đối tượng dữ liệu kiểu mảng có các đặc trưng sau:
◼ Số chiều : là một giá trị nguyên dương cố định nên
được biểu diễn bằng một hằng số nguyên dương.
◼ Kích thước mỗi chiều : cũng là một giá trị nguyên
dương cố định nên cũng được biểu diễn bằng một hằng
số nguyên dương.
◼ Kiểu phần tử mảng : là kiểu dữ liệu của mỗi phần tử
(kiểu mảng).

3
1. Kiểu mảng
Các thao tác cơ bản trên một đối tượng dữ liệu
kiểu mảng gồm có:
 Khai báo : xác định các đặc trưng của mảng
như số chiều, kích thước mỗi chiều, kiểu mảng.
 Truy nhập vào các phần tử của mảng: theo
một trong hai cách :
 Truy nhập gián tiếp: bằng cách sử dụng chỉ số.
 Truy nhập trực tiếp: thông qua con trỏ.

4
1. Kiểu mảng
1.2. Mảng một chiều
a) Khai báo
- Khai báo biến mảng: với cú pháp như sau:
kiểu_dữ_liệu tên_biến [N] ;
kiểu_dữ_liệu tên_biến [N] = {v1,v2,…,vn};
kiểu_dữ_liệu tên_biến [] = {v1,v2,…,vn};
Trong đó: N phải là một hằng số nguyên dương.
v1,v2,…,vn là các giá trị khởi tạo tương ứng cho
các phần tử của mảng.

5
1.Kiểu mảng - Mảng một chiều
VD : char s[20];
char s[10] = {’s’,’t’,’r’,’i’,’n’,’g’};
char s[10] = “string”;
char s[10] = {’s’,’t’,’r’,’i’,’n’,’g’,’\0’};
int a[10] = {1,2,3};
int a[10] = {1,2,3,0,0,0,0,0,0,0};
int a[] = {1,2,3};
int a[3] = {1,2,3};
int a[] ; //error

6
1.Kiểu mảng - Mảng một chiều
- Khai báo hằng mảng: với cú pháp như sau:
const kiểu_dữ_liệu tên_biến [N] = {v1,v2,…,vn};
kiểu_dữ_liệu const tên_biến [] = {v1,v2,…,vn};

◼ Hằng mảng là mảng các hằng số, tức là tất cả các phần
tử trong mảng đều là hằng số.
◼ Việc khởi tạo giá trị ban đầu cho các phần tử khi khai
báo là bắt buộc.
VD: const int a[3] = {1,2,3};
a[1] = 5 ; //error

7
1.Kiểu mảng
Mảng một chiều
b) Truy nhập
Trong phần này chúng ta chỉ xét phương pháp truy nhập gián
tiếp thông qua chỉ số.
- Chỉ số của một phần tử : Sau khi khai báo một đối tượng dữ
liệu mảng, các phần tử trong mảng sẽ có một số hiệu duy
nhất được gọi là chỉ số của phần tử đó. Chỉ số này vừa có vai
trò tên gọi, vừa có vai trò xác định địa chỉ tương đối của một
phần tử trong mảng.
- Cách đánh chỉ số: trong C/C++ quy ước phần tử thứ i (1 i  N)
trong mảng có chỉ số là i-1. Tức là phần tử đầu tiên có chỉ số
là 0, phần tử thứ N có chỉ số là N-1.
- Truy nhập bằng chỉ số: để truy nhập vào một phần tử có chỉ số
i ta dùng cú pháp tên_mảng[i] .

8
VD : chương trình sắp xếp một dãy N số theo trật
tự tăng dần bằng giải thuật sắp xếp nổi bọt
main(){
const int N = 200 ;
float a[N] ;
int i,j ;
…Nhập giá trị cho mảng a ;
for (i=0;i<N-1;i++)
for (j=0;j<N-i-1;j++)
if (a[j]>a[j+1]) {
float tg = a[j];
a[j]=a[j+1];
a[j+1] =tg;
}
…In kết quả
}

9
1. Kiểu mảng
1.3. Mảng hai chiều
a) Khai báo
- Khai báo biến mảng : với cú pháp như sau:
kiểu_dữ_liệu tên_biến [M][N] ;
kiểu_dữ_liệu tên_biến [M][N] = {v1,v2,…,vm*n};
kiểu_dữ_liệu tên_biến [][N] = {v1,v2,…,vm*n};

- Khai báo hằng mảng : tương tự như hằng mảng 1 chiều,


việc khai báo hằng mảng hai chiều sẽ phải thêm từ khoá
const và khởi tạo giá trị ban đầu cho các phần tử của
mảng.

10
VD : khai báo mảng 2 chiều
char s [2][3];
int a[][3] = {1,2,3,4,5};  int a[2][3] =
{1,2,3,4,5,0};
int a[2][3] = {{1,2},{3,4,5}};  int a[2][3] =
{{1,2,0},{3,4,5}};
int a[2][] ; //error
int a[2][] = {1,2,3,4,5}; //error

11
1. Kiểu mảng
Mảng hai chiều
b) Truy nhập
- Việc truy nhập vào các phần tử của mảng hai chiều kích thước MxN
cũng sử dụng chỉ số với quy ước về cách đánh chỉ số như sau:
phần tử thứ nhất có chỉ số là (0,0)
phần tử thứ hai có chỉ số (0,1)

phần tử thứ N có chỉ số (0,N-1)
phần tử thứ N+1 có chỉ số (1,0)

phần tử thứ MxN có chỉ số (M-1,N-1)
- Để truy nhập vào phần tử có chỉ số (i,j) ta dùng cú pháp
tên_mảng[i][j].
- Từ việc khai báo và sử dụng mảng một chiều và hai chiều,chúng ta
có thể dễ dàng suy ra cách khai báo và sử dụng các mảng nhiều
chiều hơn. 12
1. Kiểu mảng
1.4. Tự định nghĩa kiểu dữ liệu mới kiểu mảng
Trong C/C++ cho phép người sử dụng có thể tự định nghĩa
các kiểu dữ liệu mới dựa trên các kiểu dữ liệu đã có hay
định nghĩa lại tên của các kiểu dữ liệu đã có bằng từ
khoá typedef (viết tắt của type definition).
- Cú pháp của lệnh typedef:
typedef định_nghĩa_kiểu tên_kiểu ;
- Trong đó: định_nghĩa_kiểu là phần xác định loại, cấu trúc
của kiểu dữ liệu mới. Nó thường là định nghĩa cấu trúc
các kiểu dữ liệu mới như các cấu trúc mảng, cấu trúc
bản ghi (struct)...

13
1. Kiểu mảng
Tự định nghĩa kiểu dữ liệu mới kiểu mảng
VD: định nghĩa lại kiểu int với tên mới là integer như trong
Pascal.
typedef int integer ;
integer i, j ;
- Định nghĩa một kiểu mảng 1 chiều:
typedef kiểu_dữ_liệu tên_kiểu[N] ;
VD: typedef char ten[30] ;
ten t1=”An”,t2=”Binh”,t3;
Nếu không sử dụng định nghĩa kiểu mới ta phải khai báo
như sau:
char t1[30], t2[30] , t3[30] ;
14
1. Kiểu mảng
Tự định nghĩa kiểu dữ liệu mới kiểu mảng
- Định nghĩa một kiểu mảng hai chiều:
typedef kiểu_dữ_liệu tên_kiểu[M][N] ;
VD: typedef float matran[M][N] ;
matran A, B
- Kiểu mảng hai chiều có thể được định nghĩa
thông qua kiểu mảng một chiều như sau:
typedef float f1[N] ;
typedef f1 f2[M] ;

15
2. Kiểu con trỏ
Giới thiệu
Là đối tượng DL mà giá trị của nó là địa chỉ của
các đối tượng khác (có thể là chính nó) trong
bộ nhớ
P A

P = &A;

16
2. Kiểu con trỏ
Vai trò: con trỏ được sử dụng trong một số ứng dụng sau:
❑ Quản lý các đối tượng DL động và cấu trúc lưu trữ động (như
CTLT móc nối) để cài đặt lưu trữ các CTDL động như danh sách,
cây,…
❑ Định vị, truy nhập vào các thành phần của các kiểu DL có cấu
trúc nhằm tăng tốc độ thực hiện và độ linh hoạt trong xử lý. Ta
hay dùng con trỏ để truy nhập vào mảng, bản ghi (struct).
❑ Tổ chức các tham số đóng vai trò đầu ra của các chương trình
con (hàm con). Trong C, điều này là bắt buộc, còn trong C++ ta
có thể dùng con trỏ hoặc kiểu tham chiếu.

17
2. Kiểu con trỏ - Khai báo
Khai báo biến con trỏ
T * var;
T * var1, *var2;
Trong đó: T là kiểu DL bất kì, thậm chí là kiểu con trỏ.

VD: char * s;
int i=20;
int * pi = &i, *pj=pi;
float * pf[20]; //Mảng các con trỏ kiểu float
float (*pf)[20]; //Con trỏ kiểu mảng
void * p; //Con trỏ tổng quát.

18
2. Kiểu con trỏ - Khai báo
Khai báo hằng con trỏ
const T * cons1 = <exp>; //Con trỏ đến một hằng số
T * const cons2 = <exp> ; //Hằng con trỏ
const T * const cons3 = <exp>; //Hằng con trỏ trỏ đến hằng số

const char* pc= “abcd” ; const char* const cpc = “abcd”;


char * s = “fgh” ; cpc[1] = “f” ; //error
pc[1] = ‘f’; //error cpc = “efgh”; //error
pc = “efgh”; //OK; cpc = s ; //error
pc = s ; //OK

char* const cp = “abcd”;


cp[1] = “f” ; //OK
cp = “efgh”; //error
cp = s ; //error
19
Các thao tác cơ bản trên kiểu con trỏ
◼ Phép gán
◼ Truy nhập vào đối tượng được trỏ
◼ Phép tăng/giảm địa chỉ

20
Phép gán
Sử dụng phép gán ‘=’ để gán giá trị cho con trỏ.
Cú pháp: p= <bt>;
Với p là con trỏ, bt là biểu thức vế phải, cũng phải là kiểu con trỏ.
Nếu kiểu con trỏ của p và của bt không giống nhau, ta thường phải sử
dụng phép ép chuyển kiểu. Toán tử ‘&’ hay được dùng để lấy địa chỉ
đối tượng trong bt để gán cho con trỏ.

char c= ‘A’;
char *pc = &c; void *p;
int a[5], *pa; p = pa; //Không cần ép chuyển kiểu
pa = &a[0]; p = pc; //Không cần ép chuyển kiểu
pa = a;
pa = (int*)pc; //ép chuyển kiểu

21
Truy nhập vào đối tượng được trỏ
Khi một con trỏ p đang trỏ vào một đối tượng A, ta có thể
truy nhập vào A thông qua một trong các toán tử truy
nhập. Trong C có nhiều toán tử truy nhập khác nhau,
thường phụ thuộc vào kiểu DL của đối tượng A.
◼ Toán tử *: áp dụng khi A là đối tượng DL thuộc một
trong các kiểu DL cơ bản (kí tự, số nguyên, số thực). Cú
pháp: “*p”.
◼ Toán tử []: áp dụng khi A là dữ liệu kiểu mảng. Cú
pháp: p[N], với N là một biểu thức nhận giá trị nguyên.
◼ Toán tử →: áp dụng khi A là DL kiểu struct. Cú pháp:
p→fn, với fn là tên trường trong cấu trúc struct mà ta
muốn truy nhập.

22
Truy nhập vào đối tượng được trỏ

char c= ‘A’;
char *pc = &c; struct Person {
*pc = ‘D’; // c = ‘D’; char name[30];
int age;
int a[5], *pa; } ps;
pa = &a[0]; struct Person *p = & ps;
pa[2] = 10; //a[2] = 10;
ps.name
ps.age

scanf(“%s”, p->name);
p->age = 20;

(*p).name

23
Phép tăng/giảm địa chỉ
◼ Đó là các phép toán (+,-, ++, --) trên biến con
trỏ.
char st[10];
char * ps = st; //ps = &st[0];
ps = ps +2; //ps = &st[2];

int si[10];
int * pi = &si[1];
pi++; //pi = &si[2];
pi = pi - 2; //pi = &si[0];

24
3. Mối quan hệ giữa mảng và con trỏ
◼ Bản thân mảng là một con trỏ, trỏ đến dãy
phần tử của mảng. Nó là hằng con trỏ (như
không thể gán cho tên mảng, cũng như không
thực hiện được các phép tăng/giảm địa chỉ)
◼ Biến con trỏ có thể được dùng để truy nhập
vào các phần tử của mảng giống như mảng

25
3. Mối quan hệ giữa mảng và con
trỏ
1. char s[5];
s
2. char * ps = s; //ps = &s[0];
3. ps = ps +2; //ps = &s[2];
2.
4. s++; //Error
3.
char st1[10] = "abc"; ps
st1[0] ='x';

char * st = st1;
st[0] = 'x';

26
4. Cấp phát động bộ nhớ
◼ Thư viện <stdlib.h> cung cấp một số hàm để quản lý vùng nhớ
động:
❑ Hàm malloc(): có định dạng hàm
void * malloc(int size);
Hàm cấp phát vùng nhớ động kích thước size, rồi trả về địa chỉ của ngăn
nhớ đầu tiên của vùng nhớ đó. Hàm trả về NULL nếu cấp phát không
thành công
❑ Hàm calloc(): void * calloc(int nItems, int size_item);

Cấp phát vùng nhớ kích thước nItems * size_item


❑ Hàm realloc(): void* realloc(void* block, int new_size);

Cấp phát lại một vùng nhớ mới kích thước new_size cho vùng nhớ cũ trong
block, có copy dữ liệu từ vùng nhớ cũ sang vùng nhớ mới.
❑ Hàm free(): có định dạng hàm

void free (void * p);


Hàm giải phóng vùng nhớ động đã được cấp phát tại địa chỉ p

27
Ví dụ

#include <stdio.h> for (i=0;i<N;i++) {


#include <malloc.h> p[i]=2*i+1;
//#define N 10 }
for (i=0;i<N;i++) {
int main(){ printf("%d ",p[i]);
int i; }
int N; //Giai phong vung nho dong da cap
scanf(“%d”,&N); free(p);
//Cap phat bo nho dong cho bien p } //end main
int *p = (int*)malloc(N*sizeof(int));
if (p==NULL) {
printf("Error allocating memory");
return 0;
}

28
5. Con trỏ hàm

◼ Giới thiệu: Con trỏ hàm là một con trỏ được


dùng để trỏ vào một hàm số, nhằm cho phép
hàm đó có thể được gọi thông qua con trỏ.
P F()

P = F;
P();

29
5. Con trỏ hàm (tiếp)

◼ Các thao tác cơ bản:


❑ Khai báo:
T (*p) (T1, T2, …);
Trong đó:
T là kiểu hàm mà p định trỏ đến,
T1, T2, … là các kiểu dữ liệu của các tham số của
hàm
❑ Ví dụ:

◼ Với khai báo double (*p) (double); p có thể trỏ đến các hàm có
dạng double f(double); như các hàm lượng giác;
◼ Với khai báo float (*q)(float [], int); q có thể trỏ đến các hàm có
dạng float f(float[], int); như hàm tính tổng một dãy số;
30
5. Con trỏ hàm (tiếp)
◼ Các thao tác cơ bản (tiếp):
❑ Gán giá trị:
p=f;
Trong đó: f là tên hàm mà p sẽ trỏ đến.

❑ Gọi hàm thông qua con trỏ:


p(a1, a2, …) ;
Trong đó:
a1, a2, … là các tham số truyền vào như khi gọi hàm.

31
Ví dụ: sử dụng con trỏ để gọi hàm tính tổng (sum) hoặc
tích (product) của một dãy số.
#include <stdio.h> void main(){
float (*f)(float[], int ); //Khai báo int const n = 100;
float sum(float a[], int N){ float k[n], i, m;
int i; do {
float s = 0; for (i=0;i<n;i++) k[i] = 0.5*rand(2*n);
for (i=0;i<N;i++) s += a[i]; for (i=0;i<n;i++) printf("%0.2f ", k[i]);
return s; printf("\n");
} printf("Chon tinh tong (0) hay tich (1)
float product(float a[], int N){ cua day hay thoat (khac):");
int i; scanf("%d",&m);
float s = 1; if (m == 0) f = sum; //Gán con trỏ
for (i=0;i<N;i++) s *= a[i]; else if (m == 1) f = product;
return s; else break;
} printf("Ket qua: %f \n", f(k,n)); //Gọi
hàm
} while (m == 0 || m == 1);
}
32
Tóm tắt nội dung chính
◼ Kiểu mảng
❑ Số chiều: 1 hay nhiều chiều
❑ Các thao tác cơ bản: khai báo, định nghĩa, truy nhập
❑ Bản chất: là một hằng con trỏ trỏ vào một dãy các
phần tử
◼ Kiểu con trỏ
❑ Các thao tác cơ bản: khai báo, lấy địa chỉ, truy nhập
vào đối tượng được trỏ, tăng/giảm địa chỉ
❑ Quan hệ với kiểu mảng
❑ Con trỏ hàm.

33
Bài tập
◼ Bài 1: viết chương trình nhập vào một dãy N (không lớn
hơn 100) số nguyên khác 0 từ bàn phím (nhập số 0 để
báo kết thúc việc nhập). Sau đó in ra số lượng các số
âm và số dương đã nhập được.
◼ Bài 2: Viết chương trình nhập vào một ma trận kích
thước MxN các số thực từ bàn phím, rồi tính tổng các
phần tử theo từng cột rồi in ra.
◼ Bài 3: Viết một chương trình nhập một dãy N số (không
lớn hơn 100) nguyên, sau đó sắp xếp các số theo trật tự
tăng dần, rồi in dãy trước và sau khi sắp xếp ra màn
hình.
◼ Bài 4: Viết chương trình nhập 2 ma trận AMxN và BNxP các
số thực, rồi tính tích của 2 ma trận đó. Sau đó in toàn bộ
các ma trận đó ra màn hình. 34
Bài tập
◼ Bài 5: Viết lại bài 3 nhưng có sử dụng mảng
động với cơ chế cấp phát động thay vì dùng
các mảng tĩnh.
◼ Bài 6: Viết lại bài 4 nhưng có sử dụng mảng
động với cơ chế cấp phát động thay vì dùng
các mảng tĩnh.

35

You might also like