文章目录
- 任务一 (模板类)
- 任务二 (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");
}
结合代码和运行结果可见:
- 输入字符串对象时, cin >> s1 方式默认以空格、回车、Tab键等为间隔,不支持字符串包含空格的情形;
- 使用 getline(cin, s1) 能够读入包含空格的字符串。还可以通过参数指定读取时的分隔符,如代码line18。
- 无论是输入还是输出,都涉及到缓冲区的问题。尝试去掉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");
}
- 代码line11,把 getline(cin, s) 作为while的循环条件,实现重复录入,直到按下组合键 Ctrl+Z结束循环。
- 代码line14,使用了标准库函数 toupper() 实现小写字符-> 大写字符的转换。
- 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");
}
结合代码和运行结果,可见,这个代码中:
- 使用了方法insert()向容器中指定的迭代器位置之前插入元素,使用方法erase()移除指定>迭代器位置的元素;
- 使用算法库中的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");
}
- 使用模板类array定义对象的方法。由于array是固定大小的数组类,所以,构造对象时,必须指定类型参数和元素个数。支持数组对象元素的初始化。如line16, line22, line25。
- 支持随机访问,如line10-11。使用[]方式索引不作越界检查,而使用at()方法会作索引越界检查,超过范围,会报异常。
- 使用了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替换。
字母之外的其它字符保持不变。
abcdefghijklmnopqrstuvwxyzdecoder() 公有,用于对数据成员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");
}
}
测试结果: