Chuyển tới nội dung chính

Dạng đề 3: Thiết kế lớp phức tạp sử dụng kế thừa và đa hình

Để làm được dạng bài này, trước hết, phải bình tĩnh và không được hoảng loạn sau khi đọc đề:v. Đề câu 3 thường nhiều chữ khiến sinh viên lười đọc và đôi khi có các thông tin nhiễu để đánh lừa người đọc, tuy nhiên cấu trúc bài giải thường không quá phức tạp. Thế nên cần bình tĩnh đọc đề và xác định đúng nội dung của đề theo các bước như sau:

Bước 1: Xác định được các lớp đối tượng có trong đề bài:

  • Mối quan hệ giữa các lớp: Lớp nào là lớp cha, lớp nào là lớp con, lớp nào là một thuộc tính của lớp khác?
  • Các lớp con đa số sẽ được liệt kê sẵn trong đề bài, và lớp cha sẽ là lớp trừu tượng được đúc kết từ các điểm chung của lớp con.
  • Thông thường sẽ có thêm một lớp “danh sách” gì đó (có thể gồm một mảng các con trỏ thuộc lớp cha) để biểu diễn một danh sách các đối tượng trong đề.

Bước 2: Thiết kế thuộc tính:

  • Thuộc tính nào mà tất cả các lớp con đều có thì sẽ được khai báo ở lớp cha.
  • Các thuộc tính riêng chỉ xuất hiện ở một số lớp con thì nên đặt ở lớp con tương ứng.
  • Hạn chế để lớp con sỡ hữu các thuộc tính mà nó không cần dùng tới.

Bước 3: Thiết kế phương thức:

  • Dựa vào các yêu cầu của đề bài để xác định phương thức cho từng lớp.
  • Tìm ra các phương thức ở lớp cha trước, sau đó xác định phương thức nào nên là phương thức ảo.
  • Tiếp theo là xác định xem các lớp con sẽ định nghĩa thêm các phương thức mới nào và cần ghi đè những phương thức ảo nào từ lớp cha.
  • Lưu ý là những phương thức nào mà chỉ lớp con mới cần sử dụng thì không nên đặt ở lớp cha.

Một số tip khác:

  • Khi vẽ sơ đồ lớp thì phải ghi đầy đủ kiểu dữ liệu của các thuộc tính, kiểu trả về của các phương thức, vẽ đường nối biểu diễn các mối quan hệ (kế thừa, một nhiều, …)
  • Khi code giấy thì nên khai báo hết tất cả các phương thức bên trong lớp trước rồi định nghĩa sau.
  • Khi thao tác trên nhiều đối tượng, để áp dụng tính đa hình, ta sẽ thường sử dụng một mảng các con trỏ thuộc lớp cha. Trước khi nhập từng đối tượng trong mảng, ta sẽ cấp phát vùng nhớ cho các biến con trỏ tương ứng với loại lớp con mà ta muốn, ví dụ:
// Mảng các con trỏ thuộc lớp cha
LopCha* arr[50];
// Nhập số lượng đối tượng sẽ quản lý
int n; cin >> n;
// Khởi tạo các đối tượng lớp con
for (int i = 0; i < n; i++) {
int loai; cin >> loai;
if (loai == 1) arr[i] = new LopCon1();
if (loai == 2) arr[i] = new LopCon2();
...
}

Đề HK2 2023-2024

Có rất nhiều chủng virus sống trên các vật chủ là các sinh vật sống khác kể cả loài người. Khi nhiễm lên vật chủ, một loại virus có thể gây hậu quả từ tử vong, các triệu chứng nặng, các triệu chứng nhẹ hoặc không triệu chứng gì tùy thuộc vào khả năng miễn dịch của vật chủ.

  • Khả năng miễn dịch của vật chủ đối với các loại virus ở ba mức: thấp, trung bình và cao. Nó sẽ ảnh hưởng đến xác suất gặp các triệu chứng nặng, nhẹ, không có triệu chứng hoặc xác suất tử vong như bảng sau:
Không triệu chứngTriệu chứng nhẹTriệu chứng nặngTử vong
Cao50%35%15%50%
Trung bình10%40%50%70%
Thấp5%15%80%100%
  • Xác suất tử vong ở bảng trên được hiểu là xác suất dựa trên xác suất tử vong trung bình của từng loại virus. Chẳng hạn có một virus có xác suất tử vong trung bình là 50% và nhiễm lên vật chủ có khả năng miễn dịch trung bình thì xác suất tử vong cuối cùng sẽ là : 70% x 50% = 35%

Trong những năm 2019 đến nay, dòng virus Corona mới - tên chính thức là SARS-CoV-2 xuất hiện đã gây ra bao hậu quả tàn khốc cho khắp thế giới với hơn chục triệu người nhiễm bệnh và hơn nửa triệu người tử vong. Triệu chứng nhẹ của Covid-19 giống như cảm cúm thông thường như sốt, ho, mất vị giác trong vài ngày rồi khỏi. Triệu chứng nặng của Covid-19 bao gồm sốt cao, ho khan, khó thở và đôi lúc kèm theo đau đầu dữ dội. Tuy nhiên, Covid-19 gây tử vong với xác suất trung bình khá thấp là 3-5%.

Một dòng virus chết chóc khác là Ebola, có khả năng gây tử vong với tỷ lệ trung bình lên đến 50%. Triệu chứng nhẹ của Ebola trong vòng 2-3 tuần đầu bao gồm sốt, đau họng, đau cơ và đau đầu. Sau đó, nếu không may, người bệnh có thể gặp các triệu chứng nặng như bị nôn mửa, tiêu chảy và nặng nhất là xuất huyết cả ngoài lẫn bên trong.

Virus HIV tấn công hệ miễn dịch của con người, làm suy yếu nó để các virus/bệnh khác có cơ hội trỗi dậy. HIV chỉ gây các triệu chứng nhẹ giống cảm sốt trong thời gian ủ bệnh (từ 3 đến 5 năm). Nếu không may mắn được miễn dịch, đến giai đoạn AIDS, giai đoạn hệ miễn dịch đã bị suy yếu đáng kể, người bệnh sẽ gặp các triệu chứng nặng như mệt mỏi cực độ không giải thích được, sưng hạch kéo dài, lở loét, viêm phổi, tiêu chảy nặng và sau đó là khả năng tử vong trung bình cao đến 90%.

Hậu quả do virus gây ra (bao gồm cả tử vong) khi bị nhiễm có thể được giảm bớt bằng cách tiêm vaccine phòng ngừa. Nếu tiêm vaccine sẽ làm thay đổi xác suất gặp triệu chứng nặng/nhẹ/không triệu chứng/tử vong như sau (theo chiều hướng có lợi).

Không triệu chứngTriệu chứng nhẹTriệu chứng nặngTử vong
Cao70%25%5%40%
Trung bình20%50%30%60%
Thấp10%40%50%80%
  1. Áp dụng kiến thức Lập trình hướng đối tượng (kế thừa, đa hình), vẽ sơ đồ chi tiết các lớp đối tượng được mô tả trong đề bài (1.5đ).

    Viết chương trình mô phỏng thực nghiệm dịch tể học thực hiện các yêu cầu sau:

  2. Nhập danh sách N vật chủ (N <= 10000). Mỗi vật chủ được gắn mã số (là một chuỗi) để nhận diện theo dõi. Sau đó, cho từng vật chủ trong danh sách nhiễm tất cả 3 chủng virus kể trên (Covid-19, Ebola, HIV). (1.5đ).

  3. In ra mã các vật chủ và mô tả các triệu chứng của từng vật chủ (nếu có), cũng như cho biết vật chủ có bị tử vong hay không. (1.0đ).

  4. Nhập danh sách M vật chủ mới (trong đó M = N trước đó). Tiêm vaccine cho cả 3 loại virus. Cho từng vật chủ nhiễm cả 3 loại virus. In ra số lượng vật chủ gặp triệu chứng nặng (của bất kỳ virus nào bị nhiễm), số lượng vật chủ tử vong. (1.0đ).

Phân tích đề

Bước 1: Xác định các lớp

Khi đọc đề, ta có thể thấy ngay là sẽ có ít nhất là lớp đối tượng VatChu và 3 lớp đối tượng biểu diễn cho 3 loại virusSARS-CoV-2, EbolaHIV. Dễ thấy nhất về yếu tố kế thừa và đa hình trong bài này là nằm ở chỗ 3 loại virus có thể kế thừa từ một lớp đối tượng chung là Virus.

Một thành phần nữa cần được chú ý là khả năng miễn dịch của vật chủ. Về cơ bản, các bạn cũng có thể gộp luôn thành phần này là xem chúng như các thuộc tính của vật chủ. Tuy nhiên, nếu các bạn có thể nhận thấy sự lặp lại trong cấu trúc của các khả năng miễn dịch này, và có thể xem chúng như một dạng lớp đối tượng riêng, với các mức độ của khả năng miễn dịch lần lượt được kế thừa từ một lớp cơ sở chung là KhaNangMienDich. Điều này đôi khi có thể giúp chương trình mở rộng dễ dàng khi gặp yêu cầu chi tiết hơn, chẳng hạn như cần bổ sung thên khả năng miễn dịch

Trong bài viết này, chúng mình sẽ làm theo hướng xem khả năng miễn dịch như các lớp đối tượng riêng.

Bước 2: Thiết kế thuộc tính

Ban đầu, chúng ta có thể nghĩ ngay đến những thuộc tính của các đối tượng như sau: lớp đối tượng virus sẽ có thuộc tính xác suất gây tử vong trung bình, khả năng miễn dịch sẽ có những thuộc tính như xác suất gặp triệu chứng.

Tuy nhiên, nếu để ý kĩ thì ta có thể thấy đây là những thuộc tính không thay đổi, và nếu chúng ta chỉ cài đặt chúng như những thuộc tính và lấy chúng thông qua getter thì hoàn toàn không cần dùng tới tính đa hình. Vì vậy, chúng ta có thể coi chúng như những phương thức trả về giá trị cố định.

Ở lớp vật chủ, trước hết chúng ta cần có mã vật chủ, và câu hỏi sẽ là những thuộc tính còn lại, sẽ là gì? Trước hết chúng ta quan sát thấy, triệu chứng là những gì chúng ta sẽ cài đặt trên lớp virus, vì vậy nên để xuất ra triệu chứng thì chúng ta ít nhất phải lưu lại danh sách những virus vật chủ đã bị nhiễm. Nhưng còn triệu chứng của virus tác động lên mỗi vật chủ thì không phải của virus mà là của riêng vật chủ, và mỗi loại virus lại có thể gây nên tác động khác nhau cho vật chủ. Vì vậy, chúng ta cũng cần lưu thêm danh sách triệu chứng ứng với mỗi loại virus.

Ngoài ra còn một yếu tố nữa, đó là tình trạng tử vong của vật chủ. Giả sử chúng ta chỉ xác định riêng biệt tình trạng tử vong của từng loại virus với vật chủ, thì có thể xảy ra trường hợp virus trước đã gây tử vong cho vật chủ, khi vật chủ bị nhiễm thêm 1 loại virus nữa, nhưng không gây tử vong, thì vật chủ đó đã sống lại ??? Vì vậy trong vật chủ sẽ có một thuộc tính để xác định là đã tử vong hay chưa.

Vấn đề cuối cùng, là đề bài có đề cập đến việc tiêm vaccine, và vaccine này sẽ ảnh hưởng tới tỉ lệ mắc triệu chứng. Với trường hợp chia khả năng miễn dịch ra như chúng ta đã nói, đã tiêm vaccine hay chưa sẽ trở thành một thuộc tính của khả năng miễn dịch.

Bước 3: Thiết kế phương thức

Như lúc nãy đã nói ở trên, lớp virus sẽ có phương thức để lấy tỉ lệ tử vong trung bình, và từng loại virus sẽ trả về kết quả khác nhau, nên đây sẽ là phương thức (thuần) ảo ở lớp virus.

Tương tự là lớp khả năng miễn dịch cũng sẽ có các phương thức trả về khả năng miễn dịch các mức độ triệu chứng.

Và cuối cùng, ở lớp vật chủ, chúng ta sẽ dựa theo các yêu cầu đề bài để thiết kế các phương thức thích hợp.

Sơ đồ thiết kế lớp

Lời giải tham khảo

Lưu ý: Ở tất cả vị trí cần gọi random, lưu lại kết quả, nếu không 2 lần gọi random sẽ cho ra kết quả khác nhau.

Bài làm khi thi trên giấy không bắt buộc phải chia file như bài làm tham khảo bên dưới. Các bạn có thể trình bày dưới dạng 1 file main.cpp duy nhất.

Virus.h

#pragma once

#define LOAI_VIRUS_SARS 0
#define LOAI_VIRUS_EBOLA 1
#define LOAI_VIRUS_HIV 2

class Virus
{
public:
virtual float LayXacSuatTuVongTrungBinh() = 0;
virtual void XuatTrieuChungNhe() = 0;
virtual void XuatTrieuChungNang() = 0;
virtual int LayLoaiVirus() = 0;
};

VirusSARS.h

#pragma once
#include "Virus.h"
#include <iostream>

class VirusSARS : public Virus
{
public:
float LayXacSuatTuVongTrungBinh() override { return 0.05f; }
void XuatTrieuChungNhe() override {
std::cout << "Cam cum thong thuong nhu sot, ho, mat vi giac trong vai ngay.\n";
}
void XuatTrieuChungNang() override {
std::cout << "Sot cao, ho khan, kho tho va doi luc kem theo dau dau du doi.\n";
}
int LayLoaiVirus() { return LOAI_VIRUS_SARS; }
};

VirusEbola.h

#pragma once
#include "Virus.h"
#include <iostream>

class VirusEbola : public Virus
{
public:
float LayXacSuatTuVongTrungBinh() override { return 0.5; }
void XuatTrieuChungNhe() override {
std::cout << "Sot, dau hong, dau co va dau dau.\n";
}
void XuatTrieuChungNang() override {
std::cout << "Non mua, tieu chay va nang nhat la xuat huyet ca ngoai lan ben trong.\n";
}
int LayLoaiVirus() { return LOAI_VIRUS_EBOLA; }
};

VirusHIV.h

#pragma once
#include "Virus.h"
#include <iostream>

class VirusHIV : public Virus
{
public:
float LayXacSuatTuVongTrungBinh() override { return 0.9f; }
void XuatTrieuChungNhe() override {
std::cout << "Cam sot.\n";
}
void XuatTrieuChungNang() override {
std::cout << "He mien dich da bi suy yeu dang ke, met moi cuc do khong giai thich duoc, sung hach keo dai, lo loet, viem phoi, tieu chay nang.\n";
}
int LayLoaiVirus() { return LOAI_VIRUS_HIV; }
};

KhaNangMienDich.h

#pragma once
#include <map>

class KhaNangMienDich
{
protected:
// Ở đây, mình dùng map để có thể dễ mở rộng cho trường hợp 4, 5 loại virus cho các bạn muốn mở rộng thêm.
// Theo scope của đề bài, các bạn chỉ cần dùng mảng 3 phần tử cũng được.
std::map<int, bool> daTiemVaccine; // Đã tiêm vaccine cho từng loại virus
public:
virtual float LayMienDichKhongTrieuChung(int loaiVirus) = 0;
virtual float LayMienDichTrieuChungNhe(int loaiVirus) = 0;
virtual float LayMienDichTrieuChungNang(int loaiVirus) = 0;
virtual float LayXacSuatTuVong(int loaiVirus) = 0;

void TiemVaccine(int loaiVirus) { daTiemVaccine[loaiVirus] = true; }
};

KhaNangMienDichThap.h

#pragma once
#include "KhaNangMienDich.h"

class KhaNangMienDichThap : public KhaNangMienDich
{
public:
float LayMienDichKhongTrieuChung(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.1f;
return 0.05f;
}
float LayMienDichTrieuChungNhe(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.4f;
return 0.15f;
}
float LayMienDichTrieuChungNang(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.5f;
return 0.8f;
}
float LayXacSuatTuVong(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.8f;
return 1.f;
}
};

KhaNangMienDichTrungBinh.h

#pragma once
#include "KhaNangMienDich.h"

class KhaNangMienDichTrungBinh : public KhaNangMienDich
{
public:
float LayMienDichKhongTrieuChung(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.2f;
return 0.1f;
}
float LayMienDichTrieuChungNhe(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.5f;
return 0.4f;
}
float LayMienDichTrieuChungNang(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.3f;
return 0.5f;
}
float LayXacSuatTuVong(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.6f;
return 0.7f;
}
};

KhaNangMienDichCao.h

#pragma once
#include "KhaNangMienDich.h"

class KhaNangMienDichCao : public KhaNangMienDich
{
public:
float LayMienDichKhongTrieuChung(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.7f;
return 0.5f;
}
float LayMienDichTrieuChungNhe(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.25f;
return 0.35f;
}
float LayMienDichTrieuChungNang(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.5f;
return 0.15f;
}
float LayXacSuatTuVong(int loaiVirus) {
if (daTiemVaccine[loaiVirus])
return 0.4f;
return 0.5f;
}
};

VatChu.h

#pragma once
#include "KhaNangMienDich.h"
#include "Virus.h"
#include <vector>
#include <string>

#define TRIEUCHUNG_KHONGCO 0
#define TRIEUCHUNG_NHE 1
#define TRIEUCHUNG_NANG 2

class VatChu
{
private:
std::string maVatChu;
KhaNangMienDich* khaNangMienDich;
std::vector<Virus*> virusDaNhiem; // Danh sách các virus đã nhiễm
std::vector<int> trieuChung; // Triệu chứng của vật chủ ứng với từng loại virus
bool daTuVong;
private:
int Random(int a, int b); // Random 1 số x sao cho a <= x <= b
int XacDinhTrieuChung(Virus* virus);
bool XacDinhTuVong(Virus* virus);
public:
VatChu();
void Nhap();
void Xuat();

void ChoNhiemVirus(Virus* virus);
void TiemVaccine(int loaiVirus);

void XuatTrieuChung();
bool KiemTraTuVong();
bool KiemTraTrieuChungNang();

// Destructor không bắt buộc phải làm trong bài thi
~VatChu();
};

VatChu.cpp

#include "VatChu.h"
#include "KhaNangMienDichThap.h"
#include "KhaNangMienDichTrungBinh.h"
#include "KhaNangMienDichCao.h"
#include <iostream>

int VatChu::Random(int a, int b)
{
return rand() % (b - a + 1) + a;
}

int VatChu::XacDinhTrieuChung(Virus* virus)
{
float r = Random(0, 100) / 100.f;
float nguongMienDich = khaNangMienDich->LayMienDichKhongTrieuChung(virus->LayLoaiVirus());
if (r < nguongMienDich)
return TRIEUCHUNG_KHONGCO;
r -= nguongMienDich;
nguongMienDich = khaNangMienDich->LayMienDichTrieuChungNhe(virus->LayLoaiVirus());
if (r < nguongMienDich)
return TRIEUCHUNG_NHE;
return TRIEUCHUNG_NANG;
}

bool VatChu::XacDinhTuVong(Virus* virus)
{
float xsTuVong = khaNangMienDich->LayXacSuatTuVong(virus->LayLoaiVirus()) * virus->LayXacSuatTuVongTrungBinh();
return Random(0, 100) < xsTuVong * 100;
}

VatChu::VatChu()
{
maVatChu = "";
khaNangMienDich = nullptr;
daTuVong = false;
}

void VatChu::Nhap()
{
std::cout << "Nhap ma so: ";
std::cin >> maVatChu;

int mucDoMienDich;
std::cout << "Nhap muc do mien dich (0: thap, 1: trung binh, 2: cao): ";
std::cin >> mucDoMienDich;

switch (mucDoMienDich) {
case 0:
khaNangMienDich = new KhaNangMienDichThap();
break;
case 1:
khaNangMienDich = new KhaNangMienDichTrungBinh();
break;
case 2:
khaNangMienDich = new KhaNangMienDichCao();
break;
default:
break;
}
}

void VatChu::Xuat()
{
std::cout << "Ma vat chu: " << maVatChu << std::endl;
XuatTrieuChung();
if (daTuVong)
std::cout << "Vat chu da tu vong" << std::endl;
else
std::cout << "Vat chu chua tu vong" << std::endl;
}

void VatChu::ChoNhiemVirus(Virus* virus)
{
trieuChung.push_back(XacDinhTrieuChung(virus));
bool xdTuVong = XacDinhTuVong(virus);
if (xdTuVong)
daTuVong = xdTuVong; // Không gán trực tiếp daTuVong = xdTuVong mà không có if, vì bị tử vong mà tiêm 1 virus khác không gây tử vong thì cũng không "hồi sinh" lại được
virusDaNhiem.push_back(virus);
}

void VatChu::TiemVaccine(int loaiVirus) {
khaNangMienDich->TiemVaccine(loaiVirus);
}

void VatChu::XuatTrieuChung()
{
std::cout << "Trieu chung cua cac virus da nhiem la:" << std::endl;
for (int i = 0; i < virusDaNhiem.size(); ++i) {
if (virusDaNhiem[i]->LayLoaiVirus() == LOAI_VIRUS_SARS)
std::cout << "Trieu chung khi bi nhiem virus SARS-CoV-2 la: ";
else if (virusDaNhiem[i]->LayLoaiVirus() == LOAI_VIRUS_EBOLA)
std::cout << "Trieu chung khi bi nhiem virus Ebola la: ";
else if (virusDaNhiem[i]->LayLoaiVirus() == LOAI_VIRUS_HIV)
std::cout << "Trieu chung khi bi nhiem virus HIV la: ";

switch (trieuChung[i]) {
case TRIEUCHUNG_KHONGCO:
std::cout << "Khong co trieu chung" << std::endl;
break;
case TRIEUCHUNG_NHE:
virusDaNhiem[i]->XuatTrieuChungNhe();
break;
case TRIEUCHUNG_NANG:
virusDaNhiem[i]->XuatTrieuChungNang();
break;
default:
break;
}
}
}

bool VatChu::KiemTraTuVong()
{
return daTuVong;
}

bool VatChu::KiemTraTrieuChungNang()
{
for (int tc : trieuChung)
if (tc == TRIEUCHUNG_NANG)
return true;
return false;
}

VatChu::~VatChu()
{
delete khaNangMienDich;
for (auto& virus : virusDaNhiem)
delete virus;
}

main.cpp

#include <iostream>
#include "KhaNangMienDichThap.h"
#include "KhaNangMienDichTrungBinh.h"
#include "KhaNangMienDichCao.h"
#include "VirusSARS.h"
#include "VirusEbola.h"
#include "VirusHIV.h"
#include "VatChu.h"

using namespace std;

int main()
{
srand((unsigned int)time(0));
int n;
cout << "Nhap so luong vat chu: ";
cin >> n;

vector<VatChu*> dsVatChu;
int countTuVong = 0;
for (int i = 0; i < n; ++i) {
cout << "Nhap vat chu thu " << i + 1 << ":" << endl;

VatChu* vc = new VatChu();
vc->Nhap();

vc->ChoNhiemVirus(new VirusSARS());
vc->ChoNhiemVirus(new VirusEbola());
vc->ChoNhiemVirus(new VirusHIV());

dsVatChu.push_back(vc);
}

for (int i = 0; i < dsVatChu.size(); ++i)
dsVatChu[i]->Xuat();

cout << endl;
cout << "Nhap M = " << n << " vat chu moi:" << endl;
dsVatChu.clear();
for (int i = 0; i < n; ++i) {
cout << "Nhap vat chu thu " << i + 1 << ":" << endl;

VatChu* vc = new VatChu();
vc->Nhap();

vc->TiemVaccine(LOAI_VIRUS_SARS);
vc->TiemVaccine(LOAI_VIRUS_EBOLA);
vc->TiemVaccine(LOAI_VIRUS_HIV);

vc->ChoNhiemVirus(new VirusSARS());
vc->ChoNhiemVirus(new VirusEbola());
vc->ChoNhiemVirus(new VirusHIV());

dsVatChu.push_back(vc);
}
int soLuongTrieuChungNang = 0;
int soLuongTuVong = 0;

for (int i = 0; i < dsVatChu.size(); ++i)
soLuongTrieuChungNang += dsVatChu[i]->KiemTraTrieuChungNang(),
soLuongTuVong += dsVatChu[i]->KiemTraTuVong();

cout << "So luong vat chu gap trieu chung nang: " << soLuongTrieuChungNang << endl;
cout << "So luong vat chu tu vong: " << soLuongTuVong << endl;
}

Đề HK1 2022-2023

Một tổ chức chuyên trưng bày, mua bán các sản phẩm liên quan đến nghệ thuật đang muốn xây dựng một ứng dụng để quản lý các hoá đơn khi bán các sản phẩm. Mỗi lần bán sản phẩm thành công, cửa hàng sẽ lưu trữ các hoá đơn chứa thông tin sản phẩm liên quan. Mỗi hoá đơn sẽ có thông tin: mã hoá đơn, thông tin khách hàng, ngày lập hoá đơn, danh sách sản phẩm, tổng giá (tổng giá trị các sản phẩm trong đơn hàng). Tổ chức này hiện tại chỉ kinh doanh 2 loại sản phẩm: tranh ảnh và CD âm nhạc (tương lai có thể thay đổi sản phẩm kinh doanh khác). Mỗi sản phẩm sẽ có thông tin chung cần quản trị: mã số, tiêu đề, giá bán. Ngoài thông tin chung, các sản phẩm tranh ảnh cần thêm thông tin kích thước của bức tranh (chiều rộng, chiều cao), tên hoạ sĩ. Sản phẩm CD âm nhạc sẽ có thêm tên ca sĩ, tên đơn vị sản xuất. Mỗi khách hàng sẽ được lưu trữ các thông tin: mã khách hàng, tên khách hàng, số điện thoại.

Áp dụng tư tưởng lập trình hướng đối tượng (có sử dụng kế thừa, đa hình), anh/chị hãy:

  1. (1.5 điểm) Thiết kế và vẽ sơ đồ lớp cho ứng dụng theo bài toán được mô tả
  2. Cài đặt chi tiết theo sơ đồ lớp đã thiết kế và cũng như thành phần cần thiết khác để xây dựng chương trình thực hiện các tính năng sau:
    • (1.5 điểm) Nhập và xuất danh sách các hoá đơn bán hàng 
    • (1 điểm) Tính tổng thu nhập của cửa hàng 
    • (1 điểm) Tìm các khách hàng mua nhiều nhất ở cửa hàng (dựa vào tổng giá trị các hoá đơn khách hàng đã mua). 

Lưu ý: Các thông tin trong đề thi chỉ mang tính chất giả sử, KHÔNG nhất thiết phải đúng hoặc khớp với các thông tin hiện tại trong thế giới thực. Sinh viên cần bám sát các mô tả trong đề thi và vận dụng kiến thức về lập trình hướng đối tượng để làm bài theo yêu cầu.

Phân tích đề

Bước 1: Xác định các lớp

Vừa đọc đề ta thấy yêu cầu là phải quản lí các hóa đơn nên tất nhiên là phải có lớp Hóa đơnDanh sách hóa đơn. Bên trong lớp Hóa đơn sẽ có thêm lớp Người để biểu diễn thông tin khách hàng, lớp Ngày để mô tả ngày lập hóa đơn, và lớp Sản phẩm. Có 2 lớp con kế thừa từ lớp Sản phẩm là Tranh ảnhCD âm nhạc.

Bước 2: Thiết kế thuộc tính

Lớp Danh sách hóa đơn sẽ là một mảng các Hóa đơn (vì chỉ có 1 loại hóa đơn nên không cần dùng con trỏ). Lớp Hóa đơn có 5 thông tin cần thể hiện, trong đó danh sách sản phẩm được biểu diễn bằng 1 mảng các con trỏ thuộc lớp Sản phẩm (vì có nhiều loại sản phẩm nên sử dụng con trỏ để áp dụng tính đa hình). Đề này đã chỉ rõ luôn cho ta biết các thuộc tính nào là chung, riêng của lớp Sản phẩm, Tranh ảnh và CD âm nhạc.

Bước 3: Thiết kế phương thức

  • Các phương thức nhập, xuất cho các lớp. Phương thức nhập, xuất ở lớp Sản phẩm là phương thức ảo.
  • Phương thức tính tổng giá các sản phẩm ở lớp Hóa đơn, tính tổng giá các hóa đơn ở lớp Danh sách.
  • Phương thức tìm max ở lớp danh sách hóa đơn.

Ngoài ra cần định nghĩa thêm các phương thức truy vấn phù hợp.

Sơ đồ thiết kế lớp

Lời giải tham khảo

#include<iostream>
#include<string>
#include<map>
using namespace std;

class Nguoi {
private:
int maKhach;
string ten;
string SDT;
public:
void Nhap();
void Xuat();
int getMaKhach();
};

class Ngay {
private:
int ngay;
int thang;
int nam;
public:
void Nhap();
void Xuat();
};

class SanPham {
protected:
string maSo;
string tieuDe;
long gia;
public:
virtual void Nhap();
virtual void Xuat();
long getGia();
};

class Tranh :public SanPham {
protected:
int rong;
int cao;
string hoaSi;
public:
void Nhap();
void Xuat();
};

class CD :public SanPham {
protected:
string caSi;
string donVi;
public:
void Nhap();
void Xuat();
};

class HoaDon {
protected:
string maDon;
Nguoi khach;
Ngay ngayCap;
int n;
SanPham* hd[100];
long tongGia;
public:
void Nhap();
void Xuat();
long getTongGia();
Nguoi getKhach();
};

class DanhSach {
protected:
int size;
HoaDon ds[100];
public:
void Nhap();
void Xuat();
long ThuNhap();
void TimKiemMax();
};

void Ngay::Nhap() {
cout << "Nhap ngay: ";
cin >> ngay;
cout << "Nhap thang: ";
cin >> thang;
cout << "Nhap nam: ";
cin >> nam;
}
void Ngay::Xuat() {
cout << ngay << "/" << thang << "/" << nam << endl;
}

void Nguoi::Nhap() {
cout << "Nhap ma khach hang: ";
cin >> maKhach;
cout << "Nhap ho ten: ";
cin >> ten;
cout << "Nhap so dien thoai: ";
cin >> SDT;
}
void Nguoi::Xuat() {
cout << "Ma khach hang: " << maKhach << endl;
cout << "Ho ten: " << ten << endl;
cout << "So dien thoai: " << SDT << endl;
}
int Nguoi::getMaKhach() {
return maKhach;
}

void SanPham::Nhap() {
cout << "Nhap ma so: ";
cin >> maSo;
cout << "Nhap tieu de: ";
cin >> tieuDe;
cout << "Nhap gia ban: ";
cin >> gia;
}
void SanPham::Xuat() {
cout << "Ma so: " << maSo << endl;
cout << "Tieu de: " << tieuDe << endl;
cout << "Gia ban: " << gia << endl;
}
long SanPham::getGia() {
return gia;
}

void Tranh::Nhap() {
SanPham::Nhap();
cout << "Nhap kich thuoc: " << endl;
cout << "Nhap chieu rong: ";
cin >> rong;
cout << "Nhap chieu cao: ";
cin >> cao;
cout << "Nhap ten hoa si: ";
cin >> hoaSi;
}
void Tranh::Xuat() {
SanPham::Xuat();
cout << "Chieu rong: " << rong << endl;
cout << "Chieu cao: " << cao << endl;
cout << "Ten hoa si: " << hoaSi << endl;
}

void CD::Nhap() {
SanPham::Nhap();
cout << "Nhap ten ca si: ";
cin >> caSi;
cout << "Nhap don vi san xuat: ";
cin >> donVi;
}
void CD::Xuat() {
SanPham::Xuat();
cout << "Ten ca si: " << caSi << endl;
cout << "Don vi san xuat: " << donVi << endl;
}

void HoaDon::Nhap() {
cout << "Nhap ma hoa don: ";
cin >> maDon;
cout << "Nhap thong tin khach hang: " << endl;
khach.Nhap();
cout << "Nhap ngay cap: " << endl;
ngayCap.Nhap();
cout << "Nhap so luong san pham: ";
cin >> n;
for (int i = 0; i < n; i++) {
cout << "Nhap san pham thu " << i + 1 << endl;
int loai;
cout << "Nhap loai (0. Tranh anh, 1. CD): ";
cin >> loai;
if (loai == 0) {
hd[i] = new Tranh();
}
else {
hd[i] = new CD();
}
hd[i]->Nhap();
}
}
void HoaDon::Xuat() {
cout << "Ma hoa don: " << maDon << endl;
cout << "Ngay cap: ";
ngayCap.Xuat();
cout << "Thong tin khach hang: " << endl;
cout << "Tong gia tien: " << getTongGia() << endl;
khach.Xuat();
for (int i = 0; i < n; i++) {
cout << "San pham thu " << i + 1 << endl;
hd[i]->Xuat();
}
}
long HoaDon::getTongGia() {
tongGia = 0;
for (int i = 0; i < n; i++) {
tongGia += hd[i]->getGia();
}
return tongGia;
}
Nguoi HoaDon::getKhach() {
return khach;
}

void DanhSach::Nhap() {
cout << "Nhap so luong hoa don: ";
cin >> size;
for (int i = 0; i < size; i++) {
cout << "Nhap hoa don thu " << i + 1 << endl;
ds[i].Nhap();
}
}
void DanhSach::Xuat() {
for (int i = 0; i < size; i++) {
cout << "Hoa don thu " << i + 1 << endl;
ds[i].Xuat();
}
}
long DanhSach::ThuNhap() {
int s = 0;
for (int i = 0; i < size; i++) {
s += ds[i].getTongGia();
}
return s;
}
void DanhSach::TimKiemMax() {
map<int, long> mp;
for (int i = 0; i < size; ++i) {
int id = ds[i].getKhach().getMaKhach();
mp[id] += ds[i].getTongGia();
}
long max = 0;
for (const auto& pair : mp) {
if (pair.second > max) {
max = pair.second;
}
}
cout << "ID cac khach mua nhieu nhat: \n";
for (const auto& pair : mp) {
if (pair.second == max) {
cout << pair.first << endl;
}
}
}

int main() {
DanhSach l;
l.Nhap();
l.Xuat();
cout << "Tong thu nhap: " << l.ThuNhap() << endl;
l.TimKiemMax();
}

Đề 2019-2020

Trước hết phải khẳng định, đất đai là nguồn tài nguyên vô cùng quý giá, là tài sản quan trọng của quốc gia, là tư liệu sản xuất,… Đặc biệt, đất đai là điều kiện cần cho mọi hoạt động sản xuất và đời sống. Ở nước ta, khi còn nhiều người sống nhờ vào nông nghiệp, thì đất đai càng trở thành nguồn lực rất quan trọng.

Muốn phát huy tác dụng của nguồn lực đất đai, ngoài việc bảo vệ đất của quốc gia, còn phải quản lý đất đai hợp lý, nâng cao hiệu quả sử dụng đất sao cho vừa đảm bảo được lợi ích trước mắt, vừa tạo điều kiện sử dụng đất hiệu quả lâu dài để phát triển bền vững đất nước.

Hiện nay, ở Việt Nam đất đai được phân chia thành 2 loại chính sau:

  • Đất nông nghiệp.
  • Đất phi nông nghiệp (đất ở).

Quan điểm nhất quán của Đảng, Nhà nước và nhân dân ta đã được xác định từ năm 980 đến nay là đất đai thuộc sở hữu toàn dân, do Nhà nước đại diện chủ sở hữu và thống nhất quản lý. Để góp phần nâng cao hiệu quả quản lý nhà nước về đất đai, mỗi thửa đất được nhà nước quản lý và cấp quyền sử dụng cho một hoặc nhiều người dân (nhà nước cho phép nhiều người dân có thể đồng sở hữu quyền sử dụng đất) có nhu cầu sử dụng (Giấy chứng nhận quyền sử dụng đất hay còn được gọi là Sổ hồng).

  • Với các thửa đất nông nghiệp, thông tin cần quản lý gồm: số giấy chứng nhận (chuỗi), người sở hữu quyền sử dụng đất (gồm họ và tên, năm sinh, CMND, địa chỉ thường trú), số thửa đất, số tờ bản đồ, địa chỉ thửa đất, diện tích (m2m^2), thời gian sử dụng (được sử dụng đến năm nào), ngày cấp, đơn giá thuế phải đóng cho nhà nước hàng năm /1m2/ 1 m^2
  • Với các thửa đất phi nông nghiệp (đất ở), thông tin cần quản lý gồm: số giấy chứng nhận (chuỗi), người sở hữu quyền sử dụng đất (gồm họ và tên, năm sinh, CMND, địa chỉ thường trú), số thửa đất, số tờ bản đồ, địa chỉ thửa đất, diện tích (m2m^2), ngày cấp, đơn giá thuế phải đóng cho nhà nước hàng năm /1m2/ 1 m^2

Áp dụng kiến thức lập trình hướng đối tượng (kế thừa, đa hình) thiết kế sơ đồ chi tiết các lớp đối tượng (1 điểm) và khai báo các lớp (1 điểm) để xây dựng chương trình thực hiện các yêu cầu sau:

  1. Tạo danh sách các giấy chứng nhận quyền sử dụng đất mà nhà nước đã cấp cho người dân. (1 điểm)
  2. Tính tiền thuế mà người sử dụng đất phải đóng cho nhà nước và cho biết thửa đất nào (thông tin thửa đất) có tiền thuế phải đóng nhiều nhất. (1 điểm)
  3. Xuất ra màn hình thông tin các thửa đất nông nghiệp đã hết thời hạn sử dụng (năm sử dụng < năm hiện tại). (1 điểm)

Lưu ý: Các thông tin trong đề chỉ mô phỏng các thông tin với mục tiêu để sinh viên vận dụng kiến thức lập trình hướng đối tượng. Do vậy, các thông tin trong đề KHÔNG nhất thiết phải đúng hoặc khớp với các thông tin hiện tại trong thế giới thực. Sinh viên cần bám sát các mô tả trong đề thi để làm bài.

Phân tích đề

Bước 1: Xác định các lớp

Vừa đọc đề ta thấy được có hai lớp đối tượng chính là Đất nông nghiệpĐất ở. Ta sẽ tạo ra một lớp cơ sở có tên là Đất đai để 2 lớp trên kế thừa. Theo yêu cầu ở câu 1 và để tiện làm các câu 2, 3 thì ta sẽ tạo thêm lớp Danh sách đất.

Bước 2: Thiết kế thuộc tính

Ngoại trừ thuộc tính thời gian sử dụng chỉ có ở lớp Đất nông nghiệp, các thuộc tính còn lại ở 2 lớp con đều giống nhau nên ta sẽ cho tất cả các thuộc tính chung nằm ở lớp cơ sở Đất đai. Lớp Đất nông nghiệp khi kế thừa từ lớp Đất đai sẽ bổ sung thêm thuộc tính thời gian sử dụng, còn lớp Đất ở khi kế thừa từ lớp Đất đai thì sẽ không cần định nghĩa thêm thuộc tính nào nữa. Lớp

Cả 2 lớp con đều cần lưu thông tin của người sở hữu, mỗi người sỡ hữu thì lại có nhiều thông tin nhỏ khác, thế nên ta sẽ tạo thêm một lớp đối tượng là Người sỡ hữu. Tương tự thì cũng nên tạo thêm lớp Ngày để biểu diễn cho thuộc tính ngày cấp.

Lớp Danh sách đất sẽ là một mảng các con trỏ thuộc lớp Đất đai để có thể áp dụng tính đa hình

Bước 3: Thiết kế phương thức

  1. Đa số các bài dạng này đều cần phải làm các phương thức nhập, xuất cho từng lớp đối tượng. Các lớp nhập, xuất ở lớp cơ sở sẽ là phương thức ảo.

  2. Tạo phương thức tính tiền ở lớp cơ sở, cả 2 loại đất có cách thức tính tiền như nhau nên không cần là phương thức ảo. Phương thức tìm đất có thuế nhiều nhất ở lớp Danh sách.

  3. Tạo một phương thức kiểm tra ở lớp cơ sở để xem một loại đất có phải nông nghiệp không. Nó sẽ là phương thức ảo, phiên bản ở lớp nông nghiệp sẽ trả về giá trị true, phiên bản ở lớp đất ở trả về giá trị false. Ngoài ra lớp nông nghiệp sẽ có thêm phương thức kiểm tra hết hạn.

Sơ đồ thiết kế lớp

Lời giải tham khảo

#include <iostream>
#include <string>
using namespace std;

class NguoiSoHuu {
private:
string tenNguoi;
int namSinh;
string cmnd;
string diaChi;
public:
void Nhap() {
cout << "Nhap ten nguoi so huu: ";
cin >> tenNguoi;
cin.ignore();
cout << "Nhap nam sinh: ";
cin >> namSinh;
cout << "Nhap cmnd: ";
cin >> cmnd;
cin.ignore();
cout << "Nhap dia chi nguoi so huu: ";
cin >> diaChi;
cin.ignore();
}
void Xuat() {
cout << tenNguoi << endl << namSinh << endl
<< cmnd << endl << diaChi << endl;
}
};

class Ngay {
private:
int ngay;
int thang;
int nam;
public:
void Nhap() {
cout << "Nhap ngay: ";
cin >> ngay;
cout << "Nhap thang: ";
cin >> thang;
cout << "Nhap nam: ";
cin >> nam;
}
void Xuat() {
cout << ngay << "/" << thang << "/" << nam << endl;
}
int getNam()
{
return nam;
}
};

class DatDai {
protected:
string soGiay;
NguoiSoHuu nguoi;
int soThua;
int dienTich;
Ngay ngayCap;
string diaChiDat;
int soTo;
int giaThue;
public:
virtual void Nhap() {
cout << "Nhap so giay: ";
cin >> soGiay;
cin.ignore();
cout << "Nhap nguoi so huu:\n";
nguoi.Nhap();
cout << "Nhap so thua dat: ";
cin >> soThua;
cout << "Nhap dien tich dat: ";
cin >> dienTich;
cout << "Nhap ngay cap: ";
ngayCap.Nhap();
cout << "Nhap dia chi dat: ";
cin >> diaChiDat;
cin.ignore();
cout << "Nhap so to ban do: ";
cin >> soTo;
cout << "Nhap don gia thue: ";
cin >> giaThue;
}
virtual void Xuat() {
cout << soGiay << endl;
nguoi.Xuat();
cout << soThua << endl << dienTich << endl;
ngayCap.Xuat();
cout << diaChiDat << endl << soTo << endl << giaThue << endl;
}
virtual bool KiemTraNongNghiep() {
return false;
}
int TinhThue() {
return dienTich * giaThue;
}
};

class DatNongNghiep : public DatDai {
protected:
Ngay thoiHan;
public:
void Nhap() {
DatDai::Nhap();
cout << "Nhap thoi han su dung: ";
thoiHan.Nhap();
}
void Xuat() {
DatDai::Xuat();
thoiHan.Xuat();
}
bool KiemTraThoiHan() {
if (thoiHan.getNam() < 2023)
return true;
return false;
}
bool KiemTraNongNghiep() {
return true;
}
};

class DatO : public DatDai {};

class DanhSachDat {
protected:
DatDai* arr[50];
int size;
public:
void Nhap() {
cout << "Nhap so luong dat: ";
cin >> size;
for (int i = 0; i < size; ++i) {
cout << "Nhap kieu dat: 1 = Nong nghiep, 2 = Dat o: ";
int type;
cin >> type;
if (type == 1) {
arr[i] = new DatNongNghiep();
}
if (type == 2) {
arr[i] = new DatO();
}
arr[i]->Nhap();
}
}
DatDai* TimThueNhieuNhat() {
int maxIndex = 0;
for (int i = 1; i < size; ++i) {
if (arr[i]->TinhThue() > arr[maxIndex]->TinhThue()) {
maxIndex = i;
}
}
return arr[maxIndex];
}
void XuatDatNongNghiepHetHan() {
for (int i = 0; i < size; ++i) {
if (arr[i]->KiemTraNongNghiep()) {
if (((DatNongNghiep*)arr[i])->KiemTraThoiHan()) {
arr[i]->Xuat();
}
}
}
}
};

int main() {
DanhSachDat item;
item.Nhap();
DatDai* kq = item.TimThueNhieuNhat();
kq->Xuat();
item.XuatDatNongNghiepHetHan();
return 0;
}

Đề 2017-2018

Đầu những năm 1900, dựa trên sự hiện diện của các kháng nguyên trên màng hồng cầu, các nhà khoa học đã xác định rằng con người có 4 nhóm máu khác nhau: O, A, BAB. Hệ thống phân loại nhóm máu này (gọi là hệ thống nhóm máu ABO) cung cấp cho bác sĩ các thông tin quan trọng để lựa chọn nhóm máu phù hợp trong việc truyền máu. Và đồng thời có thể tiên đoán được nhóm máu tương đối của người con dựa trên nhóm máu của cha mẹ theo cơ chế di truyền học.

Nhóm máu của người con khi biết được nhóm máu của cha và mẹ:

alt text

Ngoài ra còn có thêm hệ thống phân loại Rh (Rhesus)

Căn cứ vào sự khác biệt khi nghiên cứu về sự vận chuyển oxy của hồng cầu thì các hồng cầu có thể mang ở mặt ngoài một protein gọi là Rhesus. Nếu có kháng nguyên D thì là nhóm Rh+ (dương tính), nếu không có là Rh- (âm tính). Các nhóm máu A, B, O, AB mà Rh- thì được gọi là âm tính A-, B-, O-, AB-. Nhóm máu Rh- chỉ chiếm 0,04% dân số thế giới. Đặc điểm của nhóm máu Rh này là chúng chỉ có thể nhận và cho người cùng nhóm máu, đặc biệt phụ nữ có nhóm máu Rh- thì con rất dễ tử vong.

Người có nhóm máu Rh+ chỉ có thể cho người cũng có nhóm máu Rh+ và nhận người có nhóm máu Rh+ hoặc Rh-

Người có nhóm máu Rh- có thể cho người có nhóm máu Rh+ hoặc Rh- nhưng chỉ nhận được người có nhóm máu Rh- mà thôi

Trường hợp người có nhóm máu Rh- được truyền máu Rh+, trong lần đầu tiên sẽ không có bất kỳ phản ứng tức thì nào xảy ra nhưng nếu tiếp tục truyền máu Rh+ lần thứ 2 sẽ gây ra những hậu quả nghiêm trọng do tai biến truyền máu. Tương tự với trường hợp mẹ Rh- sinh con (lần đầu và lần thứ hai trở đi).

alt text

Áp dụng kiến thức lập trình hướng đối tượng (kế thừa, đa hình) thiết kế sơ đồ chi tiết các lớp đối tượng (1.5 điểm) và xây dựng chương trình thực hiện các yêu cầu sau:

  1. Nhập danh sách các nhóm máu của một nhóm người. (1 điểm)
  2. Cho một bộ 3 nhóm máu của 3 người là cha, mẹ, con. Hãy kiểm tra và đưa ra kết quả nhóm máu có phù hợp với quy luật di truyền hay không? (1 điểm)
  3. Chọn một người X trong danh sách. Hãy liệt kê tất cả các người còn lại trong danh sách có thể cho máu người X này. (1 điểm)

Lưu ý: Trong trường hợp sinh viên không biết về nhóm máu và di truyền học trước đây thì phải đọc kỹ thông tin trên (các thông tin trên đủ để sinh viên thực hiện các yêu cầu của đề thi) và nghiêm túc làm bài. Giám thị coi thi không giải thích gì thêm.

Phân tích đề

Bước 1: Xác định được các lớp đối tượng có trong đề bài

Từ các câu hỏi, ta có thể thấy vấn đề cần giải quyết đều liên quan tới danh sách nhóm máu của từng người. Vì vậy, ta nên thiết kế một lớp DanhSach để phục vụ cho việc giải quyết các vấn đề ở từng câu.

Vì đối tượng chủ yếu được đề cập trong đề thi là các nhóm máu(tổng quát) mà cụ thể là những nhóm A, B, AB , O. Cùng với mục đích áp dụng kiến thức về kế thừa, đa hình, ta sẽ xây dựng một lớp NhomMau là lớp cơ sở còn những lớp A, B, AB, O là các lớp dẫn xuất kế thừa từ lớp NhomMau.

Bước 2: Thiết kế thuộc tính

Đối với lớp DanhSach, thuộc tính cần thiết là một mảng con trỏ đối tượng thuộc lớp NhomMau để gọi thực hiện các phương thức ảo theo cơ chế đa xạ (có thể lựa chọn cấp phát động, cấp phát tĩnh,…)

Từ các thông tin của đề thi, ta có thể xác định các thuộc tính chung của các nhóm máu là Rh và các lớp con không có thuộc tính riêng nào.

Bước 3: Thiết kế phương thức:

Từ các câu hỏi, ta xác định được những phương thức cần định nghĩa ở lớp DanhSach là phương thức nhập thông tin; phương thức kiểm tra theo quy luật di truyền để xác định nhóm máu của cha, mẹ, con có phù hợp hay không; phương thức kiểm tra xem nhóm máu người cho có phù hợp với người nhận. Đây là các phương thức nên được ưu tiên định nghĩa trước.

Ở lớp NhomMau, ta cần định nghĩa các phương thức để hỗ trợ cho việc định nghĩa các hàm thành phần của lớp DanhSach:

  • Phương thức nhập thông tin: Cụ thể hơn là nhập Rh, không cần nhập tên nhóm máu A, B, AB hay O vì các thông tin này sẽ được nhập khi gọi thực hiện phương thức nhập thông tin của lớp DanhSach. Ở lớp cha, phương thức này là phương thức không ảo vì các lớp con không có thuộc tính riêng(không cần ghi đè).
  • Các phương thức kiểm tra: Do các quy luật ứng với từng nhóm máu là khác nhau nên những phương thức có nhiệm vụ tương tự cần được định nghĩa lại ở các lớp dẫn xuất. Vì vậy, ở lớp NhomMau, các phương thức kiểm tra nên là phương thức thuần ảo.
  • Các phương thức truy vấn: Hai thông tin cần được truy vấn để hỗ trợ cho việc định nghĩa các phương thức kiểm tra là tên nhóm máu và Rh. Phương thức truy vấn đến Rh là phương thức không ảo vì lí do tương tự phương thức nhập thông tin. Ngược lại, phương thức truy vấn đến tên nhóm máu nên là phương thức thuần ảo để giá trị trả về là loại máu của đối tượng đang gọi thực hiện phương thức.

Ở các lớp dẫn xuất, việc định nghĩa lại các phương thức truy vấn chỉ đơn giản là trả về tên nhóm máu của lớp (có thể là kiểu kí tự hoặc kiểu dữ liệu khác theo quy ước mỗi người). Để định nghĩa các phương thức kiểm tra, ta cần đọc kĩ thông tin của đề kết hợp với kiến thức bản thân để tìm ra những cách kiểm tra tổng quát nhất theo từng quy luật. Sau đây là một số nhận định rút ra từ đề thi giúp ta định nghĩa những phương thức này nhanh hơn:

  • Nếu cha hoặc mẹ có nhóm máu A hoặc AB thì con có thể có nhóm máu A.
  • Nếu cha hoặc mẹ có nhóm máu B hoặc AB thì con có thể có nhóm máu B
  • Nếu cha và mẹ có nhóm máu AB thì con không thể có nhóm máu O
  • Nếu cha và mẹ có nhóm máu giống nhau nhưng khác nhóm máu AB hoặc một trong hai người có nhóm máu O thì con không thể có nhóm máu AB
  • Người có nhóm máu A chỉ nhận được nhóm máu A và O(chưa xét Rh)
  • Người có nhóm máu B chỉ nhận được nhóm máu B và O(chưa xét Rh)
  • Người có nhóm máu O chỉ nhận được nhóm máu O(chưa xét Rh)
  • Người có nhóm máu AB có thể nhận được tất cả nhóm máu (chưa xét Rh)
  • Người có nhóm máu Rh+ có thể nhận nhóm máu Rh+ hoặc Rh-
  • Người có nhóm máu Rh- có thể cho người có nhóm máu Rh+ hoặc Rh-

Sơ đồ thiết kế lớp

Lời giải tham khảo

#include<iostream>
using namespace std;

class NhomMau {
protected:
char Rh;
public:
void Nhap();/*Các lớp con không có thuộc tính riêng nên hàm Nhap() không cần là phương thức ảo*/
virtual char getMau() = 0;/*Lấy nhóm máu của các lớp con(không xét Rh) để thực hiện các phương thức kiểm tra*/
char getRh();//Tương tự như phương thức Nhap()
virtual bool KiemTraChaMe(NhomMau*, NhomMau*) = 0;
//Kiểm tra xem nhóm máu cha mẹ có phù hợp với nhóm máu của conn
virtual bool KiemTraTuongThich(NhomMau*) = 0;
//Kiểm tra nhóm máu người cho có phù hợp
};

class DanhSach { // Lớp DanhSach dùng để quản lí các nhóm mau
protected:
int n; // Số lượng loại máu
NhomMau* ds[100]; //Mảng ds dùng để quản lí nhóm máu của từng người
public:
void Nhap(); //Nhập danh sách nhóm máu của từng người
bool KiemTraDiTruyen(); //Kiểm tra nhóm máu của cha, mẹ, con
void TimKiemNguoiCho(int); //Xuất ra chỉ số của những người cho phù hợp
};

class A : public NhomMau {
public:
char getMau();
bool KiemTraChaMe(NhomMau*, NhomMau*);
bool KiemTraTuongThich(NhomMau*);
};

class B : public NhomMau {
public:
char getMau();
bool KiemTraChaMe(NhomMau*, NhomMau*);
bool KiemTraTuongThich(NhomMau*);
};

class O : public NhomMau {
public:
char getMau();
bool KiemTraChaMe(NhomMau*, NhomMau*);
bool KiemTraTuongThich(NhomMau*);
};

class AB : public NhomMau {
public:
char getMau();
bool KiemTraChaMe(NhomMau*, NhomMau*);
bool KiemTraTuongThich(NhomMau*);
};

//Định nghĩa các phương thức cho câu 1
void NhomMau::Nhap() {
cout << "Nhap Rh: ";
cin >> Rh;
}
void DanhSach::Nhap() {
cout << "Nhap so luong nguoi: ";
cin >> n;
for (int i = 0; i < n; i++) {
char loai;
cout << "Nhap loai mau (nhap C thay cho AB) thu [" << i << "]: ";
cin >> loai;
switch (loai) {
case 'A':
ds[i] = new A;
break;
case 'B':
ds[i] = new B;
break;
case 'O':
ds[i] = new O;
break;
case 'C':
ds[i] = new AB;
}
ds[i]->Nhap();
}
}

//Định nghĩa các phương thức cho câu 2
char A::getMau() {
return 'A';
}
char B::getMau() {
return 'B';
}
char O::getMau() {
return 'O';
}
char AB::getMau() {
return 'C';
}
char NhomMau::getRh() {
return Rh;
}
bool A::KiemTraChaMe(NhomMau* cha, NhomMau* me) {
char mauCha = cha->getMau();
char mauMe = me->getMau();
//Nếu cha hoặc mẹ có nhóm máu A hoặc AB thì con có thể có nhóm máu A
if (mauCha == 'A' || mauCha == 'C' || mauMe == 'A' || mauMe == 'C') {
return true;
}
return false;
}
bool B::KiemTraChaMe(NhomMau* cha, NhomMau* me) {
char mauCha = cha->getMau();
char mauMe = me->getMau();
//Nếu cha hoặc mẹ có nhóm máu B hoặc AB thì con có thể có nhóm máu B
if (mauCha == 'B' || mauCha == 'C' || mauMe == 'B' || mauMe == 'C') {
return true;
}
return false;
}
bool O::KiemTraChaMe(NhomMau* cha, NhomMau* me) {
char mauCha = cha->getMau();
char mauMe = me->getMau();
//Nếu cha và mẹ có nhóm máu AB thì con không thể có nhóm máu O
if (mauCha == 'C' && mauMe == 'C') {
return false;
}
return true;
}
bool AB::KiemTraChaMe(NhomMau* cha, NhomMau* me) {
char mauCha = cha->getMau();
char mauMe = me->getMau();
/*Các trường hợp con không thể có nhóm máu AB:
- Cha và mẹ có nhóm máu giống nhau nhưng khác nhóm máu AB
- Cha hoặc mẹ có nhóm máu O*/
if ((mauCha == mauMe && mauMe != 'C') || mauCha == 'O' ||mauMe == 'O') {
return false;
}
return true;
}
bool DanhSach::KiemTraDiTruyen() {
int cha, me, con;
cout << "Nhap lan luot chi so cua cha, me, con: ";
cin >> cha >> me >> con;
bool check = ds[con]->KiemTraChaMe(ds[cha], ds[me]);
if (check == 1) {
return true;
}
return false;
}

//Định nghĩa các phương thức cho câu 3
void DanhSach::TimKiemNguoiCho(int x) {
for (int i = 0; i < n; i++) {
if (i != x && ds[x]->KiemTraTuongThich(ds[i]) == 1) {
cout << i << endl;
}
}
}
bool A::KiemTraTuongThich(NhomMau* nguoiCho) {
//Người có nhóm máu A chỉ nhận được nhóm máu A và O(chưa xét Rh)
if (nguoiCho->getMau() == 'A' || nguoiCho->getMau() == 'O') {
//Người có nhóm máu Rh+ có thể nhận nhóm máu Rh+ hoặc Rh-
//Người có nhóm máu Rh- có thể cho người có nhóm máu Rh+ hoặc Rh-
if (this->getRh() == '+' || nguoiCho->getRh() == '-') {
return true;
}
}
return false;
}
bool B::KiemTraTuongThich(NhomMau* nguoiCho) {
//Người có nhóm máu B chỉ nhận được nhóm máu B và O(chưa xét Rh)
if (nguoiCho->getMau() == 'O' || nguoiCho->getMau() == 'B') {
if (this->getRh() == '+' || nguoiCho->getRh() == '-') {
return true;
}
}
return false;
}
bool O::KiemTraTuongThich(NhomMau* nguoiCho) {
//Người có nhóm máu O chỉ nhận được nhóm máu O(chưa xét Rh)
if (nguoiCho->getMau() == 'O') {
if (this->getRh() == '+' || nguoiCho->getRh() == '-') {
return true;
}
}
return false;
}
bool AB::KiemTraTuongThich(NhomMau* nguoiCho) {
//Người có nhóm máu AB có thể nhận được tất cả nhóm máu (chưa xét Rh)
if (this->getRh() == '+' || nguoiCho->getRh() == '-') {
return true;
}
return false;
}

Đề 2015-2016

Xây dựng chương trình mô phỏng biên soạn nhạc với các mô tả ký kiệu âm nhạc như sau:

  • Nốt nhạc: là ký hiệu trong bản nhạc dùng để xác định cao độ (độ cao), trường độ (độ dài, độ ngân vang) của từng âm thanh được vang lên trong bản nhạc.

    • Có 7 ký hiệu nốt nhạc dùng để xác định cao độ theo thứ tự từ thấp đến cao, đó là Đô (C), Rê (D), Mi (E), Fa (F), Sol (G), La (A), và Si (B).

    alt text

    • Để xác định trường độ của nốt nhạc có cao độ kể trên, người ta cũng dùng 7 hình nốt để thể hiện, đó là:
      • Nốt tròn có trường độ tương đương với trường độ của 44 nốt đen
      • Nốt trắng có trường độ bằng 22 nốt đen
      • Nốt đen có trường độ bằng 1 phách (đơn vị thời gian trong âm nhạc- vd như 1 bước chân người đi trong không gian)
      • Nốt móc đơn có trường độ bằng 12\frac{1}{2} nốt đen
      • Nốt móc đôi có trường độ bằng 14\frac{1}{4} nốt đen
      • Nốt móc tam có trường độ bằng 18\frac{1}{8} nốt đen
      • Nốt móc tứ có trường độ bằng 116\frac{1}{16} nốt đen

    alt text

  • Dấu lặng (Z - Zero) là ký hiệu cho biết phải ngưng, không diễn tấu âm thanh (không có cao độ) trong một thời gian nào đó. Các dấu lặng trong thời gian tương ứng (giá trị trường độ) với dạng dấu nhạc nào, thì cũng có tên gọi tương tự.

alt text

Ví dụ: Ký hiệu bản nhạc

alt text

Áp dụng kiến thức lập trình hướng đối tượng (kế thừa, đa hình) thiết kế sơ đồ chi tiết các lớp đối tượng (1.5đ) và xây dựng chương trình thực hiện các yêu cầu sau:

  1. Soạn một bản nhạc (1.5đ)
  2. Tìm và đếm có bao nhiêu dấu lặng đen (𝄽) trong bản nhạc(1đ)
  3. Cho biết nốt nhạc có cao độ cao nhất trong bản nhạc (1đ)

Phân tích đề

Bước 1: Thiết kế lớp

Dựa vào đề hoặc nếu đề dài khó hiểu thì chúng ta có thể dựa vào các câu hỏi để mường tượng ra các đối tượng có trong lớp. Ví dụ, như ở câu b yêu cầu: "Tìm và đếm có bao nhiêu dấu lặng đen (𝄽) trong bản nhạc" thì chắc chắn trong bài chúng ta phải có đối tượng DauLang.

Như vậy bài này khi đọc vào chúng ta có thể phát hiện được sẽ có một lớp KyHieu sẽ là lớp cha của lớp NotNhacDauLang. Bên cạnh đó , để quản lí tốt hơn ta cần tạo thêm 1 lớp BanNhac để quản lí các KyHieu.

Bước 2: Xác định các thuộc tính

Ở đây ta dễ dàng xác định thuộc tính chung của DauLangNotNhactrường độ vì thế lớp KyHieu sẽ có thuộc tính là truongDo . Bên cạnh đó lớp NotNhac sẽ có thêm thuộc tính riêng là caoDo.

Bài này nhìn chung các thuộc tính khá dễ xác định nhưng có một điểm lưu ý là ở kiểu dữ liệu của caoDo vì đề bài không bắt buộc kiểu nào cố định nên vì thế thay vì kiểu kí tự là string ta sẽ trừu tượng nó thành kiểu dữ liệu int lưu các các cao độ tương ứng (1 – Đô ,2- Rê,…) .Từ đó giúp ta dễ dàng làm câu c.

Bước 3: Xác định phương thức

Tương tự như các bài ở trên, các đối tượng có các thuộc tính khác nhau nên việc nhập thông tin cho các đối tượng đó cũng khác nhau. Chính vì thế ở bài này, phương thức nhập lớp cơ sở KyHieu sẽ là phương thức ảo.

Tạo phương thức để kiểm tra dấu lặng đen là LaDauLangDen(). Nó sẽ là phương thức ảo và mặc định trả về false, chúng ta sẽ override nó ở lớp dẫn xuất DauLang khi đó nó sẽ trả về true nếu đó là dấu lặng đen.

Cũng tương tự phương thức LaDauLangDen(). Ta tạo một phương thức ảo để lấy cao độ là LayCaoDo() mặc định sẽ trả về 0, nhưng ta sẽ override nó trong lớp NotNhac và trả về cao độ cần tìm.

Sơ đồ thiết kế lớp

Lời giải tham khảo

#include<iostream>
#include<vector>
using namespace std;

class KyHieu {
protected:
float truongDo;
public:
virtual void Nhap() {
int loai;
cout << "Nhap vao loai hinh not(1-Tron, 2-Trang, 3-Den, 4-Moc don, 5-Moc kep, 6-Moc tam, 7-Moc tu): ";
cin >> loai;
switch (loai) {
case 1: { truongDo = 4; break; }
case 2: { truongDo = 2; break; }
case 3: { truongDo = 1; break; }
case 4: { truongDo = 0.5; break; }
case 5: { truongDo = 0.25; break; }
case 6: { truongDo = 0.125; break; }
case 7: { truongDo = 0.0625; break; }
}
}
virtual bool LaDauLangDen() {
return false;
}
virtual int LayCaoDo() {
return 0;
}
};

class NotNhac : public KyHieu {
private:
int caoDo;
public:
void Nhap() {
cout << "Nhap vao cao do(1-Do(C), 2-Re(D), 3-Mi(E), 4-F (F), 5-Sol(G), 6-La(A), 7-Si(B)): ";
cin >> caoDo;
}
int LayCaoDo() {
return caoDo;
}

};

class DauLang :public KyHieu {
public:
bool LaDauLangDen() {
if (truongDo == 1)
return true;
return false;
}
};

class BanNhac {
private:
vector<KyHieu*> ds;
public:
void Nhap() {
cout << "Nhap vao so ky hieu cua ban nhac: ";
int n;
cin >> n;
KyHieu* tmp;
for (int i = 0; i < n; i++) {
int loai;
tmp = NULL;
cout << "Nhap ky hieu thu " << i + 1 << " can them vao ban nhac(1 - NotNhac, 2 - DauLang) : ";
cin >> loai;
if (loai == 1) tmp = new NotNhac;
else tmp = new DauLang;
tmp->Nhap();
ds.push_back(tmp);
}
}
// cau b
int DemDauLangDen() {
int dem = 0;
for (int i = 0; i < ds.size(); i++) {
if (ds[i]->LaDauLangDen()) {
dem++;
}
}
return dem;
}
// cau c
int TimCaoDoMAX() {
int max = ds[0]->LayCaoDo();
int index = 0;
for (int i = 0; i < ds.size(); i++) {
if (ds[i]->LayCaoDo() > max) {
max = ds[i]->LayCaoDo();
index = i;
}
}
return index + 1;
}
};

int main() {
BanNhac b;
b.Nhap();
cout << "So dau lang den cua ban nhac la: " << b.DemDauLangDen();
cout << "\nVi tri not nhac co cao do lon nhat la :" << b.TimCaoDoMAX();
}