ƯỜ
Ạ Ọ
TR
Ộ NG Đ I H C BÁCH KHOA HÀ N I
Ệ Ơ Ệ Ậ Ậ Ỹ K THU T L P TRÌNH H C ĐI N
TỬ
ả
ệ
Gi ng viên
ặ : TS. TS. Đ ng Thái Vi
t
ơ ệ ử
ộ
ệ
ơ
Đ n vơ ị: B môn C đi n t
, Vi n C khí
ộ
Hà N i, 09/2017
1
Programming Engineering in Mechatronics
Ệ Ơ Ệ Ử
Ậ
Ậ
Ỹ
K THU T L P TRÌNH H C ĐI N T
ữ ậ
ổ
ề
1. T ng quan v ngôn ng l p trình
ấ 7. C u trúc
ớ
ệ ơ ộ
ữ
2. Gi
i thi u s b ngôn ng C
8. Vào/ra trong C
ể
ử
ứ
3. Ki u, toán t
ể và bi u th c
ơ ở ủ 9. C s c a C++
ề
ể
4. Dòng đi u khi n
10. L pớ
ấ
ươ
ế ừ
5. Hàm và c u trúc ch
ng trình
11. K th a và đa hình
ả
ồ
ỏ 6. Con tr và m ng
12. Lu ng vào/ra trong C++
2
ƯƠ ỏ ả CH NG 6. Con tr và m ng
ề ả ề
ỏ
ệ 6.1 Khái ni m v m ng ệ 6.2 Khái ni m v con tr
3
ề ả ệ Khái ni m v m ng
1.1 Khái ni mệ
§ Ngôn ngữ lập trình C/C++ cung cấp cấu trúc dữ liệu gọi là mảng, được lưu trữ trong một tập hợp các dữ liệu cùng kiểu với độ dài cố định. Một mảng được sử dụng để lưu trữ tập hợp dữ liệu.
§ Thay vì khai báo biến một cách rời rạc, như biến so0, so1,… và so99, bạn có thể khai báo một mảng các giá trị như so[0], so[1] và … so[99] để biểu diễn các giá trị riêng biệt. Một thành viên cụ thể của mảng có thể được truy cập qua index (chỉ số).
§ Tất cả mảng đều bao gồm các vị trí nhớ liền kề nhau. Địa chỉ thấp nhất tương ứng với thành viên đầu tiền và địa chỉ cao nhất tương ứng với thành viên cuối cùng của mảng.
4
ề ả ệ Khái ni m v m ng
ả 1.2 Khai báo m ng trong C/C++
§ Để khai báo một mảng trong ngôn ngữ C/C++, bạn xác định kiểu của biến và số lượng các phần tử được yêu cầu bởi biến đó như sau:
§ Đây là mảng một chiều. Kich_co_mang phải là một số nguyên lớn hơn 0 và Kieu phải hợp lệ trong ngôn ngữ C/C++. Ví dụ, khai báo một mảng 10 phần tử gọi là balance với kiểu double, sử dụng câu lệnh sau đây:
double balance[10];
5
ề ả ệ Khái ni m v m ng
ở ạ
ả 1.3 Kh i t o m ng trong C/C++
§ Bạn có thể khởi tạo mảng trong C/C++ hoặc từng phần
tử một hoặc sử dụng một câu lệnh như dưới đây:
§ Số lượng các giá trị trong dấu ngoặc kép {} không được lớn hơn số lượng phần tử khai báo trong dấu ngoặc vuông [].
6
ề ả ệ Khái ni m v m ng
ở ạ
ả 1.3 Kh i t o m ng trong C/C++
§ Nếu bạn bỏ sót kích cỡ mảng thì mảng đó đủ lớn để giữ các giá trị được khởi tạo: Bạn sẽ tạo chính xác một chuỗi có giá trị giống hệt chuỗi bên trên bằng cách gán từng phần tử một. Dưới đây là một ví dụ khi gán giá trị cho một phần tử của mảng:
§ Tất cả các mảng đều có chỉ số (index) đầu tiên bằng 0, đây được gọi là chỉ số cơ bản và phần tử cuối cùng của mảng có chỉ số bằng độ lớn của mảng trừ đi 1. Dưới đây là cách biểu diễn hình họa cho chuỗi khai báo bên trên thông qua chỉ số:
7
ề ả ệ Khái ni m v m ng
ầ ử ả
ậ
1.3 Truy c p các ph n t
m ng trong C/C++
§ Một mảng được truy cập bởi cách đánh chỉ số trong tên của mảng. Dưới đây là một cách truy cập một giá trị của mảng:
§ Câu lệnh trên lấy phần tử thứ 56 của mảng và gán giá trị
này cho biến hocphi.
8
ề ả ệ Khái ni m v m ng
Ví dụ
9
ề ả ệ Khái ni m v m ng
ề
ả
1.4 M ng đa chi u trong C++
§ C++ hỗ trợ các mảng đa chiều. Dưới đây là mẫu chung
của một khai báo mảng đa chiều:
§ Ví dụ: int hocphi[5][10][4];
10
ề ả ệ Khái ni m v m ng
ả
ề M ng hai chi u trong C++
§ Mẫu đơn giản nhất của mảng đa chiều là mảng hai chiều. Một mảng hai chiều về bản chất là danh sách của các mảng một chiều. Để khai báo một mảng hai chiều integer với kích cỡ x, y, bạn nên viết như sau:
§ Ở đây, kieu_du_lieu có thể là bất kỳ kiểu dữ liệu có hiệu lực nào và ten_mang sẽ là một định danh C++ có hiệu lực.
11
ề ả ệ Khái ni m v m ng
ả
ề M ng hai chi u trong C++
§ Một mảng hai chiều có thể như là một bảng mà có x hàng và y cột. Một mảng hai chiều a chứa 3 hàng và 4 cột có thể được hiển thị như sau:
12
§ Như vậy, mỗi phần tử trong mảng a được định danh bởi một tên phần tử trong kiểu mẫu a[i][j], với a là tên mảng và i, j là các subscript – chỉ số được xác định duy nhất mỗi phần tử trong a.
ề ả ệ Khái ni m v m ng
ở ạ
ề
ả
Kh i t o m ng hai chi u trong C++
§ Các mảng đa chiều có thể được khởi tạo bởi xác định các giá trị trong dấu móc vuông cho mỗi hàng. Sau đây là một hàng với 3 hàng và mỗi hàng chứa 4 cột.
§ Các dấu ngoặc ôm, mà chỉ các hàng giá trị là tùy ý. Khởi
tạo sau là tương đương với ví dụ trên:
13
ề ả ệ Khái ni m v m ng
ầ ử ủ
ậ
ề
ả
Truy c p các ph n t
c a m ng hai chi u trong C++
§ Các phần tử mảng hai chiều được truy cập bởi sử dụng
các chỉ số, ví dụ chỉ số hàng và chỉ số cột. Ví dụ:
§ Lệnh trên sẽ truy cập vào phần tử thứ 4 từ hàng thứ 3 của mảng. Bạn có thể kiểm tra lại nó trong sơ đồ trên.
14
ề ả ệ Khái ni m v m ng
Ví dụ
15
ề ả ệ Khái ni m v m ng
ỏ ớ ộ
ả
1.5 Con tr t
i m t m ng trong C++
§ Một tên mảng là một con trỏ hằng số tới phần tử đầu tiên
của mảng. Vì thế, trong khai báo:
§ phithuebao là một con trỏ tới &phithuebao[0], mà là địa chỉ của phần tử đầu tiên của mảng phithuebao. Do vậy, đoạn chương trình sau gán p địa chỉ của phần tử đầu tiên của phithuebao:
16
ề ả ệ Khái ni m v m ng
ỏ ớ ộ
ả
1.5 Con tr t
i m t m ng trong C++
§ Sử dụng các tên mảng như là các con trỏ hằng số là hợp lệ, và ngược lại. Vì thế, *(phithuebao + 4) là cách chính thống để truy cập dữ liệu tại phithuebao[4].
§ Một khi bạn lưu địa chỉ của phần tử đầu tiên trong p, bạn có thể truy cập các phần tử mảng bởi sử dụng *p, *(p+1), *(p+2), …. Dưới đây là ví dụ để chỉ tất cả các khái niệm được đề cập ở trên:
17
ề ả ệ Khái ni m v m ng
Ví dụ
18
ề ả ệ Khái ni m v m ng
ề
ả
ư
ố
1.6 Truy n m ng nh là các tham s hàm trong C++
§ C++ không cho phép truyền cả một mảng như là một tham số tới một hàm. Tuy nhiên, bạn có thể truyền một con trỏ tới một mảng bởi việc xác định tên mảng đó mà không cần một chỉ mục.
§ Nếu bạn muốn truyền một mảng một chiều như là tham số trong một hàm, bạn sẽ phải khai báo tham số chính thức của hàm theo một trong 3 cách sau và tất cả 3 cách thức khai báo này đều tạo kết quả giống nhau bởi vì, mỗi cách thức sẽ nói cho bộ biên dịch rằng một con trỏ integer sẽ được nhận.
19
ề ả ệ Khái ni m v m ng
Cách 1
§ Các tham số chính thức dưới dạng một con trỏ, như sau:
20
ề ả ệ Khái ni m v m ng
Cách 2
§ Các tham số chính thức dưới dạng một mảng đã định
kích cỡ, như sau:
21
ề ả ệ Khái ni m v m ng
Cách 3
§ Các tham số chính thức dưới dạng một mảng chưa định
kích cỡ, như sau:
22
ề ả ệ Khái ni m v m ng
Ví dụ
23
ề ả ệ Khái ni m v m ng
Ví dụ
24
ề ả ệ Khái ni m v m ng
ừ
ả ề ả 1.7 Tr v m ng t
hàm trong C++
§ C++ không cho phép bạn trả về toàn bộ một mảng như là một tham số tới một hàm. Tuy nhiên, bạn có thể trả về một con trỏ tới một mảng bởi xác định tên mảng đó mà không phải là một chỉ số. Bạn sẽ học về con trỏ trong chương tới, vì thế bạn có thể bỏ qua chương này tới khi bạn hiểu khái niệm về Con trỏ trong C++.
§ Nếu bạn muốn trả về một mảng một chiều từ một hàm, bạn sẽ phải khai báo một hàm trả về một con trỏ như trong ví dụ sau:
25
ề ả ệ Khái ni m v m ng
ừ
ả ề ả 1.7 Tr v m ng t
hàm trong C++
§ Điểm thứ hai cần nhớ là C++ không ủng hộ việc trả về địa chỉ của biến cục bộ tới bên ngoài hàm, vì thế bạn sẽ phải định nghĩa biến cục bộ như là biến Static.
26
ề ả ệ Khái ni m v m ng
Ví dụ
27
ề ả ệ Khái ni m v m ng
Ví dụ
28
ề ệ ỏ Khái ni m v con tr
2.1 Khái ni mệ
§ Con trỏ - Pointer trong ngôn ngữ C /C++ rất dễ học và thú vị. Một vài tác vụ trong ngôn ngữ C /C++ được thực hiện dễ dàng hơn nhờ con trỏ, và những tác vụ khác trở nên linh hoạt hơn, như trong việc cấp phát bộ nhớ, không thể thực hiện mà không dùng con trỏ. Do đó rất cần thiết phải nắm vững con trỏ khi trở thành một lập trình viên C /C++ hoàn thiện. Bây giờ hãy bắt đầu bằng những bước đơn giản nhất.
§ Như bạn biết, mỗi biến trong một vùng nhớ nhất định và mỗi vùng nhớ này có địa chỉ có nó được định nghĩa để dễ dàng trong việc truy cập sử dụng toán tử (&) tương ứng với địa chỉ của nó trong bộ nhớ.
29
ề ệ ỏ Khái ni m v con tr
2.1 Khái ni mệ
30
ề ệ ỏ Khái ni m v con tr
ỏ
2.2 Con tr là gì?
§ Một con trỏ là một biến mà trong đó giá trị của nó là địa chỉ của biến khác. Ví dụ như địa chỉ của vùng nhớ. Giống như các biến và hằng số, bạn phải khai báo con trỏ trước khi bạn có thể sử dụng nó để lưu trữ bất kì địa chỉ của biến nào. Dạng tổng quát của việc khai báo con trỏ như sau:
§ Ở đây, kieu_du_lieu là kiểu dữ liệu cơ bản con trỏ, nó là kiểu hợp lệ trong ngôn ngữ C và ten_bien là tên giá trị của con trỏ. Phần ký tự * sử dụng trong khai báo con trỏ giống như việc bạn sử dụng cho phép nhân. Mặc dù vậy, trong khai báo này, ký tự * được thiết kế để sử dụng các biến của con trỏ.
31
ề ệ ỏ Khái ni m v con tr
ỏ
2.2 Con tr là gì?
§ Dưới đây là một số cách khai báo hợp lệ của con trỏ:
§ Kiểu dữ liệu thực sự của giá trị của tất cả các con trỏ, có thể là integer, float, character, hoặc kiểu khác, là giống như, một số long hexa biểu diễn một địa chỉ bộ nhớ. Điểm khác nhau duy nhất của các con trỏ của các kiểu dữ liệu khác nhau là kiểu dữ liệu của biến hoặc hằng số mà con trỏ chỉ tới.
32
ề ệ ỏ Khái ni m v con tr
ử ụ
ỏ
2.3 Cách s d ng con tr trong C /C++
§ Có một vài phép toán quan trọng, sẽ giúp chúng ta làm
việc với con trỏ một cách thường xuyên:
a) chúng ta định nghĩa biến con trỏ,
b) gán địa chỉ của biến đến một con trở và
c) cuối cùng truy cập các giá trị biến địa chỉ trong biến con trỏ.
Điều này được thực hiện bởi toán tử * trả về giá trị các các biến chứa trong địa chỉ được xác định bởi toán tử này. Dưới đây là các sử dụng những phép toán trên:
33
ề ệ ỏ Khái ni m v con tr
Ví dụ
34
ề ệ ỏ Khái ni m v con tr
ỏ
2.4 Con tr NULL trong C/C++
§ Nó luôn luôn là một bài thực hành tốt khi gán con trỏ NULL cho một biến con trỏ trong trường hợp bạn không biết chính xác địa chỉ để được gán. Điều này được thực hiện tại thời điểm khai báo biến. Một con trỏ mà được gán NULL được gọi là một con trỏ null.
§ Con trỏ NULL là một hằng với một giá trị là 0 được định
nghĩa trong một vài thư viện chuẩn, gồm iostream.
35
ề ệ ỏ Khái ni m v con tr
Ví dụ
36
ề ệ ỏ Khái ni m v con tr
ỏ
2.4 Con tr NULL trong C/C++
§ Để kiểm tra một con trỏ null, bạn có thể sử dụng một lệnh
if như sau:
37
ề ệ ỏ Khái ni m v con tr
ỏ ố ọ
2.5 Con tr s h c trong C/C++
§ Như đã giải thích trong chương chính, con trỏ trong C/C+ + là một địa chỉ, mà là một giá trị số. Vì thế, bạn có thể thực hiện các hoạt động số học trên một con trỏ như khi bạn thực hiện với giá trị số. Có 4 toán tử số học mà có thể được sử dụng trên các con trỏ: ++, --, +, và -.
§ Để hiểu con trỏ số học, chúng ta giả sử rằng contro là một con trỏ nguyên mà trỏ tới 1000 địa chỉ. Giả sử số nguyên là 32 bit, chúng ta thực hiện thao tác số học trên con trỏ này:
38
ề ệ ỏ Khái ni m v con tr
ộ
ỏ
Tăng m t Con tr trong C/C++
§ Chúng ta ưa thích sử dụng một con trỏ trong chương trình thay vì sử dụng một mảng bởi vì con trỏ biến có thể tăng, không giống như tên mảng, không thể tăng được, bởi vì nó là một con trỏ hằng số. Chương trình sau tăng con trỏ biến để truy cập tới mỗi phần tử của mảng:
39
40
ề ệ ỏ Khái ni m v con tr
ề ệ ỏ Khái ni m v con tr
ộ
ỏ
ả
Gi m m t Con tr trong C/C++
41
ề ệ ỏ Khái ni m v con tr
ỏ So sánh Con tr trong C/C++
§ Các con trỏ có thể được so sánh bởi sử dụng các toán tử quan hệ, như ==, <, và >. Nếu p1 và p2 trỏ tới các biến mà có liên quan với nhau, như các phần tử của cùng một mảng, thì khi đó, p1 và p2 có thể được so sánh với nhau một cách có ý nghĩa.
§ Chương trình sau chỉnh sửa ví dụ trước bởi tăng con trỏ biến để mà địa chỉ mà nó trỏ tới nhỏ hơn hoặc bằng địa chỉ của phần tử cuối cùng của mảng, là &var[MAX - 1]:
42
ề ệ ỏ Khái ni m v con tr
ỏ So sánh Con tr trong C/C++
43
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.6 Con tr và M ng trong C++
§ Con trỏ và Mảng có mối liên hệ chặt chẽ. Thực tế, con trỏ và mảng là có thể thay thế cho nhau trong một số trường hợp. Ví dụ, một con trỏ mà trỏ tới phần đầu mảng có thể truy cập mảng đó bởi sử dụng: hoặc con trỏ số học hoặc chỉ mục mảng. Bạn xét ví dụ sau:
44
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.6 Con tr và M ng trong C++
45
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.6 Con tr và M ng trong C++
§ Tuy nhiên, con trỏ và mảng không hoàn toàn thay thế
được cho nhau. Ví dụ, bạn xét chương trình sau:
46
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.6 Con tr và M ng trong C++
§ Việc áp dụng toán tử con trỏ * tới biến mang là hoàn hảo, nhưng nó không hợp lệ khi sửa đổi giá trị biến mang. Lý do là biến mang là một constant mà trỏ tới phần đầu mảng và không thể được sử dụng như là l-value.
§ Bởi vì, một tên mảng tạo một hằng con trỏ, nó có thể vẫn được sử dụng trong các biểu thức con trỏ, miễn là nó không bị sửa đổi. Ví dụ sau là một lệnh hợp lệ mà gán mang[2] giá trị 500.
Lệnh trên là hợp lệ và sẽ biên dịch thành công bởi vì mang không bị thay đổi.
47
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.7 M ng các con tr trong C++
§ Trước khi chúng ta hiểu về khái niệm mảng các con trỏ, chúng ta xem xét ví dụ sau, mà sử dụng một mảng gồm 3 số integer:
48
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.7 M ng các con tr trong C++
§ Có một tình huống khi chúng ta muốn duy trì một mảng, mà có thể lưu giữ các con trỏ tới một kiểu dữ liệu int hoặc char hoặc bất kỳ kiểu nào khác. Sau đây là khai báo một mảng của các con trỏ tới một integer:
§ Nó khai báo contro như là một mảng các con trỏ MAX kiểu integer. Vì thế, mỗi phần tử trong contro, bây giờ giữ một con trỏ tới một giá trị int. Ví dụ sau sử dụng 3 số integer, mà sẽ được lưu giữ trong một mảng các con trỏ như sau:
49
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.7 M ng các con tr trong C++
50
ề ệ ỏ Khái ni m v con tr
ỏ
ả
2.7 M ng các con tr trong C++
§ Bạn có thể sử dụng một mảng các con trỏ tới ký tự để
lưu giữ một danh sách các chuỗi như sau:
51
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ỏ
2.8 Con tr t
i con tr trong C++
§ Một con trỏ tới một con trỏ là một form không định hướng hoặc một chuỗi con trỏ. Thông thường, một con trỏ chứa địa chỉ của một biến. Khi chúng ta định nghĩa một con trỏ tới một con trỏ, con trỏ đầu tiên chứa địa chỉ của con trỏ thứ hai, mà trỏ tới vị trí mà chứa giá trị thực sự như hiển thị trong sơ đồ dưới đây:
52
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ỏ
2.8 Con tr t
i con tr trong C++
§ Một biến, mà là một con trỏ tới một con trỏ, phải được khai báo. Điều này được thực hiện bởi việc đặt một dấu sao (*) ở trước tên của nó. Ví dụ, sau đây là khai báo một con trỏ tới một con trỏ trong kiểu int:
§ Khi một giá trị mục tiêu được trỏ không định hướng bởi một con trỏ tới một con trỏ, truy cập giá trị đó yêu cầu rằng toán tử dấu sao được áp dụng hai lần, như dưới ví dụ:
53
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ỏ
2.8 Con tr t
i con tr trong C++
54
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ề 2.9 Truy n con tr t
i hàm trong C++
§ C++ cho phép bạn truyền một con trỏ tới một hàm. Để làm điều này, đơn giản bạn chỉ cần khai báo tham số hàm như ở dạng một kiểu con trỏ.
§ Ở ví dụ đơn giản dưới đây, chúng ta truyền một con trỏ unsigned long tới một hàm và thay đổi giá trị bên trong hàm, mà phản chiếu trở lại trong khi gọi hàm:
55
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ề 2.9 Truy n con tr t
i hàm trong C++
56
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ề 2.9 Truy n con tr t
i hàm trong C++
§ Hàm, mà có thể chấp nhận một con trỏ, cũng có thể chấp
nhận một mảng như ví dụ sau:
57
ề ệ ỏ Khái ni m v con tr
ỏ ớ
ề 2.9 Truy n con tr t
i hàm trong C++
58
ề ệ ỏ Khái ni m v con tr
ỏ ừ
ả ề 2.10 Tr v con tr t
hàm trong C++
§ Như chúng ta đã thấy cách C++ cho phép trả về một mảng từ một hàm, tương tự như vậy, C++ cho phép bạn trả về một con trỏ từ một hàm. Để làm điều này, bạn phải khai báo một hàm trả về một con trỏ như sau:
59
§ Điều thứ hai cần ghi nhớ là, nó không là ý kiến tốt để trả về địa chỉ của một biến cục bộ tới ngoại vi của một hàm, vì thế bạn sẽ phải định nghĩa biến cục bộ như là biến static.
ề ệ ỏ Khái ni m v con tr
ỏ ừ
ả ề 2.10 Tr v con tr t
hàm trong C++
§ Bây giờ, giả sử hàm sau sẽ tạo 10 số ngẫu nhiên và trả về chúng bởi sử dụng một tên mảng mà biểu diễn một con trỏ, ví dụ, địa chỉ đầu tiên của phần tử mảng đầu tiên.
60
ề ệ ỏ Khái ni m v con tr
ỏ ừ
ả ề 2.10 Tr v con tr t
hàm trong C++
61