实验三
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();
}

问题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);
}

问题1:测试模块1中这两行代码分别完成了什么构造? v1、v2 各包含多少个值为 42 的数据项?
A:std::vector
问题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
A:当v是vector
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';
}

问题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接口实现,都包含内存块的赋值和复制操作。使用算法库
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;
}
}

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();
}

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

浙公网安备 33010602011771号