實驗3 類和物件——基礎程式設計2

安东尼23發表於2024-11-09

一、實驗目的

加深對類的組合機制的理解,會正確使用C++正確定義,使用組合類

理解深複製,淺複製

練習標準庫string ,vector的用法,能基於問題場景靈活使用

針對具體問題場景,練習運用物件導向思維進行設計,合理設計,組合類(自定義/標準庫),程式設計解決實際問題。

二、實驗準備

系統複習瀏覽以下內容

類的抽象,設計

組合類:要解決的問題場景,定義和使用方法

資料共享,保護

標準庫string ,vector的用法

三、實驗內容

1. 實驗任務1

程式碼:button.hpp

 1 #pragma once 
 2 #include <iostream>
 3 #include<string>
 4 
 5 using std::string;
 6 using std::cout;
 7 
 8 class Button{
 9     public:
10         Button(const string &text);
11         string get_label()const;
12         void click();
13     private:
14         string label;
15 };
16 Button::Button(const string &text):label {text}{
17 
18 } 
19 
20 inline string Button::get_label()const {
21 return label;}
22 
23 void Button::click(){
24     
25     cout<<"Button "<<label<<"clicked\n";
26     
27 }

window.hpp

 1 #pragma once 
 2 #include"button.hpp"
 3 #include<vector>
 4 #include<iostream>
 5 
 6 
 7 using std::vector;
 8 using std::cout;
 9 using std::endl;
10 
11 class Window{
12     public:
13         Window(const string &win_title);
14         void display()const;
15         void close();
16         void add_button(const string &label);
17     private:
18         string title;
19         vector <Button> buttons;
20         
21 };
22 Window::Window(const string &win_title):title{win_title}{
23 buttons.push_back(Button("close "));}
24 
25 
26 inline void Window::display()const{
27 string s(40,'*');
28 cout<<s<<endl;
29 cout<<"window title:"<<title<<endl;
30 cout<<"It has "<<buttons.size()<<" buttons:"<<endl;
31 for(const auto&i:buttons)
32 cout<<i.get_label()<<"button"<<endl;
33 cout<<s<<endl;
34 
35 
36 }
37 void Window::close(){
38     cout<<"close window"<<" '"<<title<<"'"<<endl;
39     buttons.at(0).click();
40     
41 }
42 
43 void Window::add_button(const string &label){
44     buttons.push_back(Button(label));
45     
46 }

task1.cpp

 1 #include"window.hpp"
 2 #include<iostream>
 3 
 4 
 5 using std::cout;
 6 using std::cin;
 7 
 8 void test(){
 9     
10     Window w1("new window");
11     w1.add_button("maximize ");
12     w1.display();
13     w1.close();
14 }
15 
16 int main(){
17     cout<<"用組合類模擬簡單GUI:\n";
18     test();
19 }

執行截圖:

問題回答:

問題一:這個示例程式碼中,自定義了2個類,一個Button,一個Window類,使用到了標準庫的string和vector類。其中string和Button類,string 和Window類,vector和Window類存在組合關係

問題二:在Button類當中, get_label()函式被標記為const,這是合適的,因為它只返回標籤而不修改任何資料。或者設定成inline,因為它簡單且頻繁呼叫。在Windows類當中,disliay()函式可以被標記為const,因為它只顯示視窗資訊而不改變。但不應設定成inline,因為它的程式碼量太大了。close()不應設定成為const因為它會改變

問題三:string s(40,'*');是建立一個名為s 的字串,長度為40,且所有字元均為*

2. 實驗任務2

程式碼:

 1 #include<iostream>
 2 
 3 #include<vector>
 4 
 5 using namespace std;
 6 void output1(const vector<int> &v){
 7     for(auto &i:v)
 8         cout<<i<<",";
 9     cout<<"\b\b \n";
10     
11 }
12 
13 void output2(const vector <vector<int>> v){
14     for(auto &i:v){
15         for(auto &j:i)
16             cout<<j<<", ";
17         cout<<"\b\b \n";
18         
19     }
20     
21 }
22 void test1(){
23     vector<int> v1(5,42);
24     const vector<int> v2(v1);
25     
26     v1.at(0)=-999;
27     cout<<"v1: "; output1(v1);
28     cout<<"v2: ";output1(v2) ;
29     cout<<"v1.at(0) = "<<v1.at(0)<<endl;
30     cout<<"v2.at(0) = "<<v2.at(0)<<endl;
31     
32 }
33 
34 void test2(){
35     vector<vector<int>> v1{{1,2,3},{4,5,6,7}};
36     const vector <vector<int>> v2(v1);
37     
38     v1.at(0).push_back(-999);
39     cout<<"v1: \n";output2(v1);
40     cout<<"v2: \n";output2(v2);
41     
42     vector<int> t1=v1.at(0);
43     cout<<t1.at(t1.size()-1)<<endl;
44     
45     const vector<int> t2=v2.at(0);
46     cout<<t2.at(t2.size()-1)<<endl;
47     
48 }
49 
50 int main(){
51     cout<<"測試1:\n";
52     test1();
53     cout<<"測試2:\n";
54     test2();
55 }

執行截圖

問題回答:

問題一:

vector<int> v1(5,42);
const vector<int> v2(v1);

v1.at(0)=-999;

這個程式碼是定義一個常量引用的整型向量v1,內部包含5個元素,且全部初始化值為42,把v1的值複製給了v2,v2成為了不可變的一維向量;而後又將第一個元素改成了-999;

問題二:

vector<vector<int>> v1{{1,2,3},{4,5,6,7}};
const vector <vector<int>> v2(v1);

v1.at(0).push_back(-999);

這個程式碼是定義一個常量的二維整型向量v1,{{1,2,3},{4,5,6,7}};表示v1包含兩個內層向量

第一個內層向量是{1,2,3},包含三個元素。第二個內層向量是{4,5,6,7},包含四個元素。同樣把v1的值複製給了v2,v2成為了不可變的二維向量;

而後又把-999增加到了v1的第一層的末尾,變成了v1{{1,2,3,-999},{4,5,6,7}};

3. 實驗任務3

程式碼:

vectorInt.hpp

 1 #pragma once
 2 
 3 #include <iostream>
 4 #include <cassert>
 5 
 6 using std::cout;
 7 using std::endl;
 8 
 9 
10 class vectorInt{
11 public:
12     vectorInt(int n);
13     vectorInt(int n, int value);
14     vectorInt(const vectorInt &vi);
15     ~vectorInt();
16 
17     int& at(int index);
18     const int& at(int index) const;
19 
20     vectorInt& assign(const vectorInt &v);
21     int get_size() const;
22 
23 private:
24     int size;
25     int *ptr;      
26 };
27 
28 vectorInt::vectorInt(int n): size{n}, ptr{new int[size]} {
29 }
30 
31 vectorInt::vectorInt(int n, int value): size{n}, ptr{new int[size]} {
32     for(auto i = 0; i < size; ++i)
33         ptr[i] = value;
34 }
35 
36 vectorInt::vectorInt(const vectorInt &vi): size{vi.size}, ptr{new int[size]} {
37     for(auto i = 0; i < size; ++i)
38         ptr[i] = vi.ptr[i];
39 }
40 
41 vectorInt::~vectorInt() {
42     delete [] ptr;
43 }
44 
45 const int& vectorInt::at(int index) const {
46     assert(index >= 0 && index < size);
47 
48     return ptr[index];
49 }
50 
51 int& vectorInt::at(int index) {
52     assert(index >= 0 && index < size);
53 
54     return ptr[index];
55 }
56 
57 vectorInt& vectorInt::assign(const vectorInt &v) {  
58     delete[] ptr;     
59 
60     size = v.size;
61     ptr = new int[size];
62 
63     for(int i = 0; i < size; ++i)
64         ptr[i] = v.ptr[i];
65 
66     return *this;
67 }
68 
69 int vectorInt::get_size() const {
70     return size;
71 }

task3.cpp

 1 #include "vectorInt.hpp"
 2 #include <iostream>
 3 
 4 using std::cin;
 5 using std::cout;
 6 
 7 void output(const vectorInt &vi) {
 8     for(auto i = 0; i < vi.get_size(); ++i)
 9         cout << vi.at(i) << ", ";
10     cout << "\b\b \n";
11 }
12 
13 
14 void test1() {
15     int n;
16     cout << "Enter n: ";
17     cin >> n;
18 
19     vectorInt x1(n);
20     for(auto i = 0; i < n; ++i)
21         x1.at(i) = i*i;
22     cout << "x1: ";  output(x1);
23 
24     vectorInt x2(n, 42);
25     vectorInt x3(x2);
26     x2.at(0) = -999;
27     cout << "x2: ";  output(x2);
28     cout << "x3: ";  output(x3);
29 }
30 
31 void test2() {
32     const vectorInt  x(5, 42);
33     vectorInt y(10, 0);
34 
35     cout << "y: ";  output(y);
36     y.assign(x);
37     cout << "y: ";  output(y);
38     
39     cout << "x.at(0) = " << x.at(0) << endl;
40     cout << "y.at(0) = " << y.at(0) << endl;
41 }
42 
43 int main() {
44     cout << "測試1: \n";
45     test1();
46 
47     cout << "\n測試2: \n";
48     test2();
49 }

執行截圖:

問題回答:

問題一: vectorInt(const vectorInt &vi);的實現是深複製。複製一個vectorInt物件,會分配新的記憶體並複製原物件中的元素,包括指標。

問題二:vectorInt類中,這兩個at()介面,如果返回值型別改成int而非int&(相應地,實現部分也 同步修改),測試程式碼還能正確執行嗎?能正常執行,但也意味著返回的是元素的值而不是對該值的引用。在這種情況下,呼叫 at() 的程式碼將能夠正常執行,但會失去對原始陣列元素的修改能力

如果把line18返回值型別前面的const掉,針對這個測試 程式碼,是否有潛在安全隱患?會存在安全隱患。返回一個非 const 引用可能會允許外部程式碼修改物件的內部資料結構。

問題三:assign() 方法的返回值型別可以改成vectorInt,但是每次呼叫之後返回的不是原物件的引用,而是一個副本。

4. 實驗任務4

程式碼:

 1 #pragma once
 2 
 3 #include <iostream>
 4 #include <cassert>
 5 #include<cstring>
 6 
 7 using std::cout;
 8 using std::endl;
 9 
10 // 類Matrix的宣告
11 class Matrix {
12 public:
13     Matrix(int n, int m);           // 建構函式,構造一個n*m的矩陣, 初始值為value
14     Matrix(int n);                  // 建構函式,構造一個n*n的矩陣, 初始值為value
15     Matrix(const Matrix &x);        // 複製建構函式, 使用已有的矩陣X構造
16     ~Matrix();
17 
18     void set(const double *pvalue);         // 用pvalue指向的連續記憶體塊資料按行為矩陣賦值
19     void clear();                           // 把矩陣物件的值置0
20     
21     const double& at(int i, int j) const;   // 返回矩陣物件索引(i,j)的元素const引用
22     double& at(int i, int j);               // 返回矩陣物件索引(i,j)的元素引用
23     
24     int get_lines() const;                  // 返回矩陣物件行數
25     int get_cols() const;                   // 返回矩陣物件列數
26 
27     void display() const;                    // 按行顯示矩陣物件元素值
28 
29 private:
30     int lines;      // 矩陣物件內元素行數
31     int cols;       // 矩陣物件內元素列數
32     double *ptr;
33 };
34 
35 Matrix::Matrix(int n, int m):lines{n},cols{m},ptr{new double[n*m]}{
36  
37 }           // 建構函式,構造一個n*m的矩陣, 初始值為value
38    Matrix::Matrix(int n) :lines{n},cols{n}, ptr{new double[n*n] }{}
39                  // 建構函式,構造一個n*n的矩陣, 初始值為value
40   Matrix::Matrix(const Matrix &x):lines{x.lines},cols(x.cols),ptr(new double[x.lines*x.cols]){
41   std::memcpy(ptr,x.ptr,lines*cols*sizeof(double));
42               }        // 複製建構函式, 使用已有的矩陣X構造
43     Matrix::~Matrix(){delete [] ptr;
44     }
45 
46     void Matrix::set(const double *pvalue){
47     for (auto i = 0; i < lines; ++i) {
48         for (auto j = 0; j < cols; ++j) {
49             ptr[i * cols + j] = pvalue[i * cols + j];
50         }
51     }}
52         // 用pvalue指向的連續記憶體塊資料按行為矩陣賦值
53     void Matrix::clear(){std::memset(ptr ,0,lines*cols*sizeof(double));}                           // 把矩陣物件的值置0
54     
55     const double& Matrix::at(int i, int j) const {
56     assert(i >= 0 && i < lines && j >= 0 && j < cols);
57     return ptr[i * cols + j];
58 }   // 返回矩陣物件索引(i,j)的元素const引用
59     double& Matrix::at(int i, int j){
60     assert(i >= 0 && i < lines && j >= 0 && j < cols);
61     return ptr[i * cols + j];
62 }               // 返回矩陣物件索引(i,j)的元素引用
63     
64     int Matrix::get_lines() const{
65     return lines;}                  // 返回矩陣物件行數
66     int Matrix::get_cols() const{
67     return cols;}                  // 返回矩陣物件列數
68 
69     void Matrix::display() const{
70             for (auto i =0;i<lines;++i){
71            for(auto j =0;j<cols ;++j){
72                    cout<<at(i,j);
73                     if (j < cols - 1) {
74                 cout << ",";  }
75                    cout<<endl;}                 // 按行顯示矩陣物件元素值
76 }

執行截圖:

5. 實驗任務5

程式碼:

 1 #ifndef USER_HPP
 2 #define USER_HPP
 3 
 4 #include <iostream>
 5 #include <string>
 6 #include <limits>
 7 
 8 class User {
 9 private:
10     std::string name;     // 使用者名稱
11     std::string password; // 密碼
12     std::string email;    // 郵箱
13 
14 public:
15     // 建構函式
16     User(const std::string& name0, const std::string& password0 = "123456", const std::string& mail0 = "")
17         : name(name0), password(password0), email(mail0) {}
18 
19     // 介面1,用來提示設定郵箱
20     void set_email() {
21     std::string test_email;
22     
23     while (true) { 
24         std::cout << "Enter email address: ";
25         std::cin >> test_email;
26 
27         if (test_email.find('@') == std::string::npos) {
28             std::cout << "Illegal email. Please re-enter email: ";
29         } else {
30             email = test_email;
31             std::cout << "Email is set successfully..." << std::endl;
32             break; 
33         }
34     }
35 }
36 
37     // 介面2,用來修改密碼
38     void change_password() {
39         std::string old_password, new_password; // 定義 new_password
40         int attempts = 0;
41         const int max_attempts = 3;
42 
43         while (attempts < max_attempts) {
44             std::cout << "Enter old password: ";
45             std::cin >> old_password;
46            
47             if (old_password == password) {
48                 std::cout << "Enter new password: ";
49                 std::cin >> new_password;
50                 password = new_password;
51                 std::cout << "new password is set successfully..." << std::endl;
52                 return;
53             } else {
54                 attempts++;
55               
56                 if (attempts == max_attempts) {
57                     std::cout << "password input error. Please try after a while." << std::endl;
58                 } else {
59                     std::cout << "password input error. Please re-enter again." << std::endl;
60                 }
61                 
62             }
63         }
64     }
65 
66     // 介面3,用來列印使用者資訊
67     void display() const {
68         std::cout << "name: " << name << "\n";
69         std::cout << "pass: ";
70         for (int i = 0; i < password.length(); ++i) {
71             std::cout << "*"; 
72         }
73         std::cout << std::endl;
74         std::cout << "email: " << email << std::endl;
75     }
76 };
77 
78 #endif 

執行截圖:

6. 實驗任務6

程式碼:

data.h

 1 #pragma once
 2 class Date 
 3 {
 4 private:
 5     int year;
 6     int month;
 7     int day;
 8     int totalDays;
 9 public:
10    Date(int year, int month, int day);
11    int getYear()const { return year; }
12    int getMonth() const { return month; }
13    int getDay()const { return day; }
14    int getMaxDay()const;
15    bool isLeapYear()const 
16   {
17       return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
18   }
19   void show()const;
20  int distance(const Date& date)const 
21   {
22        return totalDays - date.totalDays;
23     }
24  };

data.cpp

 1 #include"data.h"
 2 #include<iostream>
 3 #include<cstdlib>
 4  
 5 using namespace std;
 6  
 7 namespace 
 8  {
 9 const int DAYS_BEFORE_MONTH[] = { 0,31,59,90,120,151,181,212,243,273,304,334,365 };
10  }
11 Date::Date(int year, int month, int day) :year(year), month(month), day(day) 
12  {
13     if (day <= 0 || day > getMaxDay()) 
14     {
15         cout << "Invalid date: ";
16         show();
17         cout << endl;
18         exit(1);
19     }
20     int years = year - 1;
21     totalDays = year * 365 + years / 4 - years / 100 + years / 400 + DAYS_BEFORE_MONTH[month - 1] + day;
22     if (isLeapYear() && month > 2) totalDays++;
23 }
24 int Date::getMaxDay()const 
25 {
26     if (isLeapYear() && month == 2)
27          return 29;
28     else
29          return DAYS_BEFORE_MONTH[month] - DAYS_BEFORE_MONTH[month - 1];
30 }
31 void Date::show() const 
32 {
33     cout << getYear() << "-" << getMonth() << "-" << getDay();
34 }

account.h

 1 #pragma once
 2 #include"data.h"
 3 #include<string>
 4 
 5 class SavingsAccount 
 6 {
 7 private:
 8     std::string id;
 9     double balance;
10     double rate;
11     Date  lastDate;
12     double accumulation;
13     static double total;
14     void record(const Date& date, double amount, const std::string& desc);
15  
16     void error(const std::string& msg)const;
17 
18     double accumulate(const Date& date) const 
19     {
20     return accumulation + balance * date.distance(lastDate);
21  
22     }
23 public:
24     SavingsAccount(const Date& date, const std::string& id, double rate);
25 
26     const std::string& getId() const { return id; }
27     double getBalance() const { return balance; }
28 
29     double getRate() const { return rate; }
30 
31     static double getTotal() { return total; }
32 
33     void deposit(const Date& date, double amount, const std::string& desc);
34 
35     void withdraw(const Date& date, double amount, const std::string& desc);
36 
37     void settle(const Date& date);
38 
39     void show() const;
40  };

account.cpp

 1 #include "account.h"
 2 #include<cmath>
 3 #include<iostream>
 4  
 5 using namespace std;
 6 
 7 double SavingsAccount::total = 0;
 8 
 9 SavingsAccount::SavingsAccount(const Date& date, const string& id, double rate) : id(id), balance(0), rate(rate), lastDate(date), accumulation(0) 
10 {
11     date.show();
12     cout << "\t#" << id << "  created" << endl;
13  }
14 
15 void SavingsAccount::record(const Date& date, double amount, const string& desc) 
16 {
17     accumulation = accumulate(date);
18  
19     lastDate = date;
20 
21     amount = floor(amount * 100 + 0.5) / 100; //保留小數點後兩位
22     balance += amount;
23     total += amount;
24     date.show();
25  
26      cout << "\t#" << id << "\t" << amount << "\t" << balance << "\t" << desc << endl;
27  
28  }
29 void SavingsAccount::error(const string& msg)const 
30 {
31     cout << "Error(#" << id << "):" << msg << endl;
32 
33 }
34 
35  void SavingsAccount::deposit(const Date& date, double amount, const string& desc) 
36 {
37     record(date, amount, desc);
38  
39 }
40 
41  void SavingsAccount::withdraw(const Date& date, double amount, const string& desc) 
42  {
43     if (amount > getBalance())
44         error("not enough money");
45     else
46         record(date, -amount, desc);
47 
48  }
49  
50  void SavingsAccount::settle(const Date& date) 
51 {
52 
53     double interest = accumulate(date) * rate / date.distance(Date(date.getYear() - 1, 1, 1)); //計算年息
54 
55     if (interest != 0)
56  
57          record(date, interest, "interest");
58 
59      accumulation = 0;
60  
61 }
62 
63 void SavingsAccount::show() const 
64 {
65     cout << id << "\tBalance:" << balance;
66  }

6_25.cpp

 1 #include"account.h"
 2 #include<iostream>
 3 
 4 using namespace std;
 5  
 6 int main() 
 7 {
 8     Date date(2008, 11, 1);
 9  
10     SavingsAccount accounts[] = 
11     {
12     SavingsAccount(date, "03755217", 0.015),
13     SavingsAccount(date, "02342342", 0.015)
14     };
15     const int n = sizeof(accounts) / sizeof(SavingsAccount);
16 
17     accounts[0].deposit(Date(2008, 11, 5), 5000, "salary");
18     accounts[1].deposit(Date(2008, 11, 25), 10000, "sell stock 0323");
19     accounts[0].deposit(Date(2008, 12, 5), 5500, "salary");
20     accounts[1].withdraw(Date(2008, 12, 20), 4000, "buy a laptop");
21 
22    cout << endl;
23     for (int i = 0; i < n; i++) 
24     {
25         accounts[i].settle(Date(2009, 1, 1));
26         accounts[i].show();
27         cout << endl;
28     }
29     cout << "Total: " << SavingsAccount::getTotal() << endl;
30     return 0;
31  }

執行截圖:

相關文章