Đặ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 Object­Oriented

Programming in C++, 3rd edition, Robert  Lafore, SAMS.

 C++ Programming Language, 3rd edition,

Bjarne Stroustrup, Addison­Wesley

 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 (OBJECT­ORIENTED 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  using namespace std; int main() {

• 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ừ a­z, 0­9 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.4x10­38

3.4x1038

4

double

1.7x10­308

1.7x10308

8

3.4x10­4932

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(i)/j;  // ép i thành kiểu float trước khi chia

É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   #include ”myprog.h”

 <>: 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) : ? … :   ?  : < false expression>  Ví dụ:

 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() { case : … break; case : … break; default : … }

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

1. Lớp và các thành phần của lớp

 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 (built­in 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

class Employee { private: // t ừ unsigned int EmpID ; // member data

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

2. Cài đặt các thành phần của lớp

 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.

Cài đặt các thành phần của lớp

 Hàm thành phần

cout << ProductName;

void Product :: Display (void) { }

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=day­d.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 non­const member function thì chỉ được  các đối tượng non­const 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 (Self­Reference)

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ế

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ở

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ế

X

A1

A2

B

 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ử

2.1. Chồng toán tử 1 toán hạng

 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ố)

2.2. Chồng toán tử 2 toán hạng

 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

Ví dụ: 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 kind max(kind d1, kind d2) {

if (d1 > d2)

return (d1);

return (d2);

}

 Cấu trúc  báo cho C++ biết rằng kind có thể được thay

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  #include  const int STACK_SIZE = 100; // số lượng phần tử lớn nhất trong Stack template class stack { private:

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 a_stack; // Các phần tử trong stack là kiểu số nguyên

 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::push(const kind item) {

data[count] = item; ++count;

}

Khuôn hình lớp …

template stack { ...}

 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, stack, stack,…

 C++ sẽ tự động tạo ra các hàm thành viên như: stack: :push,

stack: :push, và stack::push.

 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::push(const char * item) {

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 = "Forty­Two"; // 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 = "Fifty­One"; // đú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 *(ptr+k)) swap(ptr+j,ptr+k); } double array[6] = { 2.3, 4.5, 1.2, 6.8, 0.8, 4.9 }; bsort(array,n);

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 char* str =”This is an old C-style string”; int len=strlen(str); // xác đ nh kích th ướ char* ptr; ptr = new char[len+1]; // c p phát vùng nh strcpy(ptr,str); cout << ”ptr=” << ptr << endl; delete [] ptr; // gi

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 built­in 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<next;

}

}

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

Con trỏ Null và tham chiếu Null

 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

int c[ 10 ]; // m ng c g m 10 s nguyên ả float d[ 3284 ]; // m ng d g m 3284 s th c

 Có thể khai báo nhiều mảng cùng kiểu

int b[ 100 ], x[ 27 ];

 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

int n[ 5 ] = { 1, 2, 3, 4, 5 };

 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ị

int n[ 5 ] = { 0 };

 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:

int n[] = { 1, 2, 3, 4, 5 }; //Mảng n có 5 phần tử

 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

int myArray[ 24 ]; myFunction( myArray);

 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  int main() {

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 out­of­bound

Ví dụ

#include  #include  const int STACK_SIZE = 100; class stack { protected:

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ụ …