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

Dạng đề 2: Thiết kế các lớp đơn giản

Câu 2 trong đề thi các năm gần đây thường yêu cầu ta phải thiết kế các lớp đơn giản như phân số, đa thức, ngày, thời gian, mảng động,… Ở đây chủ yếu áp dụng các kiến thức về phương thức thiết lập, nạp chồng toán tử, cấp phát động,...

Link các lời giải tham khảo: https://github.com/bht-cnpm-uit/OOP.git

Đề HK1 2022-2023

Xây dựng class IntArr để hàm main hoạt động đúng như mong đợi.

class IntArr { 
private:
int count; // tổng số lượng phần tử có trong values
int* values; // mảng các số nguyên đang có trong đối tượng hiện tại
public:
/* Sinh viên bổ sung đầy đủ các thành phần cần thiết để hàm main hoạt động như mong đợi */
};

int main() {
IntArr l1; // tạo mảng không chứa bất kì phần tử nào
IntArr l2(3, 2); /* tạo một mảng với 3 phần tử, tất cả phần tử đều có giá trị là 2 */
IntArr l3(2);//tạo một mảng với 2 phần tử, tất cả phần tử đều có giá trị là 0
IntArr l4 = l2.concat(l3); /* tạo ra một IntArr mới có nội dung là kết quả của việc nối các phần tử l3 vào cuối các phần tử của l2 theo thứ tự */
l2.push(3); // thêm số 3 vào cuối danh sách trong đối tượng l2
cin >> l2; /* Xoá các giá trị hiện có trong l2 và cho phép người dùng nhập số lượng phần tử mới và giá trị các phần tử mới vào l2 (cần xoá các vùng nhớ không sử dụng nếu có) */
cout << l2; // in ra các số nguyên có trong danh sách
/* Khi vượt quá phạm vi sử dụng cần huỷ tất cả các vùng nhớ được cấp phát cho các values của IntArr */
return 0;
}

Source code tham khảo

Lưu ý: Dựa vào các câu lệnh trong hàm main để xác định đúng các phương thức cần định nghĩa. Khi xây dựng các lớp liên quan tới cấp phát động thì nên định nghĩa các phương thức thiết lập sao chép, toán tử gán cho phù hợp mặc dù đề không yêu cầu (Xem lại phần phương thức phá hủy và phương thức thiết lập sao chép).

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

class IntArr {
friend istream& operator>> (istream& is, IntArr& rhs);
friend ostream& operator<< (ostream& os, const IntArr& rhs);
private:
int count;
int* values;
public:
IntArr();
IntArr(int size);
IntArr(int size, int init);
IntArr(const IntArr& rhs);
IntArr& operator= (const IntArr& rhs);
IntArr concat(const IntArr& rhs);
void push(int val);
~IntArr();
};

IntArr::IntArr() {
count = 0;
values = NULL;
}
IntArr::IntArr(int size) {
count = size;
values = new int[count];
for (int i = 0; i < size; ++i) {
values[i] = 0;
}
}
IntArr::IntArr(int size, int init) {
count = size;
values = new int[count];
for (int i = 0; i < count; ++i) {
values[i] = init;
}
}
IntArr::IntArr(const IntArr& rhs) {
count = rhs.count;
values = new int[count];
for (int i = 0; i < count; ++i) {
values[i] = rhs.values[i];
}
}
IntArr& IntArr::operator= (const IntArr& rhs) {
delete[] this->values;
count = rhs.count;
values = new int[count];
for (int i = 0; i < count; ++i) {
values[i] = rhs.values[i];
}
return *this;
}
IntArr IntArr::concat(const IntArr& rhs) {
IntArr ret(this->count + rhs.count);
int iRet = 0;
for (int i = 0; i < this->count; ++i) {
ret.values[iRet] = this->values[i];
++iRet;
}
for (int i = 0; i < rhs.count; ++i) {
ret.values[iRet] = rhs.values[i];
++iRet;
}
return ret;
}
void IntArr::push(int val) {
IntArr temp(1, val);
*this = this->concat(temp);
}
IntArr::~IntArr() {
cout << "Destructor has been called" << endl;
delete[] values;
}
istream& operator>> (istream& is, IntArr& rhs) {
delete[] rhs.values;
cout << "Nhap so luong: ";
is >> rhs.count;
rhs.values = new int[rhs.count];
cout << "Nhap cac phan tu :\n";
for (int i = 0; i < rhs.count; ++i) {
is >> rhs.values[i];
}
return is;
}
ostream& operator<< (ostream& os, const IntArr& rhs) {
for (int i = 0; i < rhs.count; ++i) {
os << rhs.values[i] << " ";
}
return os;
}

Đề 2019-2020

Xây dựng lớp phân số

Cho lớp Phân số (PhanSo). Hãy khai báo và định nghĩa các phương thức cần thiết để các đối tượng thuộc lớp PhanSo có thể thực hiện được các câu lệnh sau:

PhanSo a(5, 3); 
PhanSo b, c, kq;
cin >> b >> c;
kq = a + b + 5 + c;
cout << "Ket qua la: " << kq;
if (a == b)
cout << "Phan so a bang phan so b" << endl;

Source code tham khảo

#include <iostream>
using namespace std;

class PhanSo {
private:
int ts, ms;
public:
PhanSo();
PhanSo(int a);
PhanSo operator+(const PhanSo&);
bool operator==(PhanSo&);
friend istream& operator>>(istream& is, PhanSo& x);
friend ostream& operator<<(ostream& os, const PhanSo& x);
};
PhanSo::PhanSo() {
ts = 0;
ms = 1;
}
PhanSo::PhanSo(int a) {
ts = a;
ms = 1;
}
PhanSo PhanSo::operator + (const PhanSo& y) {
PhanSo temp;
temp.ts = this->ts * y.ms + y.ts * this->ms;
temp.ms = this->ms * y.ms;
return temp;
}
bool PhanSo::operator ==(PhanSo& y) {
float s1 = this->ts / this->ms;
float s2 = y.ts / y.ms;
if (s1 == s2)
return true;
return false;
}
istream& operator>>(istream& is, PhanSo& x) {
cout << "Nhap tu so: ";
is >> x.ts;
cout << "Nhap mau so: ";
is >> x.ms;
return is;
}
ostream& operator<<(ostream& os, const PhanSo& x) {
os << "Tu so: ";
os << x.ts;
os << "Mau so: ";
os << x.ms;
return os;
}

Đề 2018-2019

Xây dựng lớp Thời gian (giờ, phút, giây)

Định nghĩa các phép toán:

  • ++ để tăng thời gian thêm 1 giây.
  • >><< để nhập, xuất dữ liệu thời gian.
  • +để cộng 2 thời gian.

Source code tham khảo

#include <iostream>
using namespace std;

class Time {
private:
long seconds = 0;
public:
friend istream& operator>> (istream& is, Time& rhs);
friend ostream& operator<< (ostream& os, const Time& rhs);
Time& operator++();
Time operator++(int);
Time operator+ (const Time& rhs);
};
istream& operator>> (istream& is, Time& rhs) {
long gio = 0, phut = 0, giay = 0;
cout << "Nhap gio: ";
is >> gio;
cout << "Nhap phut: ";
is >> phut;
cout << "Nhap giay: ";
is >> giay;
rhs.seconds = gio * 3600 + phut * 60 + giay;
return is;
}
ostream& operator<< (ostream& os, const Time& rhs) {
os << rhs.seconds / 3600 << ":";
os << (rhs.seconds % 3600) / 60 << ":";
os << rhs.seconds % 60;
return os;
}
Time& Time::operator++() {
++seconds;
return *this;
}
Time Time::operator++(int) {
Time ret = *this;
++seconds;
return ret;
}
Time Time::operator+ (const Time& rhs) {
Time ret;
ret.seconds = (this->seconds + rhs.seconds) % (3600 * 24);
return ret;
}

Đề HK2 2017-2018

Xây dựng lớp Đa thức bậc n với các toán tử >>, <<, +.

Source code tham khảo

#include <iostream>
using namespace std;

class CDaThuc {
private:
int n;
float a[100];
public:
CDaThuc operator+(const CDaThuc&);
friend istream& operator>> (istream& is, CDaThuc& rhs);
friend ostream& operator<< (ostream& os, const CDaThuc& rhs);
};

istream& operator>> (istream& is, CDaThuc& rhs) {
cout << "Nhap bac cua da thuc: ";
is >> rhs.n;
for (int i = rhs.n; i >= 0; i--) {
cout << "Nhap he so cua don thuc bac " << i << ": ";
is >> rhs.a[i];
}
return is;
}
ostream& operator<< (ostream& os, const CDaThuc& rhs) {
cout << "Da thuc: " << endl;
for (int i = rhs.n; i > 0; i--) {
cout << rhs.a[i] << "x^" << i << " + ";
}
cout << rhs.a[0] << endl;
return os;
}
CDaThuc CDaThuc::operator+(const CDaThuc& P) {
CDaThuc temp;
if (this->n > P.n)
temp.n = this->n;
else
temp.n = P.n;
for (int i = temp.n; i >= 0; i--) {
temp.a[i] = 0;
}
for (int i = this->n; i >= 0; i--) {
temp.a[i] += a[i];
}
for (int i = P.n; i >= 0; i--) {
temp.a[i] += P.a[i];
}
return temp;
}

Đề HK1 2017-2018

Xây dựng lớp ngày

Hãy định nghĩa lớp Date thích hợp để chương trình dưới đây không bị lỗi biên dịch và chạy đúng. Lưu ý rằng không được chỉnh sửa hàm main và sinh viên cần viết cả các lệnh #include thích hợp.

int main() { 
Date ng1; // ng1 sẽ có giá trị là ngày 1 tháng 1 năm 1
Date ng2(2017, 1); // ng2 sẽ có giá trị là ngày 1 tháng 1 năm 2017
Date ng3(2017, 1, 7); // ng3 sẽ có giá trị là ngày 7 tháng 1 năm 2017
cin >> ng1;
ng1++;
cout << ng1;
if (ng1 < ng2)
cout << "Ngay 1 truoc ngay 2" << endl;
else
cout << "Ngay 1 khong truoc ngay 2" << endl;
return 0;
}

Source code tham khảo

#include <iostream>
#include <string>

using namespace std;
class Date {
friend istream& operator>> (istream& is, Date& rhs);
friend ostream& operator<< (ostream& os, const Date& rhs);
private:
int ngay;
int thang;
int nam;
public:
Date();
Date(int nam, int thang);
Date(int nam, int thang, int ngay);
int IsNhuan();
bool operator< (const Date& rhs);
Date operator++(int);
};

Date::Date() {
nam = 1;
thang = 1;
ngay = 1;
}
Date::Date(int nam, int thang) {
this->nam = nam;
this->thang = thang;
ngay = 1;
}
Date::Date(int nam, int thang, int ngay) {
this->nam = nam;
this->thang = thang;
this->ngay = ngay;
}
int Date::IsNhuan() {
if ((nam % 4 == 0 && nam % 100 != 0) || nam % 400 == 0)
return 1;
return 0;
}
istream& operator>> (istream& is, Date& rhs) {
cout << "Nhap ngay: ";
is >> rhs.ngay;
cout << "Nhap thang: ";
is >> rhs.thang;
cout << "Nhap nam: ";
is >> rhs.nam;
return is;
}
ostream& operator<< (ostream& os, const Date& rhs) {
os << rhs.ngay << "/";
os << rhs.thang << "/";
os << rhs.nam;
return os;
}
bool Date::operator< (const Date& rhs) {
if (nam < rhs.nam)
return true;
if (thang < rhs.thang)
return true;
if (ngay < rhs.ngay)
return true;
return false;
}
Date Date::operator++(int) {
Date ret = *this;
int ngayTrongThang[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (IsNhuan()) {
ngayTrongThang[2] = 29;
}
++ngay;
if (ngay > ngayTrongThang[thang]) {
++thang;
if (thang > 12) {
++nam;
thang = 1;
}
ngay = 1;
}
return ret;
}

Đề HK2 2016-2017

Xây dựng lớp đa thức bậc hai

  • Thể hiện các đa thức bật hai có dạng: f(x)=ax2+bx+c      (a0)f(x)=ax^2+bx+c \;\;\; (a\ne0)
  • Xây dựng các phương thức:
    • Phương thức cho phép xác định giá trị của đa thức f(x0)f(x_0) ứng với x=x0x=x_0.
    • Phép toán cộng (operator+) để cộng hai đa thức bậc hai.

Source code tham khảo

#include <iostream>
using namespace std;

class DaThucBacHai {
private:
float a;
float b;
float c;
public:
float TinhGiaTri(DaThucBacHai dt, float x0);
DaThucBacHai operator+(const DaThucBacHai&);
};
float DaThucBacHai::TinhGiaTri(DaThucBacHai dt, float x0) {
return a * pow(x0, 2) + b * x0 + c;
}
DaThucBacHai DaThucBacHai::operator+(const DaThucBacHai& dt) {
DaThucBacHai tong;
tong.a = this->a + dt.a;
tong.b = this->b + dt.b;
tong.c = this->c + dt.c;
return tong;
}

Đề 2014-2015 (tham khảo)

Xét lớp phân số:

class PhanSo { 
private:
int ts, ms;
public:
PhanSo(int ts = 0, int ms = 1);
PhanSo operator+ (PhanSo);
};

Hãy cho biết trong các dòng lệnh sau đây, dòng nào có lỗi xảy ra, giải thích và sửa lỗi nếu có:

PhanSo a, b(3, 4), c(2, 5); 
a = b + c;
a = b + 3;
a = 5 + c;

Lời giải tham khảo:

Dòng lệnh a = 5 + c bị sai vì toán tử cộng đang được nạp chồng dưới dạng phương thức của lớp nên toán hạng đầu tiên phải là một đối tượng thuộc lớp PhanSo. Dòng lệnh a = b + 3 không sai vì chương trình trên có cơ chuyển kiểu tự động bằng constructor khi truyền vào phương thức một đối số là số nguyên, câu lệnh này có thể được hiểu là:

a.operator+(PhanSo(3)); 

Cách sửa: Để toán hạng đầu tiên không nhất thiết phải là một đối tượng của lớp, ta phải nạp chồng toán tử cộng bằng hàm bên ngoài như sau:

class PhanSo { //... 
friend PhanSo operator+(const PhanSo& ps1,const PhanSo& ps2);
};

PhanSo operator+(const PhanSo& ps1, const PhanSo& ps2) {
PhanSo kq;
kq.tu = ps1.tu * ps2.mau + ps1.mau * ps2.tu;
kq.mau = ps1.mau * ps2.mau;
return kq;
}

Khi đó dòng lệnh a = 5 + c có thể được hiểu như này: operator+(PhanSo(5),a);

Đề 2013-2014 (tham khảo)

Xét đoạn chương trình sau:

#include <iostream> 
using namespace std;
class A {
public:
A() {
cout << "Constructing A " << endl;
}
~A() {
cout << "Destructing A " << endl;
}
};

class B : public A {
public:
B() {
cout << "Constructing B " << endl;
}
~B() {
cout << "Destructing B " << endl;
}
};

int main() {
B b1;
return 0;
}

Hãy cho biết kết quả xuất ra màn hình khi thực thi đoạn chương trình trên. Giải thích ngắn gọn tại sao có kết quả đó.

Lời giải tham khảo:

Kết quả khi xuất ra màn hình:

Constructing A
Constructing B
Destructing B
Destructing A

Giải thích:

Khi một đối tượng của lớp con được khởi tạo mặc định, constructor mặc định của lớp cha được gọi trước để khởi tạo dữ liệu mặc định cho các thuộc tính chung, sau đó constructor của lớp con mới bắt đầu thực hiện việc gán dữ liệu cho các thuộc tính riêng. Phương thức phá hủy thì ngược lại, khi một destructor của lớp con được gọi, nó sẽ thu hồi các tài nguyên đã cấp phát cho các thuộc tính riêng trước, rồi sau đó destructor của lớp cha mới được gọi để dọn dẹp các thuộc tính chung.

Xét đoạn chương trình sau:

#include <iostream> 
using namespace std;

class A {
private:
int x;
public:
A(int t) {
x = t;
}
static void f() {
cout << x;
}
int f2() {
return x;
}
};

void main() {
A a;
f2(a);
}

Cho biết đoạn chương trình trên khi biên dịch có lỗi xảy ra hay không? Nếu có lỗi, hãy chỉ ra các lỗi đó và sửa lỗi để chương trình có thể thực thi được.

Lời giải tham khảo:

Đoạn chương trình trên có 3 lỗi: alt text

  • Lỗi thứ 1:
    • Phương thức f() là thành phần static nên nó không được gắn với một đối tượng nào, thế nên bên trong phương thức static ta không thể truy cập vào thuộc tính của một đối tượng cụ thể. Trong đoạn code trên ta muốn xuất thuộc tính x nhưng mà x ở đâu ra? Thông thường ta nói x là thuộc tính của đối tượng đang gọi phương thức, tuy nhiên phương thức static có thể được gọi độc lập không cần thông qua một đối tượng nào.
    • Cách sửa: Bỏ từ khóa static đi, lúc này phương thức trở thành phương thức bình thường.
  • Lỗi thứ 2:
    • Thiếu constructor mặc định: khi ta định nghĩa bất kì một constructor nào, chương trình sẽ không tự định nghĩa constructor mặc định cho ta.
    • Cách sửa: thêm constructor mặc định:
    A() { 
    x = 0;
    }
  • Lỗi thứ 3:
    • Sai dòng f2(a), vì hàm f2 là phương thức thuộc lớp A. Cú pháp để gọi một phương thức thuộc lớp phải là: <object>.method().
    • Cách sửa: đổi thành a.f2().