实验三

#include "window.hpp"
#include <iostream>

void test(){
    Window w("Demo");
    w.add_button("add");
    w.add_button("remove");
    w.add_button("modify");
    w.add_button("add");
    w.display();
    w.close();
}

int main() {
    std::cout << "用组合类模拟简单GUI:\n";
    test();
}
#pragma once

#include <iostream>
#include <string>

class Button {
public:
    Button(const std::string &label_);
    const std::string& get_label() const;
    void click();

private:
    std::string label;
};

Button::Button(const std::string &label_): label{label_} {
}

inline const std::string& Button::get_label() const {
    return label;
}

inline void Button::click() {
    std::cout << "Button '" << label << "' clicked\n";
}
#pragma once

#include <iostream>
#include <vector>
#include <algorithm>
#include "button.hpp"

// 窗口类
class Window{
public:
    Window(const std::string &title_);
    void display() const;
    void close();
    void add_button(const std::string &label);
    void click_button(const std::string &label);

private:
    bool has_button(const std::string &label) const;

private:
    std::string title;
    std::vector<Button> buttons;
};

Window::Window(const std::string &title_): title{title_} {
    buttons.push_back(Button("close"));
}

inline void Window::display() const {
    std::string s(40, '*');
    std::cout << s << std::endl;
    std::cout << "window : " << title << std::endl;
    int cnt = 0;
    for(const auto &button: buttons)
        std::cout << ++cnt << ". " << button.get_label() << std::endl;
    std::cout << s << std::endl;
}

inline void Window::close() {
    std::cout << "close window '" << title << "'" << std::endl;
    click_button("close");
}

inline bool Window::has_button(const std::string &label) const {
    for(const auto &button: buttons)
        if(button.get_label() == label)
            return true;
    
    return false;
}

inline void Window::add_button(const std::string &label) {
    if(has_button(label))
        std::cout << "button " << label << " already exists!\n";
    else
        buttons.push_back(Button(label));
}

inline void Window::click_button(const std::string &label) {
    for(auto &button:buttons)
        if(button.get_label() == label) {
            button.click();
            return;
        }
            
    std::cout << "no button: " << label << std::endl;
}

运行结果

image

问题 1:Window 和 Button 是组合关系吗?

是。组合关系对应 “has-a”(包含)关系,即一个类的对象包含另一个类的对象,且被包含对象的生命周期由包含它的对象管理。此范例中,Window 类包含 std::vector<Button> 成员,按钮对象在窗口内部创建、销毁,完全依赖窗口的生命周期,因此 Window 和 Button 是组合关系。

问题 2:关于 has_button 成员函数的访问控制

(1)改为公有接口的优缺点

  • 优点:
     
    提供更灵活的外部功能,允许外部代码检查窗口中是否存在某个按钮,无需修改类内部即可满足外部的查询需求。
  • 风险:
     
    破坏类的封装性,暴露了 “窗口如何管理按钮” 的内部实现细节;若后续修改按钮的存储方式(如更换容器),此接口可能需要调整,会影响依赖它的外部代码;还可能导致外部判断与窗口内部状态不一致(外部调用接口后,窗口内部操作可能改变按钮状态)。

(2)判断成员函数为 public 还是 private 的依据

  • 看用户是否需要:若函数是类对外提供的核心功能,需被外部调用,则设为 public;若仅为类内部的辅助逻辑(如 has_button 是 add_button 的内部判断工具),则设为 private。
  • 看是否是内部实现细节:若函数是支撑其他接口的内部逻辑,不涉及对外功能,则设为 private,避免暴露实现。
  • 看是否会破坏对象状态:若函数直接操作私有数据,可能导致对象状态混乱,则设为 private,防止外部误用。

问题 3:两种 get_label 接口的差异

  • 性能方面:
     
    接口 1(返回 const std::string&)直接返回内部字符串的引用,无拷贝开销,效率更高;接口 2(返回 const std::string)会拷贝内部字符串生成临时对象,存在性能损耗。
  • 安全性方面:
     
    接口 1 返回 const 引用,外部无法修改内部数据,但存在 “悬垂引用” 风险(若内部字符串被销毁,外部引用会失效);接口 2 返回值是副本,外部修改不影响内部数据,安全性更高。

问题 4:push_back 与 emplace_back 的差别

修改后程序可正常运行,两者的核心区别是:
  • push_back(Button(xxx)) 是先创建一个临时的 Button 对象,再将这个临时对象拷贝 / 移动到容器中,最后销毁临时对象,存在额外的对象构造、拷贝 / 移动开销。
  • emplace_back(xxx) 是直接在容器的内存空间中构造 Button 对象,无需创建临时对象,减少了一次对象构造和销毁的操作,效率更高。

 任务二

源代码

#include <iostream>
#include <vector>

void test1();
void test2();
void output1(const std::vector<int> &v);
void output2(const std::vector<int> &v);
void output3(const std::vector<std::vector<int>>& v);

int main() {
    std::cout << "深复制验证1: 标准库vector<int>\n";
    test1();

    std::cout << "\n深复制验证2: 标准库vector<int>嵌套使用\n";
    test2();
}

void test1() {
    std::vector<int> v1(5, 42);
    const std::vector<int> v2(v1);

    std::cout << "**********拷贝构造后**********\n";
    std::cout << "v1: "; output1(v1);
    std::cout << "v2: "; output1(v2);
    
    v1.at(0) = -1;

    std::cout << "**********修改v1[0]后**********\n";
    std::cout << "v1: "; output1(v1);
    std::cout << "v2: "; output1(v2); 
}

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

    std::cout << "**********拷贝构造后**********\n";
    std::cout << "v1: "; output3(v1);
    std::cout << "v2: "; output3(v2);

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

    std::cout << "**********修改v1[0]后**********\n";
    std::cout << "v1: \n";  output3(v1);
    std::cout << "v2: \n";  output3(v2);
}

// 使用xx.at()+循环输出vector<int>数据项
void output1(const std::vector<int> &v) {
    if(v.size() == 0) {
        std::cout << '\n';
        return;
    }
    
    std::cout << v.at(0);
    for(auto i = 1; i < v.size(); ++i)
        std::cout << ", " << v.at(i);
    std::cout << '\n';  
}

// 使用迭代器+循环输出vector<int>数据项
void output2(const std::vector<int> &v) {
    if(v.size() == 0) {
        std::cout << '\n';
        return;
    }
    
    auto it = v.begin();
    std::cout << *it;

    for(it = v.begin()+1; it != v.end(); ++it)
        std::cout << ", " << *it;
    std::cout << '\n';
}

// 使用auto for分行输出vector<vector<int>>数据项
void output3(const std::vector<std::vector<int>>& v) {
    if(v.size() == 0) {
        std::cout << '\n';
        return;
    }

    for(auto &i: v)
        output2(i);
}

运行截图

image

问题 1:测试模块 1 中构造及数据项数量

  • std::vector<int> v1(5, 42);:完成带参数的构造(填充构造),创建一个包含 5 个元素的vector,每个元素初始化为 42。
  • const std::vector<int> v2(v1);:完成拷贝构造,用v1的内容初始化v2
  • v1v2均包含5 个值为 42 的数据项。

问题 2:测试模块 2 中size()结果

  • v1.size():外层vector包含 2 个vector<int>元素,故值为2。
  • v2.size():通过拷贝构造与v1内容一致,值为2。
  • v1[0].size():第一个内层vector初始有 3 个元素(1、2、3),故值为3(执行push_back(-1)前)。

问题 3:at(0)[0]的效果及区别

  • 能实现同等效果:两者都可修改v1第一个元素的值为 - 1。
  • 区别:
    • v1.at(0)会进行越界检查,若索引超出范围(如v1.at(10)),会抛出std::out_of_range异常;
    • v1[0]不做越界检查,若索引越界会导致未定义行为(如内存访问错误)。

问题 4:测试模块 2 中push_back(-1)后的相关问题

(1)能否输出 - 1(假设代码为auto r = v1.at(0); output1(r);

能输出 - 1。因为v1.at(0)返回的是第一个内层vector<int>的引用,修改其内容(push_back(-1))后,该内层vector已包含 - 1,通过r(拷贝该内层vector)输出时会显示 - 1。

(2)const &接收返回值的优势与限制

  • 优势:避免拷贝内层vector,直接引用原数据,节省内存开销,提升效率。
  • 限制:const引用不可修改原数据,无法通过rv1.at(0)进行增删改操作(如r.push_back(0)会报错)。

问题 5:vector 的复制特性及at()接口分析

(1)vector 复制构造函数的复制类型

实现的是深复制。测试模块 1 中修改v1的元素后,v2内容未变;测试模块 2 中修改v1内层vector的内容,v2内层vector也未变,说明v2拥有独立的内存空间,而非共享v1的内存。

(2)at()的返回值类型及重载需求

  • vvector<int>时,v.at(0)返回int&(可修改的引用);
  • vconst vector<int>时,v.at(0)返回const int&(不可修改的引用);
  • at()必须提供带const修饰的重载版本:因为const对象只能调用const成员函数,若没有const重载,const vector无法调用at(),且需保证const对象的元素不被修改。

 任务三

源代码

#include "vectorInt.hpp"
#include <iostream>

void test1();
void test2();
void output1(const vectorInt &vi);
void output2(const vectorInt &vi);

int main() {
    std::cout << "测试1: \n";
    test1();

    std::cout << "\n测试2: \n";
    test2();
}

void test1() {
    int n;
    std::cout << "Enter n: ";
    std::cin >> n;

    vectorInt x1(n);
    for(auto i = 0; i < n; ++i)
        x1.at(i) = (i+1)*10;
    std::cout << "x1: ";  output1(x1);

    vectorInt x2(n, 42);
    vectorInt x3(x2);
    x2.at(0) = -1;
    std::cout << "x2: ";  output1(x2);
    std::cout << "x3: ";  output1(x3);
}

void test2() {
    const vectorInt  x(5, 42);
    vectorInt y;

    y.assign(x);

    std::cout << "x: ";  output2(x);
    std::cout << "y: ";  output2(y);
}

// 使用xx.at()+循环输出vectorInt对象数据项
void output1(const vectorInt &vi) {
    if(vi.size() == 0) {
        std::cout << '\n';
        return;
    }
        
    std::cout << vi.at(0);
    for(auto i = 1; i < vi.size(); ++i)
        std::cout << ", " << vi.at(i);
    std::cout << '\n';
}

// 使用迭代器+循环输出vectorInt对象数据项
void output2(const vectorInt &vi) {
    if(vi.size() == 0) {
        std::cout << '\n';
        return;
    }
    
    autolnt it = vi.begin();
    std::cout << *it;

    for(it = vi.begin()+1; it != vi.end(); ++it)
        std::cout << ", " << *it;
    std::cout << '\n';
}
#pragma once

#include <iostream>


class vectorInt{
public:
    vectorInt();
    vectorInt(int n_);
    vectorInt(int n_, int value);
    vectorInt(const vectorInt &vi);
    ~vectorInt();

    int size() const;
    int& at(int index);
    const int& at(int index) const;
    vectorInt& assign(const vectorInt &vi);

    int* begin();
    int* end();
    const int* begin() const;
    const int* end() const;

private:
    int n;
    int *ptr;
};

vectorInt::vectorInt():n{0}, ptr{nullptr} {
}

vectorInt::vectorInt(int n_): n{n_}, ptr{new int[n]} {
}

vectorInt::vectorInt(int n_, int value): n{n_}, ptr{new int[n_]} {
    for(auto i = 0; i < n; ++i)
        ptr[i] = value;
}

vectorInt::vectorInt(const vectorInt &vi): n{vi.n}, ptr{new int[n]} {
    for(auto i = 0; i < n; ++i)
        ptr[i] = vi.ptr[i];
}

vectorInt::~vectorInt() {
    delete [] ptr;
}

int vectorInt::size() const {
    return n;
}

const int& vectorInt::at(int index) const {
    if(index < 0 || index >= n) {
        std::cerr << "IndexError: index out of range\n";
        std::exit(1);
    }

    return ptr[index];
}

int& vectorInt::at(int index) {
    if(index < 0 || index >= n) {
        std::cerr << "IndexError: index out of range\n";
        std::exit(1);
    }

    return ptr[index];
}

vectorInt& vectorInt::assign(const vectorInt &vi) {
    if(this == &vi)
        return *this;

    int *ptr_tmp;
    ptr_tmp = new int[vi.n];
    for(int i = 0; i < vi.n; ++i)
        ptr_tmp[i] = vi.ptr[i];

    delete[] ptr;
    n = vi.n;
    ptr = ptr_tmp;
    return *this;
}

int* vectorInt::begin() {
    return ptr;
}

int* vectorInt::end() {
    return ptr+n;
}

const int* vectorInt::begin() const {
    return ptr;
}

const int* vectorInt::end() const {
    return ptr+n;
}

运行结果

image

 

问题 1:版本 2 assign 接口的安全隐患和缺陷

  • 隐患 1:未处理自赋值情况
     
    版本 2 中没有判断 this == &vi(即对象给自己赋值),若执行 x.assign(x),会先 delete[] ptr 释放自身内存,后续访问 vi.ptr 时会指向已释放的内存,导致悬空指针访问,引发未定义行为。
  • 隐患 2:内存分配失败导致数据丢失
     
    版本 2 先 delete[] ptr 再 new int[n],若 new 分配内存失败(抛出异常),原 ptr 已被释放,对象会失去原有数据,处于无效状态;而安全版本先分配新内存,成功后再释放旧内存,避免了这种风险。

问题 2:at 接口重构的类型转换分析

(1)static_cast<const vectorInt*>(this)

  • 作用:将当前对象的指针转换为 const vectorInt* 类型,调用 const 版本的 at 接口。
  • 转换前后类型:转换前 this 是 vectorInt*(非 const 对象指针),转换后是 const vectorInt*(const 对象指针)。
  • 转换目的:复用 const 版本的 at 逻辑(如越界检查),避免代码重复,遵循 “最小化接口” 原则。

(2)const_cast<int&>

  • 作用:移除返回值的 const 属性,将 const int& 转换为 int&
  • 转换前后类型:转换前是 const int&(const 引用),转换后是 int&(非 const 引用)。
  • 转换目的:非 const 版本的 at 需要返回可修改的引用,而 const 版本返回的是不可修改的引用,通过 const_cast 适配非 const 接口的需求(且当前对象本身是非 const 的,修改合法)。

问题 3:begin()/end() 重载版本的选择与迭代器理解

(1)重载版本的选择场景

  • 非 const 重载版本(int* begin()/int* end()):当调用者是非 const 的 vectorInt 对象时,编译器选择此版本,返回可修改的指针(迭代器),支持通过指针修改元素(如 *it = 10)。
  • const 重载版本(const int* begin() const/const int* end() const):当调用者是const 的 vectorInt 对象(如 const vectorInt x)或对象的 const 引用(如函数参数 const vectorInt& vi)时,编译器选择此版本,返回不可修改的指针(迭代器),保证 const 对象的只读性。

(2)迭代器的新理解

vectorInt 直接返回原始指针作为迭代器,说明迭代器的本质是 “可遍历容器元素的工具”,指针是最简单的迭代器实现 —— 它支持解引用(*it)、递增(it++)、比较(it != end)等迭代器核心操作。标准库迭代器是对指针的封装和扩展,适配不同容器(如链表、树)的遍历逻辑,但其核心功能与指针一致:访问和遍历元素。

问题 4:使用 <algorithm> 优化内存操作的可行性

可以。假设用 std::copy 替换手动循环,例如:
cpp
 
运行
// 构造函数中
vectorInt::vectorInt(int n_, int value): n{n_}, ptr{new int[n_]} {
    std::fill(ptr, ptr + n, value); // 替代循环赋值
}

// 拷贝构造/assign中
std::copy(vi.ptr, vi.ptr + vi.n, ptr); // 替代循环复制
        std::fill(ptr, ptr + n, value):将从 ptr 开始的 n 个元素赋值为 value,等价于手动循环赋值。
   std::copy(vi.ptr, vi.ptr + vi.n, ptr):将 vi.ptr 到 vi.ptr + vi.n 的元素复制到 ptr 指向的内存,等价于手动循环复制。 
  • 这些算法更简洁、高效,且经过标准库优化,能减少手动代码错误。

实验4

源代码

#pragma once

#include <iostream>
#include <algorithm>
#include <cstdlib>

class Matrix {
public:
    Matrix(int rows_, int cols_, double value = 0);
    Matrix(int rows_, double value = 0);
    Matrix(const Matrix& x);
    ~Matrix();

    void set(const double* pvalue, int size);
    void clear();

    const double& at(int i, int j) const;
    double& at(int i, int j);

    int rows() const;
    int cols() const;

    void print() const;

private:
    int n_rows;
    int n_cols;
    double* ptr;
};

Matrix::Matrix(int rows_, int cols_, double value) :n_rows(rows_), n_cols(cols_), ptr(new double[rows_* cols_]) {
    for (int i = 0; i < n_rows*n_cols; i++) {
        ptr[i] = value;
    }
}

Matrix::Matrix(int rows_, double value) :n_rows(rows_), n_cols(rows_), ptr(new double[rows_* rows_]) {
    for (int i = 0; i < n_rows * n_cols; i++) {
        ptr[i] = value;
    }
}

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

Matrix::~Matrix() { delete[]ptr; }

void Matrix::set(const double* pvalue, int size) {
    if (size != n_rows * n_cols) {
        std::cout << "size ERROR" << std::endl;
        return;
    }
    else {
        for (int i = 0; i < size; i++) {
            ptr[i] = pvalue[i];
        }
    }
}

void Matrix::clear() {
    for (int i = 0; i < n_rows * n_cols; i++) {
        ptr[i] = 0;
    }
}

const double& Matrix::at(int i, int j) const {
    if (i < 0 || j < 0 || i >= n_rows || j >= n_cols) {
        std::cout << "at ERROR" << std::endl;
    }
    else { return ptr[i * n_cols + j]; }
}

double& Matrix::at(int i, int j) { return const_cast<double&>(static_cast<const Matrix*>(this)->at(i, 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) {
        for (int j = 0; j < n_cols; ++j) {
            std::cout << ptr[i * n_cols + j];
            if(j!=n_cols-1)std::cout << ", ";
        }
        std::cout << std::endl;
    }
}
#include <iostream>
#include <cstdlib>
#include "matrix.hpp"

void test1();
void test2();
void output(const Matrix& m, int row_index);

int main() {
    std::cout << "测试1: \n";
    test1();

    std::cout << "\n测试2: \n";
    test2();
}

void test1() {
    double x[1000] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    int n, m;
    std::cout << "Enter n and m: ";
    std::cin >> n >> m;

    Matrix m1(n, m);
    m1.set(x, n * m);

    Matrix m2(m, n);
    m2.set(x, m * n);

    Matrix m3(n);
    m3.set(x, n * n);

    std::cout << "矩阵对象m1: \n";   m1.print();
    std::cout << "矩阵对象m2: \n";   m2.print();
    std::cout << "矩阵对象m3: \n";   m3.print();
}

void test2() {
    Matrix m1(2, 3, -1);
    const Matrix m2(m1);

    std::cout << "矩阵对象m1: \n";   m1.print();
    std::cout << "矩阵对象m2: \n";   m2.print();

    m1.clear();
    m1.at(0, 0) = 1;

    std::cout << "m1更新后: \n";
    std::cout << "矩阵对象m1第0行 "; output(m1, 0);
    std::cout << "矩阵对象m2第0行: "; output(m2, 0);
}

void output(const Matrix& m, int row_index) {
    if (row_index < 0 || row_index >= m.rows()) {
        std::cerr << "IndexError: row index out of range\n";
        exit(1);
    }

    std::cout << m.at(row_index, 0);
    for (int j = 1; j < m.cols(); ++j)
        std::cout << ", " << m.at(row_index, j);
    std::cout << '\n';
}

运行结果

image

 实验5

源代码

#pragma once

#include <iostream>
#include <string>

class Contact {
public:
    Contact(const std::string& name_, const std::string& phone_);

    const std::string& get_name() const;
    const std::string& get_phone() const;
    void display() const;

private:
    std::string name;
    std::string phone;
};

Contact::Contact(const std::string& name_, const std::string& phone_) :name{ name_ }, phone{ phone_ } {
}

const std::string& Contact::get_name() const {
    return name;
}

const std::string& Contact::get_phone() const {
    return phone;
}

void Contact::display() const {
    std::cout << name << ", " << phone;
}
# pragma  once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include "contact.hpp"

class ContactBook {
public:
    void add(const std::string& name, const std::string& phone);
    void remove(const std::string& name);
    void find(const std::string& name) const;
    void display() const;
    size_t size() const;

private:
    int index(const std::string& name) const;
    void sort();

private:
    std::vector<Contact> contacts;
};

void ContactBook::add(const std::string& name, const std::string& phone) {
    if (index(name) == -1) {
        contacts.push_back(Contact(name, phone));
        std::cout << name << " add successfully.\n";
        sort();
        return;
    }

    std::cout << name << " already exists. fail to add!\n";
}

void ContactBook::remove(const std::string& name) {
    int i = index(name);

    if (i == -1) {
        std::cout << name << " not found, fail to remove!\n";
        return;
    }

    contacts.erase(contacts.begin() + i);
    std::cout << name << " remove successfully.\n";
}

void ContactBook::find(const std::string& name) const {
    int i = index(name);

    if (i == -1) {
        std::cout << name << " not found!\n";
        return;
    }

    contacts[i].display();
    std::cout << '\n';
}

void ContactBook::display() const {
    for (auto& c : contacts) {
        c.display();
        std::cout << '\n';
    }
}

size_t ContactBook::size() const {
    return contacts.size();
}

int ContactBook::index(const std::string& name) const{
    int count = 0;
    for (auto& i : contacts) {
        if (i.get_name()==name) {
            return count;
        }
        count++;
    }
    return -1;
}

bool compare(Contact& a, Contact& b) {
    return a.get_name() < b.get_name();
}

void ContactBook::sort() {
    std::sort(contacts.begin(), contacts.end(), compare);
}
#include "contactBook.hpp"

void test() {
    ContactBook contactbook;

    std::cout << "1. add contacts\n";
    contactbook.add("Bob", "18199357253");
    contactbook.add("Alice", "17300886371");
    contactbook.add("Linda", "18184538072");
    contactbook.add("Alice", "17300886371");

    std::cout << "\n2. display contacts\n";
    std::cout << "There are " << contactbook.size() << " contacts.\n";
    contactbook.display();

    std::cout << "\n3. find contacts\n";
    contactbook.find("Bob");
    contactbook.find("David");

    std::cout << "\n4. remove contact\n";
    contactbook.remove("Bob");
    contactbook.remove("David");
}

int main() {
    test();
}

运行结果

image

 

posted @ 2025-11-25 21:35  交界地第一深情  阅读(0)  评论(0)    收藏  举报