实验三

task1

button.hpp
#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";
}
window.hpp
#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.emplace_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.emplace_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;
}
task1.cpp
#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();
}
运行结果:

image
问题1:这个范例中,Window和Button是组合关系吗?
A:是组合关系,因为Window类包含std::vectorbuttons成员,Button对象作为Window的一部分存在,Window对象生命周期控制Button对象的生命周期。
问题2: bool has_button(const std::string &label) const; 被设计为私有。 思考并回答:
(1)若将其改为公有接口,有何优点或风险?
A:优点:①可以提供更多功能,外部可以查询按钮是否存在 ②可以增加灵活性,用户可以在操作前先检查。
风险:①会破坏封装,暴露内部实现细节 ②可能被误用 ③会增加维护成本,如果内部数据结构改变,公有接口也要保持兼容。
(2)设计类时,如何判断一个成员函数应为 public 还是 private?(可从“用户是否需要”、“是否仅为内部实现细节”、“是否易破坏对象状态”等角度分析。)
A:①(用户是否需要)只有用户必须使用的功能才设为public
②(内部实现细节)仅为类内部服务的函数设为private
③(状态完整性)可能破坏对象状态一致性的操作要严格控制
④(最小接口原则)提供最少的公有接口完成所需功能
问题3:Button 的接口,两种接口设计在性能和安全性方面的差异并精炼陈述。
接口1:const std::string& get_label() const;返回 const std::string& get_label() const;
接口2:const std::string get_label() const;
A:接口1:(性能)返回引用,避免字符串拷贝,性能更好。
(安全性)调用者可能保存引用并在Button对象销毁后使用。
接口2:(性能)返回值拷贝,特别是长字符串时有性能开销。
(安全性)调用者获得独立副本。
问题4:把代码中所有 const std::string& 。对比以下xx.push_back(Button(xxx)) 改成 xx.emplace_back(xxx) ,观察程序是否正常运行;查阅资料,回答两种写法的差别。
A:emplace_back直接通过参数在容器内构造对象,避免创建临时对象和拷贝/移动操作,因此比push_back更高效。

task2

task2.cpp
#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);
}

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';
}

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';
}

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中这两行代码分别完成了什么构造? v1、v2 各包含多少个值为 42 的数据项?
A:std::vector v1(5,42);:带参数的构造函数;const std::vector v2(v1);:拷贝构造函数;V1V2都包含5个
问题2:测试模块2中这两行代码执行后, v1.size()、v2.size()、v1[0].size()分别是多少?
A:v1.size()=2;v2.size()=2;v1[0].size()=3
问题3:测试模块1中,把v1.at(0) = -1;写成v1[0] = -1;能否实现同等效果?两种用法有何区别?
A:能;区别:at()会进行边界检查,越界时抛出std::out_of_range异常;而operator[]不进行边界检查,越界时行为未定义,但效率更高。
问题4:测试模块2中执行v1.at(0).push_back(-1);后
(1) 用以下两行代码,能否输出-1?为什么?
A:能;因为v1.at(0)返回内部vector的引用,通过引用修改会影响原数据。
(2)r定义成用const &类型接收返回值,在内存使用上有何优势?有何限制?
A:优势:避免不必要的拷贝,节省内存;限制:不能通过r修改vector的内容。
**问题5:观察程序运行结果,反向分析、推断: **
**(1) 标准库模板类vector的复制构造函数实现的是深复制还是浅复制? **
A:深复制。
(2) vector::at() 接口思考: 当 v是vector 时,v.at(0)返回值类型是什么?当v是const vector时,v.at(0)返回值类型又是什么?据此推断 at()是否必须提供带 const 修饰的重载版本?
A:当v是vector时:v.at(0)返回int&;当v是const vector时:v.at(0)返回const int&;必须提供

task3

vectorlnt.hpp
#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;
}
task3.cpp
#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);
}

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';
}

void output2(const vectorInt &vi){
    if(vi.size()==0){
        std::cout << '\n';
        return;
    }

    auto it=vi.begin();
    std::cout << *it;

    for(it=vi.begin()+1;it!=vi.end();++it)
        std::cout << ", " << *it;
    std::cout << '\n';
}
运行结果:

image

问题1:当前验证性代码中,vectorInt 接口assign 实现是安全版本。如果把assign 实现改成版本2,逐条指出版本 2存在的安全隐患和缺陷。(提示:对比两个版本,找出差异化代码,加以分析)
A:缺少自赋值检查,没有if(this == &vi) return this;,会导致自赋值时先删除自身内存,再访问已删除的数据产生未定义行为;先删除原内存再分配新内存,如果new失败抛出异常,对象将处于无效状态;具有内存泄漏风险,没有先分配临时内存,如果拷贝过程中出现异常会导致内存泄漏。
问题2:当前验证性代码中,重载接口at内部代码完全相同。若把非 const 版本改成如下实现,可消除重复并遵循“最小化接口”原则(未来如需更新接口,只更新const接口,另一个会同步)。
A:static_cast<const vectorInt
>(this):将当前对象转为const指针,以调用const版本的at函数;const_cast<int&>:将const引用转为非const引用,保持接口一致性
原理:通过调用const版本避免代码重复,利用类型转换保证安全性
问题3:vectorInt类封装了begin()和end()的const/非const接口。
A:(1)1调用非const版本,2调用const 版本。(2)迭代器本质是提供"遍历能力"的抽象,对于连续内存容器,原始指针已具备完整的迭代器功能,复杂容器需要专门类来封装遍历逻辑。
问题4:以下两个构造函数及assign接口实现,都包含内存块的赋值和复制操作。使用算法库改成如下写法是否可以?回答这3行更新代码的功能。
A:可以;std::fill_n(ptr, n, value):将ptr开始的n个元素都设置为value
std::copy_n(v1.ptr, v1.n, ptr):从v1.ptr复制v1.n个元素到ptr
std::copy_n(v1.ptr, v1.n, ptr_tmp):从v1.ptr复制v1.n个元素到临时缓冲区ptr_tmp

task4

matrix.hpp
#pragma once

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.cpp
#include "matrix.hpp"
#include<iostream>
#include<iomanip>

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

Matrix::Matrix(int rows_,double value):Matrix(rows_,rows_,value){
}

Matrix::Matrix(const Matrix &x):n_rows(x.n_rows),n_cols(x.n_cols){
    int size=n_rows*n_cols;
    ptr=new double[size];
    for(int i=0;i<size;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::cerr << "Error:Size mismatch in set function." << std::endl;
        exit(1);
    }
    for(int i=0;i<size;i++){
        ptr[i]=pvalue[i];
    }
}

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

const double& Matrix::at(int i,int j) const{
    if(i<0||i>=n_rows||j<0||j>=n_cols){
        std::cerr << "IndexError: Index out of range." << std::endl;
        exit(1);
    }
    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){
        std::cout << at(i,0);
        for(int j=1;j<n_cols;j++){
            std::cout << "," << at(i,j);
        }
        std::cout << std::endl;
    }
}
运行结果:

image

task5

contact.hpp
#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;
}
contactBook.hpp
# 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 {
    for (size_t i = 0; i < contacts.size(); ++i) {
        if (contacts[i].get_name() == name) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

void ContactBook::sort() {
    std::sort(contacts.begin(), contacts.end(), [](const Contact &a, const Contact &b) {
        return a.get_name() < b.get_name();
    });
}
task5.cpp
#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

实验总结:
本次实验通过使用C++标准库容器std::vector和算法std::sort,有效提升了代码开发效率和程序健壮性。相比手动实现排序算法,标准库的std::sort不仅简化了字典序排序的实现逻辑,还通过高度优化的底层实现保证了运行效率。
这一实践让我深刻体会到C++标准库在工程开发中的价值:既减少了重复代码编写,又通过经过充分测试的标准化接口降低了出错概率。同时,实验过程中也意识到编码严谨性的重要性,诸如变量命名、语法细节等基础问题往往成为影响程序正确性的关键因素,需要在日常开发中加以重视。

posted @ 2025-11-25 11:34  kk_n  阅读(4)  评论(1)    收藏  举报