类和对象_基础编程2

实验任务一:

源代码:

button.hpp

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

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 private:
15     bool has_button(const std::string& label) const;
16 private:
17     std::string title;
18     std::vector<Button> buttons;
19 };
20 Window::Window(const std::string& title_) : title{ title_ } {
21     buttons.push_back(Button("close"));
22 }
23 inline void Window::display() const {
24     std::string s(40, '*');
25     std::cout << s << std::endl;
26     std::cout << "window : " << title << std::endl;
27     int cnt = 0;
28     for (const auto& button : buttons)
29         std::cout << ++cnt << ". " << button.get_label() << std::endl;
30     std::cout << s << std::endl;
31 }
32 inline void Window::close() {
33     std::cout << "close window '" << title << "'" << std::endl;
34     click_button("close");
35 }
36 inline bool Window::has_button(const std::string& label) const {
37     for (const auto& button : buttons)
38         if (button.get_label() == label)
39             return true;
40 
41     return false;
42 }
43 inline void Window::add_button(const std::string& label) {
44     if (has_button(label))
45         std::cout << "button " << label << " already exists!\n";
46     else
47         buttons.push_back(Button(label));
48 }
49 inline void Window::click_button(const std::string& label) {
50     for (auto& button : buttons)
51         if (button.get_label() == label) {
52             button.click();
53             return;
54         }
55 
56     std::cout << "no button: " << label << std::endl;
57 }
window.hpp

task1.cpp

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

运行结果截图:

image

 回答问题:

问题1:这个范例中,Window和Button是组合关系吗?

  是组合关系。Window 类的私有成员std::vector<Button> buttons直接存储 Button 对象,Button 对象作为 Window 的组成部分存在:当 Window 对象被销毁时,其内部 vector 及 vector 中存储的所有 Button 对象会自动调用析构函数销毁;同时,Button 对象无法脱离 Window 独立存在。

问题2: bool has_button(const std::string &label) const; 被设计为私有。 思考并回答:

(1)若将其改为公有接口,有何优点或风险?

   优点:增加代码的灵活性,允许用户直接查询。风险:破坏类的封装性,增加类的维护成本。

(2)设计类时,如何判断一个成员函数应为 public 还是 private?(可从“用户是否需要”、“是否仅为内 部实现细节”、“是否易破坏对象状态”等角度分析。)

    函数是类对外提供的核心功能(如 Window 的displayadd_button,Button 的click),需设为 public;若函数仅为辅助功能(如has_button辅助add_button),外部用户无直接使用需求,则设为 private。

    函数与类的内部逻辑强绑定,且可能随内部实现变更而调整,则设为 private;若函数不涉及内部细节,则设为 public。

    若函数直接操作类的私有成员,调用后可能导致对象状态变化,则设为 private;若函数仅读取状态(如add_button有重复校验),则可设为 public。

问题3:Button的接口const std::string& get_label() const;返回const std::string&。简 要说明以下两种接口设计在性能和安全性方面的差异。

接口1:const std::string& get_label() const;

接口2:const std::string get_label() const;

  性能:接口1仅返回私有成员label的 const 引用,无需创建新对象,无内存拷贝开销,性能高效。接口2会增加内存开销,影响性能。

  安全性:接口1返回 const 引用可防止外部通过引用修改私有成员label,保证成员变量不可被外部篡改,安全性高。接口2安全性较低。

问题4:把代码中所有 xx.push_back(Button(xxx))改成 xx.emplace_back(xxx),观察程序是否正 常运行;查阅资料,回答两种写法的差别。

  正常运行。差别:

  1. 对象构造方式不同:
    • push_back(Button(xxx)):先显式创建一个 Button 临时对象(调用 Button 的构造函数),再将临时对象拷贝(或移动)到 vector 的内存空间中,存在 “临时对象创建→拷贝 / 移动” 的过程。
    • emplace_back(xxx):直接在 vector 预留的内存空间中调用 Button 的构造函数创建对象,无需创建临时对象,也无需拷贝 / 移动操作,减少了中间步骤。
  2. 依赖的构造函数不同:
    • push_back要求传入的参数是 Button 对象(或可转换为 Button 的对象),依赖 Button 的构造函数和拷贝 / 移动构造函数。
    • emplace_back可直接传入 Button 构造函数所需的参数(如label字符串),通过 “可变参数模板” 将参数转发给 Button 的构造函数,仅依赖 Button 的构造函数。
  3. 性能差异:
    • emplace_back因避免了临时对象的创建和拷贝 / 移动,在对象构造成本较高(如成员变量复杂)时,性能优于push_back;在简单对象(如 Button 仅含 string 成员)场景下,性能差异不明显,但仍更高效

实验任务二:

源代码:

 

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

 

运行结果截图:

image

 

 回答问题:

问题1:测试模块1中这两行代码分别完成了什么构造? v1、v2 各包含多少个值为 42 的数据项?

  v1赋值构造,v2拷贝构造。v1,v2都为10个值为42的数据项。

 

问题2:测试模块2中这两行代码执行后, v1.size()、v2.size()、v1[0].size()分别是多少?   

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

问题3:测试模块1中,把 v1.at(0) = -1;写成 v1[0] = -1;能否实现同等效果?两种用法有何区别?

  能;区别:at()会进行边界检查,较[ ]性能更高,更加安全。

问题4:测试模块2中执行 v1.at(0).push_back(-1); 后

(1) 用以下两行代码,能否输出-1?为什么?

  能;原因:v1.at(0)会返回v1第0个元素(vector)的引用,所以push_back(-1)会在内层vector最后一个元素后插入一个值为-1的元素。r.at(r.size()-1)会获取内层vector的最后一个元素-1.

(2)r定义成用 const &类型接收返回值,在内存使用上有何优势?有何限制?

  优势:避免不必要的拷贝,减少内存开销,访问效率更高。限制:const类型只能访问值,不能修改值。

问题5:观察程序运行结果,反向分析、推断:

(1) 标准库模板类 vector 的复制构造函数实现的是深复制还是浅复制?

  深复制;v1中的值改变时,v2不变。

(2) vector<T>::at() 接口思考: 当 v是 vector<int>时, v.at(0) 返回值类型是什么?当v是 const vector<int>时,v.at(0) 返回值类型又是什么?据此推断 at() 是否必须提供带 const 修饰的重载版本?

  当 v是 vector<int>时, v.at(0) 返回值类型是 int&,当v是 const vector<int>时,v.at(0) 返回值类型是const int& ;at() 必须提供带 const 修饰的重载版本,以同时支持非 const 容器的可修改访问和 const 容器的只读访问,兼顾灵活性与安全性。

实验任务三:

源代码:

 1 #pragma once
 2 #include <iostream>
 3 // 动态int数组对象类
 4 class vectorInt {
 5 public:
 6     vectorInt();
 7     vectorInt(int n_);
 8     vectorInt(int n_, int value);
 9     vectorInt(const vectorInt& vi);
10     ~vectorInt();
11     int size() const;
12     int& at(int index);
13     const int& at(int index) const;
14     vectorInt& assign(const vectorInt& vi);
15     int* begin();
16     int* end();
17     const int* begin() const;
18     const int* end() const;
19 private:
20     int n;
21     // 当前数据项个数
22     int* ptr;  // 数据区
23 };
24 vectorInt::vectorInt() :n{ 0 }, ptr{ nullptr } {
25 }
26 vectorInt::vectorInt(int n_) : n{ n_ }, ptr{ new int[n] } {
27 }
28 vectorInt::vectorInt(int n_, int value) : n{ n_ }, ptr{ new int[n_] } {
29     for (auto i = 0; i < n; ++i)
30         ptr[i] = value;
31 }
32 vectorInt::vectorInt(const vectorInt& vi) : n{ vi.n }, ptr{ new int[n] } {
33     for (auto i = 0; i < n; ++i)
34         ptr[i] = vi.ptr[i];
35 }
36 vectorInt::~vectorInt() {
37     delete[] ptr;
38 }
39 int vectorInt::size() const {
40     return n;
41 }
42 const int& vectorInt::at(int index) const {
43     if (index < 0 || index >= n) {
44         std::cerr << "IndexError: index out of range\n";
45         std::exit(1);
46     }
47     return ptr[index];
48 }
49 int& vectorInt::at(int index) {
50     if (index < 0 || index >= n) {
51         std::cerr << "IndexError: index out of range\n";
52         std::exit(1);
53     }
54     return ptr[index];
55 }
56 vectorInt& vectorInt::assign(const vectorInt& vi) {
57     if (this == &vi)
58         return *this;
59     int* ptr_tmp;
60     ptr_tmp = new int[vi.n];
61     for (int i = 0; i < vi.n; ++i)
62         ptr_tmp[i] = vi.ptr[i];
63     delete[] ptr;
64     n = vi.n;
65     ptr = ptr_tmp;
66     return *this;
67 }
68 int* vectorInt::begin() {
69     return ptr;
70 }
71 int* vectorInt::end() {
72     return ptr + n;
73 }
74 const int* vectorInt::begin() const {
75     return ptr;
76 }
77 const int* vectorInt::end() const {
78     return ptr + n;
79 }
vectorInt.hpp
 1 #include "vectorInt.hpp"
 2 #include <iostream>
 3 void test1();
 4 void test2();
 5 void output1(const vectorInt& vi);
 6 void output2(const vectorInt& vi);
 7 int main() {
 8     std::cout << "测试1: \n";
 9     test1();
10     std::cout << "\n测试2: \n";
11     test2();
12 }
13 void test1() {
14     int n;
15     std::cout << "Enter n: ";
16     std::cin >> n;
17     vectorInt x1(n);
18     for (auto i = 0; i < n; ++i)
19         x1.at(i) = (i + 1) * 10;
20     std::cout << "x1: ";  output1(x1);
21     vectorInt x2(n, 42);
22     vectorInt x3(x2);
23     x2.at(0) = -1;
24     std::cout << "x2: ";  output1(x2);
25     std::cout << "x3: ";  output1(x3);
26 }
27 void test2() {
28     const vectorInt  x(5, 42);
29     vectorInt y;
30     y.assign(x);
31     std::cout << "x: ";  output2(x);
32     std::cout << "y: ";  output2(y);
33 }
34 
35 // 使用xx.at()+循环输出vectorInt对象数据项
36 void output1(const vectorInt& vi) {
37     if (vi.size() == 0) {
38         std::cout << '\n';
39         return;
40     }
41     std::cout << vi.at(0);
42     for (auto i = 1; i < vi.size(); ++i)
43         std::cout << ", " << vi.at(i);
44     std::cout << '\n';
45 }
46 // 使用迭代器+循环输出vectorInt对象数据项
47 void output2(const vectorInt& vi) {
48     if (vi.size() == 0) {
49         std::cout << '\n';
50         return;
51     }
52     auto it = vi.begin();
53     std::cout << *it;
54     for (it = vi.begin() + 1; it != vi.end(); ++it)
55         std::cout << ", " << *it;
56     std::cout << '\n';
57 }
task.cpp

运行结果截图:

image

回答问题:

问题1:当前验证性代码中, vectorInt 接口 assign 实现是安全版本。如果把 assign 实现改成版本2, 逐条指出版本 2存在的安全隐患和缺陷。(提示:对比两个版本,找出差异化代码,加以分析)

  (1)自赋值问题:版本2中 ptr[i] = vi.ptr[i];自赋值可能会导致程序崩溃。安全版本:if(this !=&vi) 避免了自赋值问题。

  (2)内存泄漏:版本2中当new int n[ ]执行失败时,会导致内存泄漏。安全版本:先创建一个新的,再将旧的销毁,如果内存分配失败,就会报错。


问题2:当前验证性代码中,重载接口at内部代码完全相同。若把非 const 版本改成如下实现,可消除 重复并遵循“最小化接口”原则(未来如需更新接口,只更新const接口,另一个会同步)。

查阅资料,回答:

(1) static_cast(this) 的作用是什么?转换前后 this 的类型分别是什么?转换目的?

  作用:将当前对象的指针 this 从非 const 类型 vectorInt* 安全地转换为 const 类型 const vectorInt*

  转换前:this 的类型是 vectorInt*。可以调用对象的非 const 成员函数,并且可以修改对象的数据成员。

  转换后:this 的类型被强制转换为 const vectorInt*。这意味着通过这个转换后的指针,只能调用对象的 const 成员函数,并且不能修改对象的数据成员。

  转化目的:为了能够调用 at 函数的 const 重载版本。 

(2)const_cast<int&>的作用是什么?转换前后的返回类型分别是什么?转换目的?

  作用:这个表达式的作用是去除 const 限定符。它将一个 const 引用(const int&)转换为一个非 const 引用(int&)。

  转化前:static_cast<const vectorInt*>(this)->at(index) 的返回值类型是 const int&。这是一个指向 const int 的引用,意味着你不能通过这个引用去修改它所指向的值。

  转化后:const_cast<int&>(...) 的结果类型是 int&。这是一个普通的、可修改的 int 引用。

  转化目的:满足非 const 版本 at 函数的返回类型要求。

问题3:vectorInt类封装了begin()和end()的const/非const接口。

(1)以下代码片段,分析编译器如何选择重载版本,并总结这两种重载分别适配什么使用场景。

  vectorInt v1(5);

  const vectorInt v2(5);

  auto it1 = v1.begin();    // 调用非const版本

  auto it2 = v2.begin();    // 调用const版本

  非const对象会优先匹配非const版本的迭代器,const类型对象只能匹配const版本的迭代器。

  非const:需要遍历并可能修改容器内元素。

  const:需要遍历和读取容器内元素,而不希望修改。

(2)拓展思考(选答*):标准库迭代器本质上是指针的封装。vectorInt直接返回原始指针作为迭代 器,这种设计让你对迭代器有什么新的理解?

  迭代器定义了如何遍历一个序列,而不是一种特定的数据类型。可以使用相同的遍历代码(例如 for 循环)来处理任何容器类型,而无需关心其内部存储细节。

问题4:以下两个构造函数及assign接口实现,都包含内存块的赋值/复制操作。使用算法库改写是否可以?回答这3行更新代码的功能。

  更新1:std::fill_n(ptr, n, value);初始化新分配的内存,将内部数组的每个元素都被赋予为指定值 value。

  更新2:std::copy_n(vi.ptr, vi.n, ptr); 实现深拷贝,将vi.ptr中的数据复制到ptr中。

  更新3:std::copy_n(vi.ptr, vi.n, ptr_tmp);同更新2复制数据。

实验任务四:

源代码:

 1 #pragma once
 2 #pragma once
 3 // 类Matrix声明
 4 class Matrix {
 5 public:
 6     Matrix(int rows_, int cols_, double value = 0); // 构造rows_*cols_矩阵对象, 初值value
 7     Matrix(int rows_, double value = 0);// 构造rows_*rows_方阵对象, 初值value
 8     Matrix(const Matrix& x);
 9     ~Matrix() {
10         delete[] ptr;
11     }
12     
13     // 深复制
14     void set(const double* pvalue, int size);   // 按行复制pvalue指向的数据,要求size = rows * cols, 否则报错退出
15     void clear();   // 矩阵对象数据项置0
16     const double& at(int i, int j) const;   // 返回矩阵对象索引(i,j)对应的数据项const引用(越界则报错后退出)
17     double& at(int i, int j);   // 返回矩阵对象索引(i,j)对应的数据项引用(越界则报错后退出)
18     int rows() const;   // 返回矩阵对象行数
19     int cols() const;   // 返回矩阵对象列数
20     void print() const;   // 按行打印数据
21 private:
22     int n_rows;
23     int n_cols;
24     double* ptr;
25 };
matrix.hpp
#include "matrix.hpp"
#include <iostream>
#include <iomanip>  

Matrix::Matrix(int rows_, int cols_, double value) :n_rows{ rows_ }, n_cols{ cols_ } {
    if (rows_ <= 0 || cols_ <= 0) {
        std::cout << "行和列必须为正\n";
        return;
    }
    ptr = new double[rows_ * cols_];
    for (int i =0; i <rows_ * cols_; i++) {
        ptr[i] = value;
    }
} // 构造rows_*cols_矩阵对象, 初值value

Matrix::Matrix(int rows_, double value) :Matrix(rows_, rows_, value) {}// 构造rows_*rows_方阵对象, 初值value

Matrix::Matrix(const Matrix& x) :n_rows{ x.n_rows }, n_cols{ x.n_cols } {
    ptr = new double[n_rows * n_cols];
    for (int i = 0;i <n_rows * n_cols;i++) {
        ptr[i] = x.ptr[i];
    }
}



void Matrix::set(const double* pvalue, int size) {
    if (size != n_rows * n_cols) {
        std::cout << "size数值错误\n";
        return;
    }
    if (pvalue == nullptr) {
        std::cout << "pvalue为空\n";
        return;
    }
    for (int i =0;i <size;i++) {
        ptr[i] = pvalue[i];
    }
}
void Matrix::clear() {
    for (int i = 0;i < n_cols * n_rows;i++) {
        ptr[i] = 0;
    }
}
const double& Matrix::at(int i, int j) const {
    if (i < 0 || i >= n_rows || j < 0 || j >= n_cols) {
        std::cout << "越界\n";
        exit(0);
    }
    return ptr[i * n_cols + j];
}
double& Matrix::at(int i, int j) {
    if (i < 0 || i >= n_rows || j < 0 || j >= n_cols) {
        std::cout << "越界\n";
        exit(0);
    }
    return ptr[i * n_cols+j];
}
int Matrix::rows() const {
    return n_rows;
}
int Matrix::cols() const {
    return n_cols;
}
void Matrix::print() const {
    for (int i = 0;i < n_rows;i++) {
        std::cout << at(i, 0);
        for (int j = 1;j <n_cols;j++) {
            std::cout << "," << at(i, j);
        }
        std::cout << '\n';
    }
    std::cout << '\n';
}
matrix.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 int main() {
 9     std::cout << "测试1: \n";
10     test1();
11     std::cout << "\n测试2: \n";
12     test2();
13 }
14 void test1() {
15     double x[1000] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
16     int n, m;
17     std::cout << "Enter n and m: ";
18     std::cin >> n >> m;
19     Matrix m1(n, m);
20     m1.set(x, n * m);
21     Matrix m2(m, n);
22     m2.set(x, m * n);
23     Matrix m3(n);
24     m3.set(x, n * n);
25     // 创建矩阵对象m1, 大小n×m
26      // 用一维数组x的值按行为矩阵m1赋值
27     // 创建矩阵对象m2, 大小m×n
28      // 用一维数组x的值按行为矩阵m1赋值
29     // 创建一个n×n方阵对象
30     // 用一维数组x的值按行为矩阵m3赋值
31     std::cout << "矩阵对象m1: \n";   m1.print();
32     std::cout << "矩阵对象m2: \n";   m2.print();
33     std::cout << "矩阵对象m3: \n";   m3.print();
34 }
35 void test2() {
36     Matrix m1(2, 3, -1);
37     const Matrix m2(m1);
38     std::cout << "矩阵对象m1: \n";   m1.print();
39     std::cout << "矩阵对象m2: \n";   m2.print();
40     m1.clear();
41     m1.at(0, 0) = 1;
42     std::cout << "m1更新后: \n";
43     std::cout << "矩阵对象m1第0行 "; output(m1, 0);
44     std::cout << "矩阵对象m2第0行: "; output(m2, 0);
45 }
46 // 输出矩阵对象row_index行所有元素
47 void output(const Matrix& m, int row_index) {
48     if (row_index < 0 || row_index > m.rows()) {
49         std::cerr << "IndexError: row index out of range\n";
50         std::exit(1);
51     }
52     std::cout << m.at(row_index, 0);
53     for (int j = 1; j < m.cols(); ++j)
54         std::cout << ", " << m.at(row_index, j);
55     std::cout << '\n';
56 }
task4.cpp

运行结果截图:

image

拓展思考:

 零成本:

  1. 性能成本为零:如前所述,std::vector 的核心操作(operator[]size(), 迭代器遍历)的性能与手动管理的数组完全相同。
  2. 内存成本接近零:仅仅多占用了一点微小的内存。

实验任务五:

源代码:

 

 1 #pragma once
 2 #include <iostream>
 3 #include <string>
 4 // 联系人类
 5 class Contact {
 6 public:
 7     Contact(const std::string& name_, const std::string& phone_);
 8     const std::string& get_name() const;
 9     const std::string& get_phone() const;
10     void display() const;
11 private:
12     std::string name;      // 必填项
13     std::string phone;   // 必填项
14 };
15 Contact::Contact(const std::string& name_, const std::string& phone_) :name{ name_ },
16 phone{ phone_ } {
17 }
18 const std::string& Contact::get_name() const {
19     return name;
20 }
21 const std::string& Contact::get_phone() const {
22     return phone;
23 }
24 void Contact::display() const {
25     std::cout << name << ", " << phone;
26 }
contact.hpp
 1 #pragma once
 2 
 3 #include <iostream>
 4 #include <string>
 5 #include <vector>
 6 #include <algorithm>
 7 #include "contact.hpp"
 8 
 9 // 通讯录类
10 class ContactBook {
11 public:
12     void add(const std::string& name, const std::string& phone); // 添加联系人
13     void remove(const std::string& name); // 移除联系人
14     void find(const std::string& name) const; // 查找联系人
15     void display() const; // 显示所有联系人
16     size_t size() const;
17 private:
18     int index(const std::string& name) const;  // 返回联系人在contacts内索引,如不存在,返回 - 1
19     void sort(); // 按姓名字典序升序排序通讯录
20 private:
21     std::vector<Contact> contacts;
22 };
23 
24     void ContactBook::add(const std::string& name, const std::string& phone) {
25         if (index(name) == -1) {
26         contacts.push_back(Contact(name, phone));
27         std::cout << name << " add successfully.\n";
28         sort();
29         return;
30         }
31     std::cout << name << " already exists. fail to add!\n";
32     }
33      void ContactBook::remove(const std::string & name) {
34          int i = index(name);
35          if (i == -1) {
36              std::cout << name << " not found, fail to remove!\n";
37              return;
38          }
39          contacts.erase(contacts.begin() + i);
40          std::cout << name << " remove successfully.\n";
41      }
42      void ContactBook::find(const std::string& name) const {
43          int i = index(name);
44          if (i == -1) {
45              std::cout << name << " not found!\n";
46              return;
47          }
48          contacts[i].display();
49          std::cout << '\n';
50      }
51      void ContactBook::display() const {
52          for (auto& c : contacts){
53              c.display();
54              std::cout << '\n';
55          }
56      }
57      size_t ContactBook::size() const {
58          return contacts.size();
59      }
60      int ContactBook::index(const std::string& name)const {
61          int ans = -1,j=0;
62          for (auto &i : contacts) {
63              if (name == i.get_name()) ans = j;
64              j++;
65          }
66          return ans;
67      }
68      bool cmp(const Contact& a, const Contact& b) {
69          return a.get_name() < b.get_name();
70      }
71      void ContactBook::sort() {
72          std::sort(contacts.begin(), contacts.end(),cmp);
73      }
contactBook
 1 #include "contactBook.hpp"
 2 void test() {
 3     ContactBook contactbook;
 4     std::cout << "1. add contacts\n";
 5     contactbook.add("Bob", "18199357253");
 6     contactbook.add("Alice", "17300886371");
 7     contactbook.add("Linda", "18184538072");
 8     contactbook.add("Alice", "17300886371");
 9     std::cout << "\n2. display contacts\n";
10     std::cout << "There are " << contactbook.size() << " contacts.\n";
11     contactbook.display();
12     std::cout << "\n3. find contacts\n";
13     contactbook.find("Bob");
14     contactbook.find("David");
15     std::cout << "\n4. remove contact\n";
16     contactbook.remove("Bob");
17     contactbook.remove("David");
18 }
19 int main() {
20     test();
21 }
task5.cpp

运行结果截图:

 

image

 实验总结:

1.零成本抽象:编写代码时应该优先选择标准库函数。

2.组合是‘整体包含部分’的 has-a 关系,深复制是‘内存独立副本’的保障。浅复制因共享内存会导致悬挂指针、数据篡改等问题,而深复制通过独立分配内存并拷贝数据,确保了对象间的完全独立性。

3.类的设计,需要从功能实现、封装性、异常安全等多维度优化,最终目标是让类在各种场景下都能正确、高效地工作。

 

posted @ 2025-11-24 16:18  pithia  阅读(0)  评论(0)    收藏  举报