你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

【实验2】数组、指针与C++标准库

2021-10-28 1:11:50

文章目录

  • 任务一 (模板类)
  • 任务二 (string类)
  • 任务三 (vector模板)
  • 任务四 (array模板)
  • 任务五 (LIveShow)
  • 任务六 (加密/解密)

任务一 (模板类)

#include <iostream>
using namespace std;
// 类A的定义
class A{
public:
	A(int x0=0, int y0=0): x{x0}, y{y0} {}
	void show() const { cout << x << ", " << y << endl; }
private:
	int x, y;
};
// 类B的定义
class B{
public:
	B(double x0, double y0): x{x0}, y{y0} {}
	void show() const { cout << x << ", " << y << endl;}
private:
	double x, y;
};
int main()
{
	A a(3, 4);
	a.show();
	B b(3.2, 5.6);
	b.show();
	system("pause");
}

在这里插入图片描述

观察代码,可以看到,类A和类B,除了数据成员的类型不同之外,其它都相同。类的定义存在相似性。有没有一种机制,能够把一组相似的类的定义中相同的部分抽象出来,不同的部分用变化的量表示呢?—— 类模板就提供了这样一种机制。

#include <iostream>
#include <string>
using namespace std;
// 定义类模板X
template <typename T>
class X
{
public:
    X(T x0, T y0) : x{x0}, y{y0} {}
    void show() const { cout << x << ", " << y << endl; }

private:
    T x, y;
};
int main()
{
    X<int> x1(3, 4);
    x1.show();
    X<double> x2(3.2, 5.6);
    x2.show();
    X<string> x3("hello", "c plus plus");
    x3.show();
    system("pause");
}

在这里插入图片描述

使用模板类的好处是,它可以"特化"成很多具体的类。比如,代码line23,把类模板特化到数据是string类的情形。当然,这也有一个前提,前提是类模板里的代码操作,对于这些特化的类型是支持的。比如,代码line10类模板X的show()方法中使用了输出流对象cout和插入运算符<<实现数据的输出,它必须支持int,doube, string这样类型的数据的输出操作。否则,在模板特化的过程中,就会出现报错信息。

任务二 (string类)

C++里的字符串string,严格来说,不是一个真正的类型,它其实是模板类basic_string类的特化形式。using string = std::basic_string<char>;

#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string s1, s2;
    s1 = "nuist";                                 // 赋值
    s1[0] = 'N';                                  // 支持通过[]和索引方式访问
    cout << boolalpha << (s1 == "nuist") << endl; // 字符串比较
    cout << s1.length() << endl;                  // 字符串长度
    cout << s1.size() << endl;                    // 字符串长度
    s2 = s1 + ", 2050";                           // 字符串连接
    cout << s2 << endl;
    string email{"xyz@gmail.com"};
    auto pos = email.find("@"); // 查找子串"@"第一次出现的索引位置,如果失败,返回string::npos
    if (pos == string::npos)
        cout << "illegal email address";
    else
    {
        auto s1 = email.substr(0, pos);  // 取子串, 从索引0 ~ pos-1
        auto s2 = email.substr(pos + 1); // 取子串,从pos+1到末尾
        cout << s1 << endl;
        cout << s2 << endl;
    }
    string phone{"15216982937"};
    cout << phone.replace(3, 5, "*****") << endl; // 把从索引位置为3开始的连续5个字符替换成*
    string s3{"cosmos"}, s4{"galaxy"};
    cout << "s3: " + s3 + " s4: " + s4 << endl;
    s3.swap(s4); // 交换
    cout << "s3: " + s3 + " s4: " + s4 << endl;
    string s5{"abc"};
    const char *pstr = s5.c_str(); // 方法c_str()把string类字符串组转换成C风格的字符串
    cout << pstr << endl;
    string s6{"12306"};
    int x = stoi(s6); // 把string转换成int
    cout << x << endl;
    system("pause");
}

在这里插入图片描述

#include <iostream>
#include <string>
#include <limits>
int main()
{
    using namespace std;
    string s1;
    cin >> s1; // 从输入流中提取字符串给s1,碰到空格、回车、Tab键即结束
    cout << "s1: " << s1 << endl;
    cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清空输入缓冲区
    getline(cin, s1);                                    // 从输入流中提取一行字符串给s1,直到换行
    cout << "s1: " << s1 << endl;
    string s2, s3;
    getline(cin, s2, ' '); // 从输入流中提取字符串给s2,直到指定分隔符空格
    getline(cin, s3);
    cout << "s2: " << s2 << endl;
    cout << "s3: " << s3 << endl;
    system("pause");
}

在这里插入图片描述

结合代码和运行结果可见:

  1. 输入字符串对象时, cin >> s1 方式默认以空格、回车、Tab键等为间隔,不支持字符串包含空格的情形;
  2. 使用 getline(cin, s1) 能够读入包含空格的字符串。还可以通过参数指定读取时的分隔符,如代码line18。
  3. 无论是输入还是输出,都涉及到缓冲区的问题。尝试去掉line12,再次运行程序,观察结果。查阅资料,了解哪些场景下需要作清空缓冲区的处理。

cin.ignore()的使用

#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string s;
    // 重复录入字符串,将小写字符转换成大写,直到按下ctrl+Z结束
    while (getline(cin, s))
    {
        for (auto &ch : s)
            ch = toupper(ch);
        cout << s << "\n";
    }
    system("pause");
}

在这里插入图片描述

  1. 代码line11,把 getline(cin, s) 作为while的循环条件,实现重复录入,直到按下组合键 Ctrl+Z结束循环。
  2. 代码line14,使用了标准库函数 toupper() 实现小写字符-> 大写字符的转换。
  3. line13,范围for及自动类型推导,这里使用的是引用类型。尝试把引用类型改成普通类型,重新编译后运行程序,观察结果是否有不同?结合运行结果,思考和理解不同类型在编码中的灵活引用。
    把引用类型改成普通类型后并没有改变原字符串。

任务三 (vector模板)

vector是C++标准库提供的动态数组类模板,可以看作是封装了动态大小数组的有序容器。

  • 特性
    • 相较于普通数组,它更安全。
    • 相较于固定大小的数组类模板array, 使用vector特化成具体类型的数组类后,数组对象大小是可变的。空间是在堆上按需分配的。支持随机访问。
  • 应用场景
    • 数据项个数无法确定、经常需要在尾部追加或删除数据项的场合
#include <iostream>
#include <vector>
template <typename T>
void output(T x)
{
    for (const auto &i : x)
        std::cout << i << ", ";
    std::cout << "\b\b \n";
}

int main()
{
    using namespace std;
    vector<int> v1;                // 创建一个vector对象v1, 未指定大小, 元素是int型, 未初始化
    vector<int> v2(5);             // 创建一个vector对象v2, 包含5个元素,元素是int型,初始值是默认值0
    vector<int> v3(5, 42);         // 创建一个vector对象v3, 包含5个元素,元素是int型,指定初始值是42
    vector<int> v4{9, 2, 3, 0, 1}; // 创建一个vector对象v4, 元素是int型,使用初始化列表方式
    vector<int> v5{v4};            // 创建一个vector对象v5, 使用已经存在的对象v4创建
    output(v2);
    output(v3);
    output(v4);
    output(v5);
    system("pause");
}

在这里插入图片描述

#include <iostream>
#include <vector>
int main()
{
    using namespace std;
    vector<int> v{1, 9, 2, 5, 6};
    // 遍历方式1: 范围for
    for (auto const &i : v)
        cout << i << ", ";
    cout << "\b\b \n";
    // 遍历方式2:使用迭代器
    for (auto it = v.begin(); it != v.end(); ++it)
        cout << *it << ", ";
    cout << "\b\b \n";
    // 遍历方式3: 使用反向迭代器
    for (auto it = v.rbegin(); it != v.rend(); ++it)
        cout << *it << ", ";
    cout << "\b\b \n";
    system("pause");
}

在这里插入图片描述

这个代码示例,使用三种方式对vector对象v遍历输出,三种方式里都使用了自动类型推导auto。其中,方式2使用了迭代器,方式3使用了反向迭代器。begin(), end(), rbegin(), rend()是vector常用的成员函数。其中:

成员函数功能
begin()返回指向第一个元素的迭代器
end()返回指向最后一个元素后面的位置的迭代器
rbegin()返回指向最后一个元素
rend()返回指向第一个元素前面的位置的迭代器

在这里插入图片描述

it似乎和指针有相似性,通过星号间接访问。但是,迭代器是对指针的抽象和泛化。它虽然表现行为像指针,但不是指针。

#include <iostream>
#include <vector>
int main()
{
    using namespace std;
    vector<int> v1{42};
    cout << "v1.size() = " << v1.size() << endl;
    cout << "v1.capacity() = " << v1.capacity() << endl;
    v1.push_back(55); // 向尾部添加一个数据
    v1.push_back(90);
    cout << "v1.size() = " << v1.size() << endl;
    cout << "v1.capacity() = " << v1.capacity() << endl;
    for (auto i = 0; i < 8; ++i)
        v1.push_back(i);
    cout << "v1.size() = " << v1.size() << endl;
    cout << "v1.capacity() = " << v1.capacity() << endl;
    v1.pop_back(); // 从尾部删除一个数据
    cout << "v1.size() = " << v1.size() << endl;
    cout << "v1.capacity() = " << v1.capacity() << endl;
    system("pause");
}

在这里插入图片描述

成员函数功能
size()返回目前元素个数
capacity()返回"不进行空间重新分配"条件下最多能容那的元素个数
push_back()在尾部追加元素
pop_back()从尾部删除元素
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
template <typename T>
void output(const T &obj)
{
    for (auto i : obj)
        std::cout << i << ", ";
    std::cout << "\b\b \n";
}

int main()
{
    using namespace std;
    vector<string> v{"music", "film"};
    v.insert(v.begin(), "literature"); // 在迭代器v.begin()之前插入元素
    output(v);
    cout << v.at(1) << endl; //返回索引为1位置的元素。相较于v[1], at()方法会做索引越界检查
    v.erase(v.begin());      // 移除迭代器v.begin()位置处的元素
    output(v);
    v.push_back("literature");
    v.push_back("art");
    output(v);
    reverse(v.begin(), v.end()); // 对v内元素翻转(逆序)
    output(v);
    sort(v.begin(), v.end()); // 对v内元素升序排序
    output(v);
    system("pause");
}

在这里插入图片描述

结合代码和运行结果,可见,这个代码中:

  1. 使用了方法insert()向容器中指定的迭代器位置之前插入元素,使用方法erase()移除指定>迭代器位置的元素;
  2. 使用算法库中的reverse()对容器对象v1中的数据项进行翻转;使用sort()对v1内数据项升序排序。

任务四 (array模板)

array是C++标准库提供的一种固定大小的数组类模板。

  • 特性
    • 相较于普通数组,它更安全。同时,在效率上并没有变差。
    • 相较于动态数组类模板vector, 使用array特化成具体类型的数组类后,创建的数组对象大小是固定的。
    • 支持随机访问。
  • 应用场景
    通常情况下,如果元素个数是固定的,使用array比普通数组更安全,比vector效率更高。
#include <iostream>
#include <array>
template <typename T>
void output(T const &obj)
{
    for (auto i : obj)
        std::cout << i << ", ";
    std::cout << "\b\b \n";
}

int main()
{
    using namespace std;
    array<int, 5> x1; // 定义数组对象x1, 包含5个int元素, 未初始化
    x1.fill(42);      // 把数组对象x1的每个元素都填充为42
    x1[0] = 999;      // 支持通过下标随机访问; 不作越界检查
    x1.at(1) = 666;   // 支持通过at方法访问索引; 作越界检查,索引越界时会报出异常
    output(x1);
    array<int, 5> x2{}; // 定义数组对象x2, 包含5个int元素,初始化为0
    output(x2);
    array<int, 5> x3{9, 2, 1, 0, 7}; // 定义数组对象x3, 包含5个int元素,初始化为指定值
    output(x3);
    system("pause");
}

在这里插入图片描述

  1. 使用模板类array定义对象的方法。由于array是固定大小的数组类,所以,构造对象时,必须指定类型参数和元素个数。支持数组对象元素的初始化。如line16, line22, line25。
  2. 支持随机访问,如line10-11。使用[]方式索引不作越界检查,而使用at()方法会作索引越界检查,超过范围,会报异常。
  3. 使用了array类模板提供的方法fill,实现对数组对象元素的填充。如line9。
#include <iostream>
#include <array>
#include <string>
int main()
{
    using namespace std;
    array<string, 5> names{"Bob", "Alice", "Leo"};
    names.at(3) = "Bill";
    names.at(4) = "Nancy";
    for (auto it = names.begin(); it != names.end(); ++it)
        cout << *it << ", ";
    cout << "\b\b \n";
    for (auto it = names.rbegin(); it != names.rend(); ++it)
        cout << *it << ", ";
    cout << "\b\b \n";
    system("pause");
}

在这里插入图片描述

这个代码使用了at()方法,通过索引访问对象names的元素。line14-20,使用迭代器、反向迭代器,以及自动类型推导,对字符串数组对象names进行了遍历输出。

任务五 (LIveShow)

问题场景描述如下:
某独立音乐人要举办一场免费小型liveshow。livehouse场地容量有限,最多容纳100位乐迷听众。现通过某平台开通线上预约登记。线上预约登记信息类Info如下:

  • 数据成员
    • 昵称(nickname)
    • 联系方式(contact)
    • 所在城市(city)
    • 预定参加人数(n)
  • 函数成员
    • 构造函数
      带有参数,用于对预约信息(昵称、联系方式、所在城市、预定参加人数)进行初始化
    • print()
      打印信息(昵称、联系方式、所在城市、预定参加人数)
  • 编写代码实现以下要求:
    • 设计并实现信息类Info用于记录报名登记听众信息。类的定义保存在文件info.hpp中
    • 编写主函数文件task5.cpp:
    • 定义一个vector类对象audience_info_list,用于存放线上预约登记的听众信息;
    • 定义一个const常量capacity用于存放livehouse最多能容纳的听众人数
    • 从键盘录入每一个预约登记信息,直到停止录入(按下Ctrl+Z, 也可以通过程序自行设计其它
      停止录入方式)或者预定参加人数达到上限,输出audience_info_list对象中所有预约听众信
      息。
    • 用户预约时,当预定参加人数超过livehouse场地剩余容量时,则提示用户输入q退出预定,
      或,输入u更新预定信息。
    • 打印预约参加livehouse的听众信息

ReserveInfo.cpp

#include <iostream>
#include <vector>
#include <string>
#include <iomanip>

using namespace std;

// 预约类
class ReserveInfo
{
private:
    string nick_name;
    string contact;
    string city;
    int reserve_num;

public:
    ReserveInfo();
    ReserveInfo(string nick_name, string contact, string city, int reserve_num);
    ~ReserveInfo();

    int get_reserve_num() const;
    void print();
};

ReserveInfo::ReserveInfo()
{
}
ReserveInfo::ReserveInfo(string nick_name, string contact, string city, int reserve_num) : nick_name(nick_name), contact(contact), city(city), reserve_num(reserve_num)
{
}

ReserveInfo::~ReserveInfo()
{
}

int ReserveInfo::get_reserve_num() const
{
    return reserve_num;
}

void ReserveInfo::print()
{
    cout << left << endl
         << setw(15) << "\n称呼:" << setw(15) << nick_name
         << setw(15) << "\n联系方式:" << setw(15) << contact
         << setw(15) << "\n所在城市:" << setw(15) << city
         << setw(15) << "\n预定人数:" << setw(15) << reserve_num << endl;
}

LiveShow.cpp

#include <iostream>
#include <vector>
#include <string>
#include <iomanip>
#include "ReserveInfo.cpp"
using namespace std;

// 现场表演类
class LiveShow
{
private:
    vector<ReserveInfo> reserve_list;
    const int capasity = 100;

public:
    LiveShow();
    ~LiveShow();
    int get_capacity_remain();
    void add_reserve();
    void print();
};
LiveShow::LiveShow()
{
}
LiveShow::~LiveShow()
{
}

int LiveShow::get_capacity_remain()
{
    int capacity_remain = capasity;
    for (auto const reserve_info : reserve_list)
    {
        capacity_remain -= reserve_info.get_reserve_num();
    }
    return capacity_remain;
}
void LiveShow::add_reserve()
{
    cout << "录入信息:\n\n称呼/昵称,联系方式(邮箱/手机号),所在城市,预定参加人数\n";
    string str_temp;
    while (cin >> str_temp)
    {
        string nick_name;
        string contact;
        string city;
        int reserve_num;
        nick_name = str_temp;
        cin >> contact >> city >> reserve_num;
        if (reserve_num > this->get_capacity_remain())
        {
            char option;
            cout << "亲爱的 " << nick_name << " 对不起,只剩" << this->get_capacity_remain() << "个位置."
                 << "\n1. 输入u,更新(update)预定信息"
                 << "\n2. 输入q,退出预定"
                 << "您的选择:";
            cin >> option;
            if (option == 'u')
            {
                continue;
            }
            if (option == 'q')
            {
                break;
            }
        }else{
            ReserveInfo reserveInfo(nick_name, contact, city, reserve_num);
            reserve_list.push_back(reserveInfo);
        }
    }
}

void LiveShow::print()
{
    for(auto reserve_info : reserve_list){
        cout << "截止目前,一共有 " << capasity-get_capacity_remain() << " 位听众预定参加,预定观众信息如下:\n";
        reserve_info.print();
    }
}

Test.cpp

#include <iostream>
#include <vector>
#include <string>
#include <iomanip>
#include "LiveShow.cpp"
int main()
{
    LiveShow liveShow_bth;
    liveShow_bth.add_reserve();
    liveShow_bth.print();
    system("pause");
}

测试结果
在这里插入图片描述
在这里插入图片描述

任务六 (加密/解密)

设计并实现一个类TextCoder,用于对英文文本字符串进行简单的加密和解密操作。要求如下

  • 数据成员
    text 私有,用于存放要加密或解密的字符串(字符串内只限英文字符)

  • 函数成员
    encoder() 公有,用于对数据成员text进行加密操作,以返回值形式返回加密后的英文文本
    加密规则如下:

    每个英文字母用其后的第5个英文字母替换,即:
    字符a用f替换,字符b用g替换,字符u用字符z替换,字符v用字符a替换,字符z用字符e替
    换,以此类推。
    大写字符A用大写字符F替换, 大写字符B用大写字符G替换,大写字符Z用大写字符E替换。
    字母之外的其它字符保持不变。
    abcdefghijklmnopqrstuvwxyz

    decoder() 公有,用于对数据成员text进行解密操作,以返回值形式返回解密后的英文文本
    解密规则如下:

    每个英文字母字符用其前面的第5个字符替换,即:
    字符f用a替换,字符g用b替换,字符a用v替换,字符b用字符w替换,以此类推。(大写类
    同)
    字母之外的其它字符保持不变。

在主程序中,循环输入英文文本,使用类TextCoder构造对象完成加密操作,以及,对传输后收到的加密文本进行解密操作。直到按下 Ctrl+Z 终止程序。
TextCoder.cpp

#include <iostream>
#include <vector>
#include <string>
#include <iomanip>

using namespace std;

class TextCoder
{
private:
    string text;

public:
    TextCoder();
    TextCoder(string text);
    ~TextCoder();

    void set_text(string text);
    string get_text();
    string encoder();
    string decoder();
};

TextCoder::TextCoder()
{
}
TextCoder::TextCoder(string text) : text(text)
{
}

TextCoder::~TextCoder()
{
}

void TextCoder::set_text(string text)
{
    this->text = text;
}

string TextCoder::get_text()
{
    return text;
}

string TextCoder::decoder()
{
    string text_decoded = text;
    for (int i = 0; i < text_decoded.length(); i++)
    {
        if (text_decoded[i] >= 65 && text_decoded[i] <= 90)
        {
            text_decoded[i] = 90 - (90-text_decoded[i] + 5)%26;
        }else if(text_decoded[i] >= 97 && text_decoded[i] <= 122){
            text_decoded[i] = 122 - (122-text_decoded[i] + 5)%26;
        }    
    }
    return text_decoded;
}

string TextCoder::encoder()
{
    string text_encoded = text;
    for (int i = 0; i < text_encoded.length(); i++)
    {
        if (text_encoded[i] >= 65 && text_encoded[i] <= 90)
        {
            text_encoded[i] = (text_encoded[i]-65 + 5)%26 + 65;
        }else if(text_encoded[i] >= 97 && text_encoded[i] <= 122){
            text_encoded[i] = (text_encoded[i]-97 + 5)%26 + 97;
        }    
    }
    return text_encoded;
}

Test.cpp

#include "TextCoder.cpp"
#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string text, encoded_text, decoded_text;
    cout << "输入英文文本: ";
    while (getline(cin, text))
    {
        encoded_text = TextCoder(text).encoder(); // 这里使用的是临时无名对象
        cout << "加密后英文文本:\t" << encoded_text << endl;
        decoded_text = TextCoder(encoded_text).decoder(); // 这里使用的是临时无名对象
        cout << "解密后英文文本:\t" << decoded_text << endl;
        cout << "\n输入英文文本: ";
        system("pause");
    }
}

测试结果:
在这里插入图片描述