01/10/2018, 11:06

Thắc mắc: Class và hướng đối tượng trong C++

Em đang tự học C++, hiện tại đang làm bài tập viết class cộng hai phân số ạ. em viết thì đã hiển thị đúng kết quả, nhưng code thì em nghĩ là mình dùng sai cách hoặc chưa thực hiện đúng tiêu chí hướng đối tượng. Vì thế, em đăng lên đây nhờ các anh các chị chỉ giáo giúp em tìm khuyết điểm trong code của mình ạ. Em xin chân thành cảm ơn

#include <iostream>
using namespace std;

class PhanSo{
private:
	int _ts;
	int _ms;
public:
	void nhap();
	void xuat();
	void rutGon(PhanSo &ps);
	void cong(PhanSo ps1, PhanSo ps2);
};

void PhanSo::nhap(){
	cout << "Nhap tu so: ";
	cin >> this->_ts;
	cout << "Nhap mau so: ";
	cin >> this->_ms;
}

void PhanSo::xuat(){
	cout << this->_ts << "/" << this->_ms;
}

void PhanSo::rutGon(PhanSo &ps){
	int a = ps._ts;
	int b = ps._ms;
	while (a != b){
		if (a > b) a -= b;
		else b -= a;
	}
	ps._ts = ps._ts / a;
	ps._ms = ps._ms / a;
}

void PhanSo::cong(PhanSo ps1, PhanSo ps2){
	PhanSo tong;
	tong._ts = ps1._ts*ps2._ms + ps2._ts*ps1._ms;
	tong._ms = ps1._ms * ps2._ms;
	cout << tong._ts << "/" << tong._ms;
}

int main(){

	PhanSo ps1, ps2;
	ps1.nhap();
	ps2.nhap();
	ps1.rutGon(ps1);
	ps2.rutGon(ps2);
	ps1.cong(ps1, ps2);
	system("pause");
	return 0;
}
Nguyen Dinh Dung viết 13:11 ngày 01/10/2018

@ltd anh Đạt giúp em với ạ

rogp10 viết 13:06 ngày 01/10/2018
  1. Kiểm soát dữ liệu không có.
  2. Trong rutgon nên dùng % thay vì -= (tất nhiên là phải sửa :v)
  3. Hàm cong này không kiểm tra tràn số và không rút gọn.
Nguyen Dinh Dung viết 13:21 ngày 01/10/2018

Vâng, đúng là hàm cộng không thể rút gọn được phân số ạ
Mà kiểm soát dữ liệu không có là như nào anh, thuộc mảng kiến thức nào ạ? Anh có thể chỉ rõ hơn cho em được ko?

rogp10 viết 13:07 ngày 01/10/2018

Vâng, đúng là hàm cộng không thể rút gọn được phân số ạ

Tại sao không được?

Mà kiểm soát dữ liệu không có là như nào anh, thuộc mảng kiến thức nào ạ? Anh có thể chỉ rõ hơn cho em được ko?

Mẫu phải khác 0 và nếu mẫu là số âm thì phải chỉnh lại.

Văn Dương viết 13:16 ngày 01/10/2018

Cộng thì nên khai báo là operator để có thể viết được phép tính như sau :

phansotong = phanso1 + phanso2
Nguyen Kien viết 13:07 ngày 01/10/2018

Mình đoán là cái hàm rút gọn kia không chạy đúng với số âm

rogp10 viết 13:07 ngày 01/10/2018

Ngoài ra phép cộng không tính mẫu nhỏ nhất có thể, nên một số phép cộng sẽ khó khăn hơn. Nên tách phần ucln thành helper.

Trần Hoàn viết 13:15 ngày 01/10/2018

Theo như mình thấy, bạn làm thế cũng đúng yêu cầu đề bài, tuy nhiên tư duy thì hơi ngắn, hoặc là do em mới chỉ biết 1 hình thức khai báo của phương thức/hàm.

  • Đã là class, em nên xây dựng cho nó phương thức khởi tạo thay vì hàm tạo mặc định.
  • Phương thức rutGon là phương thức của đối tượng. Giả sử em có 2 đối tượng phân số ps1ps2, em gọi phương thức ps1.rutGon(ps2) thì nó sẽ rút gọn ps2, em thấy có buồn cười không
    Cách giải quyết là viết phương thức tác động trực tiếp lên đối tượng this (anh viết rút gọn, không có this nhé), đồng thời thay đổi thuật toán để rút gọn cả phân số âm:
void PhanSo::rutGon()
{
	if (_ms < 0)
	{
		_ms = -_ms;
		_ts = -_ts;
	}
	for (int i = 2; i <= abs(abs(_ts) - abs(_ms)); i += 1)
		while (_ts % i == 0 && _ms % i == 0)
		{
			_ts /= i;
			_ms /= i;
		}
}

Như vậy, khi muốn rút gọn phân số ps69, ta chỉ việc ps69.rutGon(); thậm chí nếu gọi phương thức bằng các phương thức của chính đối tượng đó thì chỉ cần rutGon();

  • Phương thức nhap của em nó rất ư là có vấn đề vì không kiểm tra điều kiện. Cái này là tối kỵ nhé.
  • Phương thức xuat của em nên viết riêng cho trường hợp giá trị của phân số là số nguyên.
  • Phương thức cong của em chưa rút gọn phân số.
  • Phương thức cong, em phải tư duy khác một chút: Bởi vì phép cộng của 2 phân số sẽ cho kết quả là 1 phân số, thế nên em nên trả về kiểu dữ liệu PhanSo thay vì kiểu dữ liệu void

Nếu là anh, anh sẽ viết chương trình phức tạp hơn 1 chút như sau (Vì anh viết phương thức cong trả về kiểu dữ liệu PhanSo nên anh viết hàm đó nằm ở ngoài class. Vì hàm nằm ở ngoài class thì không truy cập được vào các thuộc tính private nên anh viết thêm phương thức Get):

#include <iostream>
#include <string>
using namespace std;
class PhanSo
{
private:
	int TuSo = 0;
	int MauSo = 1;
public:
	PhanSo()//Nạp chồng phương thức khởi tạo mặc định
	{
		TuSo = 0;
		MauSo = 1;
	}
	PhanSo(int ts, int ms)//Xây dựng phương thức khởi tạo có tham số
	{
		if (ms == 0)
		{
			cout << "Loi: Mau so bang 0. Khoi tao phan so mac dinh" << endl;
			TuSo = 0;
			MauSo = 1;
		}
		else
		{
			TuSo = ts;
			MauSo = ms;
			RutGon();
		}
	}
	void RutGon()
	{
		if (TuSo == 0)
			MauSo = 1;
		if (MauSo < 0)
		{
			MauSo = -MauSo;
			TuSo = -TuSo;
		}
		for (int i = 2; i <= abs(abs(TuSo) - abs(MauSo)); i += 1)
			while (TuSo % i == 0 && MauSo % i == 0)
			{
				TuSo /= i;
				MauSo /= i;
			}
	}
	string ToString()
	{
		if (MauSo == 1)
			return to_string(TuSo);
		return to_string(TuSo) + "/" + to_string(MauSo);
	}
	void Nhap()
	{
		cout << "Nhap tu so: ";
		cin >> TuSo;
		cout << "Nhap mau so: ";
		cin >> MauSo;
		if (MauSo == 0)
		{
			cout << "Loi: Mau so bang 0. Khoi tao phan so mac dinh" << endl;
			TuSo = 0;
			MauSo = 1;
		}
		else
			RutGon();
	}
	void Xuat()
	{
		cout << "Gia tri phan so: " << ToString() << endl;
	}
	int GetTuSo()
	{
		return TuSo;
	}
	int GetMauSo()
	{
		return MauSo;
	}
};
PhanSo Cong(PhanSo PS1, PhanSo PS2)
{
	int ts1 = PS1.GetTuSo();
	int ts2 = PS2.GetTuSo();
	int ms1 = PS1.GetMauSo();
	int ms2 = PS2.GetMauSo();
	return PhanSo(ts1*ms2 + ts2*ms1, ms1*ms2);
}
void main()
{
	PhanSo ps1, ps2;
	ps1.Nhap();
	ps1.Xuat();
	ps2.Nhap();
	ps2.Xuat();
	ps1.RutGon();//Thực ra không cần thiết vì đã rút gọn ở phương thức khởi tạo
	ps2.RutGon();//Thực ra không cần thiết vì đã rút gọn ở phương thức khởi tạo
	cout << "Tong 2 phan so: " << Cong(ps1, ps2).ToString() << endl;
	system("pause");
}

Trần Hoàn viết 13:14 ngày 01/10/2018

À lộn, trong C++ thì this là con trỏ, không phải đối tượng

Tao Không Ngu. viết 13:11 ngày 01/10/2018

Hi Nguyen Dinh Dung.

  1. Bạn đọc về nạp chồng toán tử trong C++.
  2. Không nên đưa hàm nhập xuất vào trong class này.
  3. Phân số thì mẫu số không thể bằng 0 nên bạn viết một hàm khởi tạo tử số mẫu số và ném ngoại lệ khi mẫu bằng 0.
  4. Hàm rút gọn thì viết thành hàm bạn hoặc phương thức thì tùy bạn,

Góp ý thêm.
VIết hết các toán tử mà phân số có thể dùng.
Thêm phương thức lấy giá trị tử số mẫu số.
Toán tử ép kiểu về float hoặc doble.

rogp10 viết 13:07 ngày 01/10/2018

Đổi dấu trước khi chạy thuật toán Euclid là xong rồi @@ đâu cần phải chia làm gì.

Kèm theo khống chế mẫu luôn dương thì chỉ thêm 1 dòng.

Tao Không Ngu. viết 13:18 ngày 01/10/2018

Theo mình thì dấu của phân số không phải là dấu của tử số hay mẫu số. Vậy nên kiểu dữ liệu của tử số và mẫu số là số nguyên không đấu mẫu khác 0 và thêm một thuộc tính dấu cho phân số nữa.

en.wikipedia.org

Rational number

In mathematics, a rational number is any number that can be expressed as the quotient or fraction p/q of two integers, a numerator p and a non-zero denominator q. Since q may be equal to 1, every integer is a rational number. The set of all rational numbers, often referred to as "the rationals", the field of rationals or the field of rational numbers is usually denoted by a boldface Q (or blackboard bold Q {\displaystyle \mathbb {Q} Th...

rogp10 viết 13:21 ngày 01/10/2018

Theo mình thì dấu của phân số không phải là dấu của tử số hay mẫu số. Vậy nên kiểu dữ liệu của tử số và mẫu số là số nguyên không đấu mẫu khác 0 và thêm một thuộc tính dấu cho phân số nữa.

Việc tách dấu lưu riêng không thật sự cần thiết, vì dung lượng sẽ đội lên mà hiệu quả không có.

Nguyen Dinh Dung viết 13:15 ngày 01/10/2018

Em cảm ơn các anh, em sẽ nghiền ngẫm lại comment của các anh và học thêm phần kiến thức còn thiếu ạ. Thanks!!!

Trần Hoàn viết 13:13 ngày 01/10/2018

Đổi dấu trước khi chạy thuật toán Euclid là xong rồi @@ đâu cần phải chia làm gì.

Kèm theo khống chế mẫu luôn dương thì chỉ thêm 1 dòng.

Vấn đề là mục đích của mình không phải là tìm UCLN mà là rút gọn phân số, kiểu gì cũng phải dùng thêm một vài biến trung gian để lưu dấu phép tính, các biến tạm để tìm UCLN, tốc độ cải thiện không đáng kể nhưng code sẽ phức tạp hơn 1 chút, trong khi thuật toán của mình nó tường minh hơn. Nếu muốn code bằng Euclid mà sáng sủa thì phải xây riêng 1 hàm tính UCLN.
Còn nếu bạn nào thấy vụ abs... phức tạp thì cho i ≤ min(abs(TuSo), abs(MauSo)) cũng không vấn đề gì.

Tao Không Ngu. viết 13:17 ngày 01/10/2018

Hi rogp10.

  1. Thực sự là đội dung lương ? Nếu không đưa trường dấu ra thì rõ rang tử số và mẫu số là kiểu số có dấu -> tốn dung lượng so với việc dùng kiểu không dấu. (phạm vi tăng gấp đôi).
  2. Rõ ràng trong quy tắc sử dụng. Nếu không thuộc tính dấu thì dấu của phân số được lưu ở đâu ? Tử số hay mẫu số hay cả hai ? Tình tranh hai phân số rút gọn bằng nhau có tử số trái dấu có thể sảy ra -> phức tập cho việc so sánh 2 phân số. (-2/3 và 2/-3).
  3. Việc quy ước dấu của phân số là dấu của tử số cũng là một giải pháp nhưng dẫn đến việc dùng số nguyên có dấu cho mẫu số là thừa nhưng nếu để tử số và mẫu số khác kiểu dữ liệu thì lại phức tập trong tính toán.
Trần Hoàn viết 13:18 ngày 01/10/2018

Thực ra kiểu dữ liệu không dấu và kiểu dữ liệu có dấu tốn dung lượng như nhau, 1 đằng thì từ 0 - 2 ^ n đến 2 ^ n - 1, một đằng thì từ 0 đến 2 ^ 2n - 1, dùng bao nhiều thì xài bấy nhiêu thôi. Chưa kể bây giờ nếu muốn làm triệt để thì người ta có BigInteger rồi, số càng to càng tốn bộ nhớ.

Nhưng mà nếu thêm 1 biến dấu thì đúng là nó phức tạp hơn vì phải có thêm các thao tác kiểm dấu trong bất kỳ phép tính nào, thiếu đi sự tự nhiên của phép tính.

Vấn đề (-2/3 và 2/-3) hay (1/2 và 2/4) thì giải quyết như em ở trên, phương thức rút gọn được đưa vào constructor. Và như vậy thì ngay cả việc xét dấu cũng chỉ cần xét tử số.

rogp10 viết 13:16 ngày 01/10/2018

Vấn đề là mục đích của mình không phải là tìm UCLN mà là rút gọn phân số, kiểu gì cũng phải dùng thêm một vài biến trung gian để lưu dấu phép tính, các biến tạm để tìm UCLN, tốc độ cải thiện không đáng kể nhưng code sẽ phức tạp hơn 1 chút, trong khi thuật toán của mình nó tường minh hơn. Nếu muốn code bằng Euclid mà sáng sủa thì phải xây riêng 1 hàm tính UCLN.
Còn nếu bạn nào thấy vụ abs… phức tạp thì cho i ≤ min(abs(TuSo), abs(MauSo)) cũng không vấn đề gì.

Thuật toán bạn đưa ra là O(n), với mẫu số đủ lớn thì chả biết chừng nào xong (gcd cỡ tỉ -> 5-6 tỉ bước, khoảng 2s), trong khi Euclid nháy mắt xong. (hàng log(n))

Hi rogp10.

  1. Thực sự là đội dung lương ? Nếu không đưa trường dấu ra thì rõ rang tử số và mẫu số là kiểu số có dấu -> tốn dung lượng so với việc dùng kiểu không dấu. (phạm vi tăng gấp đôi).

Vậy bạn tính lưu dấu ở đâu? Có phải là đội dung lượng mà không ích lợi gì không?

Tao Không Ngu. viết 13:13 ngày 01/10/2018

Hi rogp10.
VD. Mình dùng 3 biếnt int không dấu đề lưu một phân số nhưng có miền giá trị là lớn gấp đôi bạn dùng 2 biến int.

rogp10 viết 13:20 ngày 01/10/2018

Vẫn không đáng. Lấy thêm 1 byte của bạn nữa là được hẳn 63 bit rồi.

Bài liên quan
0