Các kiến thức cần nắm được sử dụng ngôn ngữ C++:
-
Class là gì?, Object là gì?, Instance của Class là gì?
-
Contructor, Destructor, Getter, Setter là gì? Ví dụ
-
Bốn tính chất của hướng đối tượng ?. Nêu rõ từng tính chất và ví dụ
-
Static là gì?, nêu tất cả những gì biết.
-
Khái niệm Overloading và Overriding và ví dụ
-
Làm ứng dụng console để quản lý dánh sách sinh viên (Yêu cầu sử dụng cả 4 tính chất của OOP?
Các yêu cầu này đều sử dụng ngôn ngữ C++, yêu cầu của tuần này là trả lời các câu hỏi trên qua word, pdf hoặc bất cứ gì…
Có thể sự dụng tài liệu dưới đây để tham khảo
Lập Trình Hướng Đối Tượng (OOP) trong C++
Mục lục
-
Giới thiệu về OOP
-
Tính đóng gói (Encapsulation)
-
Tính kế thừa (Inheritance)
-
Tính đa hình (Polymorphism)
-
Tính trừu tượng (Abstraction)
-
Class và Object
-
Constructor và Destructor
-
Overloading và Overriding
-
Friend Function và Friend Class
-
Ngoài ra còn rất nhiều nhưng mà sẽ không đào sâu
Giới thiệu về OOP
Lập trình hướng đối tượng (Object-Oriented Programming - OOP) là một phương pháp lập trình dựa trên khái niệm về "đối tượng", kết hợp dữ liệu và chức năng thành một đơn vị duy nhất. C++ là một ngôn ngữ lập trình hỗ trợ mạnh mẽ cho OOP, được phát triển bởi Bjarne Stroustrup từ năm 1979.
Các nguyên lý cơ bản của OOP:
-
Tính đóng gói (Encapsulation): Bảo vệ dữ liệu bằng cách giới hạn truy cập trực tiếp từ bên ngoài.
-
Tính kế thừa (Inheritance): Tạo lớp mới dựa trên lớp đã có, tái sử dụng code.
-
Tính đa hình (Polymorphism): Cho phép một giao diện đại diện cho nhiều hành vi khác nhau.
-
Tính trừu tượng (Abstraction): Tập trung vào tính năng mà bỏ qua chi tiết triển khai.
Tính đóng gói (Encapsulation)
Đóng gói là việc gói dữ liệu và các phương thức thao tác dữ liệu vào trong một lớp đối tượng. Tính đóng gói giúp bảo vệ dữ liệu bằng cách kiểm soát quyền truy cập từ bên ngoài.
Access Specifiers (Bộ chỉ định truy cập):
-
private: Chỉ có thể truy cập từ bên trong class.
-
protected: Có thể truy cập từ class con (kế thừa).
-
public: Có thể truy cập từ bất kỳ đâu.
Ví dụ:
class Student {
private:
// Thuộc tính - chỉ truy cập được từ bên trong lớp
std::string name;
int age;
double score;
public:
// Getter methods - cho phép truy cập có kiểm soát
std::string getName() const { return name; }
int getAge() const { return age; }
double getScore() const { return score; }
// Setter methods - cho phép thay đổi có kiểm soát
void setName(const std::string& n) { name = n; }
void setAge(int a) {
if (a > 0 && a < 100) { // Kiểm tra tính hợp lệ
age = a;
}
}
void setScore(double s) {
if (s >= 0 && s <= 10) { // Kiểm tra tính hợp lệ
score = s;
}
}
};
Tính kế thừa (Inheritance)
Kế thừa cho phép một lớp (lớp con) kế thừa các thuộc tính và phương thức từ một lớp khác (lớp cha). Lớp con có thể mở rộng hoặc ghi đè các tính năng của lớp cha.
Các loại kế thừa trong C++:
-
Đơn kế thừa (Single Inheritance): Một lớp con kế thừa từ một lớp cha.
-
Đa kế thừa (Multiple Inheritance): Một lớp con kế thừa từ nhiều lớp cha.
-
Đa cấp kế thừa (Multilevel Inheritance): Lớp C kế thừa từ lớp B, lớp B kế thừa từ lớp A.
-
Kế thừa phân cấp (Hierarchical Inheritance): Nhiều lớp con kế thừa từ một lớp cha.
-
Kế thừa hỗn hợp (Hybrid Inheritance): Kết hợp từ nhiều loại kế thừa.
Ví dụ về đơn kế thừa:
class Person {
protected:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {}
void display() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};
class Student : public Person {
private:
double gpa;
public:
Student(const std::string& n, int a, double g)
: Person(n, a), gpa(g) {}
void display() const {
Person::display(); // Gọi phương thức của lớp cha
std::cout << "GPA: " << gpa << std::endl;
}
};
Ví dụ về đa kế thừa:
class Teacher {
protected:
std::string subject;
int experience;
public:
Teacher(const std::string& s, int e) : subject(s), experience(e) {}
void displayTeacherInfo() const {
std::cout << "Subject: " << subject << ", Experience: " << experience << " years" << std::endl;
}
};
class TeachingAssistant : public Student, public Teacher {
private:
int hoursPerWeek;
public:
TeachingAssistant(const std::string& n, int a, double g,
const std::string& s, int e, int h)
: Student(n, a, g), Teacher(s, e), hoursPerWeek(h) {}
void displayAllInfo() const {
Student::display();
Teacher::displayTeacherInfo();
std::cout << "Hours per week: " << hoursPerWeek << std::endl;
}
};
Tính đa hình (Polymorphism)
Đa hình cho phép các đối tượng thuộc các lớp khác nhau phản ứng khác nhau với cùng một thông điệp. Trong C++, đa hình được thể hiện qua hai loại chính:
1. Đa hình lúc biên dịch (Compile-time Polymorphism):
-
Function Overloading: Nhiều hàm cùng tên nhưng tham số khác nhau.
-
Operator Overloading: Định nghĩa lại các toán tử cho các lớp tự định nghĩa.
2. Đa hình lúc chạy (Runtime Polymorphism):
-
Function Overriding: Lớp con định nghĩa lại phương thức của lớp cha.
-
Virtual Functions: Cho phép ghi đè phương thức ở lớp con.
Ví dụ về Function Overloading:
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
};
Ví dụ về Function Overriding và Virtual Functions:
class Shape {
protected:
int width;
int height;
public:
Shape(int w = 0, int h = 0) : width(w), height(h) {}
virtual double area() const {
return 0.0; // Default implementation
}
virtual void draw() const {
std::cout << "Drawing a generic shape" << std::endl;
}
// Pure virtual function (abstract function)
virtual void print() const = 0;
};
class Rectangle : public Shape {
public:
Rectangle(int w, int h) : Shape(w, h) {}
double area() const override {
return width * height;
}
void draw() const override {
std::cout << "Drawing a rectangle" << std::endl;
}
void print() const override {
std::cout << "Rectangle with width " << width
<< " and height " << height << std::endl;
}
};
class Circle : public Shape {
private:
int radius;
public:
Circle(int r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
void print() const override {
std::cout << "Circle with radius " << radius << std::endl;
}
};
Ví dụ về cách sử dụng đa hình:
void printArea(const Shape& shape) {
std::cout << "Area: " << shape.area() << std::endl;
}
int main() {
Rectangle rect(5, 3);
Circle circle(7);
// Đa hình lúc chạy - cùng một hàm printArea nhưng hành vi khác nhau
printArea(rect); // Gọi Rectangle::area()
printArea(circle); // Gọi Circle::area()
// Mảng các con trỏ đến đối tượng Shape
Shape* shapes[2];
shapes[0] = ▭
shapes[1] = &circle;
for (int i = 0; i < 2; i++) {
shapes[i]->draw(); // Gọi phương thức draw() phù hợp
shapes[i]->print(); // Gọi phương thức print() phù hợp
}
return 0;
}
Quan trọng về đa hình:
-
Destructor phải là virtual trong class base nếu có con trỏ đến class base trỏ đến object của class con.
-
Từ khóa
overridekhông bắt buộc nhưng giúp tránh lỗi. -
Từ khóa
finalcó thể được sử dụng để ngăn chặn ghi đè thêm.
Tính trừu tượng (Abstraction)
Trừu tượng hóa là quá trình ẩn đi các chi tiết triển khai và chỉ hiển thị các tính năng thiết yếu cho người dùng. Trong C++, trừu tượng hóa được thực hiện thông qua:
-
Abstract Classes: Lớp có ít nhất một phương thức thuần ảo (pure virtual function).
-
Interfaces: Lớp chỉ chứa các phương thức thuần ảo.
Ví dụ về Abstract Class:
class AbstractEmployee {
protected:
std::string name;
int id;
public:
AbstractEmployee(const std::string& n, int i) : name(n), id(i) {}
// Phương thức thuần ảo (pure virtual function)
virtual double calculateSalary() const = 0;
// Phương thức bình thường
void displayDetails() const {
std::cout << "ID: " << id << ", Name: " << name << std::endl;
}
virtual ~AbstractEmployee() {} // Virtual destructor
};
class FullTimeEmployee : public AbstractEmployee {
private:
double monthlySalary;
public:
FullTimeEmployee(const std::string& n, int i, double salary)
: AbstractEmployee(n, i), monthlySalary(salary) {}
double calculateSalary() const override {
return monthlySalary;
}
};
class PartTimeEmployee : public AbstractEmployee {
private:
double hourlyRate;
int hoursWorked;
public:
PartTimeEmployee(const std::string& n, int i, double rate, int hours)
: AbstractEmployee(n, i), hourlyRate(rate), hoursWorked(hours) {}
double calculateSalary() const override {
return hourlyRate * hoursWorked;
}
};
Lưu ý về Abstract Classes:
-
Không thể tạo ra đối tượng trực tiếp từ lớp trừu tượng.
-
Các lớp dẫn xuất phải triển khai tất cả các phương thức thuần ảo, nếu không chúng cũng trở thành lớp trừu tượng.
-
Lớp trừu tượng có thể chứa cả phương thức thuần ảo và phương thức thường.
Class và Object
Class (Lớp)
Lớp là một bản thiết kế (blueprint) định nghĩa thuộc tính và hành vi của một kiểu đối tượng.
class Car {
private:
std::string brand;
std::string model;
int year;
double price;
public:
// Constructor
Car(const std::string& b, const std::string& m, int y, double p)
: brand(b), model(m), year(y), price(p) {}
// Getter methods
std::string getBrand() const { return brand; }
std::string getModel() const { return model; }
int getYear() const { return year; }
double getPrice() const { return price; }
// Setter methods
void setBrand(const std::string& b) { brand = b; }
void setModel(const std::string& m) { model = m; }
void setYear(int y) { year = y; }
void setPrice(double p) { price = p; }
// Other methods
void displayInfo() const {
std::cout << year << " " << brand << " " << model
<< " ($" << price << ")" << std::endl;
}
double calculateDepreciation(int currentYear) const {
int age = currentYear - year;
return price * (1.0 - 0.1 * age);
}
};
Object (Đối tượng)
Đối tượng là một thể hiện (instance) của lớp. Nó có trạng thái (state) và hành vi (behavior) cụ thể.
int main() {
// Tạo đối tượng từ lớp Car
Car myCar("Toyota", "Corolla", 2020, 20000.0);
Car yourCar("Honda", "Civic", 2019, 18000.0);
// Sử dụng các phương thức
myCar.displayInfo();
yourCar.displayInfo();
// Sửa đổi thuộc tính
myCar.setPrice(19500.0);
// Truy cập thuộc tính
std::cout << "My car is a " << myCar.getBrand()
<< " " << myCar.getModel() << std::endl;
// Tính khấu hao
std::cout << "Current value: $"
<< myCar.calculateDepreciation(2023) << std::endl;
return 0;
}
Static Members (Thành viên tĩnh)
Các thành viên static thuộc về lớp, không phải đối tượng cụ thể.
class MathUtils {
private:
static int objectCount; // Static member variable
public:
MathUtils() {
objectCount++;
}
~MathUtils() {
objectCount--;
}
static int getObjectCount() { // Static member function
return objectCount;
}
static double PI() {
return 3.14159265359;
}
static int square(int num) {
return num * num;
}
};
// Khởi tạo static member variable
int MathUtils::objectCount = 0;
// Sử dụng static members
int main() {
std::cout << "PI: " << MathUtils::PI() << std::endl;
std::cout << "Square of 5: " << MathUtils::square(5) << std::endl;
MathUtils m1, m2, m3;
std::cout << "Object count: " << MathUtils::getObjectCount() << std::endl; // 3
return 0;
}
Constructor và Destructor
Constructor
Constructor là một phương thức đặc biệt được gọi khi một đối tượng được tạo ra. Nó có cùng tên với lớp và không có kiểu trả về.
Các loại Constructor:
-
Default Constructor: Không có tham số hoặc tất cả tham số có giá trị mặc định.
-
Parameterized Constructor: Có một hoặc nhiều tham số.
-
Copy Constructor: Tạo đối tượng mới bằng cách sao chép từ đối tượng đã tồn tại.
-
Move Constructor: Tạo đối tượng mới bằng cách "di chuyển" tài nguyên từ đối tượng đã tồn tại (C++11).
-
Delegating Constructor: Constructor gọi constructor khác trong cùng lớp (C++11).
class Complex {
private:
double real;
double imag;
public:
// Default constructor
Complex() : real(0.0), imag(0.0) {
std::cout << "Default constructor called" << std::endl;
}
// Parameterized constructor
Complex(double r, double i) : real(r), imag(i) {
std::cout << "Parameterized constructor called" << std::endl;
}
// Copy constructor
Complex(const Complex& other) : real(other.real), imag(other.imag) {
std::cout << "Copy constructor called" << std::endl;
}
// Move constructor (C++11)
Complex(Complex&& other) noexcept : real(other.real), imag(other.imag) {
other.real = 0.0;
other.imag = 0.0;
std::cout << "Move constructor called" << std::endl;
}
// Delegating constructor (C++11)
Complex(double r) : Complex(r, 0.0) {
std::cout << "Delegating constructor called" << std::endl;
}
// Display function
void display() const {
std::cout << real;
if (imag >= 0)
std::cout << " + " << imag << "i" << std::endl;
else
std::cout << " - " << -imag << "i" << std::endl;
}
};
Initializer List
Initializer list là cách hiệu quả để khởi tạo các thành viên của lớp trong constructor.
class Person {
private:
std::string name;
int age;
const int id; // Constant member
int& refAge; // Reference member
public:
// Constructor with initializer list
Person(const std::string& n, int a, int i)
: name(n), age(a), id(i), refAge(age) {
// Constructor body
}
};
Destructor
Destructor là một phương thức đặc biệt được gọi khi đối tượng bị hủy. Nó có cùng tên với lớp nhưng có dấu ~ ở trước.
class Resource {
private:
int* data;
public:
// Constructor
Resource(int size) {
std::cout << "Allocating resources" << std::endl;
data = new int[size];
}
// Destructor
~Resource() {
std::cout << "Freeing resources" << std::endl;
delete[] data;
}
};
int main() {
{
Resource r(10); // Constructor called
} // Destructor called when r goes out of scope
return 0;
}
Rule of Three/Five/Zero
-
Rule of Three: Nếu một lớp cần một destructor, copy constructor, hoặc copy assignment operator, thì nó cần cả ba.
-
Rule of Five (C++11): Nếu một lớp cần một destructor, copy constructor, copy assignment operator, move constructor, hoặc move assignment operator, thì nó cần cả năm.
-
Rule of Zero: Lớp không quản lý tài nguyên thì không cần định nghĩa các hàm đặc biệt.
class DynamicArray {
private:
int* data;
size_t size;
public:
// Constructor
DynamicArray(size_t s) : size(s) {
data = new int[size]();
}
// Destructor
~DynamicArray() {
delete[] data;
}
// Copy constructor
DynamicArray(const DynamicArray& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
}
// Copy assignment operator
DynamicArray& operator=(const DynamicArray& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// Move constructor (C++11)
DynamicArray(DynamicArray&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// Move assignment operator (C++11)
DynamicArray& operator=(DynamicArray&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
Overloading và Overriding
Function Overloading
Function overloading cho phép định nghĩa nhiều hàm có cùng tên nhưng khác tham số (số lượng, kiểu, thứ tự).
class Calculator {
public:
// Overloaded functions
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
double add(int a, double b) {
return a + b;
}
double add(double a, int b) {
return a + b;
}
};
Operator Overloading
Operator overloading cho phép định nghĩa lại hoạt động của các toán tử cho các lớp tự định nghĩa.
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// Overload + operator
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// Overload - operator
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
// Overload * operator
Complex operator*(const Complex& other) const {
return Complex(
real * other.real - imag * other.imag,
real * other.imag + imag * other.real
);
}
// Overload == operator
bool operator==(const Complex& other) const {
return (real == other.real) && (imag == other.imag);
}
// Overload != operator
bool operator!=(const Complex& other) const {
return !(*this == other);
}
// Overload << operator (as friend function)
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
};
// Implementation of << operator
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real;
if (c.imag >= 0)
os << " + " << c.imag << "i";
else
os << " - " << -c.imag << "i";
return os;
}
Function Overriding
Function overriding xảy ra khi một lớp con định nghĩa lại một phương thức đã được định nghĩa trong lớp cha.
class Base {
public:
virtual void display() const {
std::cout << "Display from Base" << std::endl;
}
void show() const {
std::cout << "Show from Base" << std::endl;
}
};
class Derived : public Base {
public:
// Override virtual function
void display() const override {
std::cout << "Display from Derived" << std::endl;
}
// Hide (not override) non-virtual function
void show() const {
std::cout << "Show from Derived" << std::endl;
}
};
int main() {
Base b;
Derived d;
b.display(); // "Display from Base"
d.display(); // "Display from Derived"
b.show(); // "Show from Base"
d.show(); // "Show from Derived"
// Polymorphic behavior
Base* ptr = &d;
ptr->display(); // "Display from Derived" (virtual function)
ptr->show(); // "Show from Base" (non-virtual function)
return 0;
}
Friend Function và Friend Class
Friend Function
Friend function là một hàm không thuộc về một lớp nhưng có quyền truy cập vào các thành viên private và protected của lớp đó.
class Box {
private:
double length;
double width;
double height;
public:
Box(double l = 0.0, double w = 0.0, double h = 0.0)
: length(l), width(w), height(h) {}
// Friend function declaration
friend double calculateVolume(const Box& box);
// Friend operator
friend std::ostream& operator<<(std::ostream& os, const Box& box);
};
// Friend function definition
double calculateVolume(const Box& box) {
// Can access private members
return box.length * box.width * box.height;
}
// Friend operator definition
std::ostream& operator<<(std::ostream& os, const Box& box) {
os << "Box dimensions: " << box.length << " x "
<< box.width << " x " << box.height;
return os;
}
Friend Class
Friend class là một lớp có quyền truy cập vào các thành viên private và protected của lớp khác.
class Node; // Forward declaration
class LinkedList {
private:
Node* head;
public:
LinkedList() : head(nullptr) {}
// Declaring NodeManager as friend
friend class NodeManager;
// Member functions...
};
class Node {
private:
int data;
Node* next;
public:
Node(int d) : data(d), next(nullptr) {}
// Declaring both LinkedList and NodeManager as friends
friend class LinkedList;
friend class NodeManager;
};
class NodeManager {
public:
// Can access private members of both LinkedList and Node
void insertAtBeginning(LinkedList& list, int data) {
Node* newNode = new Node(data);
newNode->next = list.head;
list.head = newNode;
}
void printList(const LinkedList& list) {
Node* current = list.head;
while (current) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}
};