Giáo trình
Lập trình hướng đối
tượng với C++
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 1
Mét sè tiÖn Ých vµ më réng cña C++ so
víi C
Quên đóng */ cho các chú thích
Khai báo biến sau khi biến được sử dụng
Sử dụng lệnh return để trả về giá trị nhưng khi định nghĩa hàm lại mô tả hàm kiểu
void hoặc ngược lại, quên câu lệnh này trong trường hợp hàm yêu cầu giá trị trả về.
Không có hàm nguyên mẫu cho các hàm
Bỏ qua khởi tạo cho các biến tham chiếu
Thay đổi giá trị của các hằng
Tạo các hàm cùng tên, cùng tham số.
Một số thói quen lập trình tốt
Sử dụng “//” để tránh lỗi không đóng */ khi chú thích nằm gọn trong một dòng.
Sử dụng các khả năng vào ra mới của C++ để chương trình dễ đọc hơn.
Đặt các khai báo biên lên đầu khối lệnh.
Chỉ dùng từ khoá inline với các hàm “nhỏ”,”không phức tạp”.
Sử dụng con trỏ để truyền tham số cho hàm khi cần thay đổi giá trị tham số, còn
tham chiếu dùng để truyền các tham số có kích thước lớn mà không có nhu cầu
thay đổi nội dung.
Tránh sử dụng biến cùng tên cho nhiều mục địch khác nhau trong chương trình.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 2
C/ BÀI TẬP MẪU
Ví d 1: C++ chấp nhận hai kiểu chú thích. Các lập trình viên bằng C đã quen với
cách chú thích bằng /*…*/. Trình biên dịch sẽ bỏ qua mọi thứ nằm giữa /*…*/.
Xét chương trình sau :
CT1_1.CPP
CT1_2.CPP #include <iostream.h>
void main()
{
int X, Y;
cout<< "Nhap vao mot so X:";
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 3
cin>>X;
cout<< "Nhap vao mot so Y:";
cin>>Y;
cout<<"Tong cua chung:"<<X+Y<<"\n";
cout<<"Hieu cua chung:"<<X-Y<<"\n";
} Ví d 3:
}
Ví d 4:
Tìm lỗi sai của đoạn chương trình sau:
int n;
cin>>n;
for(int i=0;i<n;i++)
{ int a[100];
cin>>a[i];
}
for(i=0;i<n;i++)
cout<<a[i];
Lời gải
Chương trình bị lỗi trong vòng for thứ hai do biến mảng a không được định nghĩa.
Mảng a được khai báo trong vòng for thứ nhất chỉ có tầm hoạt động trong vòng for
đó mài thôi. Do vậy, chương trình không thể biết ở trong vòng lặp for thứ hai. Chú ý
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 4
biến nguyên i được khai báo trong dòng lệnh for có vị trí tương đương với việc khai
{
a++;
return a;
}
int & ref3(int & a)
{
a++;
return a;
}
int a=5;
int &r1;
int & r2=22;
int &r3=a;
int &r4=ref3(5);
int &r5=ref3(a);
Trả lời:
Trong các hàm có kết quả trả về là một tham chiếu, chúng ta luôn phải chú ý rằng
biến được trả lại có giá trị là tham chiếu không bị xoá khoải bộ nhớ chương trình khi kết
thúc thực hiện hàm. Do vậy hai hàm ref1 và ref2 là sai bởi vì nó trả về tham chiếu tới
biên mà a lại là biến cục bộ trong ref1 và là tham số trong ref2 chỉ được tạo ra tạm thời
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 5
trên stack khi gọi hàm và xoá khỏi stack khi kết thúc hàm. Hàm ref3 không có lỗi vì a là
một tham chiếu tới một biến không nằm trong hàm.
Trong khái báo các tham chiếu phải được gắn với một biến nào đó trong bộ nhớ. Do
vậy các khai báo r1, r2 là sai. Lời gọi ref3(5) cũng là sai bởi vì tham số cho hàm phải là
tham chiếu đến một biến, trong khi đó ta lại truyền vào hằng số.
#include <iostream.h>
void hoanvi(int &a,int &b)
{
int tam=a; a=b; b=tam;
}
void main()
{
// Nhap du lieu
int n;
cout<<” Ban hay cho so phan tu cua mang n=”;cin>>n;
//Cap phat bo nho cho mang
int *a=new int(n);
cout<<”\n Hay nhap gia tri cho cac phan tu cua mang \n”;
for(int i=0;i<n;++i)
{
cout<<”a[“<<i<<”]=”;cin>>a[i];
}
// Sap xep
for(i=0;i<n-1;i++)
for(int j=i++;j<n;j++)
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 6
if (a[i]>a[j]) hoanvi(a[i],a[j]);
// In ket qua
}
srand((unsigned)time(NULL));
for(int I=0;I<N;++I)
P[I]=rand()%100; //Tạo các số ngẫu nhiên từ 0 đến 99
cout<<"Mang truoc khi sap xep\n";
for(I=0;I<N;++I)
cout<<P[I]<<" ";
for(I=0;I<N-1;++I)
for(int J=I+1;J<N;++J)
if (P[I]>P[J])
{
int Temp=P[I];
P[I]=P[J];
P[J]=Temp;
}
cout<<"\nMang sau khi sap xep\n";
for(I=0;I<N;++I)
cout<<P[I]<<" ";
delete []P;
} kết quả
cout<<"Nhap so dong cua ma tran:";
cin>>M;
cout<<"Nhap so cot cua ma tran:";
cin>>N;
//Cấp phát vùng nhớ cho ma trận A
if (!AllocMatrix(&A,M,N))
{ //endl: Xuất ra kí tự xuống dòng (‘\n’)
cout<<"Khong con du bo nho!"<<endl;
return 1;
}
//Cấp phát vùng nhớ cho ma trận B
if (!AllocMatrix(&B,M,N))
{
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 8
cout<<"Khong con du bo nho!"<<endl;
FreeMatrix(A);//Giải phóng vùng nhớ A
return 1;
}
//Cấp phát vùng nhớ cho ma trận C
if (!AllocMatrix(&C,M,N))
{
cout<<"Khong con du bo nho!"<<endl;
FreeMatrix(A);//Giải phóng vùng nhớ A
FreeMatrix(B);//Giải phóng vùng nhớ B
return 1;
return 1;
}
//Giải phóng vùng nhớ
void FreeMatrix(int *A)
{
if (A!=NULL)
delete [] A;
}
//Nhập các giá trị của ma trận
void InputMatrix(int *A,int M,int N,char Symbol)
{
for(int I=0;I<M;++I)
for(int J=0;J<N;++J)
{
cout<<Symbol<<"["<<I<<"]["<<J<<"]=";
cin>>A[I*N+J];
}
}
//Hiển thị ma trận
void DisplayMatrix(int *A,int M,int N)
{
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 9
for(int I=0;I<M;++I)
{
for(int J=0;J<N;++J)
{
Câu 2: Tìm lời gọi hàm sai cho hàm sau:
void func(int i=0,int j=0);
a)func()
b)dunc(1);
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 10
c)func(1.5,2.5);
d)func(1,2);
Câu 3: Cho biết giá trị của y sau khi thực hiện:
int &foo(int &a)
{ a++;
return a;
}
int i=5;
int &r=foo(i);
r++;
a) i=5;
b) i=6;
c) i=7;
d) không câu nào đúng
Câu 4: Tìm giá trị của x, y:
void test(int &a, int b)
{ a+=b;
b=a;
}
int x=1,y=2;
}
printf("Mau chung nho nhat la %d\n",i);
}
Bài 1.2: Viết chương trình nhập vào số nguyên dương h (2<h<23), sau đó in ra các tam
giác có chiều cao là h như các hình sau:
Bài 1.3: Một tam giác vuông có thể có tất cả các cạnh là các số nguyên. Tập của ba số
nguyên của các cạnh của một tam giác vuông được gọi là bộ ba Pitago. Đó là tổng bình
phương của hai cạnh bằng bình phương của cạnh huyền, chẳng hạn bộ ba Pitago (3,
4, 5). Viết chương trình tìm tất cả các bộ ba Pitago như thế sao cho tất cả các cạnh
không quá 500.
Bài 1.4: Viết chương trình in bảng của các số từ 1 đến 256 dưới dạng nhị phân, bát
phân và thập lục phân tương ứng.
Bài 1.5: Viết chương trình nhập vào một số nguyên dương n. Kiểm tra xem số nguyên
n có thuộc dãy Fibonacci không?
Bài 1.6: Viết chương trình nhân hai ma trân Amxn
và Bnxp. Mỗi ma trận được cấp phát
động và các giá trị của chúng phát sinh ngẫu nhiên (Với m, n và p nhập từ bàn phím).
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 12
Bài 1.7: Viết chương trình tạo một mảng một chiều động có kích thước là n (n nhập từ
bàn phím). Các giá trị của mảng này được phát sinh ngẫu nhiên trên đoạn [a, b] với a
và b đều nhập từ bàn phím. Hãy tìm số dương nhỏ nhất và số âm lớn nhất trong mảng;
nếu không có số dương nhỏ nhất hoặc số âm lớn nhất thì xuất thông báo "không có số
dương nhỏ nhất" hoặc "không có số âm lớn nhất".
Bài 1.8: Anh (chị) hãy viết một hàm tính bình phương của một số. Hàm sẽ trả về giá trị
Trang 13
Hàm ComputeCylinder() để tính thể tích v và diện tích bề mặt s của một
hình trụ có bán kính r và chiều cao h. Hàm này có prototype như sau:
void ComputeCylinder(float & v, float &s, float r = 1.0 , float h =
1.0);
Bài 1.11: Viết chương trình quản lý điểm học sinh với cấu trúc danh sách nối đơn.
Trong chương trình sử dụng toán tử vào ra và toán tử new để cấp phát bộ nhớ động.
Bài 1.12: Viết một hàm thực hiện việc sắp xếp một mảng số nguyên theo chiều tăng
dần hoặc giảm dần. Hàm này tự động mặc định kiểu sắp xếp theo chiều tăng dần.
Bài 1.13: Viết một hàm giải phương trình bậc hai. Hàm này trả lại thông báo rằng
phương trình có nghiệm hay không có nghiệm kép. Nếu có nghiệm thì nghiệm sẽ được
lưu vào tham số x1, x2 và được truyền như là tham biến.
Bài 1.14:Viết một hàm tìm vị trí xuất hiện đầu tiên của một từ khoá trong một xâu. Hàm
này trả lại vị trí tìm thấy của từ khoá trong xâu(bắt đầu từ 0) và thay đổi con trỏ xâu
được truyền vào thành vị trí của ký tự ngay sau ký tự cuối cùng của từ khoá. Từ khoá
cần tìm được đưa vào như là một tham số và có một giá trị mặc định.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 14§èi t−îng vµ líp (Class and Object) MỤC TIÊU CỦA BÀI NÀY GIÚP NGƯỜI HỌC
Phân tích được khái niệm đóng gói dữ liệu
Khai báo và sử dụng một lớp
Khai báo và sử dụng đối tượng.
ngầm định).
Hàm huỷ bỏ cũng có thuộc tính public, không tham số, không giá trị trả về và có tên
bắt đầu bởi ~ theo sau là tên của lớp.
Bên trong phạm vị lớp( định nghĩa của các hàm thành phần), các thành phần của lớp
được gọi theo tên. Trường hợp có một đối tượng toàn cục cùng tên, muốn xác định đối
tượng ấy phải sử dụng toán tử “::”.
Lớp có thể chứa các thành phần dữ liệu là các đối tượng của lớp khác. Các đối
tượng này phải được khởi tạo trước đối tượng tương ứng của lớp bao.
Mỗi đối tượng có một con trỏ chỉ đến bản thân nó, ta gọi đó là con trỏ this. Con trỏ
này có thể được sử dụng tường minh hoặc ngầm định để tham xác định các thành
phần bên trong đối tượng. Thông thường người ta sử dụng this dưới dạng ngầm định.
Hàm bạn của một lớp là hàm không thuộc lớp nhưng có quyền truy nhập tới các
thành phần private của lớp.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 15
Khai báo bạn bề có thể khai báo bất kỳ chỗ nào trong khai báo lớp.
B. MỘT SỐ LƯU Ý (Các lỗi thường gặp, một số thói quen lập trình tốt...)
Các lỗi thường gặp
Quên dấu “;” ở cuối khai báo lớp
Khởi tạo các thành phần giá trị trong khai báo lớp
Định nghĩa chồng một hàm thành phần bằng một hàm không thuộc lớp
Truy nhập đến các thành phần riêng của lớp từ bên ngoài phạm vi lớp
Khai báo giá trị trả về cho hàm thiết lập và hàm huỷ bỏ
Khai báo hàm huỷ bỏ có tham số, định nghĩa chồng hàm huỷ bỏ
Gọi tường minh hàm thiết lập và hàm huỷ bỏ
int x,y,m;
public:
void nhapsl();
void hien();
void an()
{
putpixcel(x,y,getbkcolor());
}
void point::nhap()
{
cout<<”\n Nhập hoành độ (cột) và tung độ (hàng) cử điểm:”; cin>>x>>y;
cout<<” Nhập màu của điểm:”;cin>>m;
}
void point::hien()
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 16
{
int mau_ht;
mau_ht=getcolor();
putpixcel(x,y,m);
setcolor(mau_ht);
}
Nhận xét:
+ Trong cả ba phương thức( dù viết trong hay viết ngoài định nghĩa lớp) đều được
truy nhập đến các thuộc tính x,y và m của lớp.
+ Các phương thức viết bên trong định nghĩa lớp (như phương thức an()) được
viết như một hàm bình thường.
Time::Time()
{
Hour = Minute = Second = 0;
}
//Thiet lap mot gia tri Time moi su dung gio quan doi
//Thuc hien viec kiem tra tinh hop le tren cac gia tri du lieu
//Thiet lap ca gia tri khong hop le thanh zero
void Time::SetTime(int H, int M, int S)
{
Hour = (H >= 0 && H < 24) ? H : 0;
Minute = (M >= 0 && M < 60) ? M : 0;
Second = (S >= 0 && S < 60) ? S : 0;
}
//In thoi gian duoi dang gio quan doi
void Time::PrintMilitary()
{
cout << (Hour < 10 ? "0" : "") << Hour << ":"
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 17
<< (Minute < 10 ? "0" : "") << Minute << ":"
<< (Second < 10 ? "0" : "") << Second;
}
//In thoi gian duoi dang chuan
cout << endl << "Standard time: ";
T.PrintStandard();
cout << endl;
return 0;
} kết quả
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 18
Ví d 3
Nhập một ngày tháng năm từ bàn phím sau đó in ra màn hình.
Lời giải
CT2_3.CPP #include <iostream.h>
#include <conio.h>
#define FALSE 0
#define TRUE !FALSE
return FALSE;
}
}
int CDate::laNamNhuan(int nam)
{
if (((nam%400)==0)||(((nam%4)==0)&&((nam%100)!=0)))
return TRUE;
else
return FALSE;
}
void CDate::in()
{
cout<<endl<<"Ban da nhap vao ngay "<<mNgay;
cout<<" thang "<<Thang[mThang];
cout<<" nam "<<mNam;
}
void main()
{
CDate ngay;
ngay.nhap();
if (ngay.hopLe())
ngay.in();
else
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 19
cout<<"BAN NHAP NGAY KHONG HOP LE";
};
Lời giải
V lp A
Cách 1: Sử dụng hàm thiết lập ngầm định
A a; hoặc A a();
Cách 2: Sử dụng hàm thiết lập sao chép mặc định. Giả sử a là một đối tượng của
lớp A đã được khai báo trước. Ta có thể khai báo đối tượng a1 như sau:
A a1(a); hoặc A a1=a;
Nhận xét:
Khi trong lớp không có một khai báo hàm thiết lập nào thì trình biên dịch tự động
tạo ra một hàm thiết lập mặc định cho lớp đó. Do vậy ta có thể sử dụng khai báo đối
tượng theo cách 1 cho lớp A. Hai chách viết khai báo
A a1(a); và A a1=a; là hoàn toàn giống nhau, chúng đều sử dụng hàm thiết lập sao
chép để khởi tạo đối tượng.
V lp B
Cách 1: Sử dụng hàm thiết lập B(int). Ví dụ:
B b(5);
Cách 2: Sử dụng hàm thiết lập B(int) với tham số ngầm định là 0.
B b; tương đương với B b(0);
Cách 3: Sử dụng hàm thiết lập sao chép tương tự như lớp A.
B b1=b;
Nhận xét
Chúng ta chỉ có thể khai báo đối tượng theo các hàm thiết lập có thuộc tính quyền
truy nhập là public
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
Trang 20
Cho biết kết quả in ra màn hình của chương trình sau:
Lời giải
CT 2_6.CPP #include <iostream.h>
#include <conio.h>
class A
{
static int count;
public:
A() { count++;}
~A() { count--;}
static void printNum()
{
cout<<” Gia tri cua count la:”<<count<<endl;
}
};
int A::count=0;
void main()
{
clrscr();
A::printNum();
A a1;
a1.printNum();
A *pa=new A;
a1.printNum();
3.
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG VỚI C++
một đối tượng a1 sử dụng hàm thiết lập dạng A(). Hàm này làm tăng count lên 1. Do
vậy, dòng gọi phương thức printNum tiếp theo sẽ đưa ra màn hình giá trị của count là
1. Tương tự đối với lệnh new ta cũng tạo ra một đối tượng mới và lúc này giá trị của
count là 2. Sau khi sử dụng lệnh new tạo đối tượng, chương trình sử dụng delete để
xoá đối tượng đó khỏi bộ nhớ. Lúc này hàm huỷ bỏ được gọi và count giảm xuống 1.
Đối tượng a2 được khai báo sử dụng hàm thiết lập sao chép mặc định, mà hàm này
không làm thay đổi giá trị count. Do vậy, count vẫn giữ giá trị 1.
Ví d 6
Tim ra chỗ sai về quyền truy xuất trong đoạn chương trình sau:
class A
{
int pri;
public:
int bub;
friend void funcA(A);
firend class B;
};
class B
{
void func(A a)
{
cout<<a.pri;
cout<<a.pub;
}
};
class C
{
void func(A a)
{
- Tên thí sinh
- Điểm của ba môn thi Toán, Lý, Hoá
- Nhập thông tin của các thí sinh gồm tên và điểm của ba môn thi Toán, Lý, Hoá
- In thông tin tên, điểm và tổng điểm thi 3 môn
- Tính tổng điểm thi của thí sinh
Trên cơ sở lớp đã xây dựng được, viết chương trình làm các công việc sau.
- Nhập danh sách kết quả thi của các thí sinh vào từ bàn phím
- Đưa ra màn hình danh sách thí sinh trung tuyển( điểm chuẩn vào trường là 18)
Lời giải
CT2_7.CPP #include <iostream.h>
#include <stdio.h>
#include <conio.h>
class ThiSinh
{
// Các thuộc tính
char ten[25];// Tên của thí sinh khôngdài quá 24 ký tự
int toan, ly, hoa;// Điểm ba môn toán, lý, hoá
public:
// Các phương thức
void nhapdl();//Nhập dữ liệu cho thí sinh
void inkq();// In kết quả thi của thí sinh
int tong();// Tính tổng điểm của thí sinh
};
void ThiSinh::nhapdl()
{
cout<<”Nhap ten:”;fflush(stdin);gets(ten);
{
cout<<” Nhap du lieu cho thi sinh thu:”<<i+1<<endl;
// Gọi phương thức nhập dữ liệu của thí sinh thứ i trong mảng
dsts[i].nhapdl();
}
// In danh sách các thí sinh truýng tuyển
cout<<”Danh sach nhung nguoi trung truyen \n”;
printf(“%-25s%6s%6s%s%6s\n”,”Ten”,”Toan”,”Ly”,”Hoa”,”Tong”);
for(i=0;i<n;++i)
if(dsts[i].tong()>=18)
dsts[i].inkq();
// Xoá các đối tượng đã tạo và kết thúc chương trình
delete dsts;
getch();
}
6.
Ví d 8: Hàm thiết lập với các tham số mặc định
CT2_8.CPP #include <iostream.H>
{
Hour = (H >= 0 && H < 24) ? H : 0;
Minute = (M >= 0 && M < 60) ? M : 0;
Second = (S >= 0 && S < 60) ? S : 0;
}
//Hien thi thoi gian theo dang gio quan doi: HH:MM:SS
void Time::PrintMilitary()
{
cout << (Hour < 10 ? "0" : "") << Hour << ":"
<< (Minute < 10 ? "0" : "") << Minute << ":"
<< (Second < 10 ? "0" : "") << Second;
}
//Hien thi thoi gian theo dang chuan: HH:MM:SS AM (hoac PM)
void Time::PrintStandard()
{
cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12)
<< ":" << (Minute < 10 ? "0" : "") << Minute
<< ":" << (Second < 10 ? "0" : "") << Second
<< (Hour < 12 ? " AM" : " PM");
}
int main()
{
Time T1,T2(2),T3(21,34),T4(12,25,42),T5(27,74,99);
cout << "Constructed with:" << endl
<< "all arguments defaulted:" << endl << " ";
T1.PrintMilitary();