Đặng Thành Trung Bộ môn CNPM – Khoa CNTT trungdt@gmail.com
LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG TRONG C++
………………………………………
………………………………………
……………………………………… ……………………………………… ……………………………………… ……………………………………… ……………………………………… ……………………………………… ……………………………………… ……………………………………… ……………………………………… ……………………………………… ………………………...
Chương 1: Giới thiệu về lập trình hướng đối tượng. Chương 2: Những vấn đề cơ bản trong C++. Chương 3: Đối tượng và lớp Chương 4: Thừa kế Chương 5: Các kiểu quan hệ Chương 6: Đa hình Chương 7: Khuôn hình Chương 8: Quản lý bộ nhớ Chương 9: Mảng Chương 10: Bắt ngoại lệ Chương 11: Stream và File Chương 12: Thiết kế hướng đối tượng
Nội dung chương trình
2 bài kiểm tra giữa kỳ (hệ số 1) Bài tập lớn (hệ số 3)
Yêu cầu
The Waite’s Group’s ObjectOriented
Programming in C++, 3rd edition, Robert Lafore, SAMS.
C++ Programming Language, 3rd edition,
Bjarne Stroustrup, AddisonWesley
Practical C++ Programming, Steve Oualline Lập trình hướng đối tượng, Phạm Văn Ất
Tài liệu tham khảo
CHƯƠNG 1: GIỚI THIỆU VỀ LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG (OBJECTORIENTED PROGRAMMING IN C+ +)
Tại sao phải lập trình hướng đối tượng Đặc điểm của lập trình hướng đối tượng C và C++
Nội dung chương 1
Chương trình viết bằng các ngôn ngữ hướng thủ tuc (C, Pascal...) bao gồm một chuỗi các câu lệnh nhằm yêu cầu máy tính thực hiện một nhiệm vụ nào đó. Chia chương trình thành các hàm. Mỗi hàm phục vụ cho một nhiệm vụ cụ thể và có
giao diện (interface) rõ ràng.
Nhóm một số các hàm lại thành các môđun hoặc
các thành phần (component).
1.Tại sao phải lập trình hướng đối tượng
Nhược điểm của ngôn ngữ lập trình hướng
cấu trúc: Hàm không hạn chế truy nhập tới các biến toàn
cục.
Hàm và dữ liệu không có quan hệ với nhau. Không thể xây dựng những kiểu dữ liệu phức tạp.
Ví dụ:
Kiểu Point gồm hai tọa độ x và y. Không thể thực hiện các phép tính trên kiểu Point.
Tại sao phải lập trình hướng đối tượng …
Ngôn ngữ lập trình hướng đối tượng kết hợp dữ liệu và các hàm thao tác trên dữ liệu này; gọi là đối tượng. Các hàm của đối tượng gọi là các hàm thành viên (member function), cung cấp phương thức để truy nhập dữ liệu của đối tượng.
Các thành phần dữ liệu thường được gọi là các thuộc tính
(attribute hoặc instance variable).
Việc gọi hàm thành viên của một đối tượng được xem như việc gửi thông điệp tới đối tượng đó (sending a mesage). Trong một chương trình C++ thường chứa một số các đối tượng, chúng giao tiếp với nhau thông qua việc gửi thông điệp.
2. Đặc điểm của lập trình hướng đối tượng
Data
Data
Object
Object
Member Function
Member Function
Member Function
Member Function
Data
Object
Member Function
Member Function
Mô hình hướng đối tượng
Lập trình hướng đối tượng chỉ quan tâm đến việc
chương trình chứa những đối tượng nào. Đối tượng là thành viên của lớp (class). Lớp là một mô tả của các đối tượng tương tự nhau. Một lớp có thể được chia thành nhiều lớp con. Một lớp có thể kế thừa từ nhiều lớp khác.
Lớp gốc được gọi là lớp cơ sở (base class) Llớp thừa kế từ lớp cơ sở gọi là lớp dẫn xuất (derived class).
Lập trình hướng đối tượng cho phép ta tạo ra kiểu dữ liệu mới và thực hiện các thao tác trên chúng một cách dễ dàng.
Đặc điểm của ngôn ngữ lập trình hướng đối tượng
C++ thừa kế từ ngôn ngữ C. Những câu lệnh trong C có thể được áp
dụng trong C++.
Những thành phần được bổ sung vào C
để trở thành C++ bao gồm: Lớp Đối tượng Lập trình hướng đối tượng
3. C và C++
CHƯƠNG 2: NHỮNG VẤN ĐỀ CƠ BẢN TRONG C++
Cấu trúc chương trình Biến Toán tử Các câu lệnh Structure Hàm
Nội dung chính
Xét ví dụ sau:
#include
• Hàm • Câu lệnh: kết thúc bởi dấu “;” • #include: yêu cầu chương trình dịch chèn thêm file vào mã nguồn. • using namespace
• cout nằm trong namespace std;
cout << “Hello world \n”; return 0;
}
• Câu chú thích: Câu chú thích bắt đầu bằng dấu // hoặc nằm trong /* */.
1. Cấu trúc chương trình
Phải khai báo biến trước khi sử dụng Có thể khai báo biến ở mọi nơi trong chương
trình
Tên biến
Phân biệt chữ hoa, chữ thường Sử dụng các ký tự từ az, 09 và dấu “_”
Ví dụ:
int var1; int var2=10;
2. Biến
Kiểu nguyên: int, long, short Kiểu ký tự: char – lưu mã ASCII của ký tự
Ký tự nằm trong dấu ‘’. Ví dụ: ‘a’ Ký tự đặc biệt: \n, \tab, \\, \’, \”, …
Kiểu không dấu: unsigned char, unsigned int,
unsigned short, unsigned long
Kiểu dấu phẩy động: float, double, long
double
Kiểu bool: có giá trị True/False
Kiểu dữ liệu đơn giản
Kiểu dữ liệu đơn giản …
Type
Low
High
Bytes
char
128
127
1
short
32768
32767
2
int
2147483648
2147483647
4
long
2147483648
2147483647
4
float
3.4x1038
3.4x1038
4
double
1.7x10308
1.7x10308
8
3.4x104932
3.4x104932
10
long double
C++ là ngôn ngữ định kiểu mạnh ().
Ví dụ:
double pi=3.14; // đúng double x=”Hello”; // sai
Ép kiểu tự động
Ví dụ:
int i = 17; float x = i; // gán 17 cho x int j = 2; float y = i/j; // gán 17/2 = 8 cho y, không phải là 8.5
Thực hiện phép toán trên 2 toán hạng có kiểu khác nhau thì toán
hạng có kiểu thấp hơn sẽ tự động bị ép về kiểu cao hơn.
Tự ép kiểu
Ví dụ:
float z = static_cast
Ép kiểu
Giá trị của hằng không thay đổi. Có 2 cách khai báo hằng:
const float PI=3.14; #define PI 3.14
Định nghĩa ở đầu chương trình Không xác định kiểu của PI
Biến hằng (Constant Variables)
cout là một đối tượng được định nghĩa trước trong C++, tương ứng với luồng ra chuẩn (standard output stream).
Toán tử << là toán tử chèn, định hướng nội dung cần hiển thị. Ví dụ:
string str=”Hello world”;
int i=8;
cout << str << endl; // endl (hoặc \n) là dấu hiệu xuống dòng. cout << ”i=” << i << endl;
Để định dạng nội dung hiển thị, sử dụng setw (nằm trong iomanip)
cout<< setw(12) << str << setw(5) << i; Kết quả: Hello world 8
Đề xác định độ chính xác, sử dụng setprecision
cout<< setprecision(3) << str << setw(5) << 10.0/3.0; Kết quả: Hello world 3.33
Sử dụng cout
cin là toán tử được định nghĩa trước trong C+ +, tương ứng với luồng vào chuẩn (standard input stream).
Toán tử >> đưa nội dung từ luồng vào chuẩn
vào biến.
Ví dụ:
int temperature; cout << ”Enter temperature in Celsius: ”; cin >> temperature;
Sử dụng cin
Một số nhiệm vụ được thực hiện bởi Library Function. Header File chứa khai báo các hàm mà ta cần sử
dụng trong chương trình.
Ví dụ:
#include
<>: yêu cầu chương trình dịch tìm từ thư mục chuẩn. ” ” : yêu cầu chương trình dịch tìm từ thư mục hiện tại. Nếu không include Header File thích hợp thì chương
trình dịch sẽ báo lỗi.
Header File và Library File
library header file
#include
myprog.cpp
somelib.h user header file
#include ”myprog.h”
myprog.h
compiler
object file
library file
myprog.obj
cs.lib
linker
executable file
myprog.exe
Toán tử toán học Toán tử quan hệ Toán tử logic
3. Toán tử
+, , *, /, %
Ví dụ:
int i = 1/3; // kết quả i = 0 float x = 1.0/3; // kết quả x=0.3333 int j = 7 % 3; // kết quả j=1
++,
Ví dụ:
int i=3; int j=7; cout << 10 * i++; // hiển thị 30, sau đó i=4 cout << 10 * ++j; // hiển thị 80, sau đó j=8
+=, =, *=, /= Ví dụ:
float x=6.0; x+=3.5; tương tự x=x+3.5;
Toán tử toán học
So sánh 2 giá trị. Kết quả trả về là true hoặc false >, <, ==, !=, >=, <= Ví dụ:
int x=44; int y=12; (x == y) // false (x >= y) // true (x != y) // true
Toán tử quan hệ
logical and : &&
(x >= 5) && ( x <= 15) // true nếu x nằm trong [5,15]
logical or : ||
(x == 5) || ( x == 10) // true nếu x=5 hoặc x=10
logical negation : !
! (x == 5) // true nếu x khác 5
Tương tự x != 5
Toán tử điều kiện (conditional operator) : ? … :
min = (alpha min = alpha; else min = beta; Toán tử logic Vòng lặp
Rẽ nhánh
Một số lệnh điều khiển khác 4. Các câu lệnh for ( i=0; i<15; i++ ) initialization expression test expression increment expression initialization
expression false test
expression exit true • Có thể đặt nhiều biểu thức trong các phần của vòng for (
; ; ); các biểu thức đó cách nhau bởi dấu phẩy. • Ví dụ: body of loop for(int j=0, float alpha=100.0; j<50; j++, alpha) { // body for } increment
expression Vòng lặp for ệ ặ int i;
for (i=1; i<=15; i++) // thân vòng l p for có 1 l nh
cout << i*i << ” ”;
cout << endl; ặ ề ệ ng hi n th b ng n ị ằ ướ ườ ể for (i=1; i<=15; i++) // thân vòng l p for có nhi u l nh
{
cout << setw(4) << i << ” ”;
// setw(n) gán kích th
c c a tr
ủ
int cube = i*i*i;
cout << setw(6) << cube << endl;
} Vòng lặp for ... c s l n l p.
ố ầ ặ c s d ng khi không bi
ượ
ể t tr
ế ướ
ị ử ụ
ứ ể ế ẫ •Vòng l p while đ
ặ
•L p cho đ n khi bi u th c ki m tra v n có giá tr True.
ặ
•Ví d :ụ char c=’n’;
while ( c != ’y’)
{ cout << ”Do you want to continue: (y/n)” << endl;
cin >> c; } Vòng lặp while test expression while ( c != ’y’ )
{
…
} false exit test
expression true body of loop Vòng lặp while ... • Trong vòng lặp do, biểu thức kiểm tra được đánh
giá ở cuối vòng lặp.
•Thân vòng lặp được thực hiện ít nhất một lần.
•Ví dụ: char c;
do
{ cout << ”Do you want to continue: (y/n)” << endl;
cin >> c; }
while ( c != ’y’); Vòng lặp do test expression do
{ … }
while ( c != ’y’ ); body of loop false test
expression exit true Vòng lặp do ... • Phụ thuộc vào điều kiện kiểm tra là true hay false
để quyết định nhánh thực hiện
• Ví dụ: int x;
cout << ”Enter a number: ”;
cin >> x;
if ( x > 100) cout << x << ” is greater than 100” << endl; else cout << x << ” is not greater than 100” << endl; If … Else test expression false test
expression true if ( x > 100)
{
…
}
else
{
…
} body of if body of else exit If … Else • Lệnh switch được sử dụng khi có nhiều nhánh rẽ
phụ thuộc vào giá trị của cùng một biến. switch( Lệnh switch char c;
cout << ”Enter your choice (a/b/c) : ”;
cin >> c;
switch (c)
{
case ’a’:
cout << ”You picked a!” << endl;
break;
case ’b’:
cout << ”You picked b!” << endl;
break;
case ’c’:
cout << ”You picked c!” << endl;
break;
default:
cout << ”You picked neither a,b,c !” << endl;
} Lệnh switch ... true first case body variable
equals
const 1 false true second case body variable
equals
const 2 false default body exit Lệnh switch ... break: thoát khỏi vòng lặp hoặc switch
continue: trở lại đầu vòng lặp.
goto: nhảy tới một nhãn xác định Một số lệnh điều khiển khác Structure là một tập hợp các biến đơn giản, tương tự như record trong Pascal.
Khai báo Structure: struct part
{ int modelnumber;
int partnumber;
float cost; }; Định nghĩa biến kiểu Structure: part part1; Khởi tạo các thành phần của Structure: part part1={6244, 373, 217.55}; Truy cập vào các thành phần của Structure part1.modelnumber = 6244;
part.modelnumber = 6244; //sai Hai biến cùng kiểu structure có thể gán cho nhau part part2;
part2=part1; 5. Structure Thành phần của Structure có thể là một structure khác. struct Distance
{ struct Room
{ int feet;
float inches; Distance length;
Distance width; }; }; Truy cập vào các thành phần của Structure lồng Room dining;
dining.length.feet=13;
Khởi tạo Structure lồng: Room dining = {{13, 6.5}, {10, 0.0}}; Structure lồng Định nghĩa enum: enum days_of_week {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; Khai báo biến kiểu enum days_of_week day1, day2; Giá trị của các biến kiểu enum là những giá trị mà enum đã định nghĩa trước. Các giá trị của enum được lưu dưới dạng số nguyên, bắt đầu từ 0.
Ta có thể thay đổi giá trị nguyên của giá trị đầu trong enum
enum days_of_week {Sun=1, Mon, Tue, Wed, Thu, Fri, Sat};
days_of_enum day=Mon;
cout << day; // Hiển thị 2 Enumeration Khai báo hàm xác định tên hàm, kiểu trả về, và các tham số cần thiết trong lời gọi hàm. Khai báo hàm thường nằm trong Header File (.h) Định nghĩa hàm chứa thân hàm. Định nghĩa hàm thường nằm trong source file (.cpp) Ví dụ int fac(int n); // function declaration
int fac(int n) // function definition { int result=1;
for (int i=1; i<=n; i++) result*=i; return result; }
int x=7;
cout << ”fac(” << x <<”)=” << fac(x); // call to a function; 6. Hàm Khi truyền trị, hàm sẽ tạo ra một biến cục bộ để lưu giá trị của biến được truyền. Giá trị của biến được truyền không thay đổi.
Ví dụ: void f(int val)
{ val++;
} int x=4;
f(x);
cout << x; // x = 4 Truyền trị (Passing by Value) Tham chiếu cung cấp bí danh (alias) cho biến.
Khi truyền tham số theo kiểu tham chiếu thì biến cục bộ là bí danh của biến được truyền. Địa chỉ của biến được truyền cho hàm.
Hàm có thể truy cập trực tiếp trên biến được truyền.
Ví dụ: void swap (int & a, int& b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
int x=3; int y=5;
swap(x,y); // a,b là bí danh của x,y Truyền tham chiếu (Passing by Reference) Tham chiếu hằng không cho phép hàm
thay đổi giá trị của biến được truyền. Ví dụ: void f( int& a, const int& b ) // b là tham số hằng
{ a=5; // ok
b=7; // fail } Tham số hằng Hàm chồng thực hiện các nhiệm vụ khác nhau phụ thuộc vào các tham số truyền. Ví dụ: void print(int a) // in số nguyên
{ cout << ”integer : ” << a; }
void print(string s) // in xâu ký tự
{
cout << ”string : ” << s;
} Chồng hàm (Overloaded Function) main() main() ….
…. Function1() ….
…. ….
…. ….
…. ….
….
Function1()
….
….
Function1()
….
….
Function1()
….
…. ….
…. ….
…. float Function1(int a, int b); inline float Function1(int a, int b); Inline Fuction #include double logn (double x, double base=10) // mặc định gán base = 10
{ return log(x)/log(base); }
double y=5.6;
cout << ”log(y) = ” << logn(y) << endl; // sử dụng giá trị mặc định
cout << ”ln(y) = ” << logn(y,2.71828) << endl; // base = e
cout << ”ld(y) = ” << logn(y,2) << endl; // base = 2 Hàm có tham số mặc định Một khối lệnh (block) nằm trong dấu { … }
Phạm vi của biến là khối lệnh mà biến được khai báo Biến được khai báo ở ngoài hàm là biến toàn cục (global variable). Khai báo biến ở block trong sẽ ẩn khai báo biến trùng tên ở block ngoài. Phạm vi của biến int x; // global variable ngoài ẩ ở ị trong ế ở ngoài int main()
{
int i=3; // local variable
{
int j=5; // local variable
int i=7; // local i n bi n i
ế
cout << i; // hi n th 7
ể
} // k t thúc ph m vi c a j và i
ủ
ạ
cout << i; // hi n th 3
ị
ể
} // k t thúc ph m vi c a i
ủ ế ạ ở Phạm vi của biến … CHƯƠNG 3: LỚP (Class) Lớp và các thành phần của lớp
Cài đặt các thành phần của lớp
Các thành phần tĩnh của lớp
Lớp lồng
Đối tượng
Hàm tạo và hàm huỷ
Sử dụng đối tượng Nội dung chính Lớp trong C++ cho phép người lập trình tự
định nghĩa các kiểu dữ liệu phức tạp (user
defined types) và được sử dụng tương tự như
kiểu dữ liệu có sẵn (builtin data types). Lớp thường được sử dụng để định nghĩa các
vấn đề trừu tượng như: số phức, ngày tháng,
vector … Lớp cho phép tách rời phần cài đặt
(implementation) và phần giao diện
(interface). Một lớp có chứa dữ liệu (member data) và hàm (member function). Class data1
data2
data3 functiona()
functionb()
functionc() Định nghĩa lớp … // khai báo tên l pớ khóa cho bi ngoài l p ế t không th truy nh p t
ể ậ ừ ớ Khai báo lớp char EmpName[30];
float EmpSalary; khóa cho bi // t t có th truy nh p t ngoài l p ừ ế ậ ừ ể ớ // member function public:
void AddEmployee();
}; Class
private: Phần được khai báo với từ khóa
private chỉ được truy nhập bởi các
hàm thành phần của cùng class data1
data2
functiond() public: Phần được khai báo với từ khóa
public có thể được truy nhập tại
bất kỳ nơi nào trong chương trình functiona()
functionb()
functionc() Điều khiển truy nhập Ẩn dữ liệu là kỹ thuật bảo vệ dữ liệu
khỏi những truy nhập không hợp lệ.
Đặt dữ liệu vào lớp với từ khóa private
Dữ liệu private chỉ được truy nhập bên trong lớp Dữ liệu hoặc hàm public có thể được truy nhập từ bên ngoài lớp. Ẩn dữ liệu Dữ liệu thành phần Nên được khai báo với từ khoá private.
Không được khởi tạo giá trị của dữ liệu thành phần trong định nghĩa lớp. Hàm thành phần Cài đặt bên ngoài lớp Cài đặt bên trong lớp Cài đặt bên ngoài lớp thường đơn giản và dễ hiểu hơn Dữ liệu thành phần tĩnh Là thành phần dữ liệu có giá trị như nhau đối với tất cả các đối tượng của lớp.
Sự tồn tại của dữ liệu thành phần tĩnh không phụ thuộc vào sự tồn tại của đối
tượng thuộc lớp. Sau khi khai báo thành phần dữ liệu tĩnh ở
trong lớp, phải khởi tạo giá trị cho nó ở bên
ngoài lớp. 3. Các thành phần tĩnh của
lớp Ví dụ: thành phần dữ liệu tĩnh Hàm thành phần tĩnh Dùng để truy nhập tới các dữ liệu thành phần tĩnh. Luôn được định nghĩa ở bên ngoài lớp Các thành phần tĩnh của lớp Ví dụ: hàm thành phần tĩnh Là lớp được khai báo hoặc định nghĩa trong một lớp khác. Đối tượng của lớp bên ngoài có chứa
tham chiếu tới đối tượng của lớp lồng.
Các thành phần của lớp lồng được khai
báo và định nghĩa tương tự lớp ở bên
ngoài 4. Lớp lồng Ví dụ: khai báo lớp lồng Đối tượng là một thể hiện cụ thể của một lớp Sử dụng toán tử “.” để truy nhập vào các thành phần của đối tượng
Chú ý: phạm vi truy nhập (public/private) 5. Đối tượng Ví dụ: Khai báo đối tượng Nếu lớp lồng được khai báo
với từ khoá private thì đối
tượng của lớp lồng chỉ được
tạo ra trong thân của lớp
ngoài. Các quy tắc truy nhập khác vấn được áp dụng Đối tượng của lớp lồng Hàm tạo là một hàm thành phần được tự động
viện dẫn khi khởi tạo một đối tượng mới của
lớp. Hàm tạo phải có tên trùng với tên lớp và không có giá trị trả về. Nếu không khai báo hàm tạo thì một hàm tạo mặc định sẽ tự động được tạo ra. Nếu đã khai báo hàm tạo thì không tự động có hàm tạo mặc định 6. Hàm tạo (Constructor) Ví dụ: Hàm tạo mặc định Một lớp có thể có nhiều hàm tạo với các tham số khác nhau (chồng hàm tạo) Chồng hàm tạo class Date
{ ủ ớ public:
// hàm t o v i các giá tr m c nh c a day, month, year.
ị ặ đị
ạ
Date(int d=1, int m=1, int y=1900); };
Date::Date(int d, int m, int y) : day(d), month(m), year(y) {} void main()
{
Date d1; // 1.1.1900
Date d2(5); // 5.1.1900
Date d3(15,8); // 15.8.1900
Date d4(12,10,1998); // 12.10.1998
} Hàm tạo với giá trị mặc định Hàm tạo sao chép khởi tạo đối tượng dựa trên một đối tượng khác thuộc cùng lớp. Mỗi lớp có một hàm tạo sao chép mặc định – có một tham số là đối tượng của cùng một lớp. Ta có thể định nghĩa lại hàm tạo sao chép. Date(Date& d) Ví dụ void main()
{ ặ đị ạ Date d1(12,4,1997);
Date d2(d1); // hàm t o sao chép m c nh
Date d3=d1; // hàm t o sao chép m c nh ặ đị ạ } Hàm tạo sao chép Hàm hủy tự động được gọi khi đối tượng bị hủy
Hàm hủy thường được sử dụng để giải phóng bộ nhớ
Ví dụ: class Date
{ public:
Date(); // hàm tạo
~Date(); // hàm hủy }; if {
Date d1;
} // kết thúc khối lệnh hàm hủy sẽ được viện dẫn để hủy d1. Hàm hủy (Destructor) Đối tượng làm tham số để truyền cho các hàm tương tự như các kiểu xây dựng sẵn khác. class Date
{
int diff(Date d); // Tính khoảng thời gian giữa hai ngày
};
int Date::diff(Date d)
{
int n=dayd.day;
n+= 30 * (month – d.month);
n+= 365* (year – d.year);
return n;
}
Date d1(14,5,2000);
Date d2(10,4,2000);
cout << d1.diff(d2) << endl; 7. Sử dụng đối tượng ộ class Date
{
void add_days (Date d, int n); // c ng thêm n ngày
};
void Date::add_days(Date d, int n)
{
day=d.day + n % 30;
month = d.month + (n % 365) / 30;
year = d.year + n / 365;
}
Date d1(14,5,2000);
Date d2;
d2.add_days(d1,65); // d2 gán b ng 29.7.2000 ằ Ví dụ: Đối tượng làm tham số object d2 object d1 day d1.day day
month
year day
month
year d2.add_days(d1,65); Hàm thành phần của d2 truy
nhập tới dữ liệu day của
đối tượng d1 Hàm thành phần của d2 truy
nhập trực tiếp tới dữ liệu day
của nó Ví dụ: Đối tượng làm tham số class Date
{
Date get_date (int n);
};
Date Date::get_date(int n)
{
Date temp;
temp.day=day + n % 30;
temp.month = month + (n % 365) / 30;
temp.year = year + n / 365;
return temp;
}
Date d1(14,5,2000);
Date d2=d1.get_date(65); // d2 gán b ng 29.7.2000 ằ Hàm trả về đối tượng Hàm thành phần được khai báo với từ khóa const thì sẽ không thể thay đổi dữ liệu của đối tượng. class Date
{
int year() const; // const
void add_year(int n); // non-const
};
int Date::year() const
{ return year; }
int Date::add_year(int n)
{ year+=n; } Hàm thành phần hằng Const member function có thể được cả đối tượng const và non
const viện dẫn, nhưng nonconst member function thì chỉ được
các đối tượng nonconst viện dẫn. void f(Date& d, const Date& cd)
{
int i=d.year(); // ok
d.add_year(2); // ok
int j=cd.year(); // ok
cd.add_year(3); // error } Hàm thành phần hằng ... Mỗi hàm thành phần biết đối tượng nào viện dẫn nó và có thể truy cập tới đối tượng đó một cách rõ ràng thông qua con trỏ this. class Date
{
Date get_date (int n);
};
Date Date::get_date(int n)
{
Date temp;
temp.day=this.day + n % 30;
temp.month = this.month + (n % 365) / 30;
temp.year = this.year + n / 365;
return temp;
} Tự tham chiếu (SelfReference) Chương 4: Thừa kế Thừa kế là gì?
Cài đặt thừa kế
Đa thừa kế Nội dung chính C++ cho phép tạo ra một lớp mới từ các lớp đã tồn tại. Lớp B kế thừa lớp A, có nghĩa là lớp B sẽ có
các thuộc tính và phương thức của A, ngoại
trừ các thành phần private.
Lớp B được gọi là lớp con hay lớp dẫn xuất.
Lớp A được gọi là lớp cha hay lớp cơ sở. 1. Thừa kế là gì? Một số kiểu thừa kế Một số kiểu thừa kế Một số kiểu thừa kế Cú pháp khai báo lớp dẫn xuất từ một lớp cơ 2. Cài đặt thừa kế public giữ nguyên mức truy nhập protected chuyển sang mức truy nhập protected private chuyển sang mức truy nhập private Lớp Salary thừa kế hai phương
thức ShowData() và Salary(). Trong lớp Salary cả hai phương
thức này đều có mức truy nhập là
protected. Ví dụ: thừa kế Trong hàm tạo của lớp dẫn xuất có thể
gọi đến hàm tạo của lớp cơ sở. Sau phép gán, các thành phần chung của hai đối tượng sẽ có giá trị như nhau. Gán đối tượng của lớp cơ sở
bằng lớp dẫn xuất Gán đối tượng của lớp dẫn
xuất bằng lớp cơ sở Chú ý: phải chồng toán tử = 3. Đa thừa kế Lớp B sẽ có hai bản sao của tất cả các thành phần từ lớp X.
Khi gọi đến một trong những thành phần này từ lớp B, chương trình dịch sẽ thông báo lỗi. Gọi tường minh Ví dụ lớp X có phương thức x được thừa kế
Lời gọi x từ một đối tượng của lớp B B b;
b.A1 :: x;
b.A2 :: x; Sử dụng lớp cơ sở ảo Giải quyết xung đột Lớp cơ sở ảo đảm bảo trong lớp dẫn
xuất chỉ tạo ra một bản sao của các
thành phần được thừa kế từ lớp cơ sở. Lớp cơ sở ảo Hàm tạo của lớp cơ sở chỉ được gọi trong
hàm tạo của lớp dẫn xuất trực tiếp từ nó. Hàm tạo của lớp cơ sở ảo thì được gọi ở tất cả các lớp dẫn xuất nó. Quy tắc như sau: Hàm tạo của lớp cơ sở ảo được gọi đầu tiên
Tiếp theo đó là hàm tạo của các lớp dẫn xuất trực tiếp
… Lớp cơ sở ảo Chương 5: Các kiểu quan hệ Quan hệ bạn
Quan hệ cấu thành Nội dung chính Khi hai lớp được khai báo là bạn của
nhau thì các thành phần được định
nghĩa trong một lớp sẽ được truy nhập
bởi các thành phần ở lớp kia. Quan hệ bạn được khai báo giữa các lớp hoặc giữa lớp và hàm. Quan hệ bạn không có tính chất bắc cầu và tính chất giao hoán. 1. Quan hệ bạn Lớp Employee khai báo lớp Supervisor là bạn. Lớp Supervisor có thể truy nhập tới các thành phần của lớp Employee. Ví dụ: Quan hệ bạn Khi hai lớp được khai báo là bạn của
nhau thì các thành phần được định
nghĩa trong một lớp sẽ được truy nhập
bởi các thành phần ở lớp kia. Quan hệ bạn … SetID gọi đến ShowID() trong lớp
Supervisor. Cho nên, phần định
nghĩa phương thức SetID sẽ được
viết cuối cùng. Quan hệ bạn còn cho phép các hàm
thành phần của một lớp có thể truy
nhập tới các hàm ở bên ngoài. Khi lớp khai báo là bạn của một hàm ở
bên ngoài thì hàm đó có thể truy nhập
tới các thành phần private của lớp này. Quan hệ bạn … Quan hệ bạn giữa hàm thành phần của một lớp với một hàm khác. Quan hệ bạn C++ cho phép ta xây dựng một lớp mà có các thành phần dữ liệu của nó là các
lớp khác. Quan hệ giữa các lớp này được gọi là quan hệ cấu thành. 2. Quan hệ cấu thành //Lỗi Cú pháp:
Lớp_cha (các tham số) : lớp con (các tham số), lớp con (các tham số)
{ … } Hàm tạo trong quan hệ cấu
thành Chương 6: Đa hình Đa hình là một đặc trưng của ngôn ngữ lập trình hướng đối tượng. Đa hình tĩnh
Chồng hàm
Chồng toán tử
Đa hình động
Hàm ảo. Nội dung chính Là kỹ thuật sử dụng:
Các hàm cùng tên
Danh sách tham số khác nhau Danh sách tham số được phân biệt theo: kiểu dữ liệu của tham số
thứ tự của tham số
và số lượng tham số Để thực hiện các nhiệm vụ khác nhau.
Khi xét các hàm chồng, chương trình dịch không xem xét đến kiểu trả về của hàm. 1. Chồng hàm Overload một phương thức, tức là tạo ra
nhiều phương thức có cùng tên nhưng
khác nhau về danh sách tham số Override một phương thức, tức là tạo ra
một phương thức trong lớp dẫn xuất có
cùng prototype với một phương thức
trong lớp cơ sở. Overriding function class Mammal
{ public: void Speak() {cout << “Mamal”;} };
class Dog : public Mammal
{ public: void Speak() {cout << “Dog”;} };
int main()
{ Mamal m;
Dog d;
m.Speak();
d.Speak(); // Mamal
// Dog } Ví dụ: Overriding function Nếu lớp cơ sở có một phương thức bị
chồng và lớp dẫn xuất lại override
phương thức này, thì phương thức của
lớp dẫn xuất sẽ ẩn tất cả các phương
thức của lớp cơ sở có cùng tên với nó. Ẩn phương thức của lớp cơ sở class Mammal
{ public: void Move() {cout << “Mamal moves 1 step”;}
void Move(int d) {cout << “Mamal moves < };
class Dog : public Mammal
{ public: void Move() {cout << “Dog moves 1 step”;} };
int main()
{ Mamal m;
Dog d;
m.Move();
m.Move(10);
d.Move();
d.Move(10); // lỗi } Ví dụ: Ẩn phương thức của lớp cơ sở Chồng toán tử cho phép thực hiện các
phép tính trên những kiểu dữ liệu do
người sử dụng tự định nghĩa cũng tương
tự như kiểu dữ liệu cơ bản khác.
Cú pháp khai báo chồng toán tử:
kiểu_dữ_liệu operator ký_hiệu (danh sách tham số); 2. Chồng toán tử Hàm chồng toán tử một toán hạng không có tham số. Ví dụ: chồng toán tử ++ (tiền tố hoặc hậu tố) Hàm chồng toán tử hai toán hạng có một tham số. Tham số này là toán hạng phía bên phải của toán tử. Hàm chồng toán tử được viện dẫn bởi
đối tượng phía bên trái của toán tử. Chồng toán tử nhân class Date
{
bool operator==(Date d)
{
return (day==d.day) && (month==d.month) && (year==d.year);
};
bool operator<(Date d)
{
if (year < d.year)
return true;
else
if (year==d.year) && (month < d.month)
return true;
else
return (month==d.month) && (day < d.day);
};
}; 2.3. Chồng toán tử quan hệ Khi một hàm được định nghĩa là hàm ảo trong lớp cơ
sở thì nó sẽ được định nghĩa lại trong lớp dẫn xuất. Khi nào sử dụng hàm ảo? Lớp Parent và Child cùng có phương thức f
Khai báo một con trỏ thuộc kiểu của lớp Parent Parent* p; Con trỏ này trỏ đến đối tượng của lớp Child p = new Child; Sau đó, thực hiện lời gọi p>f; Kết quả: f của lớp Parent sẽ được viện dẫn
Nếu f được khai báo là hàm ảo trong lớp Parent thì f của lớp Child sẽ được viện dẫn. 3. Hàm ảo class Mammal
{ public: void Move() {cout << “Mammal moves 1 step”;} };
class Dog : public Mammal
{ public: void Move() {cout << “Dog moves 1 step”;} };
int main()
{ Mamal* p = new Dog();
p>Move(); // “Mammal moves 1 step” } Ví dụ: Không sử dụng hàm ảo class Mammal
{ public: void virtual Move() {cout << “Mammal moves 1 step”;} };
class Dog : public Mammal
{ public: void Move() {cout << “Dog moves 1 step”;} };
int main()
{ Mamal* p = new Dog();
p>Move(); // “Dog moves 1 step” } Lớp cơ sở trừu tượng là lớp cơ sở không có đối tượng nào và chỉ sử dụng để cho các lớp
khác kế thừa. Hàm thuần ảo được khai báo trong lớp sẽ làm cho lớp đó trở thành lớp cơ sở trừu tượng. virtual kiểu_trả_về tên_hàm(danh sách tham số) = 0;
Tất cả các lớp dẫn xuất đều phải định nghĩa hàm thuần ảo. Hàm thuần ảo class Mammal
{ public: virtual void Move() = 0; };
class Dog : public Mammal
{ public: void Move() {cout << "Dog moves 1 step";} };
void main()
{ // “Dog moves 1 step” // ”Lỗi” Dog p;
p.Move();
Mammal m;
m.Move(); } Ví dụ: hàm thuần ảo Chương 7: Khuôn hình Khuôn hình cho phép xây dựng các hàm và lớp tổng quát
Khuôn hình hàm
Khuôn hình lớp Nội dung chính Template cho phép định nghĩa một hàm có đặc điểm chung.
C++ sử dụng template để tạo ra những thể hiện cụ thể của hàm khi cần thiết. Ví dụ: định nghĩa hàm max template if (d1 > d2) return (d1); return (d2); } Cấu trúc thế bởi bất kỳ kiểu nào, kể cả là class hoặc một kiểu bất kỳ. 1. Định nghĩa khuôn hình Ví dụ: Sử dụng template main()
{ float f = max(3.5, 8.7);
int i = max(100, 800);
char ch = max('A', 'Q');
int i2 = max(600, 200); } Khi C++ phát hiện câu lệnh: float f = max(3.5, 8.7); nó sẽ
kiểm tra để xác định xem hàm max(float, float) đã có mã
lệnh hay chưa và nó sẽ tự động tạo ra mã lệnh nếu chưa có.
Chú ý rằng khi gặp câu lệnh int i2 = max(600, 200); thì nó sẽ
không tạo ra mã lệnh cho hàm max(int,int) nữa vì nó đã tạo
ra từ trước. Định nghĩa khuôn hình… Khi sử dụng hàm max để so sánh hai string như sau: main()
{ char *namel = "Able";
char *name2 = "Baker";
cout << max(namel, name2) << '\n'; } Vì string được biểu diễn bởi một con trỏ (char *), nên câu lệnh if (dl > d2) sẽ so sánh giá trị của con trỏ chứ không phải
nội dụng của con trỏ. Mà ta muốn C++ vẫn sử dụng cách so sánh với những dữ
liệu thông thường, riêng đối với string thì phải sử dụng
strcmp. Chuyên biệt hoá hàm Thực hiện quá trình chuyên biết hoá (specialization).
Ta định nghĩa lại hàm max chỉ dùng cho kiểu string.
char *max(char *dl, char *d2)
{ if (strcmp(dl, d2) < 0) return (dl); return (d2); } Khi C++ phát hiện ra câu lệnh cout << max(namel, name2) << '\n'; thì
nó sẽ tìm những hàm thông thường có dạng max(char *, char *) ,
sau đó nó mới tìm đến template max(kind d1, kind d2). Chuyên biệt hoá hàm … #include int count; // Số lượng phần tử của Stack
kind data[STACK_SIZE]; // Mảng chứa các phần tử của Stack public: stack (void) {count = 0; // Stack ban đầu rỗng}
void push(const kind item) { data[count] = item;
++count; }
kind pop(void) { count;
return (data[count]); } }; 2. Khuôn hình lớp Nếu ta khai báo: stack a_stack; // câu lệnh không hợp lệ Stack là một template chung.
Khi C++ nhìn thấy lệnh khai báo này, nó sẽ hỏi “stack nào?”.
Phải xác định một kiểu dữ liệu cụ thể được lưu trong stack.
Phải khai báo: stack Sau đó, ta có thể sử dụng stack như sau: a_stack.push(l);
x = a_stack.pop(); Khuôn hình lớp … Định nghĩa các hàm thành phần bên ngoài định nghĩa lớp như sau: template inline void stack data[count] = item;
++count; } Khuôn hình lớp … template Giới thiệu cho C++ biết cách tạo ra một tập các lớp có tên là stack C++ sẽ tự động tạo ra các hàm thành viên như: stack stack Nếu khai báo một hàm thành viên một cách tường minh thì C++ sẽ sử dụng định nghĩa này trước.
inline void stack data[count] = strdup(item);
++count; } Từ khoá template nói cho C++ biết ”Đây là class chung, hãy tạo ra một phiên bản tương ứng với nó”. Nếu không có từ khoá template, C++ sẽ hiểu đây là hàm chính thức và phải sử dụng nó trước. Chuyên biệt hoá lớp Chương 8: Quản lý bộ nhớ Con trỏ (Pointer)
Tham chiếu (Reference)
Sử dụng tham chiếu hay con trỏ Nội dung chính Con trỏ được sử dụng để: Truy nhập vào các thành phần của mảng
Truyền tham số cho hàm theo kiểu truyền biến Truyền mảng và xâu ký tự cho hàm
Lấy thông tin từ bộ nhớ của hệ thống
Tạo ra những cấu trúc dữ liệu như: danh sách liên kết 1. Con trỏ Mỗi biến trong chương trình chiếm một vùng
nhớ, ví dụ biến kiểu int chiếm 4 byte nhớ.
Vị trí của vùng nhớ được gọi là địa chỉ của biến
int i; ị Đ a ch c a
ỉ ủ i char c; Đ a ch c a
ỉ ủ c
ị
short s; Đ a ch c a s
ỉ ủ
ị 10101011
00001111
10001000
11100011
00111011
10111100
11001100 0x1054
0x1055
0x1056
0x1057
0x1058
0x1059
0x1060 Con trỏ … Biến con trỏ là biến lưu giá trị của địa chỉ vùng nhớ. Mỗi kiểu dữ liệu có một biến con trỏ riêng: con trỏ kiểu int, con trỏ kiểu char… C++ sử dụng: Toán tử & để lấy địa chỉ của biến
Toán tử * để lấy nội dung của biến được trỏ. ỏ ể
ế ị Ví dụ:
int i=17;
int* ptr; // khai báo bi n tr ki u int
ế
ptr= &i; // gán đ a ch c a bi n i cho con tr ptr
ỉ ủ
cout << *ptr << endl; // hi n th n i dung c a bi n i
ể ị ộ ỏ
ủ ế Biến con trỏ 0x1054 17 int i; int* ptr; g n u Đ ị a c h ỉ
ộ i d N ptr=&i; cout << *ptr << endl; Biến con trỏ … i ki u int ể
ể
ể ị ỏ ỏ ớ ể
ỏ ị ị ỏ int v; // khai báo bi n v ki u int
ế
int w; // khai báo bi n w ki u int
ế
int* p; // khai báo bi n p ki u con tr tr t
ế
p=&v; // gán đ a ch c a v cho con tr p
ỉ ủ
v=3; // gán giá tr 3 cho v
*p=7; // gán giá tr 7 cho v
p=&w; // gán đ a ch c a w cho con tr p
ỉ ủ
ị
*p=12; // gán giá tr 12 cho w ị Sử dụng toán tử * để lấy nội dung của biến còn được gọi là tham chiếu ngược tới con trỏ. Biến con trỏ … // result là hằng Khai báo hằng:
const int result = 5;
result = 10; // sau đó gán lại giá trị thì C++ sẽ báo lỗi Khai báo con trỏ hằng: const char* answer_ptr = "FortyTwo";
// answer_ptr là con trỏ trỏ tới hằng kiểu char Dữ liệu được trỏ bởi con trỏ hằng thì không thể thay đổi nhưng con trỏ thì có thể. answer_ptr = "FiftyOne"; // đúng (answer_ptr là biến con trỏ)
*answer_ptr = 'X'; // sai (*answer_ptr là hằng) Con trỏ hằng Nếu khai báo: char *const nameptr = "Test"; //name_ptr là con trỏ hằng nameptr = "New"; // sai (name_ptr là hằng)
*nameptr = 'B'; // đúng (*nameptr là char) Nếu khai báo như sau thì không thể
thay đổi được cả con trỏ và nội dung
của con trỏ: const char* const titleptr = "Title"; Con trỏ hằng … C++ cung cấp 3 cách truyền tham số: Truyền tham trị: void f(int x);
Truyền tham chiếu: void f(int& x);
Truyền con trỏ: void f(int* x); Con trỏ là tham số của hàm void swap( double& x, double& y)
{
double tmp=x;
x=y;
y=tmp;
}
void swap( double* ptr1, double* ptr2)
{
double tmp=*ptr1;
*ptr1=*ptr2;
*ptr2=tmp;
}
double a=3.0;
double b=5.0
swap(a,b); // gọi tham chiếu của biến a và b
swap(&a, &b); // sử dụng địa chỉ của biến a và b Con trỏ là tham số của hàm … void bsort (double* ptr, int n)
{ int j,k; for (j=0; j Con trỏ là tham số của hàm… Toán tử new được sử dụng để tạo ra các đối tượng trên vùng nhớ heap. Date* CreateDate()
{ int day, month, year; char dummy;
cout << ”Enter dd/mm/yyyy :”;
cin >> day >> dummy >> month >> dummy >> year;
Date* tmpptr = new Date date(day, month, year);
return tmpptr;
} Date* ptr;
ptr=CreateDate();
cout << ”You entered ” << *ptr << endl; Quản lý bộ nhớ Toán tử new cũng được sử dụng để cấp phát các block nhớ. Toán tử delete được sử dụng để giải phóng
vùng nhớ được cấp phát bởi toán tử new. ị c c a str
ủ ấ ớ #include i phóng vùng nh ả ớ Quản lý bộ nhớ … class String
{
private:
char* str;
public:
String(char* s)
{
int length=strlen(s);
str = new char[length+1];
strcpy(str,s);
}
~String()
{ delete [] str; }
void Display() { cout << str << endl; }
};
String mystring=”This is my string of Type String”;
mystring.Display(); Toán tử new được sử dụng
trong hàm tạo Con trỏ trỏ tới các đối tượng cũng tương tự như các kiểu builtin khác. Truy nhập tới các thành phần thông qua toán tử >
Date date;
date.Set(12,3,1996);
date.Display(); Date* dateptr;
dateptr=new Date;
dateptr->Set(9,12,1999);
dateptr->Display();
(*dateptr).Display(); Con trỏ trỏ tới đối tượng trong danh sách ị ộ ầ ử i ph n t k ti p ỏ ỏ ớ ầ ử ế ế struct link // đ nh nghĩa m t ph n t
{
int data; // data item
link* next; // con tr tr t
}; class linklist
{
private: link* first; // con tr tr t i ph n t đ u tiên trong danh sách ỏ ỏ ớ ầ ử ầ public: ạ ố
vào danh sách linklist() { first = NULL;} // hàm t o không có tham s
ầ ử ổ ộ ị void additem(int d); // b sung thêm m t ph n t
void display(); // hi n th danh sách
ể
} ; Ví dụ: Linked List m i trong danh sách ạ ầ ử ớ đ u tiên ỏ ổ đ u tiên ph n t
ầ ử ầ
ế ắ ầ ừ
ế k ti p ể ế ầ ử ế ế void linklist::additem(int d)
{
link* newlink = new link; // t o ra m t ph n t
ộ
newlink->data = d; // gán d li u
ữ ệ
newlink->next=first; // tr vào ph n t
ầ ử ầ
first = newlink; // thay đ i con tr first
ỏ
}
void linklist::display()
{
link* current=first; // b t đ u t
while(current != NULL) // đ n khi k t thúc danh sách
{
cout << current->data << ” ”;
current=current->next; // di chuy n đ n ph n t
}
} Ví dụ: Linked List … Một lớp có thể chứa con trỏ trỏ tới đối tượng
của chính lớp đó, nhưng không thể chứa đối
tượng của lớp đó. class someclass
{
someclass* ptr; // đúng
}; class someclass
{
someclass obj; //sai
}; Cấu trúc tự trỏ class LinkList
{ private: int data;
LinkList *next, *first; public: LinkList();
void Insert(int d);
void Show(); }; Ví dụ: Sử dụng cấu trúc tự trỏ LinkList :: LinkList(){f=NULL;}
void LinkList :: Insert(int d)
{ LinkList* n = new LinkList();
n>data=d;
n>next=first;
first=next; }
void LinkList :: Show()
{ LinkList* p=first;
while (p!=NULL)
{ cout< data< } } Ví dụ: Sử dụng cấu trúc tự trỏ … Tham chiếu là một bí danh (alias)
Khi ta tạo ra một tham chiếu, khởi tạo nó bằng tên của một đối tượng khác (đối tượng
đích)thì tham chiếu đóng vai trò là một cái tên
khác của đích. Bất kỳ việc gì được thực hiện trên tham chiếu cũng tức là được thực hiện trên đích. Khai báo tham chiếu: int& rSomeRef = someInt; 2. Tham chiếu Nếu ta yêu cầu một tham chiếu tham chiếu tới địa chỉ của nó, thì tham chiếu
sẽ trả về địa chỉ của đối tượng đích của
nó.
Ví dụ: int intOne;
int& rSomeRef = intOne;
cout << "&intOne: " << &intOne << endl;
cout << "&rSomeRef: " << &rSomeRef << endl; Tham chiếu … Bất kỳ đối tượng nào cũng được tham chiếu, kể cả các đối tượng do người sử dụng định nghĩa. Tham chiếu tới các đối tượng thường được sử dụng như chính đối tượng đó.
Dữ liệu thành phần và các phương thức được truy nhập bằng cách sử dụng toán tử “.”, tham chiếu đóng vai trò là bí danh của đối tượng. Ví dụ: int& rIntRef = int; // sai
int howBig = 200;
int& rIntRef = howBig; Tương tự, không thể khởi tạo một tham chiếu tới lớp CAT: CAT& rCatRef = CAT; // sai Phải khởi tạo rCatRef tới một đối tượng CAT cụ thể CAT frisky;
CAT& rCatRef = frisky; Tham chiếu tới đối tượng Khi con trỏ không được khởi tạo hoặc bị xoá thì chúng đều được gán bằng Null. Tuy nhiên, một tham chiếu không thể là Null
Một chương trình có tham chiếu tới một đối tượng Null thì bị coi là chương trình không hợp
lệ và không thể kiểm soát được hết tất cả các
lỗi trong chương trình. Người lập trình C++ thích tham chiếu hơn con trỏ.
Tham chiếu thường rõ ràng, dễ sử dụng hơn và thực hiện nhiệm vụ che dấu thông tin tốt hơn. Không thể gán lại tham chiếu. Nếu ta cần trỏ tới một
đối tượng và sau đó lại trỏ tới một đối tượng khác, thì
ta phải sử dụng con trỏ. Tham chiếu không thể null, cho nên nếu có trường
hợp đối tượng có thể là null thì ta phải sử dụng con
trỏ.
Ví dụ: int* pInt = new int;
if (pInt != NULL) int& rInt = *pInt; 3. Sử dụng tham chiếu hay
con trỏ Ta có thể khai báo cả con trỏ và tham chiếu trong cùng một danh sách các tham số của hàm, cùng với các đối tượng truyền
trị khác. Ví dụ: CAT * SomeFunction (Person& theOwner, House* theHouse, int age);
SomeFunction có 3 tham số: Tham số thứ nhất là tham chiếu tới đối tượng Person
Tham số thứ hai là con trỏ tới đối tượng House
Tham số thứ ba là một số nguyên. Hàm trả về một con trỏ trỏ tới đối tượng CAT. Trước khi truyền tham chiếu cho hàm hoặc trả về một tham chiếu thì ta phải đặt ra câu hỏi “Tham chiếu sẽ là bí danh của
đối tượng nào và nó có tồn tại mỗi khi tham chiếu được sử dụng
không?” Sử dụng tham chiếu hay con
trỏ … Chương 9: Mảng Định nghĩa mảng
Mảng nhiều chiều
Mảng con trỏ
Xâu ký tự Nội dung chính Mảng (Array) Là một tập các vị trí nhớ liên tiếp nhau
Các phần tử trong mảng có cùng tên và cùng kiểu. 1. Định nghĩa mảng type arrayName[ arraySize ]; Khai báo mảng Có thể khai báo nhiều mảng cùng kiểu Truy nhập tới phần tử trong mảng Xác định thông qua tên mảng và chỉ số: arrayname[ position number ]
Phần tử đầu tiên ở vị trí thứ 0 Sử dụng vòng lặp for: gán giá trị cho từng phần tử trong mảng. Sử dụng danh sách khởi tạo: Khởi tạo mảng Nếu không đủ giá trị khởi tạo thì những phần tử còn lại sẽ nhận giá trị mặc định. Nếu giá trị khởi tạo nhiều hơn kích thước mảng thì sẽ báo lỗi.
Gán tất cả các phần tử với cùng một giá trị Nếu kích thước mảng không được khai báo thì danh sách khởi tạo sẽ xác định: Tham số mảng được biểu diễn bởi kiểu dữ liệu và kích thước của mảng void display(float [n][m]); Hàm có thể không cần biết kích thước của mảng nhưng phải biết kích thước của một phần tử trong mảng void myFunction(int n[]);
void display(float [][m]); Khi gọi hàm, ta chỉ cần truyền tên mảng Tham số mảng Mảng được truyền theo kiểu truyền tham chiếu Hàm có thể chỉnh sửa dữ liệu của các phần tử trong mảng
Tên mảng là địa chỉ của phần tử đầu tiên Có thể lưu trữ đối tượng trong mảng.
Khi khai báo mảng, cần cung cấp: Kiểu đối tượng được lưu trữ
Số lượng đối tượng. Chương trình dịch sẽ tính ra kích thước
vùng nhớ cần để cấp phát cho mảng. Mảng đối tượng class CAT { public: CAT() { itsAge = 1; itsWeight=5; } ~CAT() {} int main()
{
CAT Litter[5];
int i;
for (i = 0; i < 5; i++)
Litter[i].SetAge(2*i +1);
for (i = 0; i < 5; i++)
{
cout << "Cat #" << i+1<< ": ";
cout << Litter[i].GetAge() << endl;
}
return 0;
} int GetAge() const { return itsAge; } int GetWeight() const { return itsWeight; } void SetAge(int age) { itsAge = age; } private: int itsAge; int itsWeight; }; Ví dụ: Mảng đối tượng Mảng nhiều chiều được coi là mảng của mảng.
Khai báo: double sales[2][2];
sales[1][0]=2.5; Khởi tạo: double sales[2][2]={{1.2,3.0},{1.0,2.3}}; 2. Mảng nhiều chiều Ta có thể đặt toàn bộ mảng lên vùng nhớ tự do CAT *Family = new CAT[500]; Family là một co trỏ trỏ tới phần tử đầu tiên của mảng có 500 phần tử CAT Family trỏ tới hoặc có địa chỉ của phần tử Family[0]. Sử dụng con trỏ để truy nhập vào từng phần tử trong mảng CAT *Family = new CAT[500];
CAT *pCat = Family; //pCat trỏ vào Family[0]
pCat>SetAge(10); // gán Family[0] bằng 10
pCat++; // trỏ vào Family[1]
pCat>SetAge(20); // gán Family[1] bằng 20 3. Mảng con trỏ Cat FamilyOne[500]
CAT * FamilyTwo[500];
CAT * FamilyThree = new CAT[500];
FamilyOne là một mảng có 500 phần tử CAT
FamilyTwo là một mảng có 500 con trỏ trỏ tới CAT FamilyThree là một con trỏ trỏ tới một mảng có 500 phần tử CAT. Phân biệt con trỏ trỏ tới mảng
và mảng các con trỏ Trong C++, xâu ký tự là một mảng các phần tử kiểu char và kết thúc bằng ký tự null. Có hai cách khai báo: char Greeting[] = { `H', `e', `l', `l', `o', ` `, `W','o','r','l','d', `\0' };
char Greeting[] = "Hello World"; Sử dụng cin.get()
#include char buffer[80];
cout << "Enter the string: ";
cin.get(buffer, 79); // get up to 79 or newline
cout << "Here's the buffer: " << buffer << endl;
return 0; } 4. Xâu ký tự string là một lớp chuẩn trong C++
Các ký tự trong string được đánh từ 0
Khởi tạo một đối tượng string string s1 (“Man”);
string s2=“Beast”;
string s3; Ta có thể sử dụng các tóan tử tóan học, logic … trên đối tượng string
s3 = s1;
s3 = “Neither” + s1 + “nor”;
s3 += s2; Lớp string Hàm getline(cin, string str): lưu thông tin từ luồng vào chuẩn đưa vào str. Ví dụ: string full_name;
cout<< “Enter your fullname:”;
getline(cin, full_name);
// full_name lưu thông tin mà người sử dụng nhập từ bàn phím string và toán tử >> string s1=“Hello world”;
string s2=“world”;
int n=s1.find(s2);
//n=6
find() trả về vị trí lần đầu tiên s2 xuất hiện trong s1.
Nếu không tìm thấy trả về 0 Tìm kiếm erase(int i1, int i2): xóa xâu con có chiều dài i2 bắt đầu từ vị trí i1. s=“Hello world”;
s.erase(1,4);
//s=“H world” replace(int i1, int i2, string s2): thay thế xâu con s2 từ vị trí i1, số lượng ký tự thay thế là i2. insert(int i, string s): chèn xâu s vào từ vị trí thứ i.
append(int i, char c): chèn i ký tự c vào cuối xâu
substr(int i1, int i2): trả về xâu con từ i1, có kích thước i2. size() và length(): trả về kích thước của xâu Chỉnh sửa Sử dụng tóan tử []
Hàm at(i): trả về vị trí của ký tự thứ i trong xâu.
Nếu i>length() thì báo lỗi
Ví dụ: string s=“Hello world”;
char c;
c=s[1];
//c=‘e’ Truy nhập đến ký tự trong
string Chương 10: Bắt ngoại lệ Exception gồm: Mô tả lỗi có thể xảy ra.
Đoạn mã lệnh trong đó xảy ra exception, nằm trong khối try{} Những thứ gây ra exception nằm trong lệnh throw Mã lệnh bắt lỗi trong catch Ngoại lệ (Exception) Trong ví dụ về stack, lỗi có thể xảy ra khi:
Đẩy nhiều phần tử vào stack
Lấy phần tử ra khỏi stack rỗng Lỗi có thể xảy ra là dạng outofbound Ví dụ #include int count; private: int data[STACK_SIZE]; public: stack(void);
void push(const int item);
int pop(void); }; Ví dụ … inline stack::stack(void)
{ count = 0; }
inline void stack::push(const int item)
{ data[count] = item;
count++; }
inline int stack::pop(void)
{ count;
return (data[count]); } Ví dụ … const int WHAT_MAX = 80;
class bound_err { public: char what[WHAT_MAX];
bound_err(char *_what) { if (strlen(_what) < (WHAT_MAX 1)) strcpy(what, _what); else strcpy(what, "Internal error: _what is too long"); } }; Ví dụ … class b_stack: public stack { public: void push(const int item) throw(bound_err);
int pop(void) throw(bound_err); }; Ví dụ … inline void b_stack::push(const int item) throw(bound_err)
{ if (count >= STACK_SIZE)
{ bound_err overflow("Push overflows stack");
throw overflow; }
stack::push(item); }
inline int b_stack::pop(void) throw(bound_err)
{ if (count <= 0)
{ throw bound_err("Pop causes stack underflow"); }
return (stack::pop()); } Ví dụ … b_stack test_stack;
void push_a_lot(void) { for (int i = 0; i < 5000; i++) test_stack.push(i); }
main ()
{ try
{ push_a_lot(); }
catch (bound_err &err)
{ cout << "Error: Bounds exceeded\n";
cout << "Reason: " << err.what << '\n';
exit (8); }
catch (...)
{ cout << "Error: Unexpected exception occurred\n";
exit (8); }
return (0); } Ví dụ … Chương 11: Stream và File C++ File I/O
File nhị phân
Con trỏ File Nội dung chính I/O trong được xây dựng trên 3 class: istream, ostream và iostream. C++ coi các file là luồng các byte (stream).
Khi I/O với các file trên đĩa, ta phải sử dụng
các lớp ifstream, ofstream, và fstream được
định nghĩa trong Chú ý: ifstream thừa kế từ istream
ofstream thừa kế từ ostream
fstream thừa kế từ iostream 1. C++ File I/O Để đọc 100 số từ file numbers.dat, phải khai báo biến có kiểu input file. ifstream data_file; Xác định tên file cần đọc, bằng cách sử dụng hàm open. data_file.open("numbers.dat" ); Đọc dữ liệu từ file ra: for (i = 0; i < 100; ++i) data_file >> data_array[i]; Sau khi sử dụng xong, đóng file lại. data_file.close(); Input File C++ cho phép gọi open ngay trong hàm tạo: ifstream data_file("numbers.dat"); Khi mở file sử dụng chồng toán tử ! để kiểm tra file có được mở thành công hay không Khi đọc file, để kiểm tra lỗi sử dụng hàm bad. if (data_file.bad())
{ cerr << "Unable to open numbers.dat\n";
exit (8); } Hàm getline cho phép đọc cả dòng dữ liệu từ file:
istream &getline(char *buffer, int len, char delim = '\n') buffer Là bộ đệm lưu dữ liệu
len là chiều dài của bộ đệm.
Chú ý, ký tự kết thúc không được lưu trong bộ đệm. Input File … Các hàm dùng cho input file và output file tương tự nhau. ofstream out_file("out.dat"); Định nghĩa đầy đủ: ofstream::ofstream(const char *name, int mode=ios::out, int prot = filebuf::openprot);
name là tên file
mode là kiể mở file ios: :app ghi vào cuối file
ios: :ate khi file đang mở, nhảy xuống cuối file
ios::in mở để nhập vào(chỉ áp dụng cho opens của biến ifstream).
ios: :out mở để hiển thị(chỉ áp dụng cho opens của biến ofstream).
ios: :binary Binary file
ios:: trunc Loại bỏ nội dung của file đang tồn tại khi mở file để ghi.
ios::nocreate Lỗi nếu file không tồn tại.(Chỉ áp dụng cho output file, input file luôn lỗi nếu không
tồn tại file).
ios::noreplace Không ghi đè lên file đã tồn tại. prot:Bảo vệ file. Giá trị này phụ thuộc vào OS. UNIX 0644
Windows là 0. Output file Ví dụ:
ofstream out_file("data.new",ios::out|ios::binary|ios::nocreate|ios::app); Ghi tiếp dữ liệu nhị phân vào file đã tồn tại, có tên là “data.new” Output File … Vào ra nhị phân được sử dụng với 2 hàm thành viên: read và write. in_file.read(dataptr, size);
out_file.write(data_ptr, size);
data_ptr: con trỏ trỏ tới vị trí đặt dữ liệu
size: số lượng byte cần đọc Hàm gcount: trả về số byte được lấy ra từ lần
đọc cuối cùng. (có thể nhỏ hơn số byte yêu
cầu). Hàm eof: kiểm tra kết thúc file 2. File nhị phân struct
{ int width;
int height; } rectangle;
in_file.read((char *)(&rectangle), sizeof(rectangle));
if (infile.bad())
{ Dữ liệu được đưa vào một struct.
Toán tử & thể hiện là con trỏ kiểu
rectangle và được ép kiểu thành con trỏ
kiểu char (char *).
Toán tử sizeof được sử dụng để xác
định bao nhiêu byte cần đọc. cerr << "Unable to read rectangle\n";
exit (8); }
if (in_file.gcount() != sizeof(rectangle))
{ cerr << "Error: Unable to read full rectangle\n";
cerr << "I/O error of EOF encountered\n"; } Ví dụ Mỗi đối tượng file có hai giá trị nguyên là
get pointer và put poiter để xác định vị
trí nào trong file cần đọc hoặc ghi.
Hàm seekg() và tellg() cho phép thiết lập và kiểm tra get pointer. Hàm seekp() và tellp() cho phép thiết lập và kiểm tra put pointer. 3. Con trỏ File seekg() có 1 tham số: xác định vị trí bắt đầu file
có 2 tham số Tham số thứ nhất xác định vị trí offset
Tham số thứ hai xác định hướng dịch chuyển beg: từ đầu file đến vị tri offset
cur: từ vị trí hiện tại đến vị trí offset
end: từ cuối file ngược đến offset infile.seekg(0,ios::end) //di chuyển từ đầu đến cuối file Con trỏ File … tellg(): trả về vị trí hiện tại của get point Con trỏ File … class person
{ protected: char name[80];
int age; public: void getdata()
{ cout<<"\n Enter name:"; cin>>name;
cout<<"Enter age:"; cin>>age; }
void showdata()
{ cout<<"\n Name:"< } }; Ví dụ int main(int argc, char* argv[])
{ person p;
fstream outfile("Person.txt",ios::out|ios::in| ios::binary|ios::app);
for(int i=0;i<3;i++)
{ p.getdata();
outfile.write((char*)(&p),sizeof(p)); }
outfile.seekg(0);
do
{ outfile.read((char*)(&p),sizeof(p));
p.showdata(); }
while(! outfile.eof());
return 0; } Ví dụ …1. Lớp và các thành phần của lớp
class Employee
{
private:
// t
ừ
unsigned int EmpID ; // member data
2. Cài đặt các thành phần của lớp
Cài đặt các thành phần của lớp
cout << ProductName;
void Product :: Display (void)
{
}
sở như sau:
class lớp_dẫn_xuất : mức truy nhập lớp_cơ_ sở
Mức truy nhập
Thành phần được thừa kế từ lớp cơ sở
X
A1
A2
B
2.1. Chồng toán tử 1 toán hạng
2.2. Chồng toán tử 2 toán hạng
Ví dụ: Sử dụng hàm ảo
Con trỏ Null và tham chiếu Null
ồ
ố
ả
ự
ồ
ố
int c[ 10 ]; // m ng c g m 10 s nguyên
ả
float d[ 3284 ]; // m ng d g m 3284 s th c
int b[ 100 ], x[ 27 ];
int n[ 5 ] = { 1, 2, 3, 4, 5 };
int n[ 5 ] = { 0 };
int n[] = { 1, 2, 3, 4, 5 };
//Mảng n có 5 phần tử
int myArray[ 24 ];
myFunction( myArray);