630 likes | 974 Views
CHƯƠNG 5: KIỂU CON TRỎ VÀ KIỂU CẤU TRÚC. Mục tiêu Giới thiệu các kiến thức về kiểu Con trỏ và kiểu Cấu trúc: khái niệm, cách định nghĩa, khai báo biến con trỏ, biến cấu trúc, cách truy xuất dữ liệu bằng con trỏ và thao tác với từng thành phần của cấu trúc.
E N D
CHƯƠNG 5: KIỂU CON TRỎ VÀ KIỂU CẤU TRÚC • Mục tiêu • Giới thiệu các kiến thức về kiểu Con trỏ và kiểu Cấu trúc: khái niệm, cách định nghĩa, khai báo biến con trỏ, biến cấu trúc, cách truy xuất dữ liệu bằng con trỏ và thao tác với từng thành phần của cấu trúc.
CHƯƠNG 5: KIỂU CON TRỎ VÀ KIỂU CẤU TRÚC • Nội dung • Kiểu con trỏ • Kiểu cấu trúc • Cấu trúc tự trỏ • Mảng cấu trúc • Con trỏ và mảng • Mảng con trỏ
5.1 Kiểu Con trỏ • Địa chỉ và phép toán & • Khai báo biến con trỏ • Các phép toán trên con trỏ • Cấp phát và giải phóng bộ nhớ cho con trỏ
Địa chỉ và phép toán & - Địa chỉ Là một đại lượng dùng để định vị (xác định) vị trí • Đối tượng muốn xác định ở đây là các biến và các phần tử (ô nhớ) chứa dữ liệu • Để xác định được địa chỉ của một đối tượng nào đó trong C++, ta sử dụng phép toán một ngôi lấy địa chỉ là “&” theo cú pháp: • &Đốitượng • \\ Dùng để xác định vị trí ô nhớ “Đốitượng”
Địa chỉ và phép toán & Trường hợp, đối tượng là một biến, ta viết &Tenbien \\Lấy địa chỉ của biến Yêu cầu: Xác định địa chỉ của đối tượng nếu là mảng, chuỗi ký tự?
Địa chỉ và phép toán & Ví dụ: string st="hello"; float a[]={5.0,4.0,3.0,2.0,1.0}; int i; char b[1]={'a'}; cout<<&st<<endl; for (i=0; i<=4; i++) cout<<&a[i]<<endl; cout<<&a<<endl; cout<<&b<<endl; cout<<b[0]<<endl;
Khai báo biến con trỏ - Con trỏ là một biến chứa địa chỉ của biến khác. • Giả sử P là con trỏ chứa địa chỉ của biến x thì ta gọi p trỏ tới x hay x được trỏ bởi p. • Thông qua con trỏ, ta có thể làm việc được với nội dung của ô nhớ mà con trỏ trỏ đến.
Khai báo biến con trỏ - Cú pháp: kiểu_dữ_liệu *tên_biến_con_trỏ; Trong đó: - Kiểu_dữ_liệu là kiểu dữ liệu của ô nhớ mà ta muốn con trỏ trỏ tới. - Dấu (*) nhằm báo cho trình biên dịch biết đang làm việc với kiểu dữ liệu là kiểu con trỏ - tên_biến_con_trỏ cũng được đặt theo quy tắc đặt tên => Khai báo con trỏ có tên tên_biến_con_trỏ trỏ tới ô nhớ chứa dữ liệu có kiểu_dữ_liệu
Khai báo biến con trỏ - Sử dụng con trỏ Để con trỏ có tênbiếncontrỏ trỏ đến biến (ô nhớ) có kiểudữliệu ta cần phải gán con trỏ tênbiếncontrỏ cho địa chỉ của biến(ô nhớ) cần trỏ đến: tênbiếncontrỏ = địa chỉ của ô nhớ (biến) cần trỏ tới Lưu ý: Không thể gán một hằng địa chỉ cụ thể cho biếncontrỏ Để lấy nội dung ô nhớ mà con trỏ trỏ tới ta sử dụng phép toán *.
Khai báo biến con trỏ float a[]={5.2, 4.3, 3.1, 2.25, 1.0}; int i=9, *q; float *p, *p1; p=a; p1=&a[3]; cout<<a<<endl; cout<<p<<endl; cout<<p1<<endl; cout<<a[3]<<endl; cout<<*p1<<endl; cout<<*p<<endl; cout<<*p+*p<<endl; q=&i; cout<<q<<endl; Ví dụ:
Các phép toán trên con trỏ + Phép gán: - Gán con trỏ với địa chỉ một biến - Gán các con trỏ với nhau (phải cùng kiểu)
Các phép toán trên con trỏ Ví dụ: int i=9, j=4, *p, *q; p=&i; q=&j; cout<<p<<endl; cout<<q<<endl; cout<<*p<<endl; cout<<*q<<endl; p=q; cout<<p<<endl; cout<<q<<endl; cout<<*p<<endl<<*q<<endl;
Các phép toán trên con trỏ + Phép tăng giảm địa chỉ: p+n: Con trỏ trỏ đến thành phần thứ n sau p p-n: Con trỏ trỏ đến thành phần thứ n trước p Một đơn vị tăng của con trỏ bằng kích thước của biến được trỏ. Chẳng hạn, biến có kiểu nguyên thì khi con trỏ tăng hay giảm một đơn vị tương đương với 2 bytes
Các phép toán trên con trỏ Ví dụ: int a[5]={1, 2, 3, 4, 5}; int *p, i; p=a; cout<<p<<endl<<endl; cout<<*p<<endl<<endl; for (i=0; i<5; i++) { cout<<p+i<<endl<<endl; cout<<*(p+i)<<endl<<endl; }
Các phép toán trên con trỏ + Phép toán tự tăng, giảm: p++, p--, ++p, --p + Hiệu 2 con trỏ: p – q + Phép toán so sánh: <, <=, >, >=… Các phép toán này thường áp dụng cho các con trỏ trỏ đến các phần tử của cùng một mảng dữ liệu
Cấp phát và giải phóng bộ nhớ cho con trỏ • Cấp phát bộ nhớ: sử dụng toán tử new • + Cấp phát tĩnh • + Cấp phát động • Cú pháp: • p = new kieugiatri; • p = new kieugiatri[so_phan_tu]
Cấp phát và giải phóng bộ nhớ cho con trỏ • Giải phóng bộ nhớ: sử dụng toán tử delete • Cú pháp: • delete p; • delete[] p;
Cấp phát và giải phóng bộ nhớ cho con trỏ Ví dụ: int *p; cout<<"Nhap mot so nguyen : "; p = new int; cin>>*p; cout<<*p<<endl; int *p; cout<<"Nhap mot so nguyen : "; cin>>*p; cout<<*p<<endl;
Cấp phát và giải phóng bộ nhớ cho con trỏ • Bài 1. Viết chương trình khai báo biến con trỏ kiểu nguyên. Sau đó, nhập vào một dãy gồm 10 số nguyên. Hiển thị dãy đó ra màn hình: • Theo thứ tự nhập vào • Theo thứ tự ngược lại • Bài 2. Sử dụng con trỏ để nhập vào một dãy gồm n số thực. Sau đó: • Hiển thị dãy vừa nhập ra màn hình • Hiển thị dãy vừa nhập ra màn hình sau khi đã sắp xếp
5.2 Kiểu cấu trúc • Kiểu cấu trúc là gì? • Làm thế nào để định nghĩa một cấu trúc? • Cách khai báo biến cấu trúc? • Làm thế nào để làm việc với các thành phần của cấu trúc?
Khái niệm • Kiểu cấu trúc là kiểu dữ liệu bao gồm nhiều thành phần có kiểu khác nhau, mỗi thành phần được gọi là một trường (field) • Chẳng hạn, Sinh viên gồm nhiều trường: mã sinh viên, họ tên, ngày tháng năm sinh, giới tính, quê quán, …
Định nghĩa cấu trúc • - Xác định các kiểu dữ liệu đơn giản của các biến thành phần được nhóm lại trong cấu trúc đó. • Cần chỉ ra: tên của cấu trúc và các thành phần của nó. Phân biệt thành phần này với thành phần khác thông qua kiểu dữ liệu, tên biến và kết thúc bằng dấu chấm phẩy (;). Các thành phần của cấu trúc được đặt trong cặp dấu móc nhọn ({}) và dùng dấu chấm phẩy (;) để kết thúc việc định nghĩa một cấu trúc.
Định nghĩa cấu trúc Cú pháp tổng quát để định nghĩa một kiểu cấu trúc như sau: struct Tên_kiểu_cấu_trúc { /*Bắt đầu một cấu trúc*/ <Kiểudl> <Biến 1>; <Kiểudl> <Biến 2>; …….. <Kiểudl> <Biến n>; }; /*Kết thúc một cấu trúc
Định nghĩa cấu trúc Ví dụ: struct Ngaythang { unsigned int Ngay; unsigned int Thang; unsigned int Nam; }; //struct lồng nhau struct Sinhvien { char MSSV[10]; char Hoten[40]; struct Ngaythang Ngaysinh; char Gioitinh[5]; char Diachi[40]; };
Khai báo biến cấu trúc Cú pháp khai báo biến kiểu cấu trúc: Tên_kiểu_cấu_trúc <Biến 1>, <Biến 2>, …; struct Ngaythang { unsigned int Ngay; unsigned int Thang; unsigned int Nam; }; Ngaythang Ngay_den, Ngay_di //Vừa định nghĩa vừa khai báo biến struct Ngaythang { unsigned int Ngay; unsigned int Thang; unsigned int Nam; }Ngay_den, Ngay_di;
Khai báo biến cấu trúc • Lưu ý: • Có thể vừa khai báo vừa khởi tạo giá trị cho biến kiểu cấu trúc • - Không được phép gán trực tiếp một bộ giá trị cụ thể cho một biến cấu trúc.
Khai báo biến cấu trúc Ví dụ: struct Ngaythang { unsigned int Ngay, Thang, Nam; }; Ngaythang Ngay_den={20,04,2010}; cout<<Ngay_den.Ngay<<endl; cout<<Ngay_den.Thang<<endl; cout<<Ngay_den.Nam<<endl;
Khai báo biến cấu trúc Ví dụ: struct Ngaythang { unsigned int Ngay, Thang, Nam; }; Ngaythang Ngay_den; Ngay_den={20,04,2010}; cout<<Ngay_den.Ngay<<endl; cout<<Ngay_den.Thang<<endl; cout<<Ngay_den.Nam<<endl;
Phạm vi của cấu trúc - Nếu kiểu struct được khai báo trước tất cả các hàm, nó có hiệu lực trong toàn bộ chương trình có sử dụng nó. • Nếu nó đặt bên trong một hàm, thì chỉ có hàm đó được dùng nó. • - Thông thường kiểu cấu trúc được khai báo trong tệp tiêu đề (.h) và được sử dụng trong chương trình bằng lệnh #include.
Truy cập thành phần của cấu trúc - Nếu biến cấu trúc là thành phần trực tiếp của một cấu trúc, ta truy cập theo cú pháp: Tên_biến_cấu_trúc.tên_thành_phần - Nếu biến cấu trúc là thành phần trực tiếp của một cấu trúc mà bản thân cấu trúc này lại là thành phần của một cấu trúc khác lớn hơn, ta truy cập theo cú pháp: Tên_biến_cấu_trúc.tên_biến_cấu_trúc.tên_thành_phần Tên_biến_cấu_trúc.tên_biến_cấu_trúc.tên_biến_cấu_trúc.tên_thành_phần - Để làm việc với một biến cấu trúc, ta cần phải thao tác trên từng thành phần của nó.
Truy cập thành phần của cấu trúc Lưu ý: Đối với cấu trúc, ta có thể gán 2 giá trị biến cấu trúc cho nhau. Phép gán này cũng tương đương với việc gán từng thành phần của cấu trúc
Truy cập thành phần của cấu trúc Ví dụ: struct Diem { int Mon1, Mon2, Mon3; } sv, hs; cout<<"Nhap diem cho sinh vien!"<<endl; cout<<"Mon 1 : "; cin>>sv.Mon1; cout<<"Mon 2 : "; cin>>sv.Mon2; cout<<"Mon 3 : "; cin>>sv.Mon3; hs = sv; cout<<“Tong diem : "<<sv.Mon1+sv.Mon2+sv.Mon3<<endl; cout<<“Tong diem : "<<hs.Mon1+hs.Mon2+hs.Mon3<<endl;
Con trỏ cấu trúc - Con trỏ cấu trúc là con trỏ chứa địa chỉ của một biến cấu trúc hoặc một vùng nhớ có kiểu cấu trúc nào đó. • Cách khởi tạo một con trỏ cấu trúc: • + Gán địa chỉ của một biến cấu trúc cho một thành phần của mảng • + Con trỏ được khởi tạo bằng việc sử dụng toán tử new để cấp phát bộ nhớ • + Đối với con trỏ p trỏ đến mảng a, ta có thể truy cập đến các thành phần của phần tử mảng: • p[i].Hoten; (p+i)->Hoten; *(p+i).Hoten;
Con trỏ cấu trúc struct Sinhvien { char Hoten[30], Gioitinh[5]; float Diem; } lop[50]; Sinhvien *p; cout<<"Nhap ho ten sinh vien thu 11: "; cin.getline(lop[10].Hoten,30); cout<<"Nhap gioi tinh : "; cin.getline(lop[10].Gioitinh,5); cout<<"Nhap diem : "; cin>>lop[10].Diem; p = lop; cout<<p[10].Hoten<<endl; cout<<(p+10)->Gioitinh<<endl; cout<<"Diem : "<<(*(p+10)).Diem<<endl;
Địa chỉ của các thành phần cấu trúc - Các thành phần của cấu trúc cũng giống như các biến. Do đó, việc lấy địa chỉ của các thành phần này cũng tương tự như với biến. Chẳng hạn: &Sinhvien.Hoten; &Lop[10].Gioitinh; &(p->Diem);
5.3 Cấu trúc tự trỏ Cách 1: typedef struct Têncấutrúc tênbiếncấutrúc; struct Têncấutrúc { các thành phần; tênbiếncấutrúc *contrỏ; };
5.3 Cấu trúc tự trỏ Cách 2: struct Têncấutrúc { các thành phần; Têncấutrúc *contrỏ; }; typedef Têncấutrúc tênbiếncấutrúc;
5.3 Cấu trúc tự trỏ Cách 3: typedef struct tênbiếncấutrúc { các thành phần; tênbiếncấutrúc *contrỏ; };
5.3 Cấu trúc tự trỏ Cách 4: struct tênbiếncấutrúc { các thành phần; tênbiếncấutrúc *contrỏ; };
5.3 Cấu trúc tự trỏ Ví dụ: typedef struct SV sinhvien; struct SV { char Hoten[30]; float Diem; sinhvien *tiep; }; struct SV { char Hoten[30]; float Diem; SV *tiep; }; typedef SV sinhvien;
5.3 Cấu trúc tự trỏ Ví dụ: typedef struct sinhvien; { char Hoten[30]; float Diem; sinhvien *tiep; }; struct sinhvien { char Hoten[30]; float Diem; sinhvien *tiep; };
5.4 Mảng cấu trúc • Mảng cấu trúc là mảng mà kiểu phần tử của nó là kiểu cấu trúc. • Ví dụ: ta xét khai báo sau • Xây dựng danh sách sinh viên của lớp gồm n sinh viên. struct sinhvien { char Hoten[30]; float Diem; }; sinhvien SV[60];
5.5 Con trỏ và mảng • Con trỏ và mảng một chiều • Con trỏ và xâu ký tự
Con trỏ và mảng một chiều - Con trỏ có thể trỏ đến một biến hoặc một mảng. • Khi đó, địa chỉ của mảng chính là địa chỉ của phần tử đầu tiên của mảng a[0]. a+i là địa chỉ thành phần thứ i của mảng a. • Nếu cho con trỏ *p trỏ đến mảng a ta gán: p=a hoặc p=&a • Khi đó, giá trị dữ liệu của của phần tử a[0] là *p=a[0], phần tử thứ i là *(p+i)=a[i]=*(a+i) • Chú ý: ta có thể viết *(p+1), *(a+1), *(p++) nhưng không thể viết *(a++).
Con trỏ và mảng một chiều Ví dụ: int a[100]; int i, n, *p; cout<<"Nhap so phan tu cua mang n= "; cin>>n; for (i=0;i<n;i++) { cout<<"a["<<i<<"]= "; cin>>a[i];} cout<<"In cac gia tri vua nhap "<<endl; for (i=0;i<n;i++) cout<<a[i]; cout<<endl; for (p=a;p<a+n;p++) cout<<*p; cout<<endl; for (p=a,i=0;i<n;i++) cout<<*(p+i); cout<<endl; for (i=0;i<n;i++) cout<<*(a+i); cout<<endl;
Con trỏ và xâu ký tự Một con trỏ ký tự có thể xem như một xâu ký tự, trong đó xâu chính là tất cả các ký tự kể từ byte con trỏ đầu tiên đến byte “\0” gặp đầu tiên. Vì vậy, có thể khai báo các xâu dưới dạng con trỏ ký tự như sau: char *st; char *st1=“Hello”;
Con trỏ và xâu ký tự Các hàm trên xâu vẫn được áp dụng như khai báo nó dưới dạng mảng ký tự. Tuy nhiên, ta có thể gán trực tiếp 2 xâu dưới dạng con trỏ. Ví dụ: char *st; char *s=“Hello”; st = s;
Con trỏ và xâu ký tự Khi khai báo xâu dạng con trỏ, thì nó vẫn chưa cấp phát bộ nhớ cụ thể. Do đó, ta phải sử dụng toán tử new để xin cấp phát bộ nhớ cho xâu với độ dài cần thiết. Ví dụ 1: char *st, *s = new char[30]; strcpy(s, “Hello”); st = s; Ví dụ 2: char *st, *s = new char[30]; strcpy(s, “Hello”); st = new char[30];strcpy(st,s);
5.6 Mảng con trỏ Khái niệm chung • Mảng con trỏ là mảng mà các phần tử của nó là các con trỏ có cùng kiểu. • Do phần tử của mảng con trỏ là một con trỏ trỏ đến một mảng nào đó -> cho phép quản lý nhiều mảng dữ liệu cùng kiểu.
5.6 Mảng con trỏ Khai báo mảng con trỏ kiểudữliệu *tênmảng[sốphầntử]; Tác dụng: khai báo mảng con trỏ có số phần tử và kiểu dữ liệu được chỉ ra. Chú ý: mảng xâu ký tự là trường hợp riêng của mảng con trỏ nói chung, trong đó kiểu dữ liệu là kiểu char