实验3

实验任务1:

源代码Button.hpp

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

源代码window.hpp

 1 #pragma once
 2 #include<iostream>
 3 #include<vector>
 4 #include<algorithm>
 5 #include "button.hpp"
 6 
 7 class window{
 8 public:
 9     window(const std::string &title_);
10     void display() const;
11     void close();
12     void add_button(const std::string &label);
13     void click_button(const std::string &label);
14 
15 private:
16     bool has_button(const std::string &label) const;    
17 private:
18     std::string title;
19     std::vector<Button> buttons;
20 };
21 
22 window::window(const std::string &title_):title{title_}{
23     buttons.emplace_back(Button("close"));
24 }
25 
26 inline void window::display() const {
27     std::string s(40,'*');
28     std::cout << s << std::endl;
29     std::cout << "window;" << title << std::endl;
30     int cnt=0;
31     for(const auto &button:buttons)
32         std::cout << ++cnt << ". " << button.get_label() << std::endl;
33     std::cout << s << std::endl; 
34 }
35 
36 inline void window::close(){
37     std::cout << "close window '" << title << "'" << std::endl;
38     click_button("close");
39 }
40 
41 inline bool window::has_button(const std::string &label) const {
42     for(const auto &button:buttons)
43         if(button.get_label()==label)
44             return true;
45     return false;
46 }
47 
48 inline void window::add_button(const std::string &label){
49     if(has_button(label))
50         std::cout << "button " << label << " already exists!\n";
51     else
52         buttons.emplace_back(Button(label));
53 }
54 
55 inline void window::click_button(const std::string &label){
56     for(auto &button:buttons)
57         if(button.get_label()==label){
58             button.click();
59             return;
60         }
61     std::cout << "no button: " << label <<std::endl; 
62 }

源代码task1.cpp

 1 #include "window.hpp"
 2 #include<iostream>
 3 
 4 void test(){
 5     window w("Demo");
 6     w.add_button("add");
 7     w.add_button("remove");
 8     w.add_button("modify");
 9     w.add_button("add");
10     w.display();
11     w.close();
12 } 
13 
14 int main(){
15     std::cout << "用组合类模拟简单GUI:\n";
16     test();
17 }

运行结果截图:

image

问题1:

是组合关系。window对象包含Button对象,Button不存在独立于window的生命周期。

问题2:

(1)优点:用户可以直接查询按钮是否存在,增加灵活性。

风险:破坏了封装性,增加维护成本,后续若修改判断逻辑,可能影响依赖该接口的外部代码。

(2)用户直接需要的功能设为public,仅为内部辅助功能的设为private,可能破坏对象一致性的操作必须设为private。

问题3:

接口1:性能:返回引用,避免字符串拷贝,性能更好。

安全性:调用者可能保存引用并在Button对象销毁后使用。

接口2:性能:返回值拷贝,有性能开销,特别是长字符串时。

安全性:调用者获得独立副本。

问题4:

修改后程序正常运行。push_back先构造临时Button对象,再拷贝或移动到vector中,涉及两次构造。emplace_back直接在vector内存中构造Button对象,只涉及一次构造,更高效,避免了不必要的拷贝。

 

实验任务2:

源代码task2.cpp

 1 #include<iostream>
 2 #include<vector>
 3 
 4 void test1();
 5 void test2();
 6 void output1(const std::vector<int> &v);
 7 void output2(const std::vector<int> &v);
 8 void output3(const std::vector<std::vector<int>>& v);
 9 
10 int main(){
11     std::cout << "深复制验证1:标准库vector<int>\n";
12     test1();
13     std::cout << "\n深复制验证2:标准库vector<int>嵌套使用\n";
14     test2();
15 }
16 
17 void test1(){
18     std::vector<int> v1(5,42);
19     const std::vector<int> v2(v1);
20     
21     std::cout << "**********拷贝构造后**********\n";
22     std::cout << "v1: ";output1(v1);
23     std::cout << "v2: ";output1(v2);
24     
25     v1.at(0)=-1;
26     
27     std::cout << "**********修改v1[0]后**********\n";
28     std::cout << "v1: ";output1(v1);
29     std::cout << "v2: ";output1(v2);
30 }
31 
32 void test2(){
33     std::vector<std::vector<int>> v1{{1,2,3},{4,5,6,7}};
34     const std::vector<std::vector<int>> v2(v1);
35     
36     std::cout << "**********拷贝构造后**********\n";
37     std::cout << "v1: ";output3(v1);
38     std::cout << "v2: ";output3(v2);
39     
40     v1.at(0).push_back(-1);
41     
42     std::cout << "**********修改v1[0]后**********\n";
43     std::cout << "v1: \n";output3(v1);
44     std::cout << "v2: \n";output3(v2);
45 }
46 
47 void output1(const std::vector<int> &v){
48     if(v.size()==0){
49         std::cout << '\n';
50         return;
51     }
52     std::cout << v.at(0);
53     for(auto i=1;i<v.size();++i)
54         std::cout << ", " <<v.at(i);
55     std::cout << '\n';
56 }
57 
58 void output2(const std::vector<int> &v){
59     if(v.size()==0){
60         std::cout << '\n';
61         return;
62     }
63     
64     auto it=v.begin();
65     std::cout << *it;
66     
67     for(it=v.begin()+1;it!=v.end();++it)
68         std::cout << ", " << *it;
69     std::cout << '\n';
70 }
71 
72 void output3(const std::vector<std::vector<int>>& v){
73     if(v.size()==0){
74         std::cout << '\n';
75         return;
76     }
77     for(auto &i:v)
78         output2(i);
79 }

运行结果截图:

image

问题1:

std::vector<int> v1(5,42);完成默认构造,v1包含5个值为42的数据项;

const std::vector<int> v2(v1);完成拷贝构造,v2包含5个值为42的数据项。

问题2:

v1.size()=2;v2.size()=2;v1[0].size()=3.

问题3:

能。区别:v1.at(0)会进行边界检查,v1[0]不进行边界检查。

问题4:

(1)能。v1.at(0)返回第一个内层vector的引用,push_back(-1)在该末尾添加-1,r.at(r.size()-1)访问最后一个元素,即刚添加的-1.

(2)优势:避免不必要的拷贝,节省内存。限制:不能通过r修改vector的内容。

问题5:

(1)深复制。

(2)当v是vector<int>时,v.at(0)返回int&;当v是const vector<int>时,v.at(0)返回const int&.at()必须携带const修饰的重载版本。

 

实验任务3:

源代码vectorlnt.hpp

 1 #pragma once
 2 
 3 #include<iostream>
 4 
 5 class vectorInt{
 6 public:
 7     vectorInt();
 8     vectorInt(int n_);
 9     vectorInt(int n_,int value);
10     vectorInt(const vectorInt &vi);
11     ~vectorInt();
12     
13     int size() const;
14     int& at(int index);
15     const int& at(int index) const;
16     vectorInt& assign(const vectorInt &vi);
17     
18     int* begin();
19     int* end();
20     const int* begin() const;
21     const int* end() const;
22 private:
23     int n;
24     int *ptr;
25 };
26 
27 vectorInt::vectorInt():n{0},ptr{nullptr}{
28 }
29 
30 vectorInt::vectorInt(int n_):n{n_},ptr{new int[n_]}{
31 }
32 
33 vectorInt::vectorInt(int n_,int value):n{n_},ptr{new int[n_]}{
34     for(auto i=0;i<n;++i)
35         ptr[i]=value;
36 }
37 
38 vectorInt::vectorInt(const vectorInt &vi):n{vi.n},ptr{new int[n]}{
39     for(auto i=0;i<n;i++)
40         ptr[i]=vi.ptr[i];
41 }
42 
43 vectorInt::~vectorInt(){
44     delete [] ptr;
45 }
46 
47 int vectorInt::size() const{
48     return n;
49 }
50 
51 const int& vectorInt::at(int index) const{
52     if(index<0||index>=n){
53         std::cerr << "IndexError:index out of range\n";
54         std::exit(1);
55     }
56     
57     return ptr[index];
58 }
59 
60 int& vectorInt::at(int index){
61     if(index<0||index>=n){
62         std::cerr << "IndexError:index out of range\n";
63         std::exit(1);
64     }
65     
66     return ptr[index];
67 }
68 
69 vectorInt& vectorInt::assign(const vectorInt &vi){
70     if(this==&vi)
71         return *this;
72     int *ptr_tmp;
73     ptr_tmp=new int[vi.n];
74     for(int i=0;i<vi.n;++i)
75         ptr_tmp[i]=vi.ptr[i];
76     delete[] ptr;
77     n=vi.n;
78     ptr=ptr_tmp;
79     return *this;
80 } 
81 
82 int* vectorInt::begin(){
83     return ptr;
84 }
85 
86 int* vectorInt::end(){
87     return ptr+n;
88 }
89 
90 const int* vectorInt::begin() const{
91     return ptr;
92 }
93 
94 const int* vectorInt::end() const{
95     return ptr+n;
96 }

源代码task3.cpp

 1 #include "vectorInt.hpp"
 2 #include <iostream>
 3 
 4 void test1();
 5 void test2();
 6 void output1(const vectorInt &vi);
 7 void output2(const vectorInt &vi);
 8 
 9 int main(){
10     std::cout << "测试1: \n";
11     test1();
12     
13     std::cout << "\n测试2:\n";
14     test2();
15 }
16 
17 void test1(){
18     int n;
19     std::cout << "Enter n: ";
20     std::cin >> n;
21     
22     vectorInt x1(n);
23     for(auto i=0;i<n;++i)
24         x1.at(i)=(i+1)*10;
25     std::cout << "x1: ";output1(x1);
26     
27     vectorInt x2(n,42);
28     vectorInt x3(x2);
29     x2.at(0)=-1;
30     std::cout << "x2: ";output1(x2);
31     std::cout << "x3: ";output1(x3);
32 }
33 
34 void test2(){
35     const vectorInt x(5,42);
36     vectorInt y;
37     
38     y.assign(x);
39     
40     std::cout << "x: ";output2(x);
41     std::cout << "y: ";output2(y);
42 }
43 
44 void output1(const vectorInt &vi){
45     if(vi.size()==0){
46         std::cout << '\n';
47         return;
48     }
49     std::cout << vi.at(0);
50     for(auto i=1;i<vi.size();++i)
51         std::cout << ", " << vi.at(i);
52     std::cout << '\n';
53 }
54 
55 void output2(const vectorInt &vi){
56     if(vi.size()==0){
57         std::cout << '\n';
58         return;
59     } 
60     
61     auto it=vi.begin();
62     std::cout << *it;
63     
64     for(it=vi.begin()+1;it!=vi.end();++it)
65         std::cout << ", " << *it;
66     std::cout << '\n';
67 }

运行结果截图:

image

问题1:

自赋值问题,先删除ptr会导致vi.ptr成为悬空指针,后续访问vi.ptr[i]会产生未定义行为。

new int[n]内存分配失败,对象处于无效状态,破坏了异常安全保证。

原版本先分配新内存再释放旧内存,确保操作失败时原数据不受影响。

问题2:

(1)添加const限定符,转换前this类型vectorInt*,转换后this类型const vectorInt*,目的是将当前对象的指针强制转换为const版本。

(2)移除const限定符,转换前返回类型为const int&,转换后返回类型为int&,目的是移除const限定符,返回非const引用以支持修改操作。

问题3:

(1)auto it1=v1.begin();调用int* begin();auto it2=v2.begin();调用const int* begin()  const.非const版本用于需要修改容器元素的场景,返回普通指针;const版本用于只读访问场景,返回const指针保证数据不被修改。

(2)迭代器本质是提供"遍历能力"的抽象,对于连续内存容器,原始指针已具备完整的迭代器功能,复杂容器需要专门类来封装遍历逻辑。

问题4:

可以。std::fill_n(ptr, n, value);功能是从ptr指向的数组开始,连续填充n个元素,每个元素的值为value.

std::copy_n(vi.ptr, vi.n, ptr);功能是从vi.ptr指向的数组开始,复制vi.n个元素到ptr指向的目标数组.

std::copy_n(vi.ptr, vi.n, ptr_tmp);功能是从vi.ptr指向的数组开始,复制vi.n个元素到ptr指向的目标数组,用于assign方法中复制vi的元素到临时内存ptr_tmp.
 
实验任务4:
源代码matrix.hpp
 1 #pragma once
 2 
 3 class Matrix{
 4 public:
 5     Matrix(int rows_,int cols_,double value=0);
 6     Matrix(int rows_,double value=0);
 7     Matrix(const Matrix &x);
 8     ~Matrix();
 9     
10     void set(const double *pvalue,int size);
11     void clear();
12     
13     const double& at(int i,int j) const;
14     double& at(int i,int j);
15     
16     int rows() const;
17     int cols() const;
18     
19     void print() const;
20     
21 private:
22     int n_rows;
23     int n_cols;
24     double *ptr;
25 };

源代码matrix.cpp

 1 #include "matrix.hpp"
 2 #include<iostream>
 3 #include<iomanip>
 4 
 5 Matrix::Matrix(int rows_,int cols_,double value):n_rows(rows_),n_cols(cols_){
 6     int size=n_rows*n_cols;
 7     ptr=new double[size];
 8     for(int i=0;i<size;++i){
 9         ptr[i]=value;
10     }
11 }
12 
13 Matrix::Matrix(int rows_,double value):Matrix(rows_,rows_,value){
14 }
15 
16 Matrix::Matrix(const Matrix &x):n_rows(x.n_rows),n_cols(x.n_cols){
17     int size=n_rows*n_cols;
18     ptr=new double[size];
19     for(int i=0;i<size;i++){
20         ptr[i]=x.ptr[i];
21     }
22 }
23 
24 Matrix::~Matrix(){
25     delete[] ptr;
26 }
27 
28 void Matrix::set(const double *pvalue,int size){
29     if(size!=n_rows*n_cols){
30         std::cerr << "Error:Size mismatch in set function." << std::endl;
31         exit(1);
32     }
33     for(int i=0;i<size;i++){
34         ptr[i]=pvalue[i];
35     }
36 }
37 
38 void Matrix::clear(){
39     int size=n_rows*n_cols;
40     for(int i=0;i<size;i++){
41         ptr[i]=0.0;
42     }
43 }
44 
45 const double& Matrix::at(int i,int j) const{
46     if(i<0||i>=n_rows||j<0||j>=n_cols){
47         std::cerr << "IndexError: Index out of range." << std::endl;
48         exit(1);
49     }
50     return ptr[i*n_cols+j];
51 }
52 
53 double& Matrix::at(int i,int j){
54     return const_cast<double&>(static_cast<const Matrix*>(this)->at(i,j));
55 } 
56 
57 int Matrix::rows() const{
58     return n_rows;
59 }
60 
61 int Matrix::cols() const{
62     return n_cols;
63 }
64 
65 void Matrix::print() const {
66     for(int i=0;i<n_rows;++i){
67         std::cout << at(i,0);
68         for(int j=1;j<n_cols;j++){
69             std::cout << "," << at(i,j);
70         }
71         std::cout << std::endl;
72     }
73 }

源代码task4.cpp

 1 #include <iostream>
 2 #include <cstdlib>
 3 #include "matrix.hpp"
 4 
 5 void test1();
 6 void test2();
 7 void output(const Matrix &m, int row_index);
 8 
 9 int main() {
10     std::cout << "测试1: \n";
11     test1();
12 
13     std::cout << "\n测试2: \n";
14     test2();
15 }
16 
17 void test1() {
18     double x[1000] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
19 
20     int n, m;
21     std::cout << "Enter n and m: ";
22     std::cin >> n >> m;
23 
24     Matrix m1(n, m);   
25     m1.set(x, n*m);     
26 
27     Matrix m2(m, n);    
28     m2.set(x, m*n);     
29 
30     Matrix m3(n);      
31     m3.set(x, n*n);     
32 
33     std::cout << "矩阵对象m1: \n";   m1.print();
34     std::cout << "矩阵对象m2: \n";   m2.print();
35     std::cout << "矩阵对象m3: \n";   m3.print();
36 }
37 
38 void test2() {
39     Matrix m1(2, 3, -1);
40     const Matrix m2(m1);
41     
42     std::cout << "矩阵对象m1: \n";   m1.print();
43     std::cout << "矩阵对象m2: \n";   m2.print();
44 
45     m1.clear();
46     m1.at(0, 0) = 1;
47 
48     std::cout << "m1更新后: \n";
49     std::cout << "矩阵对象m1第0行 "; output(m1, 0);
50     std::cout << "矩阵对象m2第0行: "; output(m2, 0);
51 }
52 
53 void output(const Matrix &m, int row_index) {
54     if(row_index < 0 || row_index >= m.rows()) {
55         std::cerr << "IndexError: row index out of range\n";
56         exit(1);
57     }
58 
59     std::cout << m.at(row_index, 0);
60     for(int j = 1; j < m.cols(); ++j)
61         std::cout << ", " << m.at(row_index, j);
62     std::cout << '\n';
63 }

运行结果截图:

image

拓展思考:
若把内部存储 double *ptr; 换成 std::vector<double> 并保持原接口:
无需手写复制构造/析构等,自动获得 RAII 与异常安全
仍保持连续内存,性能无损,代码量精简
对比两种实现,体会“零成本抽象”
 
实验任务5:
源代码contact.hpp
 1 #pragma once
 2 
 3 #include <iostream>
 4 #include <string>
 5 
 6 class Contact {
 7 public:
 8     Contact(const std::string &name_, const std::string &phone_);
 9 
10     const std::string &get_name() const;
11     const std::string &get_phone() const;
12     void display() const;
13 
14 private:
15    std::string name;    
16    std::string phone;   
17 };
18 
19 Contact::Contact(const std::string &name_, const std::string &phone_):name{name_}, phone{phone_} {
20 }
21 
22 const std::string& Contact::get_name() const {
23     return name;
24 }
25 
26 const std::string& Contact::get_phone() const {
27     return phone;
28 }
29 
30 void Contact::display() const {
31     std::cout << name << ", " << phone;
32 }

源代码contactBook.hpp

 1 # pragma  once
 2 
 3 #include <iostream>
 4 #include <string>
 5 #include <vector>
 6 #include <algorithm>
 7 #include "contact.hpp"
 8 
 9 class ContactBook {
10 public:
11     void add(const std::string &name, const std::string &phone); 
12     void remove(const std::string &name);
13     void find(const std::string &name) const;
14     void display() const; 
15     size_t size() const;
16     
17 private:
18     int index(const std::string &name) const; 
19     void sort(); 
20 
21 private:
22     std::vector<Contact> contacts;
23 };
24 
25 void ContactBook::add(const std::string &name, const std::string &phone) {
26     if(index(name) == -1) {
27         contacts.push_back(Contact(name, phone));
28         std::cout << name << " add successfully.\n";
29         sort();
30         return;
31     }
32 
33     std::cout << name << " already exists. fail to add!\n"; 
34 }
35 
36 void ContactBook::remove(const std::string &name) {
37     int i = index(name);
38 
39     if(i == -1) {
40         std::cout << name << " not found, fail to remove!\n";
41         return;
42     }
43 
44     contacts.erase(contacts.begin()+i);
45     std::cout << name << " remove successfully.\n";
46 }
47 
48 void ContactBook::find(const std::string &name) const {
49     int i = index(name);
50 
51     if(i == -1) {
52         std::cout << name << " not found!\n";
53         return;
54     }
55 
56     contacts[i].display(); 
57     std::cout << '\n';
58 }
59 
60 void ContactBook::display() const {
61     for(auto &c: contacts) {
62         c.display(); 
63         std::cout << '\n';
64     }
65 }
66 
67 size_t ContactBook::size() const {
68     return contacts.size();
69 }
70 
71 int ContactBook::index(const std::string &name) const{
72     for(size_t i=0;i<contacts.size();i++){
73         if(contacts[i].get_name()==name){
74             return static_cast<int>(i);
75         }
76     }
77     return -1;
78 }
79 
80 void ContactBook::sort(){
81     std::sort(contacts.begin(),contacts.end(),[](const Contact &a,const Contact &b){
82         return a.get_name() < b.get_name();
83     });
84 }

源代码task5.cpp

 1 #include "contactBook.hpp"
 2 
 3 void test() {
 4     ContactBook contactbook;
 5 
 6     std::cout << "1. add contacts\n";
 7     contactbook.add("Bob", "18199357253");
 8     contactbook.add("Alice", "17300886371");
 9     contactbook.add("Linda", "18184538072");
10     contactbook.add("Alice", "17300886371");
11 
12     std::cout << "\n2. display contacts\n";
13     std::cout << "There are " << contactbook.size() << " contacts.\n";
14     contactbook.display();
15 
16     std::cout << "\n3. find contacts\n";
17     contactbook.find("Bob");
18     contactbook.find("David");
19 
20     std::cout << "\n4. remove contact\n";
21     contactbook.remove("Bob");
22     contactbook.remove("David");
23 }
24 
25 int main() {
26     test();
27 }

运行结果截图:

image

 

实验总结:

本次实验使用C++标准库容器std::vector和算法std::sort,显著减少了代码量并提升了安全性。用std::sort实现字典序排序,代码简洁且高效,比手动实现冒泡排序、选择排序更方便,这让我认识到C++标准库在代码实现中的便利。在代码实现时要注意细节问题,防止因字母敲错,语句漏掉引起运行不成功。

 

 

 

posted @ 2025-11-24 23:33  知之为吃吃  阅读(21)  评论(1)    收藏  举报