Hướng dẫn học C++

Nhằm giúp người học lập trình C++ dễ dàng. Hiệp Sĩ IT đã thiết kế bài học lập trình C++ từ cơ bản đến năng cao.
C++ là ngôn ngữ lập trình hướng đối tượng. C++ được mở rộng từ ngôn ngữ C.

Các bài hướng dẫn lập trình C++ gồm có các phần: Cấu trúc điều khiển, đối tượng (objects) và lớp (classes), kế thừa(inheritance), phương thức thiết lập (constructor), phương thức hủy(destructor), con trỏ this, static, đa hình (polymorphism), trừu tượng (abstraction), lớp trừu tượng(abstract class),giao diện (interface), namespace, đóng gói(encapsulation), arrays, strings, điều khiển lỗi ngoại lệ(exception handling), xử lý tập tin(File IO), v.v

Yêu cầu để học C++ bạn phải biết:

Trước khi học C++, bạn phải có kiến thức cơ bản về C

 

C++ là gì?

C++ là một ngôn ngữ lập trình kiểu tĩnh,dữ liệu trừ tượng, phân biệt kiểu chữ hoa ,chữ thường, hỗ trợ lập trình hướng đối tượng, lập trình thủ tục.

C++ được coi như là ngôn ngữ bậc trung (middle-level), khi nó kết hợp các đặc điểm và tính năng của ngôn ngữ bậc cao và bậc thấp.

C++ được phát triển bởi Bjarne Stroustrup năm 1979 tại Bell Labs ở Murray Hill, New Jersey, như là phiên bản nâng cao của ngôn ngữ C và với tên gọi đầu tiên là "C với các Lớp", nhưng sau đó được đổi tên thành C++ vào năm 1983.

C++ là một Superset của C, và bất kỳ chương trình C nào cũng là một chương trình C++.


Lập trình hướng đối tượng (OOPs)

C++ hỗ trợ đầy đủ lập trình hướng đối tượng, bao gồm 4 tính năng chính của lập trình hướng đối tượng là:

  • Kết thừa (Inheritance)

  • Tính đa hình (Polymorphism)

  • Tính bao đóng (Encapsulation)

  • Trừu tượng (Abstraction)


Thư việc chuẩn C++ (C++ Standard Library)

C++ chuẩn gồm 3 phần quan trọng:

  • Core Language cung cấp tất cả các khối bao gồm biến, kiểu dữ liệu (data type) và literals, …

  • Thư viện chuẩn C++ (C++ Standard Library) cung cấp tập hợp hàm đa dạng để thao tác file, string, …

  • Standard Template Library (STL) cung cấp tập hợp phương thức đa dạng để thao tác cấu trúc dữ liệu, …


Ứng dụng trong C++

Trong ngôn ngữ lập trình C ++, chúng ta có thể phát triển các loại ứng dụng bảo mật và mạnh mẽ khác nhau:

  • Ứng dụng Window 
  • Ứng dụng Client-Server 
  • Điều khiển thiết bị (Device drivers)
  • Nhúng (Embedded firmware) v.v.

 

Sự khác biệt giữa C với C++

Hầu hết IT trên thế giới đều biết về 2 ngôn ngữ lập trình C, C++. Như chúng ta đã biết, C++ là ngôn ngữ ra đời sau ngôn ngữ C, thực chất nó mở rộng cho ngô ngữ C nhằm tăng cường tính an toàn, cung cấp cho các lập trình viên nhiều lựa chọn hơn, đơn giản hóa lập trình ở mức cao hơn, và cung cấp một cách tiếp cận tốt hơn đối với những chương trình có quy mô lớn.
 C++ cũng là ngôn ngữ lớn hơn với nhiều tính năng và phức tạp hơn so với C. Giữa C và C++ có rất nhiều khác biệt. Trong khuôn khổ bài viết này, tôi chỉ liệt kê 10 điểm khác biệt chính giữa 2 ngôn ngữ này.

STT. C C++
1) C tuân theo lập trinh kiểu thủ tục. C ++ là đa mô hình. Nó hỗ trợ cả hai hướng thủ tụchướng đối tượng.
2) Dữ liệu kém an toàn hơn. Bạn có thể sử dụng công cụ sửa đổi cho các thành viên của lớp để làm cho nó không thể truy cập được đối với người dùng bên ngoài.
3) Sử dụng phương pháp tiếp cận từ trên xuống (top-down). Sử dụng phương pháp tiếp cận từ dưới lên  (bottom-up).
4) C không hỗ trợ quá tải hàm. C++ hỗ trợ quá tải hàm.
5) Không thể khai báo hàm trong các structure. Có thể khai báo hàm trong các structure.
6) Không có biến tham chiếu, chỉ hỗ trợ con trỏ. Hỗ trợ cả biến tham chiếu và con trỏ.
7) Sử dụng các hàm scanf và printf để nhập xuất. Sử dụng các hàm cin>>  cout<< để nhập xuất..
8) Không thể quá tải toán tử trong C. Có thể quá tải toán tử trong C ++.
9) Các chương trình C được chia thành các thủ tục và modules Các chương trình C ++ được chia thành các hàm và các lớp.
10) C không cấp namespace. C++ hỗ trợ namespace.
11) Bắt lỗi ngoại lệ không dễ dàng. C++ sử dụng khối Try and Catch để bắt lỗi ngoại lệ.
12) Không hỗ trợ các hàm inline, thay vào đó có thể sử dụng khai báo#define Hỗ trợ các hàm inline.
13) Được xem là một ngôn ngữ lập trình cấp thấp. Được xem là sự kết hợp giữa ngôn ngữ lập trình cấp thấp và cấp cao.

Dưới đây là danh sách các tính năng được hỗ trợ trong C++ (C không hỗ trợ).

  • Classes
  • Member functions
  • Constructors and destructors
  • Derived classes
  • Virtual functions
  • Abstract classes
  • Access control (public, private, protected)
  • friend functions
  • Pointers to members
  • static members
  • mutable members
  • Operator overloading
  • References
  • Templates
  • Inline functions
  • Default arguments
  • Function overloading
  • Namespaces
  • Exception handling
  • Run-time type identification
  • // comments
  • True const
  • Declarations as statements
  • Automatically typedef’d struct tags
  • Type safe linkage
  • new and delete
  • bool keyword
  • Safer and more robust casting 

 

Lịch sử hình thành C++:

Trước C++, ngôn ngữ lập trình C được phát triển trong năm 1972 bởi Dennis Ritchie tại phòng thí nghiệm Bell Telephone, C chủ yếu là một ngôn ngữ lập trình hệ thống, một ngôn ngữ để viết ra hệ điều hành. Vào năm 1999, ủy ban ANSI đã phát hành một phiên bản mới của ngôn ngữ lập trình C, gọi là C99.

Hai lần cập nhật gần đây nhất là C++ 11 và C++ 14 (được phê chuẩn vào năm 2011 và 2014). Phiên bản C++ 17 đã được công bố, dự đoán sẽ hoàn thành trong năm 2017. C++ là một phiên bản mở rộng của ngôn ngữ lập trình C.

 

Hãy xem các ngôn ngữ lập trình được phát triển trước ngôn ngữ C.

Ngôn ngữ Năm Phát triển bởi
Algol 1960 International Group
BCPL 1967 Martin Richard
B 1970 Ken Thompson
Traditional C 1972 Dennis Ritchie
K & R C 1978 Kernighan & Dennis Ritchie
C++ 1980 Dennis Ritchie

Một số đặc trưng của ngôn ngữ C++:

  • C++ là một ngôn ngữ lập trinh bậc trung. Nó có nghĩa là bạn có thể sử dụng C++ để phát triển những ứng dụng bậc cao, và cả những chương trình bậc thấp hoạt động tốt trên phần cứng.
  • C++ là một ngôn ngữ lập trình hướng đối tượng. Khác với ngôn ngữ lập trình C - một ngôn ngữ lập trình hướng thủ tục, chương trình được tổ chức theo thuật ngữ "chức năng", một chức năng gồm có những hành động mà bạn muốn làm. C++ được thiết kế với một cách tiếp cận hoàn toàn mới được gọi là lập trình hướng đối tượng, nơi mà chúng ta sử dụng những đối tượng, các lớp và sử dụng các khái niệm như: thừa kế, đa hình, tính đóng gói, tính trừu tượng ... Những khái niệm này khá phức tạp, nên nếu bạn chưa hiểu về chúng, đừng lo lắng, chúng ta sẽ lần lượt làm rõ từng khái niệm trong mỗi bài học khác nhau.
  • C++ là một ngôn ngữ lập trình hướng cấu trúc giống ngôn ngữ C, nó có nghĩa là chúng ta có thể tổ chức chương trình trên khái niệm functions.
  • C++ có thể chạy trên nhiều nền tảng khác nhau như Windows, Mac OS, một số biến thể của UNIX...

 


Lý do chọn ngôn ngữ lập trình C++:

Như trong bài trước, mình đã nói về phương pháp để lựa chọn 1 ngôn ngữ lập trình phù hợp cho dự án và bản thân. Mỗi ngôn ngữ lập trình có thể thích hợp cho một số loại ứng dụng. Vậy trước khi bắt đầu học về ngôn ngữ C++, chúng ta cần biết C++ có thể làm được những gì.

Theo Adam Helps5

Ngôn ngữ lập trình C++ có thể được dùng để làm những công việc sau:

  • C++ được thiết kế để viết những hệ thống lớn, thậm chí C++ được dùng để tạo nên hệ điều hành máy tính (Linux, Mac OS X, Windows...).
  • C++ được dùng để tạo nên các game lớn của hãng Blizzard (World of Warcraft, Diablo series, StarCraft series...). Gần như toàn bộ các game bom tấn trên thị trường hiện nay cũng dùng C++ để phát triển. Một số công cụ sử dụng trong việc lập trình game có sử dụng C++ như Unreal engine, Cocos2d-x framework,... Các ông lớn trong ngành công nghiệp game như Valve, CryTek cũng sử dụng C++.

Các thể loại game chơi trên Playstation, XBox, ... được tạo ra từ C++. Có thể thấy C++ là ngôn ngữ có truyền thống lâu đời trong ngành công nghiệp phát triển game.

Các sản phẩm phần mềm nổi tiếng khác được phát triển bằng C++ như MS Office, Photoshop, Maya / 3ds, Auto CAD...

  • C++ có thể được sử dụng ở phía Web server vì C++ có thể đáp ứng được yêu cầu về tốc độ xử lý, khả năng phản hồi nhanh.
    Tuy rằng bên cạnh C++ còn có những ngôn ngữ lập trình khác như C#, Java, ... có thể làm được những ứng dụng lớn cho máy tính, nhưng đối với các ứng dụng có yêu cầu về mặt tốc độ xử lý, hoặc có tính thương mại cao, người ta vẫn ưu tiên C++.
     

 

Các tính năng của ngôn ngữ C++

C++ là ngôn ngữ được sử dụng rộng rãi. Nó cung cấp rất nhiều tính năng được đưa ra dưới đây:

  1. Đơn giản (Simple).
  2. Máy độc lập hoặc di động (Machine Independent or Portable).
  3. Ngôn ngữ lập trình cấp trung (Mid-level programming language).
  4. ngôn ngữ lập trình có cấu trúc (Structured programming language).
  5. Thư viện phong phú (Rich Library).
  6. Quản lý bộ nhớ (Memory Management).
  7. Tốc độ nhanh (Fast Speed).
  8. Con trỏ (Pointers).
  9. Đệ quy (Recursion).
  10. Mở rộng (Extensible).
  11. Hướng đối tượng(Object Oriented).
  12. Dựa trên trình biên dịch(Compiler based).

 


1. Đơn giản

C++ là một ngôn ngữ đơn giản theo nghĩa nó cung cấp cách tiếp cận có cấu trúc (để phá vỡ vấn đề thành các phần), tập hợp các chức năng thư viện phong phú , các kiểu dữ liệu vv.


2. Máy độc lập hoặc di động

Không giống như ngôn ngữ assembly, chương trình C++ có thể được thực hiện trong nhiều máy với sự thay đổi chút ít hoặc không có. Nhưng nó không phải là nền tảng độc lập.


3. Ngôn ngữ lập trình cấp trung

C++ cũng được sử dụng để lập trình ở mức thấp. Nó được sử dụng để phát triển các ứng dụng hệ thống như kernel, driver, vv. Nó cũng hỗ trợ các tính năng của ngôn ngữ cấp cao. Đó là lý do tại sao nó được gọi là ngôn ngữ bậc trung.


4. Ngôn ngữ lập trình có cấu trúc

C++ là một ngôn ngữ lập trình có cấu trúc theo nghĩa chúng ta có thể chia nhỏ chương trình thành các phần bằng cách sử dụng các hàm. Vì vậy, nó rất dễ hiểu và sửa đổi.


5. Thư viện phong phú

C++ cung cấp rất nhiều chức năng sẵn có mà làm cho việc phát triển nhanh chóng.


6. Quản lý bộ nhớ

Nó hỗ trợ tính năng phân bổ bộ nhớ động. Trong ngôn ngữ C++, chúng ta có thể giải phóng bộ nhớ được phân bổ bất cứ lúc nào bằng cách gọi hàm free().


7. Tốc độ nhanh

Việc biên dịch và thời gian thực hiện của ngôn ngữ C++ là nhanh.


8. Con trỏ

C++ cung cấp tính năng của con trỏ. Chúng ta có thể trực tiếp tương tác với bộ nhớ bằng cách sử dụng các con trỏ. Chúng ta có thể sử dụng con trỏ cho bộ nhớ, cấu trúc, chức năng, mảng, vv.


9. Đệ quy

Trong C++, chúng ta có thể gọi hàm trong hàm. Nó cung cấp khả năng sử dụng lại mã cho mỗi chức năng.


10. Mở rộng

Ngôn ngữ C++ mở rộng vì nó có thể dễ dàng áp dụng các tính năng mới.


11. Hướng đối tượng

C++ là ngôn ngữ lập trình hướng đối tượng. OOPs làm cho việc phát triển và bảo trì dễ dàng hơn so với ngôn ngữ lập trình theo thủ tục, vì hướng thủ tục không dễ dàng để quản lý nếu khi kích thước dự án phát triển lớn.


12. Dựa trên trình biên dịch

C++ là một ngôn ngữ lập trình dựa trên trình biên dịch, điều đó có nghĩa là khi không biên dịch thi không có chương trình C++ nào có thể được thực thi. Đầu tiên chúng ta cần biên dịch chương trình bằng cách sử dụng trình biên dịch và sau đó chúng ta có thể thực hiện chương trình đã được biên dịch.


 

Các công cụ cần thiết

Nếu bạn muốn cài đặt môi trường lập trình C, bạn cần hai công cụ phần mềm sau đây trên máy tính của bạn:

  1. Trình soạn thảo văn bản.
  2. Trình biên dịch C.

1. Trình soạn thảo văn bản

Được sử dụng để nhập chương trình của bạn. Ví dụ về một số trình soạn thảo bao gồm Windows Notepad, lệnh Chỉnh sửa Hệ điều hành, Tóm tắt, Epsilon, EMACS và vim hoặc vi.

Tên và phiên bản của trình soạn thảo văn bản có thể khác nhau trên các hệ điều hành khác nhau. Ví dụ, Notepad sẽ được sử dụng trên Windows, và vim hoặc vi có thể được sử dụng trên các cửa sổ cũng như trên Linux hoặc UNIX.

Các tệp bạn tạo với trình biên tập của bạn được gọi là các tệp nguồn và chứa các mã nguồn của chương trình. Các tập tin nguồn cho các chương trình C thường được đặt tên với phần mở rộng “.c”.

Trước khi bắt đầu lập trình, hãy chắc chắn rằng bạn có một trình soạn thảo văn bản và bạn có đủ kinh nghiệm để viết một chương trình máy tính, lưu nó trong một tệp, biên dịch nó và cuối cùng thực hiện nó. 


2. Trình biên dịch C

Mã nguồn được viết dưới dạng mã ASCII. Nó cần phải được “biên dịch”, sang ngôn ngữ máy để CPU của bạn thực sự có thể thực hiện chương trình theo các hướng dẫn được đưa ra.

Trình biên dịch biên dịch mã nguồn vào các chương trình thực thi cuối cùng. Trình biên dịch được sử dụng thường xuyên nhất và miễn phí là trình biên dịch GNU C/C++, nếu không bạn có thể có trình biên dịch từ HP hoặc Solaris nếu bạn có hệ điều hành tương ứng.

Phần sau giải thích cách cài đặt trình biên dịch GNU C/C++ trên các hệ điều hành khác nhau. Chúng tôi tiếp tục đề cập đến C/C++ với nhau vì GNU gcc trình biên dịch hoạt động cho cả C và C++ ngôn ngữ lập trình.


Cài đặt môi trường lập trình C

  • Cài đặt trên UNIX/Linux.
  • Cài đặt trên Mac OS.
  • Cài đặt trên Windows.

1. Cài đặt trên UNIX/Linux

Nếu bạn đang sử dụng Linux hoặc UNIX , hãy kiểm tra xem GCC đã được cài đặt trên hệ thống của bạn bằng cách nhập lệnh sau từ dòng lệnh: 

$ gcc -v

Nếu bạn có trình biên dịch GNU được cài đặt trên máy của bạn thì bạn nên in một thông báo như sau:

Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr .......
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

Nếu GCC không được cài đặt, thì bạn sẽ phải tự cài đặt nó bằng cách sử dụng các hướng dẫn chi tiết có sẵn tại https://gcc.gnu.org/install/

Hướng dẫn này đã được viết dựa trên Linux và tất cả các ví dụ đưa ra đã được biên dịch trên hương vị Cent OS của hệ thống Linux.


2. Cài đặt trên Mac OS

Nếu bạn sử dụng Mac OS X, cách dễ nhất để lấy GCC là tải về môi trường phát triển Xcode từ trang web của Apple và làm theo hướng dẫn cài đặt đơn giản. Một khi bạn đã thiết lập Xcode, bạn sẽ có thể sử dụng trình biên dịch GNU cho C/C++.

Xcode hiện có tại https://developer.apple.com/xcode/ .


3. Cài đặt trên Windows

Để cài đặt GCC trên Windows, bạn cần cài đặt MinGW. Để cài đặt MinGW, hãy truy cập vào trang chủ của MinGW, www.mingw.org và làm theo liên kết tới trang download MinGW. Tải về phiên bản mới nhất của chương trình cài đặt MinGW, có tên MinGW- .exe.

Trong khi cài đặt Min GW, ở mức tối thiểu, bạn phải cài đặt gcc-core, gcc-g++, binutils và thời gian chạy MinGW, nhưng bạn có thể muốn cài đặt thêm.

Thêm thư mục con bin của cài đặt MinGW vào biến môi trường PATH của bạn , để bạn có thể chỉ định các công cụ này trên dòng lệnh bằng các tên đơn giản của chúng.

Sau khi quá trình cài đặt hoàn tất, bạn có thể chạy gcc, g++, ar, ranlib, dlltool và một số công cụ GNU khác từ dòng lệnh Windows.


Phần mềm Dev C++

Nếu bạn đang sử dụng hệ điều hành Windows bạn có thể sử dụng phần mềm Dev C++, khi cài phần mềm Dev C++ cung cấp cho bạn trình soạn thảo văn bản, trình biên dịch C/C++, tính năng debug và nhiều tính năng tiện lợi khác.

Tải Dev C++ tại đây.

Giao diện ban đầu của phần mềm Dev C++:


Phần mềm Turbo C++

Ngoài ra, bạn có thể dùng phần mềm kinh điển Turbo C++ tải về tại https://turboc.codeplex.com/.

Giao diện ban đầu của phần mềm Turbo C++ khá là tù:


 

Chương trình C++ đầu tiên

Trước khi bắt đầu lập trình C++, bạn cần phải học cách viết, biên dịch và chạy chương trình C++ đầu tiên.
Để viết chương trình C++ đầu tiên, mở chương trình Dev C++.
Chọn File -> New -> Source File và viết mã sau:

#include <iostream>
#include <conio.h>
 
using namespace std;
 
int main() {
    cout << "Hello C++ Language";
    getch();
    return 0;
}

#include <iostream.h>: bao gồm các phương thức input/output của thư viện chuẩn. Nó cung cấp phương thức cin và cout để đọc dữ liệu đầu vào và ghi dữ liệu đầu ra.

#include <conio.h>: bao gồm các các chức năng thư viện console input output. Hàm getch() được định nghĩa trong file conio.h.

getch(): Hàm getch() yêu cầu người dùng nhập một ký tự đơn. Cho đến khi người dùng bấm phím bất kỳ, nó sẽ khóa màn hình.


Làm thế nào để biên dịch và chạy chương trình C++

Có 2 cách để biên dịch và chạy chương trình C++ với Dev C++, theo menu và phím tắt.

Compile: chọn Execute -> Compile (hoặc F9)

Run: chọn Execute -> Compile (hoặc F10)

Compile và Run: chọn Execute -> Compile (hoặc F11)


Biên dịch và chạy chương trình trên

Đầu tiên, bạn cầu Save đoạn code trên vào tập tin hello.cpp.

Tại trình Dev C++, nhấn phím F9 để biên dịch chương trình, sau đó nhấn phím F10 để chạy chương trình.

Kết quả:

Sau khi nhấn phím bất kỳ:


 

Vào / ra (input / output) dữ liệu cơ bản trong C++

Như bạn đã biết các phương thức printf() và scanf() trong C được sử dụng để vào / ra dữ liệu.
Trong C++ cũng có một cặp phương thức tương ứng cho hoạt động I/O, đó là các phương thức cout và cin.
Hoạt động IO trong C++ sử dụng khái niệm stream. Stream là chuỗi các byte hoặc luồng dữ liệu. Nó làm cho hiệu suất nhanh.
Nếu byte truyền từ bộ nhớ chính sang thiết bị như máy in, màn hình hiển thị, hoặc kết nối mạng, vv, đây được gọi là hoạt động output.
Nếu byte được nhập từ thiết bị như máy in, màn hình hiển thị, hoặc kết nối mạng, vv vào bộ nhớ chính, đây này được gọi là hoạt động input.


I/O Library Header

Bảng sau mô tả các file header thông dụng được sử dụng trong C++ cho hoạt động I/O.  

Header File Phương thức và mô tả
<iostream> Nó được sử dụng để định nghĩa các đối tượng cout, cin và cerr tương ứng với luồng đầu ra tiêu chuẩn, luồng đầu vào tiêu chuẩn và luồng lỗi tiêu chuẩn tương ứng.
<iomanip> Nó được sử dụng để khai báo các dịch vụ hữu ích cho việc thực hiện định dạng I/O, chẳng hạn như setprecision và setw.
<fstream> Nó được sử dụng để khai báo các dịch vụ xử lý tin.

Ouput stream chuẩn (cout)

cout là một đối tượng được xác định trước của lớp ostream. Nó được kết nối với thiết bị đầu ra chuẩn, thường là màn hình hiển thị. Cout được sử dụng kết hợp với toán tử (<<) để hiển thị đầu ra trên console.

Ví dụ sử dụng cout trong C++:

#include <iostream>
 
using namespace std;
 
int main() {
    char charArr[] = "Welcome to C++ HiepSiIt!";  
    cout << "Value of charArr is: " << charArr << endl;  
}  

Kết quả:

Value of charArr is: Welcome to C++ HiepSiIt!

Input stream chuẩn (cin)

cin là một đối tượng được xác định trước của lớp istream. Nó được kết nối với thiết bị đầu vào chuẩn, mà thường là một bàn phím. Cin được sử dụng cùng với toán tử (>>) để đọc đầu vào từ console.

Ví dụ sử dụng cin trong C++:

#include <iostream>
 
using namespace std;
 
int main() {
    int age; 
    cout << "Enter your age: ";
    cin >> age;
    cout << "Your age is: " << age << endl;
}  

Kết quả:

Enter your age: 30
Your age is: 30

Xuống dòng (endl)

endl là một đối tượng được xác định trước của lớp ostream. Nó được sử dụng để chèn một ký tự xuống dòng.

Ví dụ sử dụng endl trong C++:

#include <iostream>
 
using namespace std;
 
int main() {
    cout << "Learn";
    cout << " C++"<< endl;
    cout << "New line" << endl;
}  

Kết quả:

Learn C++
New line

 

Biến trong ngôn ngữ C++

Một biến trong C++ là tên của vị trí bộ nhớ. Nó được sử dụng để lưu trữ dữ liệu. Giá trị của nó có thể được thay đổi và nó có thể được sử dụng lại nhiều lần. Mỗi biến trong C++ có một loại dữ liệu cụ thể, xác định kích thước của bộ nhớ của biến; phạm vi các giá trị có thể được lưu trữ trong bộ nhớ đó.

Biến là một cách để thể hiện vị trí bộ nhớ thông qua một cái tên để nó có thể được xác định một cách dễ dàng. Tên của một biến có thể bao gồm các chữ cái, chữ số và ký tự gạch dưới. Nó phải bắt đầu bằng một chữ cái thư hoặc một gạch dưới. Biến trong C++ có phân biệt chữ hoa và chữ thường.


Cú pháp khai báo biến

Cú pháp để khai báo một biến trong C:

type variable_list;

Ví dụ về khai báo biến:

int a;
float b;
char c;

Ở đây, a, b, c là các biến và int, float, char là các kiểu dữ liệu.
Chúng ta cũng có thể cung cấp giá trị trong khi khai báo các biến như được đưa ra dưới đây:

int a = 10, b = 20; // Khai báo 2 biến kiểu số nguyên
float = 20,8;
char c = 'A';

Quy tắc khai báo biến trong C

  • Một biến có thể có các chữ cái, chữ số và dấu gạch dưới.
  • Tên biến chỉ có thể bắt đầu bằng bảng chữ cái và dấu gạch dưới. Nó không thể bắt đầu bằng chữ số.
  • Không có khoảng trắng trong tên biến.
  • Tên biến không phải là bất kỳ từ hoặc từ khóa dành riêng như int, float, vv. 

Tên biến hợp lệ:

int a;
int _ab;
int a30;

Tên biến không hợp lệ:

int 2;
int a b;
int long;

 


Các kiểu biến trong C++

Có một vài kiểu biến trong C++ như sau:

  1. Biến local (cục bộ).
  2. Biến global (toàn cục).
  3. Biến static.
  4. Biến automatic.
  5. Biến external.

1. Biến local (cục bộ).

Một biến được khai báo bên trong hàm hoặc khối lệnh được gọi là biến địa phương.

Nó phải được khai báo khi bắt đầu khối.

void function1() {
    int x = 10; // bien local
}

2. Biến global (toàn cục).

Một biến được khai báo bên ngoài hàm hoặc khối lệnh được gọi là biến toàn cầu. Bất kỳ hàm nào cũng có thể thay đổi giá trị của biến toàn cầu. Nó có sẵn cho tất cả các chức năng.

Trong ví dụ dưới đây, biến a là biến global.

int a = 20; // bien global
 
void function1() {
    int x = 10; // bien local
}

3. Biến static (tĩnh).

Một biến được khai báo với từ khóa static được gọi là biến tĩnh.

Nó giữ lại giá trị của nó sau nhiều lần gọi hàm.

#include <iostream>
 
using namespace std;
 
void function1() {
    int x = 10; // bien local
    static int y = 10; // bien static
    x = x + 1;
    y = y + 1;
    cout << "x = " << x << ", y = " << y <<endl;
}
 
int main() {
    function1();
    function1();
    function1();
    return 0;
}

Kết quả:

x = 11, y = 11
x = 11, y = 12
x = 11, y = 13

Hàm function1() được gọi 3 lần, biến địa phương x sẽ in cùng một giá trị cho mỗi lệnh chức năng gọi là 11, 11, 11. Nhưng biến tĩnh sẽ in giá trị được tăng lên 1 trong mỗi lần gọi hàm là 11, 12, 13.

4. Biến automatic (tự động).

Tất cả các biến trong C được khai báo trong khối lệnh, là các biến tự động theo mặc định. Bởi chúng ta có thể khai báo một cách rõ ràng biến tự động bằng cách sử dụng từ khóa auto.

void main(){  
    int x = 10; // bien local (cung la bien automatic)
    auto int y = 20; // bien automatic
}  

5. Biến external.

Chúng ta có thể chia sẻ một biến trong nhiều tập tin mã nguồn C bằng cách sử dụng biến external. Để khai báo biến bên ngoài, bạn cần sử dụng từ khóa extern

File: myfile.h

#include <iostream>
#include "myfile.h"
 
using namespace std;
 
void printValue() {
    cout << "x: " << x;
}
 
int main() {
    printValue();
}

Kết quả:

x: 10

Kiểu dữ liệu trong C++

Kiểu dữ liệu trong C++ xác định loại dữ liệu mà một biến có thể lưu trữ như số nguyên, số thực, ký tự vv.

Bạn có thể lưu thông tin của các kiểu dữ liệu (Data Type) đa dạng như Character, Wide Character, integer, floating-point, double floating point, Boolean, …. Dựa trên kiểu dữ liệu của một biến, hệ thống sẽ cấp phát bộ nhớ và quyết định những gì có thể được lưu giữ trong bộ nhớ dành riêng đó.


Kiểu dữ liệu nguyên cơ sở trong C++

Tên tiếng Anh là Primitive Type, còn có thể gọi là kiểu dữ liệu cơ sở, kiểu dữ liệu cơ bản, hay kiểu dữ liệu có sẵn trong C++. Bên cạnh các kiểu dữ liệu cơ sở này, C++ cũng cung cấp các kiểu dữ liệu do người dùng tự định nghĩa (user-defined). Bảng dưới đây liệt kê danh sách 7 kiểu dữ liệu cơ bản trong C++:

Kiểu dữ liệu Từ khóa
Boolean bool
Ký tự char
Số nguyên int
Số thực float
Số thực dạng Double double
Kiểu không có giá trị void
Kiểu Wide character wchar_t

Một số kiểu cơ bản có thể được sửa đổi bởi sử dụng một hoặc nhiều modifier sau:

  • signed (kiểu có dấu)
  • unsigned (kiểu không có dấu)
  • short
  • long

Các loại dữ liệu cơ sở

Các kiểu dữ liệu cơ bản dựa trên số nguyên và số thực. Ngôn ngữ C hỗ trợ cả signed và unsigned.
Kích thước bộ nhớ của các loại dữ liệu cơ bản có thể thay đổi theo hệ điều hành 32 hoặc 64 bit.
Hãy xem các kiểu dữ liệu cơ bản. Kích thước của nó được cho theo kiến ​​trúc 32 bit.
Bảng sau hiển thị kiểu biến, lượng bộ nhớ nó dùng để lưu giá trị trong bộ nhớ, và giá trị lớn nhất và nhỏ nhất có thể được lưu giữ với các kiểu biến đó:

Kiểu Kích thước bộ nhớ Vùng giá trị
char 1 byte -127 tới 127 hoặc 0 tới 255
unsigned char 1 byte 0 tới 255
signed char 1 byte -127 tới 127
int 4 byte -2147483648 tới 2147483647
unsigned int 4 byte 0 tới 4294967295
signed int 4 byte -2147483648 tới 2147483647
short int 2 byte -32768 tới 32767
unsigned short int Range 0 tới 65,535
signed short int Range -32768 tới 32767
long int 4 byte -2,147,483,647 tới 2,147,483,647
signed long int 4 byte Tương tự như long int
unsigned long int 4 byte 0 tới 4,294,967,295
float 4 byte +/- 3.4e +/- 38 (~7 chữ số)
double 8 byte +/- 1.7e +/- 308 (~15 chữ số)
long double 8 byte +/- 1.7e +/- 308 (~15 chữ số)
wchar_t 2 hoặc 4 byte 1 wide character

Ví dụ sử dụng toán tử để lấy gia kích cỡ kiểu dữ liệu

#include <iostream>
 
using namespace std;
 
int main() {
   cout << "Kich co cua char la: " << sizeof(char) << endl;
   cout << "Kich co cua int la: " << sizeof(int) << endl;
   cout << "Kich co cua short int la: " << sizeof(short int) << endl;
   cout << "Kich co cua long int la: " << sizeof(long int) << endl;
   cout << "Kich co cua float la: " << sizeof(float) << endl;
   cout << "Kich co cua double la: " << sizeof(double) << endl;
   cout << "Kich co cua wchar_t la: " << sizeof(wchar_t) << endl;
   return 0;
}

Kết quả:

Kich co cua char la: 1
Kich co cua int la: 4
Kich co cua short int la: 2
Kich co cua long int la: 4
Kich co cua float la: 4
Kich co cua double la: 8
Kich co cua wchar_t la: 2

 

Danh sách từ khóa trong C++

Từ khóa là một từ dành riêng. Bạn không thể sử dụng nó như là một tên biến, tên hằng số, vv. Danh sách 32 từ khoá trong C++ mà cũng có sẵn trong C được đưa ra dưới đây.

auto break case char const continue default do
double else enum extern float for goto if
int long register return short signed sizeof static
struct switch typedef union unsigned void volatile while

Danh sách 30 từ khoá trong C ++ nhưng không có trong ngôn ngữ C được đưa ra dưới đây.

asm dynamic_cast namespace reinterpret_cast bool
explicit new static_cast false catch
operator template friend private class
this inline public throw const_cast
delete mutable protected true try
typeid typename using virtual wchar_t

Chúng ta sẽ học về tất cả các từ khóa trong ngôn ngữ lập trình C ++trong các bài học sau này.


 

Toán tử trong C

Toán tử trong C là một ký hiệu được sử dụng để thực hiện một phép tính/chức năng nào đó. Ngôn ngữ lập trình C cung cấp các dạng toán tử sau:

  1. Toán tử số học.
  2. Toán tử quan hệ.
  3. Toán tử logic.
  4. Toán tử so sánh bit.
  5. Toán tử gán.
  6. Toán tử hỗn hợp.
  7. Thứ tự ưu tiên.

1. Toán tử số học trong C

Bảng dưới đây mô tả các toán tử số học được hỗ trợ bởi ngôn ngữ C. Giả sử biến A = 10 và biến B = 20:
 

Toán tử Miêu tả Ví dụ
+ Cộng hai toán hạng A + B sẽ cho kết quả là 30
Trừ hai toán hạng A – B sẽ cho kết quả là -10
* Nhân hai toán hạng A * B sẽ cho kết quả là 200
/ Chia lấy phần nguyên hai toán hạng B / A sẽ cho kết quả là 2
% Chia lấy phần dư B % A sẽ cho kết quả là 0
++ Tăng giá trị toán hạng thêm 1 đơn vị A++ sẽ cho kết quả là 11
Giảm giá trị toán hạng một đơn vị A– sẽ cho kết quả là 9

2. Toán tử quan hệ trong C

Các toán tử quan hệ được sử dụng kiểm tra mối quan hệ giữa hai toán hạng. Kết quả của một biểu thức có dùng các toán tử quan hệ là những giá trị Boolean (logic “true” hoặc “false”). Các toán tử quan hệ được sử dụng trong các cấu trúc điều khiển.

Bảng dưới đây mô tả các toán tử quan hệ được hỗ trợ bởi ngôn ngữ C. Giả sử biến A = 10 và biến B = 20:

Toán tử Miêu tả Ví dụ
== Kiểm tra nếu 2 toán hạng bằng nhau hay không. Nếu bằng thì điều kiện là true. (A == B) là không đúng.
!= Kiểm tra 2 toán hạng có giá trị khác nhau hay không. Nếu không bằng thì điều kiện là true. (A != B) là true.
> Kiểm tra nếu toán hạng bên trái có giá trị lớn hơn toán hạng bên phải hay không. Nếu lớn hơn thì điều kiện là true. (A > B) là không đúng.
< Kiểm tra nếu toán hạng bên trái nhỏ hơn toán hạng bên phải hay không. Nếu nhỏ hơn thì là true. (A < B) là true.
>= Kiểm tra nếu toán hạng bên trái có giá trị lớn hơn hoặc bằng giá trị của toán hạng bên phải hay không. Nếu đúng là true. (A >= B) là không đúng.
<= Kiểm tra nếu toán hạng bên trái có giá trị nhỏ hơn hoặc bằng toán hạng bên phải hay không. Nếu đúng là true. (A <= B) là true.

3. Toán tử logic trong C

Bảng dưới đây mô tả các toán tử logic được hỗ trợ bởi ngôn ngữ C. Giả sử biến A = true (hoặc 1) và biến B = false (hoặc 0): 

Toán tử Miêu tả Ví dụ
&& Được gọi là toán tử logic AND (và). Nếu cả hai toán tử đều có giá trị khác 0 thì điều kiện trở lên true. (A && B) là false.
|| Được gọi là toán tử logic OR (hoặc). Nếu một trong hai toán tử khác 0, thì điều kiện là true. (A || B) là true.
! Được gọi là toán tử NOT (phủ định). Sử dụng để đảo ngược lại trạng thái logic của toán hạng đó. Nếu điều kiện toán hạng là true thì phủ định nó sẽ là false. !(A && B) là true.

4. Toán tử bit trong C

Các toán tử dạng bit cho phép chúng ta thao tác trên từng bit riêng biệt trong các kiểu dữ liệu nguyên thủy. Giả sử A = 60 (= 0011 1100) và B = 13 (= 0000 1101)
 

Toán tử Miêu tả Ví dụ
& Toán tử AND (và) nhị phân sao chép một bit tới kết quả nếu nó tồn tại trong cả hai toán hạng. (A & B) sẽ cho kết quả là 12, tức là 0000 1100
| Toán tử OR (hoặc) nhị phân sao chép một bit tới kết quả nếu nó tồn tại trong một hoặc hai toán hạng. (A | B) sẽ cho kết quả là 61, tức là 0011 1101
^ Toán tử XOR nhị phân sao chép bit mà nó chỉ tồn tại trong một toán hạng mà không phải cả hai. (A ^ B) sẽ cho kết quả là 49, tức là 0011 0001
~ Toán tử đảo bit (đảo bit 1 thành bit 0 và ngược lại). (~A ) sẽ cho kết quả là -61, tức là 1100 0011.
<< Toán tử dịch trái. Giá trị toán hạng trái được dịch chuyển sang trái bởi số các bit được xác định bởi toán hạng bên phải. A << 2 sẽ cho kết quả 240, tức là 1111 0000 (dịch sang trái hai bit)
>> Toán tử dịch phải. Giá trị toán hạng trái được dịch chuyển sang phải bởi số các bit được xác định bởi toán hạng bên phải. A >> 2 sẽ cho kết quả là 15, tức là 0000 1111 (dịch sang phải hai bit)

5. Toán tử gán trong C

Toán tử gán dùng để gán một giá trị vào một biến và có thể gán nhiều giá trị cho nhiều biến cùng một lúc.

Toán tử Miêu tả Ví dụ
= Toán tử gán đơn giản. Gán giá trị toán hạng bên phải cho toán hạng trái. C = A + B sẽ gán giá trị của A + B vào trong C
+= Thêm giá trị toán hạng phải tới toán hạng trái và gán giá trị đó cho toán hạng trái. C += A tương đương với C = C + A
-= Trừ đi giá trị toán hạng phải từ toán hạng trái và gán giá trị này cho toán hạng trái. C -= A tương đương với C = C – A
*= Nhân giá trị toán hạng phải với toán hạng trái và gán giá trị này cho toán hạng trái. C *= A tương đương với C = C * A
/= Chia toán hạng trái cho toán hạng phải và gán giá trị này cho toán hạng trái. C /= A tương đương với C = C / A
%= Lấy phần dư của phép chia toán hạng trái cho toán hạng phải và gán cho toán hạng trái. C %= A tương đương với C = C % A
<<= Dịch trái toán hạng trái sang số vị trí là giá trị toán hạng phải. C <<= 2 tương đương với C = C << 2
>>= Dịch phải toán hạng trái sang số vị trí là giá trị toán hạng phải. C >>= 2 tương đương với C = C >> 2
&= Phép AND bit C &= 2 tương đương với C = C & 2
^= Phép OR loại trừ bit C ^= 2 tương đương với C = C ^ 2
|= Phép OR bit. C |= 2 tương đương với C = C | 2

6. Toán tử hỗn hợp trong C

Có một số toán tử hỗn hợp quan trọng là sizeof và ? : được hỗ trợ bởi ngôn ngữ C.

Toán tử Miêu tả Ví dụ
sizeof() Trả lại kích cỡ của một biến sizeof(a), với a là integer, thì sẽ trả lại kết quả là 4.
& Trả lại địa chỉ của một biến. &a; sẽ cho địa chỉ thực sự của biến a.
* Trỏ tới một biến. *a; sẽ trỏ tới biến a.
? : Biểu thức điều kiện Nếu điều kiện là true ? thì giá trị X : Nếu không thì giá trị Y

7. Thứ tự ưu tiên

Thứ tự ưu tiên của các toán tử trong C

Thứ tự ưu tiên quyết định trật tự thực hiện các toán tử trên các biểu thức. Bảng dưới đây liệt kê thứ tự thực hiện các toán tử trong C.

Loại  Toán tử  Thứ tự ưu tiên 
Postfix  () -> . ++ – –   Trái sang phải 
Unary  + – ! ~ ++ – – (type)* & sizeof  Phải sang trái 
Tính nhân  * / %  Trái sang phải 
Tính cộng  + –  Trái sang phải 
Dịch chuyển  << >>  Trái sang phải 
Quan hệ  < <= > >=  Trái sang phải 
Cân bằng  == !=  Trái sang phải 
Phép AND bit  Trái sang phải 
Phép XOR bit  Trái sang phải 
Phép OR bit  Trái sang phải 
Phép AND logic  &&  Trái sang phải 
Phép OR logic  ||  Trái sang phải 
Điều kiện  ?:  Phải sang trái 
Gán  = += -= *= /= %=>>= <<= &= ^= |=  Phải sang trái 
Dấu phảy  Trái sang phải 

Thay đổi thứ tự ưu tiên của các toán tử trong C

Để thay đổi thứ tự ưu tiên trên một biểu thức, bạn có thể sử dụng dấu ngoặc đơn ():

  • Phần được giới hạn trong ngoặc đơn được thực hiện trước.
  • Nếu dùng nhiều ngoặc đơn lồng nhau thì toán tử nằm trong ngoặc đơn phía trong sẽ thực thi trước, sau đó đến các vòng phía ngoài.
  • Trong phạm vi một cặp ngoặc đơn thì quy tắc thứ tự ưu tiên vẫn giữ nguyên tác dụng.

Ví dụ, x = 10 + 5 * 2; ở đây, x được gán giá trị 20, chứ không phải 30 bởi vì toán tử * có quyền ưu tiên cao hơn toán tử +, vì thế đầu tiên nó thực hiện phép nhân 5 * 2 và sau đó thêm với 10.

Để thay đổi thứ tự ưu tiên ta dùng dấu (), ví dụ, x = (10 + 5) * 2; ở đây x = 30.


 

Ghi chú (Comment) trong C

Ghi chú trong C++ được sử dụng để cung cấp thông tin (giải thích) các dòng code. Nó được sử dụng rộng rãi để tài liệu hóa code. Có 2 loại comment trong ngôn ngữ lập trình C++.

  1. Ghi chú  một dòng.
  2. Ghi chú  nhiều dòng.

1. Ghi chú một dòng

Comment một dòng trong C++ được đại diện bởi dấu gạch chéo kép //. Hãy xem một ví dụ về comment dòng đơn trong C.

#include <iostream>
 
using namespace std;
 
int main() {
    // in thong tin
    cout << "Hello C++";
    return 0;
}

 Kết quả:

Hello C++

Thậm chí là bạn có thể đặt comment ngay sau câu lệnh, ví dụ:

cout << "Hello C++"; // in thong tin

2. Ghi chú nhiều dòng

Ghi chú nhiều dòng trong C++ được đại diện bởi dấu gạch chéo /*…*/. Nó có thể chiếm nhiều dòng nhưng nó không thể được lồng nhau. Cú pháp:

/*  
code 
to be commented 
*/ 

Ví dụ comment nhiều dòng trong C++:

#include <iostream>
 
using namespace std;
 
int main() {
    /* in thong tin
     * comment nhieu dong 
     */
    cout << "Hello C++";
    return 0;
}

Kết quả:

Hello C++

 

Ép kiểu trong C++

Ép kiểu trong C++ là việc gán giá trị của một biến có kiểu dữ liệu này tới biến khác có kiểu dữ liệu khác.

Cú pháp:

(type) value;

Ví dụ:

float c = 35.8f;
int b = (int)c + 1;

Trong ví dụ trên, đầu tiên giá trị dấu phảy động c được đổi thành giá trị nguyên 35. Sau đó nó được cộng với 1 và kết quả là giá trị 36 được lưu vào b.


Phân loại ép kiểu trong C++

Trong C++, có hai loại ép kiểu dữ liệu:

  1. Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin.
  2. Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin

1. Nới rộng (widening)

Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Ví dụ chuyển từ int sang float. Chuyển kiểu loại này có thế được thực hiện ngầm định bởi trình biên dịch.

Ví dụ:

#include <iostream>
 
using namespace std;
  
int main() {
    int i = 100;
    long l = i;    // khong yeu cau chi dinh ep kieu
    float f = l;   // khong yeu cau chi dinh ep kieu
    cout << "Gia tri Int: " << i << endl;
    cout << "Gia tri Long: " << l << endl;
    cout << "Gia tri Float: " << f << endl;
    return 0;
}

 Kết quả:

Giá trị Int: 100
Giá trị Long: 100
Giá trị Float:  100

2.Thu hẹp (narrowwing)

ep-kieu-du-lieu-noi-rong

Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên. Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.

Ví dụ:

#include <iostream>
 
using namespace std;
  
int main() {
    double d = 100.04;
    long l = (long) d; // yeu cau chi dinh kieu du lieu (long)
    int i = (int) l; // yeu cau chi dinh kieu du lieu (int)
    cout << "Gia tri Double: " << d << endl;
    cout << "Gia tri Long: " << l << endl;
    cout << "Gia tri Int: " << i << endl;
    return 0;
}

Kết quả:

Giá trị Double: 100.04
Giá trị Long: 100
Giá trị Int:  100

 

Ký tự đặc biệt trong C++

Ký tự đặc biệt trong C++ là một dãy các ký tự không đại diện cho chính nó khi được sử dụng bên trong chuỗi ký tự hoặc ký tự.

Nó bao gồm hai hoặc nhiều ký tự bắt đầu bằng dấu gạch chéo ngược \. Ví dụ: \n đại diện cho dòng mới.


Ký tự đặc biệt Ý nghĩa
\a Báo thức hoặc tiếng bíp
\b Dấu Backspace
\f Form Feed
\n Xuống dòng mới (LF)
\r Xuống dòng (CR)
\t Dấu Tab ngang
\v Dấu Tab dọc
\\ Dấu ngạch chéo ngược
\’ Dấu nháy đơn
\” Dấu nháy kép
\? Dấu chấm hỏi
\nnn Số octal
\xhh Số hexadecimal
\0 Null

Ví dụ ký tự đặc biệt trong C

#include <iostream>
 
using namespace std;
 
int main() {
    cout << "Ban Ty\nHoc Lap Trinh\n\'C++\'\n\"Chuc Ban Hoc Vui Ve!\"";
    return 0;
}

Kết quả:

Ban Tu
Hoc Lap Trinh
'C++'
"Chuc Ban Hoc Vui Ve!"

 

Cấu trúc điều khiển if else trong C++

Thông thường để kiểm giá trị có thỏa mãn với giá trị mong muốn hay không?. Trong ngôn ngữ C chúng ta thường dùng lệnh If để kiểm tra.
Các Cấu Trúc Điều Khiển

  • Câu lệnh if
  • Câu lệnh if else
  • Câu lênh if…elseif…else
  • Câu lệnh điều kiện lồng nhau

Câu lệnh IF: sử dụng câu lệnh IF để lọc kết quả đúng

if(expression){  
//code to be executed  
}  

Lưu đồ của lệnh If 

Ví dụ lệnh if:

#include <iostream>  
using namespace std;  
   
int main () {  
   int num = 10;    
            if (num % 2 == 0)    
            {    
                cout<<"It is even number";    
            }   
   return 0;  
}  

Kết quả:

It is even number

Câu lệnh if else.
Phần trên chúng ta mới chỉ dùng lệnh if để kiểm tra nếu đúng thì sao còn chưa bắt được sự kiện nếu sai thì làm gì. Do đó trong C hay bất kỳ ngôn ngữ lập trình nào đó đều cung cấp cho chúng ta câu lệnh else để xử lý điều kiện sai, theo cú pháp:

if(expression){  
//code to be executed if condition is true  
}else{  
//code to be executed if condition is false  
}  

Lưu đồ của lệnh If - else


Ví dụ lệnh if-else

#include <iostream>  
using namespace std;  
int main () {  
   int num = 11;    
            if (num % 2 == 0)    
            {    
                cout<<"It is even number";    
            }   
            else  
            {    
                cout<<"It is odd number";    
            }  
   return 0;  

Kết quả:

It is odd number

Ví dụ lệnh if-else nhập vào từ người sử dụng

#include <iostream>  
using namespace std;  
int main () {  
    int num;  
    cout<<"Enter a Number: ";  
    cin>>num;  
            if (num % 2 == 0)    
            {    
                cout<<"It is even number"<<endl;    
            }   
            else  
            {    
                cout<<"It is odd number"<<endl;    
            }  
   return 0;  
}  

Kết quả:

Enter a number:11
It is odd number

Kết quả:

Enter a number:12
It is even number

Câu lệnh if - else - if 
Thực thi nhiều câu lệnh với nhiều điều kiện khác nhau.
Cấu trúc:

if(condition1){  
//code to be executed if condition1 is true  
}else if(condition2){  
//code to be executed if condition2 is true  
}  
else if(condition3){  
//code to be executed if condition3 is true  
}  
...  
else{  
//code to be executed if all the conditions are false  
}  

Lưu đồ lệnh if-else-if:

Ví dụ lệnh if-else-if:

#include <iostream>  
using namespace std;  
int main () {  
       int num;  
       cout<<"Enter a number to check grade:";    
       cin>>num;  
            if (num <0 || num >100)    
            {    
                cout<<"wrong number";    
            }    
            else if(num >= 0 && num < 50){    
                cout<<"Fail";    
            }    
            else if (num >= 50 && num < 60)    
            {    
                cout<<"D Grade";    
            }    
            else if (num >= 60 && num < 70)    
            {    
                cout<<"C Grade";    
            }    
            else if (num >= 70 && num < 80)    
            {    
                cout<<"B Grade";    
            }    
            else if (num >= 80 && num < 90)    
            {    
                cout<<"A Grade";    
            }    
            else if (num >= 90 && num <= 100)    
            {    
                cout<<"A+ Grade";  
            }    
    }    

Kết quả:

Enter a number to check grade:66
C Grade

Kết quả:

Enter a number to check grade:-2
wrong number

 

Câu lệnh switch

Switch so sánh một biểu thức nguyên với một danh sách giá trị các số nguyên, các hằng kí tự hoặc biểu thức hằng. Mỗi giá trị trong danh sách chính là một case (trường hợp) trong khối lệnh của switch. Ngoài ra, trong khối lệnh switch còn có thể có một default case (trường hợp mặc định) có thể có hoặc không. Mặt khác, trong mỗi trường hợp còn chứa các khối lệnh chờ được thực thi.

Cú pháp của cấu trúc rẽ nhánh switch…case

Muốn sử dụng cấu trúc switch…case, bạn dùng cú pháp như sau:

switch(expression){    
case value1:    
 //code to be executed;    
 break;  //optional  
case value2:    
 //code to be executed;    
 break;  //optional  
......    
    
default:     
 code to be executed if all cases are not matched;    
}    

Các qui tắc của lệnh switch tronng ngôn ngữ C++
Biểu thức nguyên trong switch được tính toán và kiểm tra lần lượt với giá trị của từng case. Đầu tiên, nó sẽ được so sánh với giá trị của case đầu tiên, nếu bằng nhau thì sẽ thực hiện các lệnh (statement) trong case này cho đến khi nó gặp được từ khoá break. Khi đó, cấu trúc switch…case kết thúc. Chương trình sẽ thực hiện tiếp những dòng lệnh sau cấu trúc switch…case. Ngược lại, nếu như giá trị biểu thức nguyên không bằng giá trị case đầu tiên thì nó sẽ tiếp tục so sánh đến giá trị của case thứ hai và tiếp tục thực hiện như những bước trên. Giả sử, đến cuối cùng vẫn không tìm được giá trị bằng nó thì các khối lệnh trong default sẽ được thực hiện nếu như có tồn tại default.

Chú ý:
1) Biểu thức trong lệnh switch phải là kiểu dữ liệu số nguyên, ký tự.
2) case value phải là hằng số nguyên hoặc ký tự.
3) case value có thể sử dụng trong lệnh switch.
4) Lệnh break trong switch không bắt buộc.

Xem ví dụ sau: Kiểu dữ liệu nào hợp lệ trong lệnh switch:

int x,y,z;  
char a,b;  
float f;  
Lệnh Switch đúng Lệnh Switch sai Lệnh Switch đúng Lệnh Switch sai
switch(x) switch(f) case 3; case 2.5;
switch(x>y) switch(x+2.5) case 'a'; case x;
switch(a+b-2)   case 1+2; case x+2;
switch(func(x,y))   case 'x'>'y'; case 1,2,3;

Sơ đồ hoạt động của cấu trúc switch…case như sau:

Ví dụ lệnh switch :

#include <iostream>  
using namespace std;  
int main () {  
       int num;  
       cout<<"Enter a number to check grade:";    
       cin>>num;  
           switch (num)    
          {    
              case 10: cout<<"It is 10"; break;    
              case 20: cout<<"It is 20"; break;    
              case 30: cout<<"It is 30"; break;    
              default: cout<<"Not 10, 20 or 30"; break;    
          }    
    }    

Kết quả:

Enter a number:
10
It is 10

 Kết quả:

Enter a number:
55
Not 10, 20 or 30

 

Các lệnh lặp trong C++

Cũng giống như cấu trúc rẽ nhánh, cấu trúc vòng lặp cũng là một trong những thành phần quan trọng của một ngôn ngữ lập trình, vòng lặp là hành động lặp lại một khối lệnh với số lần hữu hạn.

Ngô ngữ C++ hỗ trợ chúng ta 3 kiểu vòng lặp(loop):

  1. while
  2. do while
  3. for

Cấu trúc lặp while
Vòng lặp while là vòng lặp cơ bản trong C++, điều kiện dừng vòng lặp khi expression bằng false

Cú pháp:

while (expression) { 
    statement
}

Sự hoạt động của vòng lặp while:
Bước 1: tính giá trị của (biểu thức) sau while.
Bước 2: nếu giá trị tính được của (biểu thức) là ‘sai’ (==0) thì kết thúc  vòng lặp while.
Bước 3: nếu giá trị của (biểu thức) là ‘đúng’ (!=0) thì thực hiện khối lệnh sau while.
Bước 4: quay lại bước 1

Chú ý: Trong thân vòng lặp while có thể được thực hiện một lần hoặc nhiều lần và cũng có thể không được thực hiện lần nào nếu ngay từ đầu biểu thức sau while đã sai.

Biểu diễn bằng lưu đồ: 

Ví dụ lệnh lặp while:

#include <iostream>  
using namespace std;  
int main() {         
 int i=1;      
         while(i<=10)   
       {      
            cout<<i <<"\n";    
            i++;  
          }       
    }  

Kết quả:

1
2
3
4
5
6
7
8
9
10

Lặp không xác định trong while

Nếu bạn truyền cho while giá trị là 1, thì vòng lặp while sẽ lặp không xác định

while(1){  
//statement  
}  

Cấu trúc lặp do...while
Vòng lặp do-while cũng giống như while nhưng chỉ có điều là do-while xét điều kiện lặp sau khi mỗi vòng lặp. Cấu trúc do-while chỉ có một cú pháp.
Cú pháp:   

do{
     statement
   }while (expresion);

Giải thích cú pháp:
Bước 1: thực hiện khối lệnh sau do.
Bước 2: kiểm tra giá trị biểu thức <biểu thức> sau while, nếu có giá trị ‘đúng’ ( khác 0) thì lặp lại bước 1, nếu ‘sai’ (=0) thì kết thúc vòng lặp.
Biểu diễn bằng lưu đồ: 

Ví dụ do-while

include <iostream>  
using namespace std;  
int main() {  
     int i = 1;    
          do{    
              cout<<i<<"\n";    
              i++;    
          } while (i <= 10) ;    
}  

Kết quả:

1
2
3
4
5
6
7
8
9
10

Lặp không xác định trong  do-while

Nếu bạn truyền cho while giá trị là 1, thì vòng lặp do-while sẽ lặp không xác định

do{  
//statement  
}while(1);  

Cấu trúc lặp for
Vòng lặp for là vòng lặp khá là phức tạp hơn vòng lặp while. Vòng lặp for có hai cú pháp như sau:

for (expr1; expr2; expr3) {
    statement
}

Trong đó:
    Biểu thức 1: biểu thức khởi đầu.
    Biểu thức 2: biểu thức điều kiện - điều kiện lặp.
    Biểu thức 3: bước nhảy - thường dùng với ý nghĩa là thay đổi bước nhảy.

Cả 3 biểu thức này đều là tuỳ chọn, chúng có thể vắng mặt trong câu lệnh cụ thể nhưng các dấu chấm phẩy vẫn phải có.
Sự hoạt động của vòng lặp for:

  • Bước 1: Thực hiện biểu thức khởi đầu – Biểu thức 1.
  • Bước 2: Tính giá trị biểu thức 2 để xác định điều kiện lặp.
    - Nếu biểu thức 2 có giá trị ‘sai’ (==0) thì ra khỏi vòng lặp.
    - Ngược lại, nếu biểu thức có giá trị ‘đúng’ ( khác 0) thì chuyển tới bước 3.
  • Bước 3: Thực hiện khối lệnh sau for ( thân của for ), chuyển tới bước 4.
  • Bước 4: Thực hiện biểu thức 3, rồi quay về bước 2.

Biểu diễn bằng lưu đồ: 

Ví dụ vòng lặp for:

#include <iostream>  
using namespace std;  
int main() {  
         for(int i=1;i<=10;i++){      
            cout<<i <<"\n";      
          }       
    }   

Kết quả:

1
2
3
4
5
6
7
8
9
10

Lặp không xác định trong for 

Ví dụ:

#include <iostream>  
using namespace std;  
   
int main () {  
        for (; ;)    
          {    
                  cout<<"Infinitive For Loop";    
          }    
    }    

Kết quả:

Infinitive For Loop
Infinitive For Loop
Infinitive For Loop
Infinitive For Loop
Infinitive For Loop
ctrl+c

 For lồng nhau:

#include <iostream>  
using namespace std;  
   
int main () {  
        for(int i=1;i<=3;i++){      
             for(int j=1;j<=3;j++){      
            cout<<i<<" "<<j<<"\n";      
          }     
        }  
    }    

Kết quả: 

1 1
1 2
1 3
2 1
2 2 
2 3
3 1
3 2
3 3

 

Lệnh break trong ngôn ngữ C++

Trong ngôn ngữ C++, lệnh break là một lệnh mà khi gặp lệnh đó thì chương trình sẽ nhảy ra khỏi vòng lặp chứa nó.
Cú pháp:

 break;

Lưu đồ lệnh break:

Ví dụ lệnh break trong vòng lặp

#include <iostream>  
using namespace std;  
int main() {  
      for (int i = 1; i <= 10; i++)    
          {    
              if (i == 5)    
              {    
                  break;    
              }    
        cout<<i<<"\n";    
          }    
}  

Kết quả:

1
2
3
4
5

Ví dụ trên chúng ta thấy rằng vòng lặp đi tứ 1 đến 10, nhưng nó sẽ không print các giá trị sau số 5 (6,7,8,9,10)


Ví dụ lệnh break trong vòng lặp lồng nhau:

#include<stdio.h>  
int main(){  
int i=1,j=1;//initializing a local variable    
for(i=1;i<=3;i++){      
 for(j=1;j<=3;j++){    
  cout<<i<<j<<enld;    
  if(i==2 && j==2){    
    break;//will break loop of j only    
  }    
  }//end of for loop    
return 0;  
}    

Kết quả:

1 1
1 2
1 3
2 1
2 2
3 1
3 2
3 3

Như bạn thấy đầu ra trên bảng kết quẢ, 2 3 không được in vì có lệnh break  sau khi in i == 2 và j == 2. Nhưng 3 1, 3 2 và 3 3 được in vì lệnh break chỉ có tác động bên trong vòng for trong (for(j=1;j<=3;j++).


 

Lệnh Continue trong c++

Lệnh continue là lệnh mà khi gặp nó thì chương trình sẽ bỏ qua những câu lệnh phía dưới nó(trong cùng 1 câu lệnh lặp) để thực hiện 1 vòng lặp mới.
Cú pháp:

continue;

Ví dụ lệnh continue trong C:

#include <iostream>  
using namespace std;  
int main()  
{  
     for(int i=1;i<=10;i++){      
            if(i==5){      
                continue;      
            }      
            cout<<i<<"\n";      
        }        
}  

Kết quả: 

1
2
3
4
6
7
8
9
10

Ví dụ trên ta thấy số 5 không được in


Ví dụ lệnh continue trong vòng lặp lồng nhau:

#include <iostream>  
using namespace std;  
int main()  
{  
 for(int i=1;i<=3;i++){        
            for(int j=1;j<=3;j++){        
             if(i==2&&j==2){        
                continue;        
                        }        
                cout<<i<<" "<<j<<"\n";                  
                    }        
            }            
}  

Kết quả:

1 1
1 2
1 3
2 1
2 3
3 1
3 2
3 3

Ví dụ trên ta thấy 2,2 không được in. Vì vòng lặp trong tại vị trí i==2 và j==2 có lệnh continue
 


 

Lệnh go trong ngôn ngữ C++

Trong ngôn ngữ C để nhảy (jump) đến nhãn được định nghĩa trước chúng ta dùng lệnh go

Cú pháp:

goto label;  

Ví dụ dùng lệnh go:

#include <iostream>  
using namespace std;  
int main()  
{  
ineligible:    
         cout<<"You are not eligible to vote!\n";    
      cout<<"Enter your age:\n";    
      int age;  
      cin>>age;  
      if (age < 18){    
              goto ineligible;    
      }    
      else    
      {    
              cout<<"You are eligible to vote!";     
      }         
}  

Kết quả:

You are not eligible to vote!
Enter your age:
16
You are not eligible to vote!
Enter your age:
7
You are not eligible to vote!
Enter your age:
22
You are eligible to vote!

 

Hàm Trong C++ (Funtions)

Một chương trình được viết trong ngôn ngữ C++ gọi là một dãy các hàm, trong đó có một hàm chính ( hàm main()). Hàm chia các bài toán lớn thành các công việc nhỏ hơn giúp thực hiện những công việc lặp lại nào đó một cách nhanh chóng mà không phải viết lại đoạn chương trình. Thứ tự các hàm trong chương trình C++  là bất kỳ, song chương trình bao giờ cũng đi thực hiện từ hàm main(). Hàm có thể xem là một đơn vị độc lập của chương trình. Hàm trong C++ có vai trò ngang nhau, vì vậy không có phép xây dựng một hàm bên trong các hàm khác.


Các tiện ích của hàm

  1. Tái sử dụng
    Là một module chương trình, giải quyết một công việc hoàn chỉnh, được dùng nhiều lần trong chương trình
  2. Tối ưu mã (code)
    • Tránh việc viết lại các đoạn trình giống nhau trong chương trình
    • Phân cấp (chia) chương trình theo tư tưởng: chia để trị => chương trình dễ hiểu, dễ quản lý, dễ bảo trì

 Các loại hàm trong C++

Có hai loại hàm trong ngôn ngữ C++

  1. Các hàm thư viện (Library Functions): Là các hàm được khai báo trong tập tin header của C++ như: ceil(x), cos(x), exp(x), cout, cin v.v..
  2. Các hàm do người lập trình định nghĩa (User-defined functions): Là các hàm do lập trình viên định nghĩa. 

 


Khai báo hàm

Cú pháp khai báo một hàm trong ngôn ngữ C++:

kieu_ham ten_ham (danh sach tham so){
/* noi dung cua ham */
}

Trong đó :

  • kieu_ham : là một kiểu dữ liệu chuẩn của C++, hoặc kiểu do người dùng tự định nghĩa
  • ten_ham : được đặt theo quy tắc của tên biến, có phân biệt chữ hoa với chữ thường
  • danh sach tham so : các tham số hình thức của  các hàm trong C++

Giá trị trả về

Một hàm trong C++ có thể có giá trị trả về hoặc không có giá trị trả về. Trường hợp hàm không có giá trị, sử dụng từ khóa void để mô tả kiểu của hàm.

Ví dụ hàm không có giá trị trả vê:

void hello(){  
  cout<<"Hello C++"
}  

Nếu một hàm có giá trị trả về, chúng ta cần sử dụng kiểu dữ liệu như: int, long, char .v.v. Dữ liệu trả về phụ thuộc vào kiểu khai báo của hàm

Ví dụ hàm có giá trị trả về:

int get(){  
return 10;  
}  

Ví dụ trên về là số 10 kiểu dữ liệu số nguyên. Nếu chúng ta muốn trả về kiểu số thực thì phải khai báo float trước tên hàm

float get(){  
return 10.2;  
}  

Vị trí của hàm

  • Có thể khai báo sau hoặc trước hàm main() trong C++.
  • Nếu khai báo sau hàm main() thì phải khai báo prototype của hàm ở đầu chương trình, sau đó phần triển khai nội dung hàm được đặt sau hàm main()
  • Funtion prototype:
    • Khai báo kiểu hàm, tên hàm và các tham số , kết thúc bằng dấu chấm phẩy.
    • Ví dụ : int SUM (int x , int y);        => prototype của hàm SUM
    • Chú ý : Trong phần triển khai của hàm , thì danh sách tham biến và kiểu dữ liệu của hàm phải trùng khớp với những gì đã khai báo trong phần prototype

Lời gọi hàm

  • Hàm được gọi bất kì nơi đâu trong chương trình, có thể từ hàm main() hoặc một hàm khác trong chương trình.
  • Khi hàm A muốn gọi hàm B thì hoặc là B phải triển khai trước A hoặc là B đã khai báo prototype ở đầu chương trình.
  • Hàm được gọi thông qua tên hàm và danh sách các tham số. Ví dụ : Sum(10,20) hoặc Sum(20,a),..
  • Hàm có thể được gọi theo nhiều cách khác nhau
  • Trong C, không có các hàm lồng nhau

Các tham số trong ngôn ngữ C++

Một hàm trong ngôn ngữ C++ không có tham số hoặc nhiều tham số
Ví dụ hàm không có tham số:

void hello(){  
 cout<<"hello c";  
}  

Ví dụ hàm có một tham số:

int add(int a, int b){  
  return a+b;  
}  

Cách gọi một hàm trong ngôn ngữ C++

Nếu hàm có giá trị trả về, chúng ta nhận giá trị của hàm. Cú pháp gọi một hàm trong ngôn ngữ C++ như sau:

variable=function_name(arguments...);  

1) variable: Nếu hàm có kiểu dữ liệu là void, không cần khai báo variable, bởi vì hàm void không có giá trị trả về.

2) function_name: Là tên hàm sẽ được gọi.

3) arguments: Truyền các tham số cho hàm.

Ví dụ gọi một hàm:

hello();//Gọi hàm không có giá trị trả về
int value=get();//Gọi hàm có giá trị trả về
int value2=add(10,20);//gọi hàm truyền 2 tham số  

Ví dụ về hàm không có giá trị trả về:

#include<stdio.h>  
void hello(){    
  cout<<"hello c programming \n";    
}    
int main(){        
hello();//gọi hàm
hello();    
hello();    
return 0;  
}    

Kết quả:

hello c programming
hello c programming
hello c programming

Ví dụ về hàm có giá trị trả về:

#include<stdio.h>  
//defining function      
int cube(int n){    
return n*n*n;    
}    
int main(){        
int result1=0,result2=0;      
    
result1=cube(2);//calling function    
result2=cube(3);      
        
cout<<result1<<endl;    
cout<<result2<<endl;    
return 0;  
}    

Kết quả:

8
27

 

Truyền tham chiếu và tham trị trong C++

Có hai cách truyền tham số cho hàm: Tham  trị và Tham chiếu (biến) :

Truyền tham chiếu là truyền địa chỉ ô nhớ của biến, do đó khi thay đổi giá trị của biến bên trong phương thức thì giá trị của biến cũng bị thay đổi bên ngoài phương thức.

Truyền tham trị là truyền giá trị của biến (không phải là địa chỉ ô nhớ), khi đó phương thức sẽ tự động tạo ra một địa chỉ ô nhớ mới để lưu trữ giá trị này, do đó nó chỉ được thay đổi trong phương thức hiện hành và giá trị của biến không bị thay đổi bên ngoài phương thức hiện hành.

 

 
Hãy xem các ví dụ sau để hiểu rõ về truyền tham chiếu và truyền giá trị trong C++.


Truyền theo tham trị

Truyền theo tham trị, giá trị được truyền cho hàm được lưu trữ cục bộ bởi tham số của hàm trong vị trí ngăn xếp của bộ nhớ.

Chúng ta tìm hiểu ví dụ truyền tham  trị:

#include<stdio.h>
using namespace std;  
void change(int num) {
    num = num + 1;
}
 
int main() {
    int x = 100;
    cout<<"Truoc khi goi phuong thuc x = "<<x<<"\n";
    change(x); // truyen tham tri vao phuong thuc
    cout<<"Sau khi goi phuong thuc x = "<<x<<"\n";
    return 0;
}

Kết quả:

Truoc khi goi phuong thuc x = 100
Sau khi goi phuong thuc x = 100

Trong ví dụ trên, giá trị của biến x không bị thay đổi bên ngoài phương thức change(), mặc dù bên trong phương thức change() chúng ta đã cố gắng thay đổi bằng cách tăng m lên 1.
 


Truyền theo tham chiếu

Truyền theo tham chiếu giá trị ban đầu của biến bị thay đổi.

Lưu ý: để hiểu về truyền tham chiếu trong C++, bạn phải có hiểu biết về con trỏ (pointer) trong C++.

Chúng ta tìm hiểu ví dụ truyền tham chiếu:

Cách 1:

#include<stdio.h>
using namespace std;  
void change(int *num) {
    *num = *num + 1;
}
 
int main() {
    int x = 100;
    cout<<"Truoc khi goi phuong thuc x = "<<x<<"\n";
    change(&x); // truyen tham tri vao phuong thuc
    cout<<"Sau khi goi phuong thuc x = "<<x<<"\n";
    return 0;
}

Cách 2:

#include<stdio.h>
using namespace std;  
void change(int &num) {
    num = num + 1;
}
 
int main() {
    int x = 100;
    cout<<"Truoc khi goi phuong thuc x = "<<x<<"\n";
    change(x); // truyen tham tri vao phuong thuc
    cout<<"Sau khi goi phuong thuc x = "<<x<<"\n";
    return 0;
}

Kết quả:

Truoc khi goi phuong thuc x = 100
Sau khi goi phuong thuc x = 101

Trong ví dụ trên, cách 1 chúng ta thêm *x trong hàm change, còn cách 2 chúng ta thêm &x. Giá trị của biến x bị thay đổi cả bên trong và bên ngoài phương thức change()


 

Hàm đệ quy trong C++

Một hàm được gọi là đệ quy nếu bên trong thân nó có một lời gọi đến chính nó. Nghe có vẻ vô lý nhỉ ? Một hàm làm sao có thể gọi nó mãi được, vì nếu như vậy sẽ sinh ra một vòng lặp vô tận. Nhưng trong thực tế, một hàm đệ quy luôn có điều kiện đừng được gọi là “điểm neo”. Khi đạt tới điểm neo, hàm sẽ không gọi chính nó nữa.

Cú pháp tổng quát:

void tenhamdequi()
{
   tenhamdequi(); /* goi chinh no */
}

int main()
{
   tenhamdequi();
}

Hàm đệ quy không thể gọi tới nó mãi, cần phải có một điểm dừng (còn gọi là điểm neo) tại một trường hợp đặc biệt, gọi là trường hợp suy biến (degenerate case).

Thành phần của một hàm đệ quy

Hàm đệ quy gồm 2 phần:

  • Phần cơ sở: Điều kiện thoát khỏi đệ quy
  • Phần đệ quy: Thân hàm có chứa lời gọi đệ quy

Thiết kế giải thuật đệ quy

Thực hiện 3 bước sau:

  • Tham số hóa bài toán
  • Phân tích trường hợp chung: Đưa bài toán về bài toán nhỏ hơn cùng loại, dần dần tiến tới trường hợp suy biến
  • Tìm trường hợp suy biến

Ưu và nhược điểm

Giải thuật đệ quy có ưu điểm là thuận lợi cho việc biểu diễn bài toán, đồng thời làm gọn chương trình. Tuy nhiên cũng có nhược điểm, đó là không tối ưu về mặt thời gian (so với sử dụng vòng lặp), gây tốn bộ nhớ.


Ví dụ tính giai thừa của N dùng đệ quy:

#include<stdio.h>  
int factorial (int n)    
{    
    if ( n < 0)    
        return -1; /*Wrong value*/    
    if (n == 0)    
        return 1; /*Terminating condition*/    
    return (n * factorial (n -1));    
}   
int main(){    
  int fact=0;    
  fact=factorial(5);    
  cout<<"\n factorial of 5 is<<fact;    
  return 0;  
}    

Kết quả:

factorial of 5 is 120

Hàm trên hoạt động như hình sau:


 

Lớp lưu trữ (Storage Classes) trong C++

Lớp lưu trữ được sử dụng để định nghĩa phạm vi và chu trình sống của biến. Có 5 lớp lưu trữ trong ngôn ngữ C++.

  1. Automatic
  2. Register
  3. Static
  4. External
  5. Mutable
Storage Class Keyword Lifetime Visibility Initial Value
Automatic auto Function Block Local Garbage
Register register Function Block Local Garbage
Mutable mutable Class Local Garbage
External extern Whole Program Global Zero
Static static Whole Program Local Zero

Lớp lưu trữ Automatic 

Đây là lớp lưu trữ mặc định cho tất cả các biến cục bộ. Từ khóa tự động được áp dụng cho tất cả các biến cục bộ một cách tự động.

{   
auto int y;  
float y = 3.45;  
}  

Lớp lưu trữ Register

Biến thanh ghi cấp phát bộ nhớ trong thanh ghi hơn RAM. Kích thước của nó bằng kích thước thanh ghi. Nó truy cập nhanh hơn các biến khác.

Bạn nên sử dụng biến đăng ký chỉ để truy cập nhanh như truy cập.

Chúng ta không thể lấy địa chỉ của biến trong thanh ghi

register int counter=0;    

Lớp lưu trữ Static

Biến tĩnh được khởi tạo chỉ một lần và tồn tại cho đến khi kết thúc chương trình. Nó giữ lại giá trị của nó giữa nhiều hàm gọi.

Biến tĩnh có giá trị mặc định 0 được trình biên dịch cung cấp.

Ví dụ:

#include <iostream>  
using namespace std;  
void func() {    
   static int i=0; //static variable    
   int j=0; //local variable    
   i++;    
   j++;    
   cout<<"i=" << i<<" and j=" <<j<<endl;    
}    
int main()  
{  
 func();    
 func();    
 func();    
}  

Kết quả:

i= 1 and j= 1
i= 2 and j= 1
i= 3 and j= 1

Lớp lưu trữ External

Biến extern được hiển thị cho tất cả các chương trình. Nó được sử dụng  hai hoặc nhiều tập tin đang chia sẻ cùng một biến hoặc một hàm.

extern int counter=0;    

 

Mảng (array) trong C++

Mảng (array) trong C++ là một tập hoặc một nhóm các phần tử (dữ liệu) có kiểu dữ liệu đồng nhất(tương tự). Các phần tử của mảng được lưu trong các vùng nhớ liên tiếp.

Mảng trong C++ là rất hữu ích nếu bạn muốn lưu trữ các phần tử tương tự. Giả sử bạn phải lưu trữ thông tin của 50 sinh viên. Bạn có thể làm điều này bằng việc sử dụng 50 biến, nhưng với cách này bạn sẽ khó quản lý vì bạn không thể truy cập giá trị của các biến này với chỉ 1 hoặc 2 dòng code.

Một cách khác để làm điều này là sử dụng mảng. Bằng cách sử dụng mảng, bạn có thể truy cập các phần tử một cách dễ dàng với chỉ vài dòng code.


Lợi thế của mảng trong C++

  1. Tối ưu hóa code.
  2. Dễ dàng để duyệt các phần tử của mảng.
  3. Dễ dàng thao tác dữ liệu
  4. Dễ dàng sắp xếp dữ liệu.
  5. Truy cập ngẫu nhiên.

Bất lợi của mảng trong C++

  1. Kích thước cố định: Kích thước của mảng trong C phải được định nghĩa tại thời điểm khai báo, chúng ta không thể lưu số phần tử vượt quá kích thước này.

Khai báo mảng (array) trong C++

Bạn có thể khai báo một mảng trong ngôn ngữ c theo cách sau.

data_type array_name[array_size];

Ví dụ:

int marks[5];

Ở đây, int là data_type, marks là array_name và 5 là array_size.


Khởi tạo mảng (array) trong C++

Chú ý rằng chỉ số mảng bắt đầu từ 0 và kết thúc bằng [SIZE-1].

marks[0] = 80; // khoi tao mang
marks[1] = 60;  
marks[2] = 70;  
marks[3] = 85;  
marks[4] = 75;  


Ví dụ mảng trong C++

#include <iostream>  
using namespace std;   
int main() {
    int i = 0;
    int marks[5];  // khai bao mang
    marks[0] = 80; // khoi tao mang
    marks[1] = 60;
    marks[2] = 70;
    marks[3] = 85;
    marks[4] = 75;
    //duyet mang
    for (i = 0; i < 5; i++) {
        cout<< marks[i]<<"\n";
    }
    return 0;
}

Kết quả:

80
60
70
85
75

Khai báo với khởi tạo mảng nặc danh trong C++

Trong lập trình C, chúng ta có thể khởi tạo mảng nặc danh khi khai báo mảng.
Ví dụ:

int marks[5] = {20, 30, 40, 50, 60};

Trong trường hợp này, mảng marks có kích thước bằng kích thước của mảng nặc danh.

Ví dụ về mảng nặc danh số nguyên:

#include <iostream>  
using namespace std;  
int main() {
    int i = 0;
    // khai bao mang nac danh
    int marks[5]={20, 30, 40, 50, 60};
    //duyet mang
    for (i = 0; i < 5; i++) {
        cout<< marks[i]<<"\n";
    }
    return 0;
}

Kết quả:

20
30
40
50
60

Ví dụ về mảng nặc danh có kiểu dữ liệu là char:

#include <iostream>  
using namespace std;  
int main() {
    int i = 0;
    // khai bao mang nac danh
    char c[5]={'h', 'o', 'c', ' ', 'C'};
    //duyet mang
    for (i = 0; i < 5; i++) {
        cout<<c[i];
    }
    return 0;
}

Kết quả:

hoc C

 

Mảng 2 chiều trong C++

Mảng 2 chiều trong C++ được biểu diễn dưới dạng hàng và cột, còn được gọi là ma trận. Nó còn được gọi là mảng các mảng hoặc danh sách các mảng.

Mảng 2 chiều, 3 chiều hoặc n chiều được gọi là mảng đa chiều trong C++.


Khai báo mảng 2 chiều trong C

data_type array_name[size1][size2];

Ví dụ:

int ma_tran [4][3];

Ở đây, 4 là số hàng và 3 là số cột.


Khởi tạo mảng 2 chiều trong C

1. Khởi tạo mang 2 chieu bằng mảng nặc danh.

#include <iostream>  
using namespace std;   
int main() {
    int i = 0;
    int j = 0;
    // khai bao mang 2 chieu bang mang nac danh
    int ma_tran[4][3] = {{ 1, 2, 3 }, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}};
    //duyet mang
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 3; j++) {
           cout<<ma_tran[i][j];
        }
        cout<<endl;
    }
    return 0;
}

Kết quả:

1 2 3
2 3 4
3 4 5
4 5 6

2. Khởi tạo mảng 2 chiều bằng cách nhập data từ bàn phím.

#include <iostream>  
using namespace std;  
int main() {
    int i, j;
     
    // khai bao mang 2 chieu bang mang nac danh
    int ma_tran[4][3];
     
    //nhap mang
    cout<<"Nhap mang: \n";
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 3; j++) {
            cout<<"Nhap a["<<i<<"]["<<j<<"]";
            cin>>ma_tran[i][j];
        }
        cout<<endl;
    } 
     
    //duyet mang
    cout<<"Ket qua: \n";
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 3; j++) {
            cout<< ma_tran[i][j];
        }
       cout<<endl;
    }
    return 0;
}

Kết quả:

Nhap mang:
Nhap a[0][0] = 1
Nhap a[0][1] = 2
Nhap a[0][2] = 3

Nhap a[1][0] = 4
Nhap a[1][1] = 5
Nhap a[1][2] = 6

Nhap a[2][0] = 7
Nhap a[2][1] = 8
Nhap a[2][2] = 9

Nhap a[3][0] = 9
Nhap a[3][1] = 6
Nhap a[3][2] = 3

Ket qua:
1 2 3
4 5 6
7 8 9
9 6 3

 

Truyền mảng dưới dạng tham số hàm trong C++

Muốn truyền một mảng một chiều dưới dạng tham số trong một hàm, bạn sẽ phải khai báo tham số chính thức của hàm theo một trong 3 cách sau và tất cả 3 phương thức khai báo này đều tạo kết quả giống nhau.

Cách 1
Các tham số chính thức là một con trỏ như sau.

return_type function(type *arrayname)  

Cách 2
Các tham số chính thức là một mảng đã định kích cỡ như sau:

return_type function(type arrayname[SIZE])  

Cách 3
Các tham số chính thức là một mảng chưa định kích cỡ như sau:

return_type function(type arrayname[])  

Ví dụ truyền tham số mảng cho hàm:

#include <iostream>  
using namespace std;  
int minarray(int arr[],int size){    
 int min=arr[0];    
 int i=0;    
 for(i=1;i<size;i++){    
 if(min>arr[i]){    
 min=arr[i];    
 }    
}//end of for    
return min;    
}//end of function    
    
int main(){      
 int i=0,min=0;    
 int numbers[]={4,5,7,3,8,9};//declaration of array    
  
 min=minarray(numbers,6);//passing array with size    
 cout<<"minimum number is"<<min;    
return 0;  
}    

Kết quả:

minimum number is 3

 

Con trỏ (pointer) trong C++

Con trỏ (Pointer) trong C++ là một biến, nó còn được gọi là locator hoặc indicator chỉ ra một địa chỉ của một giá trị.


Các biểu tượng được sử dụng trong con trỏ

Biểu tượng Tên Mô tả
& Địa chỉ của toán tử Xác định địa chỉ của một biến.
* Toán tử liên kết. Truy cập đến giá trị của địa chỉ.

Toán tử địa chỉ

Toán tử địa chỉ ‘&’ trả về địa chỉ của một biến.

#include<stdio.h>  
 
int main() {
    int number = 50;
    cout<<"Gia tri cua number la"<< number<<endl;
    cout<<"Dia chi cua number la"<< &number;
    return 0;
}

Kết quả:

Gia tri cua number la 50
Dia chi cua number la 23fe4c

Khai báo con trỏ trong C++

Con trỏ bằng ngôn ngữ C được khai báo bằng cách sử dụng dấu ‘*’.

int *a;  //con tro tro toi int
char *c; //con tro tro toi char

Ví dụ con trỏ trong C++

Ví dụ 1: sử dụng con trỏ để in ra màn hình địa chỉ và giá trị mà con trỏ trỏ đến.

Ví dụ con trỏ trong C

Như bạn thấy trong hình trên, biến con trỏ lưu trữ địa chỉ của biến số ví dụ fff4. Giá trị của biến số là 50. Nhưng địa chỉ của biến con trỏ p là aaa3.

Bằng cách sử dụng toán tử * ( toán tử liên kết ), chúng ta có thể in giá trị của biến con trỏ p.

#include <iostream>  
using namespace std;  
 
int main() {
    int number = 50;
    int *p;
    p = &number; // luu tru dia chi cua bien number
    cout<<"Dia cua con tro p la "<<*p;
    cout<<"Dia chi cua con tro p la"<<p;
    return 0;
}

Kết quả:

Dia cua con tro p la 50
Dia chi cua con tro p la 23fe44

Ví dụ 2: sử dụng con trỏ để hoán đổi 2 số mà không sử dụng biến số thứ 3.

#include <iostream>  
using namespace std;  
int main() {
    int a = 10, b = 20;
    int *p1 = &a,*p2 = &b;  
    cout<<"Truoc khi hoan doi: *p1="<<*p1<<" *p2="<<*p2;
    // hoan doi
    *p1 = *p1 + *p2;
    *p2 = *p1 - *p2;
    *p1 = *p1 - *p2;
    cout<<"Sau khi hoan doi: *p1="<<*p1<<" *p2="<<*p2;
    return 0;
}

Kết quả:

Dia cua con tro p la 50
Dia chi cua con tro p la 23fe44

Con trỏ NULL (NULL Pointer)

Một con trỏ không được gán bất kỳ giá trị nào được gọi là con trỏ NULL. Nếu không có địa chỉ nào được chỉ định trong con trỏ tại thời điểm khai báo, bạn có thể chỉ định giá trị NULL. Đó là một cách tiếp cận tốt hơn.

int *p = NULL;

Trong hầu hết các thư viện, giá trị của con trỏ là 0 (zero).
 


Sử dụng con trỏ trong C++

Có rất nhiều cách sử dụng con trỏ trong lập trình C++.

Phân bổ bộ nhớ động

Trong ngôn ngữ C++, chúng ta có thể tự động phân bổ bộ nhớ bằng các hàm new()  nơi con trỏ được sử dụng.

Mảng, hàm và cấu trúc (structure)

Con trỏ trong ngôn ngữ c được sử dụng rộng rãi trong các mảng, các hàm và cấu trúc. Nó giúp chúng ta viết ít code hơn và cải thiện hiệu suất. Ví dụ, sử dụng con trỏ trong việc truyền tham chiếu trong C.
 


 

 

Khái niệm hướng đối tượng trong C++

Lập trình hướng đối tượng (tiếng Anh: Object-oriented programming, viết tắt: OOP) là một mẫu hình lập trình dựa trên khái niệm "công nghệ đối tượng", mà trong đó, đối tượng chứa đựng các dữ liệu, trên các trường, thường được gọi là các thuộc tính; và mã nguồn, được tổ chức thành các phương thức. Phương thức giúp cho đối tượng có thể truy xuất và hiệu chỉnh các trường dữ liệu của đối tượng khác, mà đối tượng hiện tại có tương tác (đối tượng được hỗ trợ các phương thức "this" hoặc "self"). Trong lập trình hướng đối tượng, chương trình máy tính được thiết kế bằng cách tách nó ra khỏi phạm vi các đối tượng tương tác với nhau.


OOPs (Hệ thống lập trình hướng đối tượng)

Đối tượng có nghĩa là một thực thể từ thực tế như bút, ghế, bảng vv Lập trình hướng đối tượng là một phương pháp hoặc mô hình để thiết kế một chương trình bằng cách sử dụng các lớp và các đối tượng. Nó đơn giản hóa việc phát triển và bảo trì phần mềm bằng cách cung cấp một số khái niệm:

  • Đối tượng (Object)
  • Lớp (Class)
  • Kế thừa (Inheritance)
  • Đa hình (Polymorphism)
  • Trừu tượng (Abstraction)
  • Đóng gói(Encapsulation)

 

Đối tượng (Object)
Đối tượng Đối tượng là sự kết hợp giữa dữ liệu và thủ tục (còn gọi là phương thức) thao tác trên dữ liệu đó. Ta có công thức: Đối tượng = Dữ liệu + Phương thức.
Lớp (class)
Một lớp có thể được hiểu là khuôn mẫu để tạo ra các đối tượng. Trong một lớp, người ta thường dùng các biến để mô tả các thuộc tính và các hàm để mô tả các phương thức của đối tượng. Khi đã định nghĩa được lớp, ta có thể tạo ra các đối tượng từ lớp này. Để việc sử dụng được dễ dàng, thông qua hệ thống hàm tạo (constructor), người ta dùng lớp như một kiểu dữ liệu để tạo ra các đối tượng.
Kế thừa
Kế thừa Kế thừa là khả năng cho phép xây dựng một lớp mới dựa trên các cơ sở (định nghĩa) của một lớp có sẵn và có bổ sung phương thức hay thành phần dữ liệu. Khả năng sử dụng kế thừa các module chương trình rất dễ dàng mà không cần thay đổi module khác.
Đa hình
Tính đa hình sẽ xuất hiện khi có khái niệm kế thừa và có khả năng cho phép gửi cùng một thông điệp đến những đối tượng khác nhau mà không cần biết đối tượng nhận thuộc lớp dữ liệu nào, chỉ cần tập hợp các đối tượng nhận có chung hoặc gắn liền với một tính chất nào đó.
Lớp trừu tượng hay lớp cơ sở trừu tượng (abstract class)
Lớp trừu tượng là một lớp mà nó không thể thực thể hóa thành một đối tượng thực dụng được. Lớp này được thiết kế nhằm tạo ra một lớp có các đặc tính tổng quát nhưng bản thân lớp đó chưa có ý nghĩa (hay không đủ ý nghĩa) để có thể tiến hành viết mã cho việc thực thể hóa. (xem Ví dụ)

Ví dụ: Lớp "hinh" được định nghĩa không có dữ liệu nội tại và chỉ có các phương thức (hàm nội tại) "tinh_chu_vi", "tinh_dien_tich". Nhưng vì lớp hình này chưa xác định được đầy đủ các đặc tính của nó (cụ thể các biến nội tại là tọa độ các đỉnh nếu là đa giác, là đường bán kính và toạ độ tâm nếu là hình tròn,...) nên nó chỉ có thể được viết thành một lớp trừu tượng. Sau đó, người lập trình có thể tạo ra các lớp con chẳng hạn như là lớp "tam_giac", lớp "hinh_tron", lớp "tu_giac",.... Và trong các lớp con này người viết mã sẽ cung cấp các dữ liệu nội tại (như là biến nội tại r làm bán kính và hằng số nội tại Pi cho lớp "hinh_tron" và sau đó viết mã cụ thể cho các phương thức "tinh_chu_vi" và "tinh_dien_tich").

Đóng gói dữ liệu
Trong lập trình hướng đối tượng, cả chức năng và dữ liệu đều phải được đóng gói, mỗi đối tượng sẽ không thể truy cập trực tiếp vào các thành phần dữ liệu mà phải thông qua các thành phần chức năng để làm việc đó. Như vậy, đóng gói cho phép dữ liệu của đối tượng sẽ bị che đi một phần khi nhìn từ bên ngoài.


Các ưu điểm của lập trình hướng đối tượng

Những ưu điểm chính của lập trình hướng đối tượng:

- Thông qua nguyên lý kế thừa, trong quá trình mô tả các lớp có thể loại bỏ những chương trình bị lặp, dư và có thể mở rộng khả năng sử dụng các lớp mà không cần thực hiện lại.
- Đối tượng sẽ trao đổi với nhau về thiết kế và lập trình được dựng sẵn và phải được thực hiện theo quy trình nhất định chứ không phải dựa vào kinh nghiệm và kỹ thuật như trước. Điều này đảm bảo rút ngắn thời gian xây dựng hệ thống và tăng năng suất thực hiện.
- Nguyên lý đóng gói và che dấu thông tin giúp người lập trình bảo vệ lập trình an toàn hơn và không bị thay đổi bởi những lập trình khác.
- Tiếp cận các đối tượng trọng tâm để thiết kế, xây dựng mô hình chi tiết có liên quan chặt chẽ đến các dạng cài đặt.
- Những hệ thống hướng đối tượng ngày càng được mở rộng và được nâng cấp thành những hệ thống lớn hơn.
- Truyền thông và trao đổi thông tin với các đối tượng giúp cho việc mô tả giao diện trở nên đơn giản hơn với các hệ thống bên ngoài.


Lớp và Đối tượng trong lập trình C++

 Để quản lý một hoặc nhiều đối tượng, trong lập trình hướng đối tượng người ta tạo ra một khung gọi là lớp. Trong lớp nó sẽ có các biến mà biến này ta gọi là các thuộc tính (properties), và lớp nó có thể chứa các hàm mà các hàm này chúng ta gọi nó là phương thức (method).

Hình trên ta thấy có 1 class Car và 3 đối tượng Object1, Object1,Object3

Cú pháp khai báo lớp:

class <Ten lop>
{
private:
    <Khai bao cac thanh phan private>
public:
    <Khai bao cac thanh phan public>
protected:
    <Khai bao cac thanh phan protected>
};

Trong đó:

  • class : là từ khóa bắt buộc để định nghĩa một lớp đối tượng trong  C++
  • Ten_lop : là do người dùng tự định nghĩa. Ten_lop có tính chất như định nghĩa kiểu dữ liệu để sử dụng sau này. Cách đặt tên lớp theo quy tắc đặt tên biến trong ngôn ngữ C++

Qui tắc khai báo lớp:

Một lớp được định nghĩa bắt đầu với từ khóa class, theo sau là tên của 1 lớp, và cặp dấu ngoặc tròn bên trong chứa các thuộc tính (properties) và các phương thức (methods) thuộc về lớp.
Tên lớp phải bắt đầu bằng 1 ký tự hoặc dấu gạch chân (_), theo sau là bất kỳ ký tự số, chữ cái hoa thường, hay dấu gạch chân và không trùng tên với các từ khóa của PHP. Tên lớp có thể diễn giải bằng biểu thức chính quy như sau: ^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$.
Một lớp có thể chứa các hằng số, biến (hay gọi là thuộc tính) và các hàm (hay gọi là phương thức). 
Ví dụ: về 1 lớp cơ bản như sau:

class Car{
};

Lưu ý: 

  • Từ khóa class là bắt buộc để định nghĩa một lớp đối tượng trong C++. Hơn nữa, C++ có phân biệt chữ hoa chữ thường trong khai báo cho nên chữ class phải được viết bằng chữ thường. 

Ví dụ: khai báo đúng

class Car{
}

Khai báo sai 

Class Car{// Lỗi từ khóa
}
  •  Bắt buộc phải có dấu chấm phẩy “;” ở cuối định nghĩa lớp vì C++ coi định nghĩa một lớp như định nghĩa một kiểu dữ liệu, cho nên phải có dấu chấm phẩy cuối định nghĩa (tương tự định nghĩa kiểu dữ liệu kiểu cấu trúc). 
  • Để phân biệt với tên biến thông thường, ta nên (nhưng không bắt buộc) đặt tên lớp bắt đầu bằng một chữ in hoa và các tên biến bắt đầu bằng một chữ in thường. 

1. Sử dụng lớp đối tượng 
Lớp đối tượng được sử dụng khi ta khai báo các thể hiện của lớp đó. Một thể hiện của một lớp chính là một đối tượng cụ thể của lớp đó. Việc khai báo một thể hiện của một lớp được thực hiện như cú pháp khai báo một biến có kiểu lớp: 

<Tên lớp> <Tên biến lớp>; 

Trong đó: 

  • Tên lớp: là tên lớp đối tượng đã được định nghĩa trước khi khai báo biến. 
  • Tên biến lớp: là tên đối tượng cụ thể. Tên biến lớp sẽ được sử dụng như các biến thông thường trong C++, ngoại trừ việc nó có kiểu lớp đối tượng. 

Ví dụ, muốn khai báo một thể hiện (biến) của lớp Car
Car myCar; 
Sau đó, ta có thể sử dụng biến per trong chương trình như các biến thông thường: truyền tham số cho hàm, gán cho biến khác … Lưu ý: 
•    Khi khai báo biến lớp, ta không dùng lại từ khóa class nữa. Từ khóa class chỉ được sử dụng khi định nghĩa lớp mà không dùng khi khai báo biến lớp. 
Ví dụ, khai báo: 

Car myCar;   // đúng là đúng, nhưng khai báo: 
class Car myCar;  ; // Lỗi cú pháp là sai cú pháp. 

2. Các thành phần của lớp
Việc khai báo các thành phần của lớp có dạng như sau: 

class <Tên lớp>{ 
    private: 
      <Khai báo các thành phần riêng> 
    protected: 
         <Khai báo các thành phần được bảo vệ> 
    public: 
         <Khai báo các thành phần công cộng> 
};

 Trong đó:

  • private: là từ khóa chỉ tính chất của C++ để chỉ ra rằng các thành phần được khai báo trong phạm vi từ khóa này là riêng tư đối với lớp đối tượng. Các đối tượng của các lớp khác không truy nhập được các thành phần này. 
  • protected: các thành phần được khai báo trong phạm vi từ khóa này đều được bảo vệ. Qui định loại đối tượng nào được truy nhập đến các thành phần được bảo vệ 
  • public: các thành phần công cộng. Các đối tượng của các lớp khác đều có thể truy nhập đến các thành phần công cộng của một đối tượng bất kì. 

Các thành phần của lớp được chia làm hai loại: 

  1. Các thành phần chỉ dữ liệu của lớp, được gọi là thuộc tính của lớp 
  2. Các thành phần chỉ hành động của lớp, được gọi là phương thức của lớp. 

3. Thuộc tính của lớp 
Khai báo thuộc tính 
Thuộc tính của lớp là thành phần chứa dữ liệu, đặc trưng cho các tính chất của lớp. Thuộc tính của lớp được khai báo theo cú pháp sau: 

<Kiểu dữ liệu> <Tên thuộc tính>;

Trong đó: 

  •  Kiểu dữ liệu: có thể là các kiểu dữ liệu cơ bản của C++, cũng có thể là các kiểu dữ liệu phức tạp do người dùng tự định nghĩa như struct, hoặc kiểu là một lớp đã được định nghĩa trước đó. 
  •  Tên thuộc tính: là tên thuộc tính của lớp, có tính chất như một biến thông thường. Tên thuộc tính phải tuân theo quy tắc đặt tên biến của C++.

Ví dụ, khai báo: 

class Car{ 
   private: 
     int speed; 
   public: 
     string mark; 
}; 

Khai báo một lớp xe ô tô (Car), có hai thuộc tính: thuộc tính tốc độ (speed) có tính chất private, thuộc tính nhãn hiệu xe (mark) có tính chất public. 
Lưu ý: 

  • Không được khởi tạo giá trị ban đầu cho các thuộc tính ngay trong lớp. Vì các thuộc tính chỉ có giá trị khi nó gắn với một đối tượng cụ thể, là một thể hiện (biến) của lớp. 
    Ví dụ: 
    class Car{ 
      private: 
        int speed;      // đúng      
        int weight = 500; // lỗi 
    }; 
     
  •  Khả năng truy nhập thuộc tính của lớp là phụ thuộc vào thuộc tính ấy được khai báo trong phạm vi của từ khóa nào: private, protected hay public. 
  • các hàm public truy nhập (get / set) đến thuộc tính đó. Thông thường, do yêu cầu đóng gói dữ liệu của hướng đối tượng, ta nên khai báo các thuộc tính có tính chất riêng tư (ptivate). Nếu muốn các đối tượng khác truy nhập được vào các thuộc tính này, ta xây dựng

Sử dụng thuộc tính 
Thuộc tính có thể được sử dụng cho các chương trình nằm ngoài lớp thông qua tên biến lớp hoặc sử dụng ngay trong lớp bởi các phương thức của lớp. 

  • Nếu thuộc tính được dùng bên ngoài phạm vi lớp, cú pháp phải thông qua tên biến lớp (cách này chỉ sử dụng được với các biến có tính chất public): 
<Tên biến lớp>.<tên thuộc tính>; 
  •  Nếu thuộc tính được dùng bên trong lớp, cú pháp đơn giản hơn: <Tên thuộc tính>; 

Ví dụ, với định nghĩa lớp: 

class Car{ 
   private: 
     int speed;
   public: 
     string mark; 
}; 
//ta khai báo một biến lớp: 
Car myCar; 

Thì có thể sử dụng thuộc tính nhãn hiệu xe khi in ra màn hình như sau: 

cout << myCar.mark; 

Lưu ý: 
•    Khi dùng thuộc tính bên trong các phương thức của lớp, mà tên thuộc tính lại bị trùng với tên biến toàn cục (tự do) của chương trình, ta phải chỉ rõ việc dùng tên thuộc tính của lớp (mà không phải tên biến toàn cục) bằng cách dùng chỉ thị phạm vi lớp “::”
cú pháp: 

<Tên lớp>::<Tên thuộc tính>; 

4. Phương thức của lớp 
Khai báo khuôn mẫu phương thức 
Một phương thức là một thao tác thực hiện một số hành động đặc trưng của lớp đối tượng. 
Phương thức được khai báo tương tự như các hàm trong C++: 

<Kiểu trả về> <Tên phương thức>([<Các tham số>]); 

Trong đó: 

  • Kiểu trả về: là kiểu dữ liệu trả về của phương thức. Kiểu có thể là các kiểu dữ liệu cơ bản của C++, cũng có thể là kiểu do người dùng định nghĩa, hoặc kiểu lớp đã được định nghĩa. 
  • Tên phương thức: do người dùng tự đặt tên, tuân theo quy tắc đặt tên biến của C++. 
  • Các tham số: Các tham số đầu vào của phương thức, được biểu diễn bằng kiểu dữ liệu tương ứng. Các tham số được phân cách bởi dấu phẩy “,”. Các tham số là tùy chọn (Phần trong dấu ngoặc vuông “[]” là tùy chọn). 

Ví dụ, khai báo: 

class Car{ 
   private: 
        int speed;      
        string mark;
   public: 
       void show(); 
}; 

Định nghĩa một lớp Car có hai thuộc tính cục bộ là speed và mark, và khai báo một phương thức show() để mô tả đối tượng xe tương ứng.
Show() là một phương thức không cần tham số và kiểu trả về là void. 
Lưu ý: 
 Khả năng truy nhập phương thức từ bên ngoài là phụ thuộc vào phương thức được khai báo trong phạm vi của từ khóa nào: private, protected hay public. 


Định nghĩa phương thức 
Trong C++, việc cài đặt chi tiết nội dung của phương thức có thể tiến hành ngay trong phạm vi lớp hoặc bên ngoài phạm vi định nghĩa lớp. Cú pháp chỉ khác nhau ở dòng khai báo tên phương thức. 
Nếu cài đặt phương thức ngay trong phạm vi định nghĩa lớp, cú pháp là: 

<Kiểu trả về> <Tên phương thức>([<Các tham số>]){ … // Cài đặt chi tiết 
}

 Nếu cài đặt phương thức bên ngoài phạm vi định nghĩa lớp, ta phải dùng chỉ thị phạm vi “::” để chỉ ra rằng đấy là một phương thức của lớp mà không phải là một hàm tự do trong chương trình: 

<Kiểu trả về> <Tên lớp>::<Tên phương thức>([<Các tham số>]){ … // Cài đặt chi tiết 
} 

Ví dụ, nếu cài đặt phương thức show() của lớp Car ngay trong phạm vi định nghĩa lớp, ta cài đặt như sau: 

class Car{ 
  private: 
     int  speed; // Tốc độ  
     string  mark; // Nhãn hiệu 
  public: 
     void show(){     // Khai báo phương thức ngay trong lớp           
        cout << "This is a " << mark << " having a speed of " << speed << "km/h!" << endl; 
                  
         } 
}; 

Nếu muốn cài đặt bên ngoài lớp, ta cài đặt như sau: 

class Car{ 
  private: 
    int  speed; // Tốc độ  
    string  mark; // Nhãn hiệu 
  public: 
      void show();     // Giới thiệu xe 
}; 
/* Khai báo phương thức bên ngoài lớp */ 
void Car::show(){ 
         cout << "This is a " << mark << " having a speed of "<< speed << "km/h!" << endl; 
              
} 

Lưu ý: 

  • Nếu phương thức được cài đặt ngay trong lớp thì các tham số phải tường minh, nghĩa là mỗi tham số phải được biểu diễn bằng một cặp <Kiểu dữ liệu> <Tên tham số> như khi cài đặt chi tiết một hàm tự do trong chương trình. 
  • Thông thường, chỉ các phương thức ngắn (trên một dòng) là nên cài đặt ngay trong lớp. Còn lại nên cài đặt các phương thức bên ngoài lớp để chương trình được sáng sủa, rõ ràng và dễ theo dõi. 

Sử dụng phương thức 

Cũng tương tự như các thuộc tính của lớp, các phương thức cũng có thể được sử dụng bên ngoài lớp thông qua tên biến lớp, hoặc có thể được dùng ngay trong lớp bởi các phương thức khác của lớp định nghĩa nó. Nếu phương thức được dùng bên ngoài phạm vi lớp, cú pháp phải thông qua tên biến lớp (cách này chỉ sử dụng được với các phương thức có tính chất public): 

<Tên biến lớp>.<Tên phương thức>([<Các đối số>]); 

Nếu thuộc tính được dùng bên trong lớp, cú pháp đơn giản hơn: 

<Tên phương thức>([<Các đối số>]); 

Ví dụ, với định nghĩa lớp: 

class Car{ 
  private: 
     int  speed; // Tốc độ  
     string  mark; // Nhãn hiệu 
 public: 
         void show();     // Giới thiệu xe 
}; 
/* Khai báo phương thức bên ngoài lớp */ 
void Car::show(){ 
         cout << "This is a " << mark << " having a speed of "<< speed << "km/h!" << endl; 
         
} 
ta khai báo một biến lớp: 
Car myCar; 
Thì có thể sử dụng phương thức giới thiệu xe như sau: 
myCar.show();

Lưu ý: 

  •  Khi dùng phương thức bên trong các phương thức khác của lớp, mà phương thức lại bị trùng với các phương thức tự do của chương trình, ta phải chỉ rõ việc dùng phương thức của lớp (mà không phải dùng phương thức tự do) bằng cách dùng chỉ thị phạm vi lớp “::”

Cú pháp: 

<Tên lớp>::<Tên phương thức>([<Các đối số>]); 

Chương trình  cài đặt đầy đủ một lớp xe ô tô (Car) với các thuộc tính có tính chất cục bộ: 

  • Tốc độ xe (speed) 
  • Nhãn hiệu xe (mark) 
  • Giá xe (price) 

Và các phương thức có tính chất public: 

  • Khởi tạo các tham số (init)
  • Giới thiệu xe (show) 
  • Các phương thức truy nhập (get/set) các thuộc tính 

Sau đó, chương trình main sẽ sử dụng lớp Car này để định nghĩa các đối tượng cụ thể và sử dụng các phương thức của lớp này. 

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 
public:
	void	setSpeed(int);     // Gán tốc độ cho xe
	int		getSpeed();           // Lấy tốc độ xe    
	void	setMark(string);      // Gán nhãn cho xe     
	string 	getMark();           // Lấy nhãn xe      
	void	setPrice(float);      // Gán giá cho xe     
	float	getPrice();           // Lấy giá xe
	void	init(int, string, float);// Khởi tạo thông tin về xe
	void	show();               // Hiển thị thông tin về xe
};

/* Khai báo phương thức bên ngoài lớp */
void Car::setSpeed(int speedIn) { // Gán tốc độ cho xe   
	speed = speedIn;
}

int Car::getSpeed() {           // Lấy tốc độ xe       
	return speed;
}

void Car::setMark(string markIn) {     // Gán nhãn cho xe      
	mark = markIn;
}

string Car::getMark() {           // Lấy nhãn xe      
	return mark;
}
void Car::setPrice(float priceIn) { // Gán giá cho xe      
	price = priceIn;
}
float Car::getPrice() {           // Lấy giá xe    
	return price;
}
void Car::init(int speedIn, string markIn, float priceIn) {
	speed = speedIn;
	mark = markIn;
	price = priceIn;
	return;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a" << mark << "having a speed of  " << speed << "km/h and its price is $" << price << endl;
	return;
}

// Hàm main, chuong trình chính 
int main() {

	Car myCar;     // Khai báo bie       
				   // Kh?i t?o l?n th? nh?t      
	cout << "Xe thu nhat: " << endl;     
	myCar.init(100, "Ford", 3000); 
	cout << "Toc do (km/h): " << myCar.getSpeed() << endl; 
	cout << "Nhan hieu :" << myCar.getMark() << endl; 
	cout << "Gia ($):" << myCar.getPrice() << endl;

	// Thay d?i thu?c tính xe 
	cout << "Xe thu hai:" << endl;
	myCar.setSpeed(150);
	myCar.setMark("Mercedes");
	myCar.setPrice(5000);
	myCar.show();
	system("pause");
	return 0;
}

 Kết quả:

Xe thu nhat: 
Toc do (km/h): 100 
Nhan hieu: Ford 
Gia ($): 3000 
Xe thu hai: 
This is a Mercedes having a speed of 150km/h and its price is $5000 

 


5. Phạm vi truy nhập lớp 
Trong C++, có một số khái niệm về phạm vi, xếp từ bé đến lớn như sau: 

  • Phạm vi khối lệnh: Trong phạm vi giữa hai dấu giới hạn “{}” của một khối lệnh. Ví dụ các lệnh trong khối lệnh lặp while(){} sẽ có cùng phạm vi khối lệnh. 
  • Phạm vi hàm: Các lệnh trong cùng một hàm có cùng mức phạm vi hàm. 
  • Phạm vi lớp: Các thành phần của cùng một lớp có cùng phạm vi lớp với nhau: các thuộc tính và các phương thức của cùng một lớp. 
  • Phạm vi chương trình (còn gọi là phạm vi tệp): Các lớp, các hàm, các biến được khai báo và định nghĩa trong cùng một tệp chương trình thì có cùng phạm vi chương trình. 
  • Trong phạm vi truy nhập lớp, ta chỉ quan tâm đến hai phạm vi lớn nhất, đó là phạm vi lớp và phạm vi chương trình. Trong C++, phạm vi truy nhập lớp được quy định bởi các từ khóa về thuộc tính truy nhập: 
  • private: Các thành phần của lớp có thuộc tính private thì chỉ có thể được truy nhập trong phạm vi lớp.  
  • protected: Trong cùng một lớp, thuộc tính protected cũng có ảnh hưởng tương tự như thuộc tính private: các thành phần lớp có thuộc tính protected chỉ có thể được truy nhập trong phạm vi lớp. Ngoài ra nó còn có thể được truy nhập trong các lớp con khi có kế thừa 
  • public: các thành phần lớp có thuộc tính public thì có thể được truy nhập trong phạm vi chương trình, có nghĩa là nó có thể được truy nhập trong các hàm tự do, các phương thức bên trong các lớp khác… 

Ví dụ, thuộc tính price của lớp Car có tính chất private nên chỉ có thể truy nhập bởi các phương thức của lớp Car. Không thể truy nhập từ bên ngoài lớp (phạm vi chương trình), chẳng hạn trong một hàm tự do ngoài lớp Car. 

void Car::setPrice(float priceIn){  
     price = priceIn; // Đúng, vì setPrice là một phương thức của lớp Car 
} 
nhưng: 
void freeFunction(Car myCar){  
         myCar.price = 3000;// Lỗi, vì freeFunction là một hàm tự do 
                        // nằm ngoài phạm vi lớp Car 
} 
Khi đó, hàm freeFunction phải truy nhập gián tiếp đến thuộc tính price thông qua phương thức truy nhập có tính chất public như sau: 
void freeFunction(Car myCar){  
 myCar.setPrice(3000);// Đúng, vì setPrice là một phương thức của 
                        // lớp Car có thuộc tính public 
} 

Tuy nhiên, C++ cho phép một cách đặc biệt để truy nhập đến các thành phần private và protected của một lớp bằng khái niệm hàm bạn và lớp bạn của một lớp: trong các hàm bạn và lớp bạn của một lớp, có thể truy nhập đến các thành phần private và protected như bên trong phạm vi lớp đó. 


Sự khác nhau giữa lớp và đối tượng trong C++

Sự khác nhau giữa lớp và đối tượng trong C++ được thống kê trong bảng sau:

STT Đối tượng Lớp
1. Đối tượng là thể hiện của 1 lớp. Lớp là một khuân mẫu hay thiết kế để tạo ra các đối tượng.
2. Đối tượng là 1 thực thể trong thế giới thực như Bút chì, Xe đạp, … Lớp là một nhóm các đối tượng tương tự nhau.
3. Đối tượng là 1 thực thể vật lý Lớp là 1 thực thể logic
4. Đối tượng được tạo ra chủ yếu từ từ khóa new.
Ví dụ: Student s1=new Student();
Lớp được khai báo bằng việc sử dụng từ khóa class.
Ví dụ: class Student{}
5. Đối tượng có thể được tạo nhiều lần. Lớp được khai báo 1 lần duy nhất.
6. Đối tượng được cấp bộ nhớ khi nó được tạo ra. Lớp không được cấp bộ nhớ khi nó được tạo ra.
7. Chỉ có một  cách để tạo ra đối tượng trong C++ như từ khóa new Chỉ có một cách để định nghĩa lớp trong C++ sử dụng từ khoá class.

 

Hàm khởi tạo (Constructor)

Hàm khởi tạo được gọi mỗi khi khai báo một đối tượng của lớp. Ngay cả khi không được khai báo tường minh, C++ cũng gọi hàm khởi tạo ngầm định khi đối tượng được khai báo.  

Có 2 loại hàm khởi tạo

  • Hàm khởi tạo mặc định
  • Hàm khỏi tạo có tham số

Khai báo hàm khởi tạo 
Hàm khởi tạo của một lớp được khai báo tường minh theo cú pháp sau: 

class <Tên lớp>{      
   public: 
        <Tên lớp>([<Các tham số>]); // Khai báo hàm khởi tạo 
};

Ví dụ: 

class Car{ 
 int  speed;          
 string mark;           
 float price;      
 public: 
      Car(int speedIn, string markIn, float priceIn){ 
               speed = speedIn;                
               mark  = markIn              
               price = priceIn; 
    } 
};

Khai báo một hàm khởi tạo với ba tham số của lớp Car. 
Lưu ý: 
•    Hàm khởi tạo phải có tên trùng với tên của lớp 
•    Hàm khởi tạo không có giá trị trả về 
•    Hàm khởi tạo có tính chất public 
•    Có thể có nhiều hàm khởi tạo của cùng một lớp 


Sử dụng hàm khởi tạo của lớp 
Hàm khởi tạo được sử dụng khi khai báo biến lớp. Khi đó, ta có thể vừa khai báo, vừa khởi tạo giá trị các thuộc tính của đối tượng lớp theo cú pháp sau: 

<Tên lớp> <Tên đối tượng>([<Các đối số khởi tạo>]); 

Trong đó: 

  • Tên lớp: là tên kiểu lớp đã được định nghĩa 
  • Tên đối tượng: là tên biến có kiểu lớp, tuân thủ theo quy tắc đặt tên biến của C++ 
  • Các đối số khởi tạo: Là các đối số tương ứng với hàm khởi tạo của lớp tương ứng. Tương tự như việc truyền đối số khi dùng các lời gọi hàm thông thường. 

Ví dụ, nếu lớp Car có hàm khởi tạo Car(int, string, float) thì khi khai báo biến có kiểu lớp Car, ta có thể sử dụng hàm khởi tạo này như sau: 
Car myCar(100, “Ford”, 3000);

Lưu ý: 

  • Khi sử dụng hàm khởi tạo, phải truyền đúng số lượng và đúng kiểu của các tham số của các hàm khởi tạo đã được định nghĩa của lớp. 
  • Khi một lớp đã có ít nhất một hàm khởi tạo tường minh, thì không được sử dụng hàm khởi tạo ngầm định của C++. Do đó, khi đã khai báo ít nhất một hàm khởi tạo, nếu muốn khai báo biến mà không cần tham số, lớp tương ứng phải có ít nhất một hàm khởi tạo không có tham số. 

Ví dụ, nếu lớp Car chỉ có duy nhất một hàm khởi tạo như sau: 

class Car{ 
   public: 
         Car(int, string, float); 
}; 
//thì không thể khai báo một biến như sau: 
Car myCar; // Khai báo lỗi 
  • Trong trường hợp dùng hàm khởi tạo không có tham số, ta không cần phải sử dụng cặp dấu ngoặc đơn “()” sau tên biến đối tượng. Việc khai báo trở thành cách khai báo thông thường. 

Ví dụ: Định nghĩa và sử dụng lớp Car với hai hàm khởi tạo khác nhau. 

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */ 
class Car {
private:
	int  speed;             // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;            // Giá xe 
public:
	Car(); // Khởi tạo không tham số 
	Car(int, string, float);// Khởi tạo đầy đủ tham số 
	void show(); //Giới thiệu xe 
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
	// Khởi tạo không tham số    
	speed = 0; 
	mark=	"";
	price = 0;
}

// Khởi tạo có đầy đủ tham số 
Car::Car(int speedIn, string markIn, float priceIn) {
	speed = speedIn; 
	mark  =markIn;
	price = priceIn;
}

void Car::show() { 
	// Phương thức giới thiệu xe 
	cout << "This is a " << mark << "having a speed of "<< speed << "km / h and its price is $" << price << endl;
	return;
}

// Hàm main, chương trình chính 
int main(){
	
	Car myCar1;// Sử dụng hàm khởi tạo không tham số 
	Car myCar2(150, "Mercedes", 5000);// Dùng hàm khởi tạo đủ tham số 
	 // Giới thiệu xe thứ nhất      
	cout << "Xe thu nhat: " << endl; myCar1.show();
	// Giới thiệu xe thứ hai 
	cout << "Xe thu hai: " << endl; myCar2.show(); 
	system("pause");
	return 0;
}

 
Kết quả:

Xe thu nhat: 
This is a  having a speed of 0km/h and its price is $0 Xe thu hai: 
This is a Mercedes having a speed of 150km/h and its price is $5000 

Lí do là xe thứ nhất sử dụng hàm khởi tạo không có tham số nên xe không có tên, tốc độ và giá đều là mặc định (0). Trong khi đó, xe thứ hai được khởi tạo đầy đủ cả ba tham số nên thông tin giới thiệu xe được đầy đủ. 
Tuy nhiên, khi đối tượng có nhiều thuộc tính riêng, để tránh trường hợp phải định nghĩa nhiều hàm khởi tạo cho các trường hợp thiếu vắng một vài tham số khác nhau. Ta có thể sử dụng hàm khởi tạo với các giá trị khởi đầu ngầm định. Chương trình dưới đây  cho kết quả hoàn toàn giống chương trình ví dụ trên, nhưng đơn giản hơn vì chỉ cần định nghĩa một hàm khởi tạo với các tham số có giá trị ngầm định. 
 
Ví dụ:


#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */ 
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
	// Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car(int speedIn = 0, string markIn ="", float priceIn = 0);
	void show(); // Giới thiệu xe 
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speedIn, string markIn, float priceIn) {
	speed = speedIn; 
	mark = markIn;
	price = priceIn;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}

// Hàm main, chương trình chính 
int  main() {

	Car myCar1;// Các tham số nhận giá trị mặc định 

	Car myCar2(150, "Mercedes", 5000);// Dùng hàm khởi tạo đủ tham số  											
	// Giới thiệu xe thứ nhất 
	cout << "Xe thu nhat: " << endl; 
	myCar1.show();
	// Giới thiệu xe thứ hai 
	cout << "Xe thu hai: " << endl; 
	myCar2.show(); 
	system("pause");
	return 0;
}

 

Hàm hủy bỏ (Destructor)

Hàm hủy bỏ được tự động gọi đến khi mà đối tượng được giải phóng khỏi bộ nhớ. Nhiệm vụ của hàm hủy bỏ là dọn dẹp bộ nhớ trước khi đối tượng bị giải phóng. Cú pháp khai báo hàm hủy bỏ như sau: 

class <Tên lớp>{     
 public: 
        ~<Tên lớp>(); // Khai báo hàm hủy
};

Ví dụ: 

class Car{          
  int  speed;           
  string mark;          
  float price;      
public: 
   ~Car(){                
     cout << "The object has been destroyed!"<< endl;
 };
}; 

Khai báo một hàm hủy bỏ của lớp Car với thuộc tính mark có kiểu con trỏ. Hàm này sẽ giải phóng vùng nhớ đã cấp phát cho con trỏ kiểu char của thuộc tính nhãn hiệu xe. 
Lưu ý: 

  • Hàm hủy bỏ phải có tên bắt đầu bằng dấu “~”, theo sau là tên của lớp tương ứng. 
  • Hàm hủy bỏ không có giá trị trả về. 
  • Hàm hủy bỏ phải có tính chất public 
  • Mỗi lớp chỉ có nhiều nhất một hàm hủy bỏ. Trong trường hợp không khai báo tường minh hàm hủy bỏ, C++ sẽ sử dụng hàm hủy bỏ ngầm định. 
  • Nói chung, khi có ít nhất một trong các thuộc tính của lớp là con trỏ, thì nên dùng hàm hủy bỏ tường minh để giải phóng triệt để các vùng nhớ của các thuộc tính, trước khi đối tượng bị giải phóng khỏi bộ nhớ. 

Ví dụ: Định nghĩa lớp Car với một hàm khởi tạo có các tham số với giá trị mặc định và một hàm hủy bỏ tường minh.  

#include<iostream>
#include<string> 
using namespace std;
/* Định nghĩa lớp */ 
class Car {
private:
	int  speed;          // Tốc độ     
	string  mark;          // Nhãn hiệu      
	float price;          // Giá xe 
public: 						 // Khởi tạo với các giá trị ngầm điịnh cho các tham số 
	Car(int speedIn , string markIn, float priceIn );
	void  show();          // Giới thiệu xe 
	~Car();        // Hàm hủy bỏ tường minh 
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speedIn, string markIn, float priceIn) {
	speed = speedIn;      
	mark = markIn;      
	price = priceIn;
}
// Phương thức giới thiệu xe 
void Car::show() {   
	cout<< "This is a " << mark << " having a speed of "<< speed << "km / h and its price is $" << price << endl;
	return;
}

Car::~Car() {                    // Hàm hủy bỏ tường minh      
	cout << "The object has been destroyed!"<< endl;
}

// Hàm main, chương trình chính 
int main(){ 
	Car myCar(150 , "Mercedes", 5000);// Dùng hàm khởi tạo đủ tham số  	
	// Giới thiệu xe 
	cout << "Gioi thieu xe : " << endl; 
	myCar.show(); 
	system("pause");
	return 0;
}

Kết quả:

Gioi thieu xe: 
This is a Mercedes having a speed of 150km/h and its price is $5000 The object has been destroyed! 

Dòng cuối cùng là của hàm hủy bỏ, mặc dù ta không gọi hàm hủy bỏ trực tiếp, nhưng khi thoát khỏi hàm main() của chương trình chính, đối tượng myCar bị giải phóng khỏi bộ nhớ và khi đó, C++ tự động gọi đến hàm hủy bỏ mà ta đã định nghĩa tường minh. Do vậy, mà có dòng thông báo cuối cùng này. 


Con trỏ this

Mỗi đối tượng trong C ++ có quyền truy cập vào địa chỉ riêng của nó thông qua một con trỏ quan trọng được gọi là con trỏ this. Con trỏ this là một tham số ngầm định cho tất cả các hàm thành viên. Vì vậy, bên trong một hàm thành viên, điều này có thể được sử dụng để chỉ đối tượng gọi.

Hàm friend không có con trỏ this, bởi vì friend không phải là thành viên của một lớp. Chỉ các hàm thành viên mới có con trỏ này.

Tôi hiểu rằng là hơi khó để hình dung khái niệm này, vậy nên đã làm cho các bạn 1 hình vẽ.

Có thể có 3 cách sử dụng chính của từ khóa này trong C ++.

  • Có thể được sử dụng để truyền đối tượng hiện tại làm tham số cho phương thức khác.
  • Có thể được sử dụng để tham chiếu thuộc tính của lớp hiện tại.
  • Có thể được sử dụng để khai báo các chỉ mục.

Mỗi đối tượng đều có sở hữu con trỏ này.

this được sử dụng trong tất cả các lớp trong ngôn ngữ C++. Các bạn sẽ không thể tạo ra 1 biến tên là this bởi vì việc này sẽ sinh ra xung đột. Cũng vì lý do này, các bạn sẽ không thể tạo ra các biến hay hàm có tên là classnewdeletereturn, vv… vì các từ khóa này đã được giữ bởi C++ dùng vào các mục đích riêng.

Ví dụ:

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car(int speed, string mark , float price);
	void show(); // Giới thiệu xe 
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}

// Hàm main, chương trình chính 
int  main() {

	Car myCar1(120, "Toyota", 3000); // Khởi tạo đối tượng 1
	Car myCar2(150, "Mercedes", 5000);// Khởi tạo đối tượng 2											
									  
	// Giới thiệu xe thứ nhất 
	cout << "Xe thu nhat: " << endl;
	myCar1.show();
	// Giới thiệu xe thứ hai 
	cout << "Xe thu hai: " << endl;
	myCar2.show();
	system("pause");
	return 0;
}

Kết quả:

Xe thu nhat:
This is a Toyota having a speed of 120km / h and its price is $3000
Xe thu hai:
This is a Mercedes having a speed of 150km / h and its price is $5000
Press any key to continue . . .

 

Từ khóa static trong C++

Static member là những thành phần được khởi tạo và cấp phát vào 1 vùng nhớ cố định. Thông thường 1 thuộc tính chỉ có thể được sử dụng khi có 1 object đại diện được khởi tạo, tuy nhiên với static member, ta có thể sử dụng mà không cần phải khai báo 1 object nào cả.

Ví dụ:

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car(int speed, string mark, float price);
	void show(); // Giới thiệu xe 
	static int totalCar;
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
	this->totalCar++;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}
int Car::totalCar = 0;
// Hàm main, chương trình chính 
int  main() {

	Car myCar1(120, "Toyota", 3000); // Khởi tạo đối tượng 1
	Car myCar2(150, "Mercedes", 5000);// Khởi tạo đối tượng 2										
	cout << "Co tong cong " << myCar2.totalCar<<" Cars";	
	system("pause");
	return 0;
}

Kết quả:

Co tong cong 2 Cars

Phương thức tĩnh

Tất nhiên, trong đối tượng thì thành phần bao gồm cả thuộc tính và phương thức, vì vậy phương thức có cả kiểu static. Một điều lưu ý là phương thức này chỉ có thể truy cập thuộc tính static, các phương thức bên trong và ngoài hàm, cũng như hàm. Phương thức static không thể truy cập đến các thuộc tính, phương thức thông thường trong 1 object.

Ví dụ:
 

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car(int speed, string mark, float price);
	void show(); // Giới thiệu xe 
	static int totalCar;
	static int getTotal() {
		return totalCar;
	}
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
	this->totalCar++;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}
int Car::totalCar = 0;
// Hàm main, chương trình chính 
int  main() {

	Car myCar1(120, "Toyota", 3000); // Khởi tạo đối tượng 1
	Car myCar2(150, "Mercedes", 5000);// Khởi tạo đối tượng 2
	// Ta có thể sử dụng myCar1.getTotal() hoặc myCar2.getTotal() thay cho Car::getTotal()
	cout << "Co tong cong " << Car::getTotal()<<" Cars";	
	system("pause");
	return 0;
}

Kết quả:

Co tong cong 2 Cars

Kiểu cấu trúc trong C++

Kiểu dữ liệu có cấu trúc được dùng khi ta cần nhóm một số biến dữ liệu luôn đi kèm với nhau. 
Khi đó, việc xử lí trên một nhóm các biến được thực hiện như trên các biến cơ bản thông thường. 
Khai báo cấu trúc 
Trong C++, một cấu trúc do người dùng tự định nghĩa được khai báo thông qua từ khoá struct: 

struct <Tên cấu trúc>{ 
         <Kiểu dữ liệu 1> <Tên thuộc tính 1>; 
<Kiểu dữ liệu 2> <Tên thuộc tính 2>; 
         … 
<Kiểu dữ liệu n> <Tên thuộc tính n>; }; 

Trong đó: 

  • struct: là tên từ khoá để khai báo một cấu trúc, bắt buộc phải có khi định nghĩa cấu trúc. 
  • Tên cấu trúc: là tên do người dùng tự định nghĩa, tuân thủ theo quy tắc đặt tên biến trong C++. Tên này sẽ trở thành tên của kiểu dữ liệu có cấu trúc tương ứng. 
  • Thuộc tính: mỗi thuộc tính của cấu trúc được khai báo như khai báo một biến thuộc kiểu dữ liệu thông thường, gồm có kiểu dữ liệu và tên biến tương ứng. Mỗi khai báo thuộc tính phải kết thúc bằng dấu chấm phẩy “;” như một câu lệnh C++ thông thường. 

Ví dụ, để quản lí nhân viên của một công ty, khi xử lí thông tin về mỗi nhân viên, ta luôn phải xử lí các thông tin liên quan như: 

  • Tên 
  • Tuổi 
  • Chức vụ 
  • Lương 

Do đó, ta sẽ dùng cấu trúc để lưu giữ thông tin về mỗi nhân viên bằng cách định nghĩa một cấu trúc có tên là Employeee với các thuộc tính như sau: 

struct Employeee{ 
     char name; // Tên nhân viên 
     int age;      // Tuổi nhân viên 
     string role; // Chức vụ của nhân viên 
     float salary;     // Lương của nhân viên 
}; 

Lưu ý: 
Cấu trúc chỉ cần định nghĩa một lần trong chương trình và có thể được khai báo biến cấu trúc nhiều lần. Khi cấu trúc đã được định nghĩa, việc khai báo biến ở lần khác trong chương trình được thực hiện như khai báo biến thông thường: 

<Tên cấu trúc> <tên biến 1>, <tên biến 2>; 


Ví dụ, sau khi đã định nghĩa cấu trúc Employeee, muốn có biến myEmployeee, ta khai báo như sau: 

Employee myEmployeee; 

Cấu trúc lồng nhau 
Các cấu trúc có thể được định nghĩa lồng nhau khi một thuộc tính của một cấu trúc cũng cần có kiểu là một cấu trúc khác. Khi đó, việc định nghĩa cấu trúc cha được thực hiện như một cấu trúc bình thường, với khai báo về thuộc tính đó là một cấu trúc con: 

struct <Tên cấu trúc cha>{ 
         <Kiểu dữ liệu 1> <Tên thuộc tính 1>; 
         // Có kiểu cấu trúc 
         <Kiểu cấu trúc con> <Tên thuộc tính 2>;  
         … 
         <Kiểu dữ liệu n> <Tên thuộc tính n>; 
}; 

Ví dụ, với kiểu cấu trúc Employee, ta không quan tâm đến tuổi nhân viên nữa, mà quan tâm đến ngày sinh của nhân viên. Vì ngày sinh cần có các thông tin luôn đi với nhau là ngày sinh, tháng sinh, năm sinh. Do đó, ta định nghĩa một kiểu cấu trúc con cho kiểu ngày sinh: 

struct Date{ 
     int day;      int month;      int year; 
}; 

khi đó, cấu trúc Employee trở thành: 

struct Employee{ 
     char name;  // Tên nhân viên 
Date birthDay;      // Ngày sinh của nhân viên 
     string role;  // Chức vụ của nhân viên 
    float salary;    // Lương của nhân viên 
}; 

 Lưu ý: 
Trong định nghĩa các cấu trúc lồng nhau, cấu trúc con phải được định nghĩa trước cấu trúc cha để đảm bảo các kiểu dữ liệu của các thuộc tính của cấu trúc cha là tường minh tại thời điểm nó được định nghĩa. 


Định nghĩa cấu trúc với từ khoá typedef 
Để tránh phải dùng từ khoá struct mỗi khi khai báo biến cấu trúc, ta có thể dùng từ khóa typedef khi định nghĩa cấu trúc: 

typedef struct { 
         <Kiểu dữ liệu 1> <Tên thuộc tính 1>; 
         <Kiểu dữ liệu 2> <Tên thuộc tính 2>; 
         … 
         <Kiểu dữ liệu n> <Tên thuộc tính n>; 
} <Tên kiểu dữ liệu cấu trúc>; 

Trong đó: 
Tên kiểu dữ liệu cấu trúc: là tên kiểu dữ liệu của cấu trúc vừa định nghĩa. Tên này sẽ được dùng như một kiểu dữ liệu thông thường khi khai báo biến cấu trúc. 
Ví dụ, muốn có kiểu dữ liệu có cấu trúc nhân viên, có tên là Employee, ta dùng từ khoá typedef để định nghĩa cấu trúc như sau: 

typedef struct { 
     string name; // Tên nhân viên 
     int age;      // Tuổi nhân viên 
     string role; // Chức vụ của nhân viên 
     float salary;     // Lương của nhân viên 
} Employee; 

Khi đó, muốn có hai biến là myEmployee1 và myEmployee2 có kiểu cấu trúc Employee, ta chỉ cần khai báo như sau mà không cần từ khoá struct: 
Employee myEmployee1, myEmployee2; 
Trong ví dụ khai báo lồng cấu trúc Employee, dùng từ khoá typedef cho kiểu Date: 

typedef struct { 
     int day;      int month;      int year; 
} Date; cấu trúc Employee trở thành:
 
typedef struct { 
         string name; // Tên nhân viên 
Date birthDay; // Ngày sinh của nhân viên      
        string role; // Chức vụ của nhân viên 
        float salary;    // Lương của nhân viên 
} Employee; 

Lưu ý: 

  • Khi không dùng từ khoá typedef, tên cấu trúc (nằm sau từ khoá struct) được dùng để khai báo biến. Trong khi đó, khi có từ khoá typedef, tên kiểu dữ liệu cấu trúc (dòng cuối cùng trong định nghĩa) mới được dùng để khai báo biến. 
  • Khi dùng từ khoá typedef thì không thể khai báo biến đồng thời với định nghĩa cấu trúc. 

Thao tác trên cấu trúc

Các thao tác trên cấu trúc bao gồm: 

  • Khai báo và khởi tạo giá trị ban đầu cho biến cấu trúc 
  • Truy nhập đến các thuộc tính của cấu trúc 

Khởi tạo giá trị ban đầu cho cấu trúc 
Khởi tạo biến có cấu trúc đơn 
Biến cấu trúc được khai báo theo các cách sau: 

<Tên kiểu dữ liệu cấu trúc> <tên biến>; 

Ngoài ra, ta có thể khởi tạo các giá trị cho các thuộc tính của cấu trúc ngay khi khai báo bằng các cú pháp sau: 

<Tên kiểu dữ liệu cấu trúc> <tên biến> = {      
    <giá trị thuộc tính 1>, 
    <giá trị thuộc tính 2>, 
    … 
    <giá trị thuộc tính n> 
}; 

Trong đó: 
Giá trị thuộc tính: là giá trị khởi đầu cho mỗi thuộc tính, có kiểu phù hợp với kiểu dữ liệu của thuộc tính. Mỗi giá trị của thuộc tính được phân cách bằng dấu phẩy “,”. 
Ví dụ, với định nghĩa cấu trúc: 

typedef struct { 
     string name; // Tên nhân viên 
     int age;      // Tuổi nhân viên 
     string role; // Chức vụ của nhân viên 
     float salary;  // Lương của nhân viên 
} Employee; 

thì có thể khai báo và khởi tạo cho một biến như sau: 

Employee myEmployee1 = {
	"Nguyen Van A",
	27,
	"Nhan vien",
	300f
};

Khởi tạo các biến có cấu trúc lồng nhau 
Trong trường hợp các cấu trúc lồng nhau, phép khởi tạo cũng thực hiện như thông thường với phép khởi tạo cho tất cả các cấu trúc con. 
Ví dụ với khai báo cấu trúc như sau: 

typedef struct { 
     int day;      int month;      int year; 
} Date; 

và: 

typedef struct { 
         string name; // Tên nhân viên 
         Date birthDay; // Ngày sinh của nhân viên      
         string role; // Chức vụ của nhân viên 
         float salary;  // Lương của nhân viên 
} Employee; 

Thì khai báo và khởi tạo một biến có kiểu Employee có thể thực hiện như sau: 

Employee myEmployee1 = { 
         "Nguyen Van A", 
         {15, 05, 1980}, // Khởi tạo cấu trúc con 
         "Nhan vien", 
         300f 
}; 

Truy nhập đến thuộc tính của cấu trúc 
Có hai cách để truy cập vào các thành viên cấu trúc:

  1. Bởi . (thành viên hoặc toán tử chấm).
  2. Bởi ->(toán tử con trỏ cấu trúc).

Ví dụ, với một biến cấu trúc kiểu Employee đơn: 

Employee myEmployee1 = { 
         "Nguyen Van A", 
         27, 
         "Nhan vien", 
         300f 
}; 

ta có thể truy xuất như sau: 

cout << myEmployee1.name;  // hiển thị ra “Nguyen Van A” 
myEmployee1.age += 1;      // Tăng số tuổi lên 1 

Đối với kiểu cấu trúc lồng nhau, phép truy nhập đến thuộc tính được thực hiện lần lượt từ cấu trúc cha đến cấu trúc con. 
Ví dụ, với một biến cấu trúc kiểu Employee lồng nhau:  

Employee myEmployee1 = { 
         "Nguyen Van A", 
         {15, 05, 1980}, 
         "Nhan vien", 
         300f 
}; 

 ta có thể truy xuất như sau: 

cout << myEmployee1.name;       // hiển thị ra “Nguyen Van A” 
myEmployee1.birthDay.day = 16;     // Sửa lại ngày sinh thành 16 
myEmployee1.birthDay.month = 07; // Sửa lại tháng sinh thành 07 

Ví dụ về kiểu cấu trúc

Ví dụ chúng ta có lớp Rectangle có 2 thuộc tính width and height.

#include <iostream>  
using namespace std;
struct Rectangle
{
	int width, height;

};
int main(void) {
	struct Rectangle rec;
	rec.width = 8;
	rec.height = 5;
	cout << "Area of Rectangle is: " << (rec.width * rec.height) << endl;
	system("pause");
	return 0;
}

Kết quả:

Area of Rectangle is: 40

Chúng ta hãy xem một ví dụ khác về cấu trúc sử dụng hàm khởi tạo dữ liệu và phương thức để tính diện tích hình chữ nhật

#include <iostream>  
using namespace std;
struct Rectangle
{
	int width, height;
	Rectangle(int w, int h)
	{
		width = w;
		height = h;
	}
	void areaOfRectangle() {
		cout << "Area of Rectangle is: " << (width*height);
	}
};
int main(void) {
	struct Rectangle rec = Rectangle(4, 6);
	rec.areaOfRectangle();
	system("pause");

	return 0;
}

Kết quả:

Area of Rectangle is: 24

<

Kiểu Enum trong C++

Trong lập trình, đôi khi những kiểu dữ liệu không mang lại ý nghĩa phù hợp với mục đích người lập trình, hoặc tệ hơn có thể làm trở ngại quá trình lập trình. Lấy ví dụ khi ta lưu trữ các ngày trong tuần bằng một biến integer. Ta buộc phải nhớ chính xác bảy giá trị tương tứng với 7 ngày trong tuần. Điều này gây khó khăn cho việc ghi nhớ và phát triển lâu dài. Chúng ta cần một kiểu dữ liệu mới mang lại ý nghĩa rõ ràng hơn.

Cú pháp:

enum <tên enum>
{
<tên hằng 1>,
<tên hằng 2>,
...
}
  • Sau từ khóa enum là tên kiểu liệt kê.
  • Bên trong là các hằng số, mỗi một hằng số được cách nhau bằng dấu ','. Riêng hằng số cuối cùng không có dấu ','.
  • Tên hằng không được có khoảng trắng.
  • Kết thúc enum phải có dấu ';'.
  • Có thể tạo giá trị cho hằng số. Nếu hằng số đầu tiên không được gán giá trị, thì giá trị mặc định là 0.

Các ví dụ

Cách khai báo một enum

enum eDayOfWeek
{
	MONDAY,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY
};

Sử dụng enum để in ra thứ ngày trong tuần

#include <iostream>
using namespace std;

enum eDayOfWeek
{
	MONDAY,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY
};

void PrintScreen(eDayOfWeek day)
{
	switch (day)
	{
	case eDayOfWeek::MONDAY:
		cout << "Now day is a Monday" << endl;
		break;
	case eDayOfWeek::TUESDAY:
		cout << "Now day is a Tuesday" << endl;
		break;
	case eDayOfWeek::WEDNESDAY:
		cout << "Now day is a Wednesday" << endl;
		break;
	case eDayOfWeek::THURSDAY:
		cout << "Now day is a Thursday" << endl;
		break;
	case eDayOfWeek::FRIDAY:
		cout << "Now day is a Friday" << endl;
		break;
	case eDayOfWeek::SATURDAY:
		cout << "Now day is a Saturday" << endl;
		break;
	case eDayOfWeek::SUNDAY:
		cout << "Now day is a Sunday" << endl;
		break;
	default:
		break;
	}
}

int main()
{
	eDayOfWeek nowDay = eDayOfWeek::MONDAY;
	PrintScreen(nowDay);
	system("pause");
	return 0;
}

Kết quả:

Now day is a Monday

Thay vì sử dụng biến integer để lưu trữ giá trị, việc sử dụng một enum giúp chúng ta dễ dàng hiểu ý nghĩa của đoạn mã hơn. Thuận lợi cho quá trình phát triển. 


 

Hàm bạn trong C++

Nếu một hàm được định nghĩa là một hàm friend trong C ++ thì dữ liệu là proctected private của một lớp có thể được truy cập bằng hàm này.
Bằng cách sử dụng từ khóa friend để cho trình biên dịch biết hàm đã cho là một hàm của bạn.
Để truy cập dữ liệu, việc khai báo một hàm friend nên được thực hiện bên trong phần thân của một lớp bắt đầu từ khóa friend.

Có hai kiểu hàm bạn cơ bản trong C++: 

  • Một hàm tự do là hàm bạn của một lớp 
  • Một hàm thành phần (phương thức) của một lớp là bạn của một lớp khác Ngoài ra còn có một số kiểu hàm bạn mở rộng từ hai kiểu này: 
  • Một hàm là bạn của nhiều lớp 
  • Tất cả các hàm của một lớp là bạn của lớp khác (lớp bạn) 

Hàm tự do bạn của một lớp 
Một hàm bạn của một lớp được khai báo bằng từ khóa friend khi khai báo khuôn mẫu hàm trong lớp tương ứng. 

class <Tên lớp>{ 
       // Khai báo các thành phần lớp như thông thường 
       // Khai báo hàm bạn 
       friend <Kiểu trả về> <Tên hàm bạn>([<Các tham số>]);
 }; 
     
 Khi đó, định nghĩa chi tiết hàm bạn được thực hiện như định nghĩa một hàm tự do thông thường:
    <Kiểu trả về> <Tên hàm bạn>([<Các tham số>]){ 
      // Có thể truy nhập trực tiếp các thành phần private  
      // của lớp đã khai báo 
} 

Lưu ý: 

  • Mặc dù hàm bạn được khai báo khuôn mẫu hàm trong phạm vi lớp, nhưng hàm bạn tự do lại không phải là một phương thức của lớp. Nó là hàm tự do, việc định nghĩa và sử dụng hàm này hoàn toàn tương tự như các hàm tự do khác. 
  • Việc khai báo khuôn mẫu hàm bạn trong phạm vi lớp ở vị trí nào cũng được: hàm bạn không bị ảnh hưởng bởi các từ khóa private, protected hay public trong lớp. 
  • Trong hàm bạn, có thể truy nhập trực tiếp đến các thành phần private và protected của đối tượng có kiểu lớp mà nó làm bạn (truy nhập thông qua đối tượng cụ thể). 

Ví dụ minh họa việc định nghĩa một hàm bạn của lớp Car, hàm này so sánh xem hai chiếc xe, chiếc nào đắt hơn. 
 

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 
public:	    
	
	void	init(int, string, float);// Khởi tạo thông tin về xe									 
	friend void moreExpensive(Car, Car); // Khai báo hàm bạn của lớp  	
	
};

/* Khai báo phương thức bên ngoài lớp */
void Car::init(int speedIn, string markIn, float priceIn) {
	speed = speedIn;
	mark = markIn;
	price = priceIn;
	return;
}

/* Định nghĩa hàm bạn tự do */
void moreExpensive(Car car1, Car car2) {
	if (car1.price > car2.price)//Truy nhập đến các thuộc tính private  	
		cout << "xe thu nhat dat hon" << endl; 
	else if (car1.price < car2.price)
		cout << "xe thu nhat dat hon" << endl;
	else
		cout << "hai xe dat nhu nhau" << endl;  	
	return;
}

// Hàm main, chuong trình chính 
int main() {

	Car car1, car2;  	 	 	// Khai báo biến lớp 
								// Khởi tạo xe thứ nhất, thứ hai 							
	car1.init(100, "Ford", 3000); 
	car2.init(150, "Mercedes", 3500); 

								// So sánh giá hai xe 
	moreExpensive(car1, car2); // Sử dụng hàm bạn tự do return; 

	system("pause");
	return 0;
}

Kết quả:

xe thu nhat dat hon// vì xe thứ hai có giá $3500, trong khi xe thứ nhất được khởi tạo giá là $3000. 

Phương thức lớp là bạn của một lớp khác 
Trong C++, một phương thức của lớp này cũng có thể làm bạn của một lớp kia. Để khai báo một phương thức f của lớp B là bạn của lớp A và f nhận một tham số có kiểu lớp A, ta phải khai báo tuần tự như sau (trong cùng một chương trình): 

  • Khai báo khuôn mẫu lớp A, để làm tham số cho hàm f của lớp B: 
    class A; 

     

  •  Khai báo lớp B với hàm f như khai báo các lớp thông thường: 
    class B{ 
    …     // Khai báo các thành phần khác của lớp B 
       void f(A); 
    }; 

     

  •  Khai báo chi tiết lớp A với hàm f của lớp B là bạn 
    class A{ 
         …     // Khai báo các thành phần khác của lớp A     
     friend void B::f(A); 
    }; 

     

  • Định nghĩa chi tiết hàm f của lớp B: 
    void B::f(A){ 
             …     // Định nghĩa chi tiết hàm f 
    }

     

Lưu ý: 

  • Trong trường hợp này, hàm f chỉ được định nghĩa chi tiết một khi lớp A đã được định nghĩa chi tiết. Do vậy, chỉ có thể định nghĩa chi tiết hàm f ngay trong lớp A (ở bước 3) hoặc sau khi định nghĩa lớp A (ở bước 4), mà không thể định nghĩa chi tiết hàm f ngay trong lớp B (ở bước 2). 
  •  Hàm f có thể truy nhập đến các thành phần private và protected của cả hai lớp A và B. Tuy nhiên, muốn f truy nhập đến các thành phần của lớp A thì phải thông qua một đối tượng cụ thể có kiểu lớp A. 

Ví dụ: việc cài đặt và sử dụng một hàm permission() của lớp Person, là hàm bạn của lớp Car. Hàm này thực hiện việc kiểm tra xem một người có đủ quyền điều khiển xe hay không, theo luật sau: 

  • Với các loại xe thông thường, người điều khiển phải đủ 18 tuổi. 
  •  Với các loại xe có tốc độ cao hơn 150km/h, người điều khiển phải đủ 21 tuổi. 
#include<iostream> 
#include<string> 
using namespace std;
class Car;// Khai báo nguyên mẫu lớp 

/* Định nghĩa lớp Person */ 
class Person {
	private:
		string  name;  	 	// Tên 
		int  age;  	 	 	// Tuổi 

	public:
		void init(string, int);  	// Khởi tạo thông tin về người  	 	
		int permission(Car);  	// Xác định quyền điều khiển xe 
};

/* Định nghĩa lớp Car */ 
class Car {
	private:	 
		int  speed;               // Tốc độ  
		string  mark;           // Nhãn hiệu
		float price;               // Giá xe 
	public:	   		
		void	init(int, string, float);// Khởi tạo thông tin về xe
		friend int Person::permission(Car);  // Khai báo hàm bạn của lớp 
};

/* Khai báo phương thức bên ngoài lớp */ 
void Person::init(string nameIn, int ageIn) {
	name	= nameIn;  	
	age = ageIn;  	
	return;
}
void Car::init(int speedIn, string markIn, float priceIn) {
		speed = speedIn;
		mark = markIn;
		price = priceIn;
		return;
}

/* Định nghĩa hàm bạn */ 
int Person::permission(Car car) {
	if (age < 18) 	  	return 0;
	//Truy nhập thuộc tính private thông qua đối tượng car 
	if((age < 21)&&(car.speed > 150))  return 0; 
	return 1;
}

// Hàm main, chương trình chính 
int main(){ 

// Khai báo các biến lớp 
	Car car;
	Person person;
	// Khởi tạo các đối tượng 
	car.init(100, "Ford", 3000); 
	person.init("Vinh", 20);
	// Xác định quyền điều khiển xe 
	if(person.permission(car))  	// Sử dụng hàm bạn 
		cout << "Co quyen dieu khien" << endl; 
	else
		cout << "Khong co quyen dieu khien" << endl; 
	system("pause");
	return 0;
} 	 	

 Kết quả:

Co quyen dieu khien //Vì người chủ xe đã 20 tuổi và xe chỉ có tốc độ 100km/h. 

 

Lớp bạn trong C++

Khi tất cảc các phương thức của một lớp là bạn của một lớp khác, thì lớp của các phương thức đó cũng trở thành lớp bạn của lớp kia. Muốn khai báo một lớp B là lớp bạn của lớp A.

Lớp friend được xây dựng để khắc phục điểm yếu lớp dẫn xuất không thể truy cập tới các biến private của lớp cơ sở.

  1. Định nghĩa:
    • Một friend có thể là một hàm, một mẫu hàm, hoặc hàm thành viên, hoặc một lớp hoặc một mẫu lớp, trong trường hợp này, toàn bộ lớp và tất cả thành viên của nó là friend.

    • Hàm friend trong C++ của một lớp được định nghĩa bên ngoài phạm vi lớp đó, nhưng nó có quyền truy cập tất cả thành viên private và protected của lớp đó. Ngay cả khi các nguyên mẫu cho hàm friend xuất hiện trong định nghĩa lớp, thì các hàm friend không là các hàm thành viên.

  2. Tính chất:

    • Friend của một class có thể là thành viên của 1 class khác
    • Friend của 1 class có thể là thành viên của class khác hoặc tất cả các class trong cùng 1 chương trình. Như là 1 GLOBAL FRIEND
    • Friend có thể access private hoặc protected của class được khai báo là Friend.
    • Friends không phải là một thành viên vì vậy không có con trỏ "this"
    • Friend có thể khai báo ở bất cứ đâu ( public, protected or private section) trong một class.
  3. Khai báo tuần tự theo sau:

  • Khai báo khuôn mẫu lớp B: 
    class B; 

     

  • Định nghĩa lớp A, với khai báo B là lớp bạn: 
    class A{ 
         ...// Khai báo các thành phần của lớp A 
         // Khai báo lớp bạn B     
          friend class B; 
    }; 

     

  •  Định nghĩa chi tiết lớp B: 
    class B{ 
        ... // Khai báo các thành phần của lớp B 
    }; 

     

Lưu ý: 
•    Trong trường hợp này, lớp B là lớp bạn của lớp A nhưng không có nghĩa là lớp A cũng là bạn của lớp B: tất cả các phương thức của lớp B có thể truy nhập các thành phần private của lớp A (thông qua các đối tượng có kiểu lớp A) nhưng các phương thức của lớp A lại không thể truy nhập đến các thành phần private của lớp B. 
•    Muốn các phương thức của lớp A cũng truy nhập được đến các thành phần private của lớp B, thì phải khai báo thêm là lớp A cũng là lớp bạn của lớp B. 


Ví dụ về lớp friend:

class Person
{
private:
   string name;
public:
   friend void DisplayName( Person person); //hàmhiển thị tên
   void setName( string name );
};
  • Để khai báo tất cả hàm thành viên của lớp Employee là dạng friend của lớp Person, đặt một khai báo sau trong định nghĩa của lớp Person:
    class Person
    {
     friend class Employee; // Employee là friend của person
    };

    Như đã nói ở trên
    - Trong lớp dẫn xuất không cho phép truy nhập đến các thuộc tính private của lớp cơ sở.

    Vậy nếu bạn muốn tạo quan hệ giữa 2 class A và B. Sao cho B có thể truy cập các private hay protected của A nhưng bạn không muốn xây dựng các phương thức setter hoặc getter để tránh các class khác ngoài B cũng truy cập được thì làm thế nào?. Đó là truy cập có chọn lọc và từ khóa friend sẽ giúp bạn.

    Ví dụ method friend.

    class Person
    {
    private:
       string name;
    public:
     //Hàm DisplayName không phải thành viên của person mà là Friend. 
    Có thể gọi ở bất cứ đâu trong chương trình
       friend void DisplayName( Person person); 
       void setName( string name );
    };
    
    // phan dinh nghia ham setname
    void Person::setName( string name )
    {
        this->name = name;
    }
    
    // Ghi chu: DisPlayName() khong la ham thanh vien cua bat cu class nao.
    void DisPlayName( Peson person )
    {
       /* Boi vi, ham DisPlayName() la ham friend cua Person, do vay no co the
        truc tiep truy cap bat cu thanh vien nao cua class nay */
        
       cout << "Ten ban la: " << person.name<<endl;
    }
     
    // ham main cua chuong trinh
    int main( )
    {
       Person person;
     
       // set tên cho person  khong su dung ham thanh vien
       person.setName("Tran Thanh Cong");
       
       // su dung ham friend de in ra tên.
       DisPlayName( person);
     
       return 0;
    }

     

Kết quả:

Ten ban la: Tran Thanh Cong

 


 

Con trỏ đối tượng 

Con trỏ đối tượng là con trỏ trỏ đến địa chỉ của một đối tượng có kiểu lớp. Các thao tác liên quan đến con trỏ đối tượng bao gồm: 

  • Khai báo con trỏ đối tượng 
  • Cấp phát bộ nhớ cho con trỏ đối tượng 
  • Sử dụng con trỏ đối tượng 
  • Giải phóng bộ nhớ cho con trỏ đối tượng 

Khai báo con trỏ đối tượng 
Con trỏ đối tượng được khai báo tương tự như khai báo các con trỏ có kiểu thông thường: 

<Tên lớp> *<Tên con trỏ đối tượng>; 

Ví dụ, muốn khai báo một con trỏ đối tượng có kiểu của lớp Car, ta khai báo như sau: 

Car *myCar; 

Khi đó, myCar là một con trỏ đối tượng có kiểu lớp Car. 
Cấp phát bộ nhớ cho con trỏ đối tượng 
Con trỏ đối tượng cũng cần phải cấp phát bộ nhớ hoặc trỏ vào một địa chỉ của một đối tượng lớp xác định trước khi được sử dụng. Cấp phát bộ nhớ cho con trỏ đối tượng cũng bằng thao tác new: 

<Tên con trỏ đối tượng> = new <Tên lớp>([<Các đối số>]); 


Ví dụ, nếu lớp Car có hai hàm khởi tạo như sau: 

class Car{ 
  public: 
      Car(); 
      Car(int, string, float); 
}; 

thì ta có thể cấp phát bộ nhớ theo hai cách, tương ứng với hai hàm khởi tạo của lớp: 

myCar = new Car();    // Khởi tạo không tham số 
myCar = new Car(100, "Ford" 3000); // Khởi tạo đủ tham số Lưu ý: 

 

  • Các đối số truyền phải tương ứng với ít nhất một trong các hàm khởi tạo của lớp. 
  • Khi sử dụng hàm khởi tạo không có tham số, ta vẫn phải sử dụng cặp ngoặc đơn “()” trong thao tác new. 
  •  Khi lớp không có một hàm khởi tạo tường minh nào, sẽ dùng hàm khởi tạo ngầm định của C++ và cú pháp tương tự như sử dụng hàm khởi tạo tường minh không có tham số. 
  • Có thể vừa khai báo, vừa cấp phát bộ nhớ cho con trỏ đối tượng. 
     

Ví dụ:   

  Car myCar = new Car();       // Khởi tạo không tham số 

Sử dụng con trỏ đối tượng 
Con trỏ đối tượng được sử dụng qua các thao tác: 

  • Trỏ đến địa chỉ của một đối tượng cùng lớp 
  • Truy nhập đến các phương thức của lớp 

Con trỏ đối tượng có thể trỏ đến địa chỉ của một đối tượng có sẵn, cùng lớp theo cú pháp sau: 

<Tên con trỏ đối tượng> = &<Tên đối tượng có sẵn>; 

Ví dụ, ta có một con trỏ và một đối tượng của lớp Car: 

Car *ptrCar, myCar(100, "Ford",3000); 


Khi đó, có thể cho con trỏ ptrCar trỏ vào địa chỉ của đối tượng myCar như sau: 

ptrCar = &myCar; 

Khi muốn truy nhập đến các thành phần của con trỏ đối tượng, ta dùng cú pháp sau: 

<Tên con trỏ đối tượng> -> <Tên thành phần lớp>([<Các đối số>]); 


Ví dụ, đoạn chương trình sau sẽ thực hiện phương thức giới thiệu xe của lớp Car thông qua con trỏ ptrCar: 
 

Car *ptrCar = new Car(100, “Ford”,3000); ptrCar->show(); 

Lưu ý: 

  • Danh sách các đối số phải tương thích với tên phương thức tương ứng. 
  • Các quy tắc phạm vi truy nhập vẫn áp dụng trong truy nhập các thành phần lớp thông qua con trỏ. 

Giải phóng bộ nhớ cho con trỏ đối tượng 
Con trỏ đối tượng cũng được giải phóng thông qua thao tác delete: 

delete <Tên con trỏ đối tượng>; 

Ví dụ:          

Car *ptrCar = new Car();// Khai báo và cấp phát bộ nhớ 
     …                 // Sử dụng con trỏ ptrCar 
     delete ptrCar;      // Giải phóng bộ nhớ. 


Lưu ý: 

  •  Thao tác delete chỉ được dùng khi trước đó, con trỏ được cấp phát bộ nhớ qua thao tác new: 
    Car *ptrCar = new Car(); 
        delete ptrCar;      // Đúng. 
  • Nhưng không được dùng delete khi trước đó, con trỏ chỉ trỏ vào một địa chỉ của đối tượng có sẵn (tĩnh): 
    Car *ptrCar, myCar(100, "Ford", 3000); 
    ​​​​​​​ptrCar = &myCar; 
    delete ptrCar;      // Không được 

     

Ví dụ: Dùng con trỏ đối tượng có kiểu lớp là Car. 

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 
public:	
			Car(int, string, float);// Khởi tạo thông tin về xe
	void	show();               // Hiển thị thông tin về xe
};

/* Khai báo phương thức bên ngoài lớp */
 Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
	return;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a" << mark << "having a speed of  " << speed << "km/h and its price is $" << price << endl;
	return;
}

// Hàm main, chuong trình chính 
int main() {

	Car *myCar = new Car(150, "Mercedes", 5000);
	// Giới thiệu xe 
	cout << "Gioi thieu xe : " << endl; 
	myCar->show();
	// Giải phóng con trỏ 
	delete myCar; 
	system("pause");
	return 0;
}

Kết quả:

Gioi thieu xe :
This is aMercedeshaving a speed of  150km/h and its price is $5000

 

Mảng đối tượng trong C++

Mảng các đối tượng cũng có thể được khai báo và sử dụng như mảng của các biến có kiểu thông thường. 
Khai báo mảng tĩnh các đối tượng 
Mảng các đối tượng được khai báo theo cú pháp: 

<Tên lớp> <Tên biến mảng>[<Số lượng đối tượng>]; 

Ví dụ: 

Car cars[10]; 

Là khai báo một mảng có 10 đối tượng có cùng kiểu lớp Car. 
Lưu ý: 

  • Có thể khai báo mảng tĩnh các đối tượng mà chưa cần khai báo độ dài mảng, cách này thường dùng khi chưa biết chính xác độ dài mảng: Car cars[]; 
  •  Muốn khai báo được mảng tĩnh các đối tượng, lớp tương ứng phải có hàm khởi tạo không có tham số. Vì khi khai báo mảng, tương đương với khai báo một dãy các đối tượng với hàm khởi tạo không có tham số. 

Khai báo mảng động với con trỏ 
Một mảng các đối tượng cũng có thể được khai báo và cấp phát động thông qua con trỏ đối tượng như sau: 

<Tên lớp> *<Tên biến mảng động> = new <Tên lớp>[<Độ dài mảng>]; 

Ví dụ: 

Car *cars = new Car[10]; 

Sau khi được sử dụng, mảng động các đối tượng cũng cần phải giải phóng bộ nhớ: 

delete [] <Tên biến mảng động>; 

Ví dụ: 

Car *cars = new Car[10];// Khai báo và cấp phát động 
…                 // Sử dụng biến mảng động 
delete [] cars;      // Giải phóng bộ nhớ của mảng động 

Sử dụng mảng đối tượng 
Khi truy nhập vào các thành phần của một đối tượng có chỉ số xác định trong mảng đã khai báo, ta có thể sử dụng cú pháp: 

<Tên biến mảng>[<Chỉ số đối tượng>].<Tên thành phần>([<Các đối số>]); 

Ví dụ: 
Car cars[10]; cars[5].show(); sẽ thực hiện phương thức show() của đối tượng có chỉ số thứ 5 (tính từ chỉ số 0) trong mảng cars. 
Chương trình sau sẽ cài đặt một chương trình, trong đó nhập vào độ dài mảng, sau đó yêu cầu người dùng nhập thông tin về mảng các xe. Cuối cùng, chương trình sẽ tìm kiếm và hiển thị thông tin về chiếc xe có giá đắt nhất trong mảng. 

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 
public:
	void	setSpeed(int);     // Gán tốc độ cho xe
	int		getSpeed();           // Lấy tốc độ xe    
	void	setMark(string);      // Gán nhãn cho xe     
	string 	getMark();           // Lấy nhãn xe      
	void	setPrice(float);      // Gán giá cho xe     
	float	getPrice();           // Lấy giá xe
	void	init(int, string, float);// Khởi tạo thông tin về xe
	void	show();               // Hiển thị thông tin về xe
};

/* Khai báo phương thức bên ngoài lớp */
void Car::setSpeed(int speedIn) { // Gán tốc độ cho xe   
	speed = speedIn;
}

int Car::getSpeed() {           // Lấy tốc độ xe       
	return speed;
}

void Car::setMark(string markIn) {     // Gán nhãn cho xe      
	mark = markIn;
}

string Car::getMark() {           // Lấy nhãn xe      
	return mark;
}
void Car::setPrice(float priceIn) { // Gán giá cho xe      
	price = priceIn;
}
float Car::getPrice() {           // Lấy giá xe    
	return price;
}
void Car::init(int speedIn, string markIn, float priceIn) {
	speed = speedIn;
	mark = markIn;
	price = priceIn;
	return;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a " << mark 
<< " having a speed of  " << speed << "km/h and its price is $" << price << endl;
	return;
}

// Hàm main, chuong trình chính 
int main() {

	int length;  	 	 	// Chiều dài mảng  	
	float maxPrice = 0; 	 	// Giá đắt nhất 
	int index = 0;  	 	// Chỉ số của xe đắt nhất 
	Car *cars;  	 	 	// Khai báo mảng đối tượng 
							// Nhập số lượng xe, tức là chiều dài mảng  	
	cout << "So luong xe: ";  	
	cin >> length; 
	// Cấp phát bộ nhớ động cho mảng  	
	cars = new Car[length]; 
	// Khởi tạo các đối tượng trong mảng  	
	for(int i=0;i<length; i++){ 
		int speed;  	 	// (Biến tạm) tốc độ  	 	
		string mark;  	// (Biến tạm) nhãn hiệu  	 	
		float price; 	 	// (Biến tạm) giá xe  	 	
		cout << "Xe thu " << i << ": " <<endl;  	 	
		cout << "Toc do (km/h): ";  
		cin >> speed; 
		cars[i].setSpeed(speed); // Nhập tốc độ 
		cout << "Nhan hieu : "; 
		cin >> mark; 
		cars[i].setMark(mark);  	// Nhập nhãn xe 
		cout << "Gia ($): "; 
		cin >> price; 
		cars[i].setPrice(price); // Nhập giá xe 
		if (maxPrice < price) {
			maxPrice = price;  	
            index = i;
		}
	}

// Tìm xe đắt nhất 
	for(int i=0; i<length; i++)  	
		if(i == index){ 
		cars[i].show(); // Giới thiệu xe đắt nhất  	 	
		break; 
		}
	// Giải phóng bộ nhớ của mảng 
	delete [] cars; 
	system("pause");
	return 0;
}

Kết quả:

So luong xe: 3
Xe thu 0:
Toc do (km/h): 100
Nhan hieu : Toyota
Gia ($): 2000
Xe thu 1:
Toc do (km/h): 110
Nhan hieu : Honda
Gia ($): 2200
Xe thu 2:
Toc do (km/h): 120
Nhan hieu : Kia
Gia ($): 1900
This is a Honda having a speed of  110km/h and its price is $2200

 

KHÁI NIỆM KẾ THỪA 

Lập trình hướng đối tượng có hai đặc trưng cơ bản: 

  • Đóng gói dữ liệu, được thể hiện bằng cách dùng khái niệm lớp để biểu diễn đối tượng với các thuộc tính private, chỉ cho phép bên ngoài truy nhập vào thông qua các phương thức get/set. 
  • Dùng lại mã, thể hiện bằng việc thừa kế giữa các lớp. Việc thừa kế cho phép các lớp thừa kế (gọi là lớp dẫn xuất) sử dụng lại các phương thức đã được định nghĩa trong các lớp gốc (gọi là lớp cơ sở). 

 


Khai báo thừa kế 
Cú pháp khai báo một lớp kế thừa từ một lớp khác như sau: 

class <Tên lớp dẫn xuất>: <Từ khóa dẫn xuất> <Tên lớp cơ sở>{ 
…  // Khai báo các thành phần lớp 
}; 

Trong đó: 

  • Tên lớp dẫn xuất: là tên lớp được cho kế thừa từ lớp khác. Tên lớp này tuân thủ theo quy tắc đặt tên biến trong C++. 
  • Tên lớp cở sở: là tên lớp đã được định nghĩa trước đó để cho lớp khác kế thừa. Tên lớp này cũng tuân thủ theo quy tắc đặt tên biến của C++. 
  • Từ khóa dẫn xuất: là từ khóa quy định tính chất của sự kế thừa. Có ba từ khóa dẫn xuất là private, protected và public. Mục tiếp theo sẽ trình bày ý nghĩa của các từ khóa dẫn xuất này. 

Ví dụ: 

class Bus: public Car{ 
…  // Khai báo các thành phần 
}; 

 Khai báo một lớp Bus (xe buýt) kế thừa từ lớp Car (xe ô tô) với tính chất kế thừa là public. 


Tính chất dẫn xuất 
Sự kế thừa cho phép trong lớp dẫn xuất có thể sử dụng lại một số mã nguồn của các phương thức và thuộc tính đã được định nghĩa trong lớp cơ sở. Nghĩa là lớp dẫn xuất có thể truy nhập trực tiếp đến một số thành phần của lớp cơ sở. Tuy nhiên, phạm vi truy nhập từ lớp dẫn xuất đến lớp cơ sở không phải bao giờ cũng giống nhau: chúng được quy định bởi các từ khóa dẫn xuất private, protected và public. 
Dẫn xuất private 
Dẫn xuất private quy định phạm vi truy nhập như sau: 

  • Các thành phần private của lớp cơ sở thì không thể truy nhập được từ lớp dẫn xuất. 
  • Các thành phần protected của lớp cơ sở trở thành các thành phần private của lớp dẫn xuất 
  • Các thành phần public của lớp cơ sở cũng trở thành các thành phần private của lớp dẫn xuất. 
  • Phạm vi truy nhập từ bên ngoài vào lớp dẫn xuất được tuân thủ như quy tắc phạm vi lớp thông thường. 

Dẫn xuất protected 
Dẫn xuất protected quy định phạm vi truy nhập như sau: 

  • Các thành phần private của lớp cơ sở thì không thể truy nhập được từ lớp dẫn xuất. 
  • Các thành phần protected của lớp cơ sở trở thành các thành phần protected của lớp dẫn xuất 
  • Các thành phần public của lớp cơ sở cũng trở thành các thành phần protected của lớp dẫn xuất. 
  • Phạm vi truy nhập từ bên ngoài vào lớp dẫn xuất được tuân thủ như quy tắc phạm vi lớp thông thường. 

Dẫn xuất public 
Dẫn xuất public quy định phạm vi truy nhập như sau: 

  • Các thành phần private của lớp cơ sở thì không thể truy nhập được từ lớp dẫn xuất. 
  • Các thành phần protected của lớp cơ sở trở thành các thành phần protected của lớp dẫn xuất. 
  • Các thành phần public của lớp cơ sở vẫn là các thành phần public của lớp dẫn xuất. 
  • Phạm vi truy nhập từ bên ngoài vào lớp dẫn xuất được tuân thủ như quy tắc phạm vi lớp thông thường. 

Chúng ta tổng kết các kiểu truy cập khác nhau, tương ứng với ai đó có thể truy cập chúng như sau:

Truy cập public protected private
Trong cùng lớp
Lớp kế thừa Không
Bên ngoài lớp Không Không

Ví dụ: Chúng ta xây dựng lớp Car có 2 thuộc tính và 2 phương thức input() show(), lớp Bus kế thừa từ lớp Car có 1 thuộc tính lable và 2 phương thức input() show()

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
	private:
		int  speed;               // Tốc độ  
		string  mark;           // Nhãn hiệu
		float price;               // Giá xe 
	public:
		void	input();
		void	show();               // Hiển thị thông tin về xe
};
// khai báo xe Bus kế thừa từ xe
class Bus: public Car{
	private:
		string lable;	// số xe
	public:
		void	input();
		void	show();
};

void Car::input() {
	cout << "Nhap toc do ";
	cin >> speed;
	cout << "Nhap nhan hieu ";
	cin >> mark;
	cout << "Nhap gia xe ";
	cin >> price;		
}
void Car::show() {
	cout << "Toc do xe: " << speed << ", Nhan hieu: " << mark << ", Gia xe:" << price;
}
void Bus::input() {
	Car::input();
	cout << "Nhap so xe ";
	cin >> lable;
}
void Bus::show() {
	Car::show();
	cout << ", So xe: " << lable;
}
int  main() {
	Bus myBus;
	myBus.input();
	myBus.show();
	system("pause");
	return 0;
}

Kết quả:

Nhap toc do 120
Nhap nhan hieu Toyota
Nhap gia xe 2300
Nhap so xe 9999
Toc do xe: 120, Nhan hieu: Toyota, Gia xe:2300, So xe: 9999

Hàm khởi tạo trong kế thừa 
Khi khai báo một đối tượng có kiểu lớp được dẫn xuất từ một lớp cơ sở khác. Chương trình sẽ tự động gọi tới hàm khởi tạo của lớp dẫn xuất. Tuy nhiên, thứ tự được gọi sẽ bắt đầu từ hàm khởi tạo tương ứng của lớp cơ sở, sau đó đến hàm khởi tạo của lớp dẫn xuất. Do đó, thông thường, trong hàm khởi tạo của lớp dẫn xuất phải có hàm khởi tạo của lớp cơ sở. 
Cú pháp khai báo hàm khởi tạo như sau: 

<Tên hàm khởi tạo dẫn xuất>([<Các tham số>]): <Tên hàm khởi tạo cơ sở>([<Các đối số>]){ 
… // Khởi tạo các thuộc tính mới bổ sung của lớp dẫn xuất 
};  

Vì tên hàm khởi tạo là trùng với tên lớp, nên có thể viết lại thành: 

<Tên lớp dẫn xuất>([<Các tham số>]): <Tên lớp cơ sở>([<Các đối số>]){ 
… // Khởi tạo các thuộc tính mới bổ sung của lớp dẫn xuất 
};  

Ví dụ:  

Bus():Car(){ 
    …     // Khởi tạo các thuộc tính mới bổ sung của lớp Bus 
} 

 Định nghĩa một hàm khởi tạo của lớp Bus kế thừa từ lớp Car. Định nghĩa này được thược hiện trong phạm vi khai báo lớp Bus. Đây là một hàm khởi tạo không tham số, nó gọi tới hàm khởi tạo không tham số của lớp Car. 
Lưu ý: 

  • Nếu định nghĩa hàm khởi tạo bên ngoài phạm vi lớp thì phải thêm tên lớp dẫn xuất và toán tử phạm vi “::” trước tên hàm khởi tạo. 
  • Giữa tên hàm khởi tạo của lớp dẫn xuất và hàm khởi tạo của lớp cơ sở, chỉ có môt dấu hai chấm “:”, nếu là hai dấu “::” thì trở thành toán tử phạm vi lớp. 
  • Nếu không chỉ rõ hàm khởi tạo của lớp cơ sở sau dấu hai chấm “:” chương trình sẽ tự động gọi hàm khởi tạo ngầm định hoặc hàm khởi tạo không có tham số của lớp cơ sở nếu hàm đó được định nghĩa tường minh trong lớp cơ sở. 

Ví dụ, định nghĩa hàm khởi tạo: 

Bus():Car(){ 
… 
};  

Có thể thay bằng:     // Khởi tạo các thuộc tính mới bổ sung của lớp Bus 

Bus(){     // Gọi hàm khởi tạo không tham số của lớp Car 
…     // Khởi tạo các thuộc tính mới bổ sung của lớp Bus 
};  

Ví dụ: Định nghĩa lớp Car có 3 thuộc tính với  hàm khởi tạo và phương thức show(), sau đó định nghĩa lớp Bus có thêm thuộc tính label là số hiệu của tuyến xe buýt. Lớp Bus sẽ được cài đặt  phương thức show() và hàm khởi tạo tường minh, gọi đến hai hàm khởi tạo tương ứng của lớp Car. 

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car(int speed, string mark, float price);
	void show(); // Giới thiệu xe 
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}
// Khai báo lớp Bus kế thừa lớp Car
class Bus : public Car {
	private:	
		int lable;
	public:	
		Bus(int speed, string mark, float price, int lable);
		void show();
};
// Cài đặt lớp Buss
Bus::Bus(int speed, string mark, float price, int lable) :Car(speed, mark, price) {
	this->lable = lable;
}

void Bus::show() {
	Car::show();
	cout << "So xe la: " << lable;
}

// Hàm main, chương trình chính 
int  main() {

	Car c(120, "Honda civic", 20000);
	c.show();//phuong thuc cua lop car

	Bus b(100, "\Huyndai", 15000, 26);
	
	b.show();//phuong thuc cua lop bus

	cout << "\nchuyen doi kieu giua lop dan xuat va lop co so" << endl;
	c = b; // phải kế thừa public
	c.show();
	system("pause");
	return 0;
}

Kết quả:

This is a Honda civic having a speed of 120km / h and its price is $20000
This is a Huyndai having a speed of 100km / h and its price is $15000
So xe la: 26
chuyen doi kieu giua lop dan xuat va lop co so
This is a Huyndai having a speed of 100km / h and its price is $15000


Trong hàm khởi tạo của lớp Bus, muốn khởi tạo các thuộc tính của lớp Car, ta phải khởi tạo gián tiếp thông qua hàm khởi tạo của lớp Car mà không thể gán giá trị trực tiếp cho các thuộc tính speed, mark và price. Lí do là các thuộc tính này có tính chất private, nên lớp dẫn xuất không thể truy nhập trực tiếp đến chúng. 


Hàm hủy bỏ trong kế thừa 
Khi một đối tượng lớp dẫn xuất bị giải phóng khỏi bộ nhớ, thứ tự gọi các hàm hủy bỏ ngược với thứ tự gọi hàm thiết lập: gọi hàm hủy bỏ của lớp dẫn xuất trước khi gọi hàm hủy bỏ của lớp cơ sở. 
Vì mỗi lớp chỉ có nhiều nhất là một hàm hủy bỏ, nên ta không cần phải chỉ ra hàm hủy bỏ nào của lớp cơ sở sẽ được gọi sau khi hủy bỏ lớp dẫn xuất. Do vậy, hàm hủy bỏ trong lớp dẫn xuất được khai báo và định nghĩa hoàn toàn giống với các lớp thông thường: 

<Tên lớp>::~<Tên lớp>([<Các tham số>]){ 
         … // giải phóng phần bộ nhớ cấp phát cho các thuộc tính bổ sung 
}

Lưu ý: 

  • Hàm hủy bỏ của lớp dẫn xuất chỉ giải phóng phần bộ nhớ được cấp phát động cho các thuộc tính mới bổ sung trong lớp dẫn xuất, nếu có, mà không được giải phóng bộ nhớ được cấp cho các thuộc tính trong lớp cơ sở (phần này là do hàm hủy bỏ của lớp cơ sở đảm nhiệm). 
  • Không phải gọi tường minh hàm hủy bỏ của lớp cơ sở trong hàm hủy bỏ của lớp dẫn xuất. 
  • Ngay cả khi lớp dẫn xuất không định nghĩa tường minh hàm hủy bỏ (do không cần thiết) mà lớp cơ sở lại có định nghĩa tường minh. Chương trình vẫn gọi hàm hủy bỏ ngầm định của lớp dẫn xuất, sau đó vẫn gọi hàm hủy bỏ tường minh của lớp cơ sở. 

Ví dụ: cài đặt lớp Bus kế thừa từ lớp Car: lớp Car có một thuộc tính có dạng con trỏ nên cần giải phóng bằng hàm hủy bỏ tường minh. Lớp Bus có thêm một thuộc tính có dạng con trỏ là danh sách các đường phố mà xe buýt đi qua (mảng động các chuỗi kí tự *char[]) nên cũng cần giải phóng bằng hàm hủy bỏ tường minh. 


#include<string> 

/* Định nghĩa lớp Car */ 
class Car {
	char  *mark; 	 	 	// Nhãn hiệu xe
public: 
	~Car(); 	 	 	 	// Hủy bỏ tường minh 
};

Car::~Car() { 	 	 	 	 	// Hủy bỏ tường minh  	
	delete [] mark; 
}

/* Định nghĩa lớp Bus kế thừa từ lớp Car */ 
class Bus : public Car {
	char *voyage[];  	 	// Hành trình tuyến xe  	
public: 
	~Bus(); 	 	 	 	// Hủy bỏ tường minh  
};

Bus::~Bus() { 	// Hủy bỏ tường minh  	
	delete [] voyage; 
}

 

Phạm vi truy xuất trong kế thừa

Khi cài đặt kế thừa trong lập trình hướng đối tượng người ta vẫn phải quan tâm đến tính đóng gói và che giấu thông tin. Điều này ảnh hưởng đến phạm vi truy xuất các thành phần trong class.

Phạm vi truy nhập 
Mối quan hệ giữa các thành phần của lớp cơ sở và lớp dẫn xuất được quy định bởi các từ khóa dẫn xuất.

Kiểu dẫn xuất Tính chất ở lớp cơ sở Tính chất ở lớp dẫn xuất
private private protected Không truy nhập được private
  public private
protected private protected public Không truy nhập được protected protected
public private protected public Không truy nhập được protected public

Ta xét phạm vi truy nhập theo hai loại: 

  • Phạm vi truy nhập từ các hàm bạn, lớp bạn của lớp dẫn xuất 
  • Phạm vi truy nhập từ các đối tượng có kiểu lớp dẫn xuất 

Truy nhập từ các hàm bạn và lớp bạn của lớp dẫn xuất 

Nhìn vào bảng tổng kết trên, phạm vi truy nhập của hàm bạn, lớp bạn của lớp dẫn xuất vào lớp cơ sở như sau: 

  • Với dẫn xuất private, hàm bạn có thể truy nhập được các thành phần protected và public của lớp cơ sở vì chúng trở thành các thành phần private của lớp dẫn xuất, có thể truy nhập được từ hàm bạn. 
  • Với dẫn xuất protected, hàm bạn cũng có thể truy nhập được các thành phần protected và public của lớp cơ sở vì chúng trở thành các thành phần protected của lớp dẫn xuất, có thể truy nhập được từ hàm bạn. 
  • Với dẫn xuất public, hàm bạn cũng có thể truy nhập được các thành phần protected và public của lớp cơ sở vì chúng trở thành các thành phần protected và public của lớp dẫn xuất, có thể truy nhập được từ hàm bạn. 
  • Đối với cả ba loại dẫn xuất, hàm bạn đều không truy nhập được các thành phần private của lớp cơ sở, vì các thành phần này cũng không truy nhập được từ lớp dẫn xuất. 
Kiểu dẫn xuất Tính chất ở lớp cơ sở Tính chất ở lớp dẫn xuất Truy nhập từ hàm bạn của lớp dẫn xuất Truy nhập từ đối tượng của lớp dẫn xuất
private private protected public --- private private --- ok ok --- --- ---
protected private protected public --- protected protected --- ok ok --- --- ---
public private protected public --- protected public --- ok ok --- --- ok

Sử dụng các thành phần của lớp cơ sở từ lớp dẫn xuất 

Từ bảng tổng kết phạm vi truy nhập, ta thấy rằng chỉ có dẫn xuất theo kiểu public thì đối tượng của lớp dẫn xuất mới có thể truy nhập đến các thành phần (thuộc loại public) của lớp cơ sở. Khi đó, việc gọi đến các thành phần của lớp cơ sở cũng tương tự như gọi các thành phần lớp thông thường: 

  •  Đối với biến đối tượng thông thường: 
    <Tên đối tượng>.<Tên thành phần>([Các đối số]); 
  •  Đối với con trỏ đối tượng: 
    <Tên đối tượng>-><Tên thành phần>([Các đối số]); 

Lưu ý: 

  • Cách gọi hàm thành phần này được áp dụng khi trong lớp dẫn xuất, ta không định nghĩa lại các hàm thành phần của lớp cơ sở. 

Ví dụ: việc sử dụng các thành phần lớp cơ sở từ đối tượng lớp dẫn xuất: lớp Bus kế thừa từ lớp Car. Lớp Bus có định nghĩa bổ sung một số phương thức và thuộc tính mới. 
Khi đó, đối tượng của lớp Bus có thể gọi các hàm public của lớp Bus cũng như của lớp Car. 

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 
public:
	Car(int speed, string mark, float price);
	void	setSpeed(int);     // Gán tốc độ cho xe
	int		getSpeed();           // Lấy tốc độ xe    
	void	setMark(string);      // Gán nhãn cho xe     
	string 	getMark();           // Lấy nhãn xe      
	void	setPrice(float);      // Gán giá cho xe     
	float	getPrice();           // Lấy giá xe	
	void	show();               // Hiển thị thông tin về xe
};

/* Khai báo phương thức bên ngoài lớp */
void Car::setSpeed(int speedIn) { // Gán tốc độ cho xe   
	speed = speedIn;
}

int Car::getSpeed() {           // Lấy tốc độ xe       
	return speed;
}

void Car::setMark(string markIn) {     // Gán nhãn cho xe      
	mark = markIn;
}

string Car::getMark() {           // Lấy nhãn xe      
	return mark;
}
void Car::setPrice(float priceIn) { // Gán giá cho xe      
	price = priceIn;
}
float Car::getPrice() {           // Lấy giá xe    
	return price;
}
/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a" << mark << "having a speed of  " << speed << "km/h and its price is $" << price << endl;
	return;
}
/* Định nghĩa lớp Bus kế thừa từ lớp Car */
class Bus : public Car {
	int label;  	 	// Số hiệu tuyến xe  	
public: 
						// Khởi tạo đủ tham số  
	Bus(int speed = 0, string mark = "", float price = 0, int lable = 0);  	 	
	void setLabel(int); 	// Gán số hiệu tuyến xe  	 	
	int getLabel();  	// Đọc số hiệu tuyến xe 
};

// Cài đặt lớp Bus
Bus::Bus(int speed, string mark, float price, int label) :Car(speed, mark, price) {
	this->label = label;
}

void Bus::setLabel(int label) { // Gán số hiệu tuyến xe  	
	this->label = label;
}

int Bus::getLabel() {  	 	// Đọc số hiệu tuyến xe  	
	return this->label; 
}

// Hàm main, chuong trình chính 
int main() {

	Bus myBus;  	 	 	// Biến đối tượng của lớp Bus 
	int speedIn, labelIn; 
	float priceIn; string markIn; 

	// Nhập giá trị cho các thuộc tính
	cout << "Toc do xe bus:";  
	cin >> speedIn; 
	cout << "Nhan hieu xe bus : ";  
	cin >> markIn; 
	cout << "Gia xe bus : ";  
	cin >> priceIn;
	cout << "So hieu tuyen xe bus : ";  
	cin >> labelIn;

	myBus.setSpeed(speedIn); 	// Phương thức của lớp Car 
	myBus.setMark(markIn);  	// Phương thức của lớp Car 
	myBus.setPrice(priceIn); 	// Phương thức của lớp Car 
	myBus.setLabel(labelIn); 	// Phương thức của lớp Bus 
	myBus.show(); 	// Phương thức của lớp Car 
	system("pause");
	return 0;
} 	

Đối tượng myBus có kiểu lớp Bus, là lớp dẫn xuất của lớp cơ sở Car, có thể sử dụng các phương thức của lớp Car và lớp Bus một cách bình đẳng. Khi đó, lệnh myBus.show() sẽ gọi đến phương thức show() của lớp Car, do vậy, chương trình trên sẽ in ra màn hình kết quả như sau 

Kết quả:

Toc do xe bus:120
Nhan hieu xe bus : Toyota
Gia xe bus : 2000
So hieu tuyen xe bus : 9999
This is a Toyota having a speed of  120km/h and its price is $2000

Trong dòng giới thiệu xe bus (vì ta đang dùng đối tượng myBus của lớp Bus), không có giới thiệu số hiệu tuyến xe. Lí do là vì ta đang dùng hàm show của lớp Car. Muốn có thêm phần giới thiệu về số hiệu tuyến xe buýt, ta phải định nghĩa lại hàm show trong lớp Bus.


 

Chuyển đổi kiểu giữa lớp cơ sở và lớp dẫn xuất 

Về mặt dữ liệu, một lớp dẫn xuất bao giờ cũng chứa toàn bộ dữ liệu của lớp cơ sở: Ta luôn tìm thấy lớp cơ sở trong lớp dẫn xuất, nhưng không phải bao giờ cũng tìm thấy lớp dẫn xuất trong lớp cơ sở. Do vậy: 

  •  Có thể gán một đối tượng lớp dẫn xuất cho một đối tượng lớp cơ sở: 
    <Đối tượng lớp cơ sở> = <Đối tượng lớp dẫn xuất>; // Đúng 
  • Nhưng không thể gán một đối tượng lớp cơ sở cho một đối tượng lớp dẫn xuất: 
    <Đối tượng lớp dẫn xuất> = <Đối tượng lớp cơ sở>; // Không được 

     

Ví dụ, ta có lớp Bus kế thừa từ lớp Car và: Bus myBus; 

Car myCar; khi đó, phép gán: 
myCar = myBus;  // đúng thì chấp nhận được, nhưng phép gán: 
myBus = myCar; // không được thì không chấp nhận được. 

Lưu ý: 

  •  Nguyên tắc chuyển kiểu này cũng đúng với các phép gán con trỏ: một con trỏ đối tượng lớp cơ sở có thể trỏ đến địa chỉ của một đối tượng lớp dẫn xuất. Nhưng một con trỏ đối tượng lớp dẫn xuất không thể trỏ đến địa chỉ một đối tượng lớp cơ sở. 
  • Nguyên tắc chuyển kiểu này cũng đúng với truyền đối số cho hàm: có thể truyền một đối tượng lớp dẫn xuất vào vị trí của tham số có kiểu lớp cơ sở. Nhưng không thể truyền một đối tượng lớp cơ sở vào vị trí một tham số có kiểu lớp dẫn xuất. 

Ví dụ minh họa việc chuyển kiểu giữa các đối tượng của lớp cơ sở và lớp dẫn xuất.  

#include<iostream>
#include<string>

using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 
public:
	Car(int speed, string mark, float price);
	void	setSpeed(int);     // Gán tốc độ cho xe
	int		getSpeed();           // Lấy tốc độ xe    
	void	setMark(string);      // Gán nhãn cho xe     
	string 	getMark();           // Lấy nhãn xe      
	void	setPrice(float);      // Gán giá cho xe     
	float	getPrice();           // Lấy giá xe	
	void	show();               // Hiển thị thông tin về xe
};

/* Khai báo phương thức bên ngoài lớp */
void Car::setSpeed(int speedIn) { // Gán tốc độ cho xe   
	speed = speedIn;
}

int Car::getSpeed() {           // Lấy tốc độ xe       
	return speed;
}

void Car::setMark(string markIn) {     // Gán nhãn cho xe      
	mark = markIn;
}

string Car::getMark() {           // Lấy nhãn xe      
	return mark;
}
void Car::setPrice(float priceIn) { // Gán giá cho xe      
	price = priceIn;
}
float Car::getPrice() {           // Lấy giá xe    
	return price;
}
/* Khai báo phương thức bên ngoài lớp */
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a " << mark << " having a speed of  " << speed << "km/h and its price is $" << price << endl;
	return;
}
/* Định nghĩa lớp Bus kế thừa từ lớp Car */
class Bus : public Car {
	int label;  	 	// Số hiệu tuyến xe  	
public:
	// Khởi tạo đủ tham số  
	Bus(int speed = 0, string mark = "", float price = 0, int lable = 0);
	void setLabel(int); 	// Gán số hiệu tuyến xe  	 	
	int getLabel();  	// Đọc số hiệu tuyến xe 
	void show();
};

// Cài đặt lớp Bus
Bus::Bus(int speed, string mark, float price, int label) :Car(speed, mark, price) {
	this->label = label;
}
// Định nghĩa nạp chồng phương thức 
void Bus::show() {  	 	 	// Giới thiệu xe bus 
	cout << "This is a bus of type " << getMark() << ", on the line "
		<< label << ", having a speed of " << getSpeed()
		<< "km / h and its price is $" << getPrice() << endl;
	return;
}

void Bus::setLabel(int label) { // Gán số hiệu tuyến xe  	
	this->label = label;
}

int Bus::getLabel() {  	 	// Đọc số hiệu tuyến xe  	
	return this->label;
}

// Hàm main, chuong trình chính 
int main() {
	Car myCar(100, "Ford", 3000); // Biến đối tượng lớp Car 
	Bus myBus(80, "Mercedes", 5000, 27);// Biến đối tượng lớp Bus 

	cout << "Gioi thieu xe o to lan 1:" << endl; 
	myCar.show();
	cout << "Gioi thieu xe o to lan 2:" << endl; 
	myCar = myBus; 
	myCar.show();
	cout << "Gioi thieu xe bus : " << endl; 
	myBus.show();
	system("pause");
	return 0;
}

Kết quả:

his is a Ford having a speed of  100km/h and its price is $3000
Gioi thieu xe o to lan 2:
This is a Mercedes having a speed of  80km/h and its price is $5000
Gioi thieu xe bus :
This is a bus of type Mercedes, on the line 27, having a speed of 80km / h and its price is $5000

Ở thông báo thứ nhất, đối tượng myCar gọi phương thức show() của lớp Car với các dữ liệu được khởi đầu cho myCar: (100, “Ford”, 3000). Ở thông báo thứ hai, myCar gọi phương thức show() của lớp Car, nhưng với dữ liệu vừa được gán từ đối tượng myBus: (80, “Mercedes”, 5000). Ở thông báo thứ ba, myBus gọi phương thức show() của lớp Bus với các dữ liệu của myBus: (80, “Mercedes”, 5000, 27). 


 

Đa kế thừa

C++ cho phép đa kế thừa, tức là một lớp có thể được dẫn xuất từ nhiều lớp cơ sở khác nhau, với những kiểu dẫn xuất khác nhau. 


Khai báo đa kế thừa 
Đa kế thừa được khai báo theo cú pháp: 

class <Tên lớp dẫn xuất>: <Từ khoá dẫn xuất> <Tên lớp cơ sở 1>, 
                   <Từ khoá dẫn xuất> <Tên lớp cơ sở 2>, 
                   … 
                   <Từ khoá dẫn xuất> <Tên lớp cơ sở n>{ 
         …  // Khai báo thêm các thành phần lớp dẫn xuất 
};

Ví dụ: 
 

class Bus: public Car, public PublicTransport{      …     // Khai báo các thành phần bổ sung 
}; 

Khai báo lớp Bus (xe buýt) kế thừa từ hai lớp xe Car (ô tô) và PublicTransport (phương tiện giao thông công cộng) theo cùng một kiểu dẫn xuất là public. 
Lưu ý: 

  • Trong đa kế thừa, mỗi lớp cơ sở được phân cách nhau bởi dấu phẩy “,”. 
  • Mỗi lớp cơ sở cơ thể có một kiểu dẫn xuất bởi một từ khoá dẫn xuất khác nhau. 
  • Nguyên tắc truy nhập vào các thành phần lớp cơ sở cũng hoàn toàn tương tự như trong kế thừa đơn. 

Hàm khởi tạo trong đa kế thừa 
Hàm khởi tạo trong đa kế thừa được khai báo tương tự như trong đơn kế thừa, ngoại trừ việc phải sắp xếp thứ tự gọi tới hàm khởi tạo của các lớp cơ sở: thông thường, thứ tự gọi đến hàm khởi tạo của các lớp cơ sở nên tuân theo thứ tự dẫn xuất từ các lớp cơ sở trong đa kế thừa. 
Ví dụ: Định nghĩa hàm khởi tạo tường minh trong đa kế thừa: thứ tự gọi hàm khởi tạo của các lớp cơ sở trong hàm khởi tạo của lớp Bus là tương tự thứ tự dẫn xuất: hàm khởi tạo của lớp Car trước hàm khởi tạo của lớp PublicTransport. 

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car();
	Car(int speed, string mark, float price);
	
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
	this->speed = 0;
	this->mark = "";
	this->price = 0;

}
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}
/* Định nghĩa lớp PublicTransport */ 
class PublicTransport {
	float ticket; 	 	 	// Giá vé phương tiện 
public: 
	PublicTransport(); 	 	// Khởi tạo không tham số 
	PublicTransport(float);  	// Khởi tạo đủ tham số 
};

PublicTransport::PublicTransport() {  	// Khởi tạo không tham số  	
	this->ticket = 0; 
}

// Khởi tạo đủ tham số 
PublicTransport::PublicTransport(float ticket) {
	this->ticket = ticket;
}
/* Định nghĩa lớp Bus kế thừa từ lớp Car và PublicTransport */
class Bus : public Car, public PublicTransport { // Thứ tự khai báo  	 
	int label;  	 	 	// Số hiệu tuyến xe  	
public: 
	Bus(); 	 	 	 	// Khởi tạo không tham số  
	Bus(int, string, float, float, int);// Khởi tạo đủ tham số 
};

// Khởi tạo không tham số 
Bus::Bus() : Car(), PublicTransport() { 	 	// Theo thứ tự dẫn xuất  	
	label = 0; 
}

// Khởi tạo đủ tham số 
Bus::Bus(int speed, string mark, float price, float ticket, int label) :
	Car(speed, mark, price), PublicTransport(ticket) { // Theo thứ tự dẫn xuất 
	this->label = label;
}

Lưu ý: 

  • Trong trường hợp dùng hàm khởi tạo ngầm định hoặc không có tham số, ta có thể không cần gọi tường minh các hàm khởi tạo của các lớp cơ sở, trình biên dịch sẽ tự động gọi tới chúng theo đúng thứ tự dẫn xuất 

Ví dụ, trong chương trình trên hai cách định nghĩa hàm khởi tạo không tham số của lớp Bus sau là tương đương: 

Bus::Bus(): Car(), Transport(){// Theo thứ tự dẫn xuất      
label = 0; 
}

là tương đương với: 

Bus::Bus(){                // Theo thứ tự dẫn xuất ngầm định      
  label = 0; 
} 

Hàm huỷ bỏ trong đa kế thừa 
Vì hàm huỷ bỏ là duy nhất của mỗi lớp, hơn nữa hàm huỷ bỏ của lớp cơ sở sẽ được tự động gọi đến khi giải phóng đối tượng của lớp dẫn xuất. Cho nên hàm huỷ bỏ trong đa kế thừa hoàn toàn tương tự hàm huỷ bỏ trong đơn kế thừa: 

  • Hàm huỷ bỏ của lớp dẫn xuất chỉ giải phóng bộ nhớ cho các thành phần bổ sung, nếu có, của lớp dẫn xuất. 
  • Hàm huỷ bỏ của lớp dẫn xuất sẽ được gọi đến sớm nhất. Sau đó các hàm huỷ bỏ của các lớp cơ sở sẽ được gọi đến.  
  • Quá trình này được trình biên dịch thực hiện tự động. 

Truy nhập các thành phần lớp trong đa kế thừa 
Việc truy nhập đến các thành phần của các lớp trong đa kế thừa được dựa trên các nguyên tắc sau: 

  • Việc truy nhập từ đối tượng lớp dẫn xuất đến các thành phần của mỗi lớp cơ sở được tuân theo quy tắc phạm vi tương tự như trong đơn kế thừa. 
  • Trong trường hợp các lớp cơ sở đều có các thành phần cùng tên, việc truy xuất đến thành phần của lớp nào phải được chỉ rõ bằng toán tử phạm vi: “<Tên lớp>::” đối với thành phần lớp cơ sở đó. 

Ví dụ, ta định nghĩa lớp Bus kế thừa từ hai lớp cơ sở: Car và PublicTransport. Nhưng cả ba lớp này đều định nghĩa một phương thức show() để tự giới thiệu: 

class Car{  
   public: 
          void show(); 
}; 
class PublicTransport{     
   public: 
          void show(); 
}; 
class Bus: public Car, public PublicTransport{     
   public: 
          void show(); 
}; 

Khi đó, khai báo:  

Bus myBus; và lời gọi hàm: 
myBus.show();               // Gọi đến hàm của lớp Bus 
myBus.Car::show();          // Gọi đến hàm của lớp Car 
myBus.PublicTransport::show();// Gọi đến hàm của lớp PublicTransport 
 

Ví dụ về việc truy nhập đến các thành phần trùng nhau trong các lớp cơ sở và được định nghĩa lại trong lớp dẫn xuất. 
 

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car();
	Car(int speed, string mark, float price);
	void show(); 	 	 	// Giới thiệu  
	float getSpeed(){
		return speed;
	};  	
	string getMark(){
		return mark;
	};  	
	float getPrice(){
		return price;
	}; 
	
};

/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
	this->speed = 0;
	this->mark = "";
	this->price = 0;

}
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a " << mark << " having a speed of  " << speed << "km/h and its price is $" << price << endl;
}
/* Định nghĩa lớp PublicTransport */ 
class PublicTransport {
	float ticket; 	 	 	// Giá vé phương tiện 
public: 
	PublicTransport(); 	 	// Khởi tạo không tham số 
	PublicTransport(float);  	// Khởi tạo đủ tham số 
	void show(); 	 	 	// Giới thiệu 
	float getTicket(){
		return ticket;
	}; 
};

PublicTransport::PublicTransport() {  	// Khởi tạo không tham số  	
	this->ticket = 0; 
}

// Khởi tạo đủ tham số 
PublicTransport::PublicTransport(float ticket) {
	this->ticket = ticket;
}
// Giới thiệu 
void PublicTransport::show() {
	cout << "This public transport had a ticket of $"<< ticket << endl;  	
}

/* Định nghĩa lớp Bus kế thừa từ lớp Car và PublicTransport */
class Bus : public Car, public PublicTransport { // Thứ tự khai báo  	 
	int label;  	 	 	// Số hiệu tuyến xe  	
public: 
	Bus(); 	 	 	 	// Khởi tạo không tham số  
	Bus(int, string, float, float, int);// Khởi tạo đủ tham số 
	void show();
};

// Khởi tạo không tham số 
Bus::Bus() : Car(), PublicTransport() { 	 	// Theo thứ tự dẫn xuất  	
	label = 0; 
}

// Khởi tạo đủ tham số 
Bus::Bus(int speed, string mark, float price, float ticket, int label) :
	Car(speed, mark, price), PublicTransport(ticket) { // Theo thứ tự dẫn xuất 
	this->label = label;
}
// Giới thiệu
void Bus::show(){ 
	cout << "This is a bus on the line "<< label 
	<< ", its speed is " << getSpeed()
	<< "km / h, mark is " << getMark()
	<< ", price is $" << getPrice()
	<< " and ticket is " << getTicket() << endl;	
}
int main() {
	
	Bus myBus(100, "Mercedes", 3000, 1.5, 27);

	myBus.Car::show(); 	 	 	// Hàm của lớp Car 
	myBus.PublicTransport::show(); 	// Hàm của lớp PublicTransport 
	myBus.show(); 	 	 	 	// Hàm của lớp Bus 
	system("pause");

	return 0; 
}

Kết quả:

This is a Mercedes having a speed of  100km/h and its price is $3000
This public transport had a ticket of $1.5
This is a bus on the line 27, its speed is 100km / h, mark is Mercedes, price is $3000 and ticket is 1.5


Dòng thứ nhất là kết quả của phương thức show() của lớp Car, dòng thứ hai, tương ứng là kết quả phương thức show() của lớp PublicTransport, dòng thứ ba là kết quả phương thức show() của lớp Bus. 

Lớp cơ sở trừu tượng

Sự cho phép đa kế thừa trong C++ dẫn đến một số hậu quả xấu, đó là sự đụng độ giữa các thành phần của các lớp cơ sở, khi có ít nhất hai lớp cơ sở lại cùng được kế thừa từ một lớp cơ sở khác. 
Xét trường hợp: 

  • Lớp Bus kế thừa từ lớp Car và lớp PublicTransport. 
  • Nhưng lớp Car và lớp PublicTransport lại cùng được thừa kế từ lớp Engine (động cơ).
    Lớp Engine có một thuộc tính là power (công suất của động cơ). 

Khi đó, nảy sinh một số vấn đề như sau: 

  • Các thành phần dữ liệu của lớp Engine bị lặp lại trong lớp Bus hai lần: một lần do kế thừa theo đường Bus::Car::Engine, một lần theo đường Bus::PublicTransport::Engine. Điều này là không an toàn. 
  • Khi khai báo một đối tượng của lớp Bus, hàm khởi tạo của lớp Engine cũng được gọi hai lần: một lần do gọi truy hồi từ hàm khởi tạo lớp Car, một lần do gọi truy hồi từ hàm khởi tạo lớp PublicTransport. 
  • Khi giải phóng một đối tượng của lớp Bus, hàm huỷ bỏ của lớp Engine cũng sẽ bị gọi tới hai lần. 

Để tránh các vấn đề này, C++ cung cấp một khái niệm là kế thừa từ lớp cơ sở trừu tượng. Khi đó, ta cho các lớp Car và PublicTransport kế thừa trừu tượng từ lớp Engine. Bằng cách này, các thành phần của lớp Engine chỉ xuất hiện trong lớp Bus đúng một lần. Lớp Engine được gọi là lớp cơ sở trừu tượng của các lớp Car và PublicTransport. 
 


Khai báo lớp cơ sở trừu tượng 
Việc chỉ ra một sự kế thừa trừu tượng được thực hiện bằng từ khoá virtual khi khai báo lớp cơ sở: 

class <Tên lớp cơ sở>: <Từ khoá dẫn xuất> virtual <Tên lớp cơ sở>{ 
     …  // Khai báo các thành phần bổ sung 
}; 

Ví dụ: 
 

class Engine{ 
     …     // Các thành phần lớp Engine 
}; 
class Car: public virtual Engine{ 
         …  // Khai báo các thành phần bổ sung 
}; là khai báo lớp Car, kế thừa từ lớp cơ sở trừu tượng Engine, theo kiểu dẫn xuất public. 

Lưu ý: 

  • Từ khoá virtual được viết bằng chữ thường. 
  • Từ khoá virtual không ảnh hưởng đến phạm vi truy nhập thành phần lớp cơ sở, phạm vi này vẫn được quy định bởi từ khoá dẫn xuất như thông thường.  
  • Từ khoá virtual chỉ ra một lớp cơ sở là trừu tượng nhưng lại được viết trong khi khai báo lớp dẫn xuất. 
  •  Một lớp dẫn xuất có thể được kế thừa từ nhiều lớp cơ sở trừu tượng 

Hàm khởi tạo lớp cơ sở trừu tượng 
Khác với các lớp cơ sở thông thường, khi có một lớp dẫn xuất từ một lớp cơ sở trừu tượng, lại được lấy làm cơ sở cho một lớp dẫn xuất khác thì trong hàm khởi tạo của lớp dẫn xuất cuối cùng, vẫn phải gọi hàm khởi tạo tường minh của lớp cơ sở trừu tượng. Hơn nữa, hàm khởi tạo của lớp cơ sở trừu tượng phải được gọi sớm nhất. 
Ví dụ, khi lớp Car và lớp PublicTransport được kế thừa từ lớp cơ sở trừu tượng Engine. Sau đó, lớp Bus được kế thừa từ hai lớp Car và PublicTranport. Khi đó, hàm khởi tạo của lớp Bus cũng phải gọi tường minh hàm khởi tạo của lớp Engine, theo thứ tự sớm nhất, sau đó mới gọi đến hàm khởi tạo của các lớp Car và PublicTransport. 

class Engine{  	public: 
	 	 	Engine(){… }; 
}; 
class Car: public virtual Engine{  	 	//Lớp cơ sở virtual  	
     public: 
	 	 	Car(): Engine(){… }; 
}; 
class PublicTransport: public virtual Engine{ //Lớp cơ sở virtual  	
      public: 
	 	 	PublicTransport():Engine(){… }; 
}; 
class Bus: public Car, public PublicTransport{  	
    public: 
	 	 	// Gọi hàm khởi tạo tường minh của lớp cơ sở trừu tượng 
	   Bus():Engine(), Car(), PublicTransport(){… }; 
}; 

Lưu ý: 

  • Trong trường hợp lớp Engine không phải là lớp cơ sở trừu tượng của các lớp Car và PublicTransport, thì trong hàm khởi tạo của lớp Bus không cần gọi hàm khởi tạo của lớp Engine, mà chỉ cần gọi tới các hàm khởi tạo của các lớp cơ sở trực tiếp của lớp Bus là lớp Car và lớp PublicTransport. 

Ví dụ minh hoạ việc khai báo và sử dụng lớp cơ sở trừu tượng: lớp Engine là lớp cơ sở trừu tượng của các lớp Car và lớp PublicTransport. Hai lớp này, sau đó, lại làm lớp cơ sở của lớp Bus.  

#include<string> 
#include <iostream>
using namespace std;
/* Định nghĩa lớp Engine */
class Engine {
	int  power; 	 	 	// Công suất 
public:
	Engine() { power = 0; };  	// Khởi tạo không tham số  
	Engine(int power) {
		this->power = power;
	};
	void show(); 	 	 	// Giới thiệu  
	float getPower() {
		return power;
	};
};

// Giới thiệu 
void Engine::show() {
	cout << "This is an engine having a power of " << power << "KWH" << endl;

}

/* Định nghĩa lớp Car dẫn xuất từ lớp cơ sở trừu tượng Engine*/
class Car : public virtual Engine {
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car();
	Car(int speed, string mark, float price);
	void show(); 	 	 	// Giới thiệu  
	float getSpeed() {
		return speed;
	};
	string getMark() {
		return mark;
	};
	float getPrice() {
		return price;
	};
};
/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
	this->speed = 0;
	this->mark = "";
	this->price = 0;
}
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() { // Phương thức giới thiệu xe 
	cout << "This is a " << mark << " having a speed of "
		<< speed << "km / h and its price is $" << price << endl;
	return;
}

/* Định nghĩa lớp PublicTransport dẫn xuất trừu tượng từ lớp Engine */
class PublicTransport : public virtual Engine {
	float ticket; 	 	 	// Giá vé phương tiện
public:
	PublicTransport(); 	 	// Khởi tạo không tham số 
	PublicTransport(int, float); // Khởi tạo đủ tham số
	void show(); 	 	 	// Giới thiệu 
	float getTicket() {
		return ticket;
	};
};

PublicTransport::PublicTransport() {  	// Khởi tạo không tham số  	
	this->ticket = 0;
}

// Khởi tạo đủ tham số 
PublicTransport::PublicTransport(int power, float ticket) : Engine(power) {
	this->ticket = ticket;
}

// Giới thiệu 
void PublicTransport::show() {
	cout << "This is a public transport havìn a ticket of $" << ticket << " and its power is " << getPower()
		<< "KWh" << endl;
}
/* Định nghĩa lớp Bus kế thừa từ lớp Car và PublicTransport */
class Bus : public Car, public PublicTransport { // Thứ tự khai báo  	 	
	int label;  	 	 	// Số hiệu tuyến xe  	
public:
	Bus(); 	 	 	 	// Khởi tạo không tham số  
	Bus(int, int, string, float, float, int);// Khởi tạo đủ tham số  	 	
	void show(); 	 	 	// Giới thiệu 
};

// Khởi tạo không tham số 
Bus::Bus() : Engine(), Car(), PublicTransport() {  	// Theo thứ tự dẫn xuất  	
	label = 0;
}

// Khởi tạo đủ tham số 
Bus::Bus(int power, int speed, string mark, float price, float ticket, int label) :
	Engine(power), Car(speed, mark, price), PublicTransport(power, ticket) {
	this->label = label;
}

// Giới thiệu 
void Bus::show() {
	cout << "This is a bus on the line " << label
		<< ", its speed is " << getSpeed()
		<< "km / h, power is" << Car::getPower()
		<< "KWh, mark is " << getMark() << ", price is $" << getPrice()
		<< " and ticket is " << getTicket() << endl;

}

// phương thức main 
int main() {
	Bus myBus(250, 100, "Mercedes", 3000, 1.5, 27);
	myBus.Car::Engine::show();  	// Hàm của lớp Engine 
	myBus.PublicTransport::Engine::show();// Hàm của lớp Engine 
	myBus.Car::show(); 	 	 	// Hàm của lớp Car 
	myBus.PublicTransport::show(); 	// Hàm của lớp PublicTransport 
	myBus.show(); 	 	 	 	// Hàm của lớp Bus 
	system("pause");
	return 0;
}

Kết quả:
 

This is an engine having a power of 250KWH
This is an engine having a power of 250KWH
This is a Mercedes having a speed of 100km / h and its price is $3000
This is a public transport hav∞n a ticket of $1.5 and its power is 250KWh
This is a bus on the line 27, its speed is 100km / h, power is250KWh, mark is Mercedes, price is $3000 and ticket is 1.5

Hai dòng đầu là kết quả của phương thức show() của lớp Engine: một lần gọi qua lớp Car, một lần gọi qua lớp PublicTransport, chúng cho kết quả như nhau. Dòng thứ ba là kết quả phương thức show() của lớp Car. Dòng thứ tư, tương ứng là kết quả phương thức show() của lớp PublicTransport. Dòng thứ năm là kết quả phương thức show() của lớp Bus. 


 

Đa hình (Polymorphsim) trong C++

Đa hình là khái niệm luôn đi kèm với kế thừa. Do tính kế thừa, một lớp có thể sử dụng lại các phương thức của lớp khác. Tuy nhiên, nếu cần thiết, lớp dẫn xuất cũng có thể định nghĩa lại một số phương thức của lớp cơ sở. Đó là sự nạp chồng phương thức trong kế thừa. Nhờ sự nạp chồng phương thức này, ta chỉ cần gọi tên phương thức bị nạp chồng từ đối tượng mà không cần quan tâm đó là đối tượng của lớp nào. Chương trình sẽ tự động kiểm tra xem đối tượng là thuộc kiểu lớp cơ sở hay thuộc lớp dẫn xuất, sau đó sẽ gọi phương thức tương ứng với lớp đó. Đó là tính đa hình. 


Đặt vấn đề 
Sự kế thừa trong C++ cho phép có sự tương ứng giữa lớp cơ sở và các lớp dẫn xuất trong sơ đồ thừa kế:  

  • Một con trỏ có kiểu lớp cơ sở luôn có thể trỏ đến địa chỉ của một đối tượng của lớp dẫn xuất. 
  • Tuy nhiên, khi thực hiện lời gọi một phương thức của lớp, trình biên dịch sẽ quan tâm đến kiểu của con trỏ chứ không phải đối tượng mà con trỏ đang trỏ tới: phương thức của lớp mà con trỏ có kiểu được gọi chứ không phải phương thức của đối tượng mà con trỏ đang trỏ tới được gọi. 

Ví dụ: Lớp Bus kế thừa từ lớp Car, cả hai lớp này đều định nghĩa phương thức show()

class Car{  
	public: 	 	 	
	void show(); 
}; 
class Bus: public Car{  	
    public: 	 	 	
    void show(); 
};

khi đó, nếu ta khai báo một con trỏ lớp Bus, nhưng lại trỏ vào địa chỉ của một đối tượng lớp Car: 

Bus myBus; 
Car *ptrCar = &myBus; // đúng nhưng khi gọi: 
ptrCar->show(); 

thì chương trình sẽ gọi đến phương thức show() của lớp Car (là kiểu của con trỏ ptrCar), mà không gọi tới phương thức show() của lớp Bus (là kiểu của đối tượng myBus mà con trỏ ptrCar đang trỏ tới). 
Để giải quyết vấn đề này, C++ đưa ra một khái niệm là phương thức trừu tượng. Bằng cách sử dụng phương thức trừu tượng. Khi gọi một phương thức từ một con trỏ đối tượng, trình biên dịch sẽ xác định kiểu của đối tượng mà con trỏ đang trỏ đến, sau đó nó sẽ gọi phương thức tương ứng với đối tượng mà con trỏ đang trỏ tới. 


Khai báo phương thức trừu tượng 
Phương thức trừu tượng (còn gọi là phương thức ảo, hàm ảo) được khai báo với từ khoá virtual: 

  •  Nếu khai báo trong phạm vi lớp: 
    virtual <Kiểu trả về> <Tên phương thức>([<Các tham số>]); 
  • Nếu định nghĩa ngoài phạm vi lớp: 

    virtual <Kiểu trả về> <Tên lớp>::<Tên phương thức>([<Các tham 
    số>]){…} 

Ví dụ: 

class Car{     
    public: 
          virtual void show(); 
};

là khai báo phương thức trừu tượng show() của lớp Car: phương thức không có tham số và không cần giá trị trả về (void). 
Lưu ý: 

  • Từ khoá virtual có thể đặt trước hay sau kiểu trả về của phương thức. 
  • Với cùng một phương thức được khai báo ở lớp cơ sở lẫn lớp dẫn xuất, chỉ cần dùng từ khoá virtual ở một trong hai lần định nghĩa phương thức đó là đủ: hoặc ở lớp cơ sở, hoặc ở lớp dẫn xuất. 
  • Trong trường hợp cây kế thừa có nhiều mức, cũng chỉ cần khai báo phương thức là trừu tượng (virtual) ở một mức bất kì. Khi đó, tất cả các phương thức trùng tên với phương thức đó ở tất cả các mức đều được coi là trừu tượng. 
  • Đôi khi không cần thiết phải định nghĩa chồng (trong lớp dẫn xuất) một phương thức đã được khai báo trừu tượng trong lớp cơ sở. 

Sử dụng phương thức trừu tượng – đa hình 
Một khi phương thức được khai báo là trừu tượng thì khi một con trỏ gọi đến phương thức đó, chương trình sẽ thực hiện phương thức tương ứng với đối tượng mà con trỏ đang trỏ tới, thay vì thực hiện phương thức của lớp cùng kiểu với con trỏ. Đây được gọi là hiện tượng đa hình (tương ứng bội) trong C++. 
Chương trình sau ví dụ về việc sử dụng phương thức trừu tượng: lớp Bus kế thừa từ lớp Car, hai lớp này cùng định nghĩa phương thức trừu tượng show().  

  • Khi ta dùng một con trỏ có kiểu lớp Car trỏ vào địa chỉ của một đối tượng kiểu Car, nó sẽ gọi phương thức show() của lớp Car.  
  • Khi ta dùng cũng con trỏ đó, trỏ vào địa chỉ của một đối tượng kiểu Bus, nó sẽ gọi phương thức show() của lớp Bus. 
     

Xem ví dụ sau:

#include<iostream> 
#include<string> 
using namespace std;
/* Định nghĩa lớp */
class Car {
private:
	int  speed;               // Tốc độ  
	string  mark;           // Nhãn hiệu
	float price;               // Giá xe 						
							   // Khởi tạo với các giá trị ngầm định cho các tham số 
public:
	Car();
	Car(int speed, string mark, float price);
	virtual void show();	// Giới thiệu xe, trừu tượng
	int getSpeed() {
		return speed;
	};
	string getMark() {
		return mark;
	};
	float getPrice() {
		return price;
	};

};

/* Khai báo phương thức bên ngoài lớp */
Car::Car() {
	this->speed = 0;
	this->mark = "";
	this->price = 0;

}
Car::Car(int speed, string mark, float price) {
	this->speed = speed;
	this->mark = mark;
	this->price = price;
}

void Car::show() {                // Phương thức hiển thị xe
	cout << "This is a " << mark << " having a speed of  " << speed << "km/h and its price is $" << price << endl;
}

/* Định nghĩa lớp Bus kế thừa từ lớp Car */
class Bus : public Car {
	int label;  	 	// Số hiệu tuyến xe  	
public:
	// Khởi tạo đủ tham số  
	Bus(int speed = 0, string mark = "", float price = 0, int lable = 0);
	void setLabel(int); 	// Gán số hiệu tuyến xe  	 	
	int getLabel();  	// Đọc số hiệu tuyến xe 
	void show();
};

// Cài đặt lớp Bus
Bus::Bus(int speed, string mark, float price, int label) :Car(speed, mark, price) {
	this->label = label;
}
// Định nghĩa nạp chồng phương thức 
void Bus::show() {  	 	 	// Giới thiệu xe bus 
	cout << "This is a bus of type " << getMark() << ", on the line "
		<< label << ", having a speed of " << getSpeed()
		<< "km / h and its price is $" << getPrice() << endl;
	return;
}

int main() {

	Car *ptrCar, myCar(100, "Ford", 3000);
	Bus myBus(150, "Mercedes", 5000, 27);// Biến đối tượng của lớp Bus
	ptrCar = &myCar;  	 	// Trỏ đến đối tượng lớp Car 
	ptrCar->show();  	 	// Phương thức của lớp Car 

	ptrCar = &myBus;  	 	// Trỏ đến đối tượng lớp Bus 
	ptrCar->show();  	// Phương thức của lớp Bus return; 
							// Hàm của lớp Bus 
	system("pause");

	return 0;
}


 Chương trình trên hiển thị kết quả thông báo như sau: 

This is a Ford having a speed of  100km/h and its price is $3000
This is a bus of type Mercedes, on the line 27, having a speed of 150km / h and its price is $5000

 

Nạp chồng (Overloading) trong C++

Trong một lớp, ta có thể tạo ra nhiều hàm với cùng một tên gọi nhưng khác nhau các dữ liệu đầu vào hoặc tham số, đó gọi là nạp chồng. Trong C++, Chúng ta có thể nạp chồng:

  • Phương thức (methods),
  • Phương thức thiết lập(constructors), 
  • indexed properties

Các kiểu quá nạp chồng trong C++:

  • Nạp chồng phương thức
  • Nạp chồng các toán tử 

 Nạp chồng phương thức

Trong một lớp, ta có thể tạo ra nhiều hàm với cùng một tên gọi nhưng khác nhau các dữ liệu đầu vào hoặc tham số, đó gọi là nạp chồng phương thức.
Lới ích của việc quá tải phương thức là chúng ta có thể khai báo cùng một tên phương thức trong cùng chương trình, không cần phải khai báo tên khác cho cùng một hành đông.


Ví dụ nạp chồng phương thức C++

Ví dụ  trong lớp Cal sau có hai phương thức add nhưng khác nhau về tham số:

#include <iostream>  
using namespace std;
class Cal {
public:
	static int add(int a, int b) {
		return a + b;
	}
	static int add(int a, int b, int c)
	{
		return a + b + c;
	}
};
int main(void) {
	Cal C;
	cout << C.add(10, 20) << endl;
	cout << C.add(12, 20, 23);
	system("pause");
	return 0;
}

Kết quả:

30
55

Nạp chồng toán tử trong C++

Việc nạp chồng toán tử thường được sử dụng trong C++ là định nghĩa lại toán tử của C++. 
Lợi ích của nạp chồng toán tử là thực hiện các thao tác khác nhau trên cùng một toán hạng.

#include <iostream>  
using namespace std;
class Test
{
private:
	int num;
public:
	Test() : num(8) {}
	void operator ++()
	{
		num = num + 2;
	}
	void Print() {
		cout << "The Count is: " << num;
	}
};
int main()
{
	Test tt;
	++tt;  // calling of a function "void operator ++()"  
	tt.Print();
	system("pause");
	return 0;
}

Kết quả:

The Count is: 10

 

Ghi đè (Overriding) trong C++

Nếu lớp dẫn xuất định nghĩa cùng một phương thức đã  được định nghĩa trong lớp cơ sở của , nó được gọi là phương thức ghi đè trong C ++. Phương thức ghi đè được sử dụng để đạt được tính đa hình thời . Phương thức ghi đè cho phép bạn cung cấp việc thực hiện cụ thể chức năng đã được lớp cơ sở của nó cung cấp.

Overriding thường được sử dụng trong method ở lớp con.
Một số quy tắc sử dụng phương thức overriding:
Các phương thức được mô tả static thì không overriden nhưng được mô tả lại.
Các phương thức không kế thừa sẽ không được overriden (hiển nhiên).


Ví dụ sau chúng ta ghi đè phương thức eat():

#include <iostream>  
using namespace std;
class Animal {
public:
	void eat() {
		cout << "Eating...";
	}
};
class Dog : public Animal
{
public:
	void eat()
	{
		cout << "Eating bread...";
	}
};
int main(void) {
	Dog d = Dog();
	d.eat();
	system("pause");
	return 0;
}

Kết quả:

Eating bread...

 

Phương thức ảo (virtual) trong C++

Phương thức ảo nằm trong lớp cơ sở chúng ta có thể định nghĩa lại trong lớp dẫn xuất. Nó được khai báo bằng từ khóa virtual

Phương thức ảo được sử dụng để thực hiện liên kết động (dynamic linkage) hoặc ràng buộc (binding) trong phương thức.


Ràng buộc hoặc liên kết động

Phương thức được gọi là ràng buộc  được giải quyết trong thời gian chạy. Do đó trình biên dịch xác định loại đối tượng trong thời gian chạy, và sau đó liên kết các phương thức được gọi.


Ví dụ về phương ảo sau:

#include <iostream>  
using namespace std;
class A
{
public:
	virtual void display()
	{
		cout << "Lop co so duoc goi" << endl;
	}
};
class B :public A
{
public:
	void display()
	{
		cout << "Lop ke thua duoc goi" << endl;
	}
};
int main()
{
	A* a;    //pointer of base class  
	B b;     //object of derived class  
	a = &b;
	a->display();   //Late Binding occurs  
	system("pause");
}

Kết quả:

Lop ke thua duoc goi

 

Giao diện (Interfaces) trong C++ (Lớp trừu trượng)

Các lớp trừu tượng là cách để đạt được sự trừu tượng trong C ++. Sự trừu tượng trong C ++ là quá trình ẩn các chi tiết bên trong và chỉ hiển thị chức năng. Sự trừu tượng có thể đạt được bằng hai cách:

  1. Lớp trừu tượng
  2. Giao diện

Lớp trừu tượng và giao diện có thể có phương thức trừu tượng, cần thiết cho sự trừu tượng


Lớp trừu tượng

Trong lớp C++ để tạo ra trừu tượng bằng cách khai báo ít nhất một phương thức thuần ảo. Một phương thức thuần ảo được xác định bằng cách đặt "= 0" trong khai báo của nó. Được dùng cung cấp bởi các lớp dẫn xuất.

Hãy xem một ví dụ về lớp trừu tượng trong C ++ có một phương thức abstract (). Nó được cung cấp bởi các lớp dẫn xuất: Rectangle và Circle. Cả hai lớp đều có cách triển khai khác nhau.

#include <iostream>  
using namespace std;
class Shape
{
public:
	virtual void draw() = 0;
};
class Rectangle : Shape
{
public:
	void draw()
	{
		cout <<"drawing rectangle..." <<endl;
	}
};
class Circle : Shape
{
public:
	void draw()
	{
		cout << "drawing circle..." <<endl;
	}
};
int main() {
	Rectangle rec;
	Circle cir;
	rec.draw();
	cir.draw();
	system("pause");
	return 0;
}

Kết quả:

drawing rectangle...
drawing circle...

 

Trừu tượng dữ liệu (Data Abstraction) trong C++

Tóm tắt dữ liệu trong C ++Trong chương trình C ++ nếu chúng ta thực hiện lớp với các thành viên private và public. Đó là một ví dụ về trừu tượng dữ liệu.


Hãy xem ví dụ đơn giản về trừu tượng dữ liệu.
 

#include <iostream>  
using namespace std;
class Sum
{
private: int x, y, z;
public:
	void add()
	{
		cout << "Enter two numbers: ";
		cin >> x >> y;
		z = x + y;
		cout << "Sum of two number is: " << z << endl;
	}
};
int main()
{
	Sum sm;
	sm.add();
	system("pause");
	return 0;
}

Kết quả:

Enter two numbers:
3
6
Sum of two number is: 9

 

Namespace (Không gian tên) trong C++

Namespace trong C ++ được sử dụng để tổ chức nhiều lớp để có thể dễ dàng xử lý ứng dụng.
Để truy cập vào lớp của Namespace, chúng ta cần sử dụng namespacename :: classname. Chúng ta có thể sử dụng từ khóa using cho việc truy cập lớp trong  namespace.Trong C ++, Namespace chung là không gian tên gốc. Global :: std sẽ luôn luôn tham chiếu đến không gian tên "std" của Framework C++.


Ví dụ đơn giản về namespace:

#include <iostream>
using namespace std;
namespace First {
	void sayHello() {
		cout << "Hello First Namespace" << endl;
	}
}
namespace Second {
	void sayHello() {
		cout << "Hello Second Namespace" << endl;
	}
}
int main()
{
	First::sayHello();
	Second::sayHello();
	system("pause");
	return 0;
}

Kết quả:

Hello First Namespace
Hello Second Namespace