实验1 现代C++编程初体验
1. 实验任务1 验证性实验。 在C++编码环境中,输入、运行并观察以下代码,结合运行结果和注释,体验使用C++标准库进行编程的便捷性。从面 向对象编程范式的角度,体会封装与基于接口编程的意义。
#include <iostream> #include <string> #include <vector> #include <algorithm> template<typename T> void output(const T &c); void test1(); void test2(); void test3(); int main() { std::cout << "测试1: \n"; test1(); std::cout << "\n测试2: \n"; test2(); std::cout << "\n测试3: \n"; test3(); } template <typename T> void output(const T &c) { for(auto &i : c) std::cout << i << ' '; std::cout << '\n'; } void test1() { using namespace std; string s0{"0123456789"}; cout << "s0 = " << s0 << endl; string s1(s0); reverse(s1.begin(), s1.end()); cout << "s1 = " << s1 << endl; string s2(s0.size(), ' '); reverse_copy(s0.begin(), s0.end(), s2.begin()); cout << "s2 = " << s2 << endl; } void test2() { using namespace std; vector<int> v0{2, 0, 4, 9}; cout << "v0: "; output(v0); vector<int> v1(v0); reverse(v1.begin(), v1.end()); cout << "v1: "; output(v1); vector<int> v2(v0); reverse_copy(v0.begin(), v0.end(), v2.begin()); cout << "v2: "; output(v2); } void test3() { using namespace std; vector<int> v0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; cout << "v0: "; output(v0); vector<int> v1(v0); rotate(v1.begin(), v1.begin()+1, v1.end()); cout << "v1: "; output(v1); vector<int> v2(v0); rotate(v2.begin(), v2.begin()+2, v2.end()); cout << "v2: "; output(v2); vector<int> v3(v0); rotate(v3.begin(), v3.end()-1, v3.end()); cout << "v3: "; output(v3); vector<int> v4(v0); rotate(v4.begin(), v4.end()-2, v4.end()); cout << "v4: "; output(v4); }
(1)reverse函数会反转容器中的元素顺序且该反转是直接修改原容器的;
reverse_copy函数则是将原容器中的元素反转后存到另一个容器里,不改变原容器。
(2)rotate函数通过确定容器的头部与尾部,同时设定中间的形参来控制容器中元素移动相应的位数。如果将rotate函数写成rotate(begin,begin+n,end),那么begin+n对于的元素代表新的首位,begin到end间的所有元素循环右移+n位(-n则是左移)。
2. 实验任务2
验证性实验。
在C++编码环境中,输入、运行并观察以下代码,体验使用C++标准库高效完成数据赋值、排序和基本统计。
#include <iostream> #include <vector> #include <algorithm> #include <numeric> #include <iomanip> #include <cstdlib> #include <ctime> // 模板函数声明 template<typename T> void output(const T &c); int generate_random_number(); void test1(); void test2(); int main() { std::srand(std::time(0)); // 添加随机种子 std::cout << "测试1: \n"; test1(); std::cout << "\n测试2: \n"; test2(); } // 输出容器对象c中的元素 template <typename T> void output(const T &c) { for(auto &i: c) std::cout << i << ' '; std::cout << '\n'; } // 返回[0, 100]区间内的一个随机整数 int generate_random_number() { return std::rand() % 101; } // 测试1:对容器类对象指定迭代器区间赋值、排序 void test1() { using namespace std; vector<int> v0(10); // 创建一个动态数组对象v0, 对象大小为10 generate(v0.begin(), v0.end(), generate_random_number); // 生成随机数填充v0 cout << "v0: "; output(v0); vector<int> v1{v0}; sort(v1.begin(), v1.end()); // 对整个vector排序 cout << "v1: "; output(v1); vector<int> v2{v0}; sort(v2.begin()+1, v2.end()-1); // 只对中间部分排序,不包含首尾元素 cout << "v2: "; output(v2); } // 测试2:对容器类对象指定迭代器区间赋值、计算最大值/最小值/均值 void test2() { using namespace std; vector<int> v0(10); generate(v0.begin(), v0.end(), generate_random_number); cout << "v0: "; output(v0); // 求最大值和最小值 auto min_iter = min_element(v0.begin(), v0.end()); auto max_iter = max_element(v0.begin(), v0.end()); cout << "最小值: " << *min_iter << endl; cout << "最大值: " << *max_iter << endl; // 同时求最大值和最小值 auto ans = minmax_element(v0.begin(), v0.end()); cout << "最小值: " << *(ans.first) << endl; cout << "最大值: " << *(ans.second) << endl; // 求平均值 double avg1 = accumulate(v0.begin(), v0.end(), 0.0) / v0.size(); cout << "均值: " << fixed << setprecision(2) << avg1 << endl; sort(v0.begin(), v0.end()); double avg2 = accumulate(v0.begin()+1, v0.end()-1, 0.0) / (v0.size()-2); cout << "去掉最大值、最小值之后,均值: " << avg2 << endl; }
(1)generate函数规定了动态数组的头尾位置,再对数组中的元素批量赋值,赋值的规则由generate函数中的第三个参数确定。
(2)minmax_element函数与分别调用min_element、max_element相比,前者可以一次性将最大值最小值分别计算好,空间上只要一个而非两个辅助变量即可。
(3)两者功能相同,都是生成 [0, 100] 的随机整数。lambda表达式用法如下
3. 实验任务3
验证性实验。
在C++编码环境中,输入、运行并观察以下代码,体验使用C++标准库高效完成字符串变换操作。
#include <iostream> #include <string> #include <algorithm> #include <cctype> unsigned char func(unsigned char c); void test1(); void test2(); int main() { std::cout << "测试1: 字符串大小写转换\n"; test1(); std::cout << "\n测试2: 字符变换\n"; test2(); } unsigned char func(unsigned char c) { if(c == 'z') return 'a'; if(c == 'Z') return 'A'; if(std::isalpha(c)) return static_cast<unsigned char>(c+1); return c; } void test1() { std::string s1{"Hello World 2049!"}; std::cout << "s1 = " << s1 << '\n'; std::string s2; for(auto c: s1) s2 += std::tolower(c); std::cout << "s2 = " << s2 << '\n'; std::string s3; for(auto c: s1) s3 += std::toupper(c); std::cout << "s3 = " << s3 << '\n'; } void test2() { std::string s1{"I love cosmos!"}; std::cout << "s1 = " << s1 << '\n'; std::string s2(s1.size(), ' '); std::transform(s1.begin(), s1.end(), s2.begin(), func); std::cout << "s2 = " << s2 << '\n'; }
(1)此处自定义函数 func 的功能是仅将字母字符进行后移一位的变换,对于特殊情况规定了'z'→'a'和'Z'→'A',非字母字符保持原样。
(2)tolower的功能是将大写字母字符变为小写字母,非大写字母字符保持原样;toupper的功能是将小写字母字符变为大写字母,非小写字母字符保持原样。
(3)第一个参数:原始序列的起始位置;
第二个参数:原始序列的终止位置;
第三个参数:新序列从原始序列中开始执行的位置;
第四个参数:序列变换时附加的规则。
如果把第3个参数 s2.begin() 改成 s1.begin() ,其区别在于变换会在原始容器内部执行,使原序列发生变化。
4. 实验任务4
编写函数,实现判断回文串。是返回 true ,否则,返回 false 。
在 main 函数中调用,输入字符串,调用其判断,输出结果。要求支持多组输入。
严格区分大小写: bool is_palindrome(const std::string &s)
不区分大小写: bool is_palindrome_ignore_case(const std::string &s);
#include <iostream> #include <string> #include <algorithm> #include <cctype> bool is_palindrome(const std::string &s); bool is_palindrome_ignore_case(const std::string &s); int main() { using namespace std; string s; while(cin >> s) { cout << boolalpha << "区分大小写:" << is_palindrome(s) << "\n" << "不区分大小写:" << is_palindrome_ignore_case(s) << "\n\n"; } } // 以下两个函数需要补全 bool is_palindrome(const std::string &s) { using namespace std; string t=s; reverse(t.begin(),t.end()) ; if(t.compare(s)==0){ return true; } else{ return false; } } bool is_palindrome_ignore_case(const std::string &s) { using namespace std; string t=s,s0; for(auto i : s){ s0+=tolower(i); } reverse(t.begin(),t.end()) ; string t0; for(auto i : t){ t0+=tolower(i); } if(t0.compare(s0)==0){ return true; } else{ return false;
如果希望测试字符串包含空格(如 hello oop ),可以先用get函数确认输入是否包含空格。若无,则沿用原程序;若有,则将空格以外的元素依次存到新容器中,再沿用原程序做回文判断。
5. 实验任务5
编写程序实现进制转换。具体要求如下:
编写函数 std::string dec2n(x, n) 实现把一个十进制数x转换成n进制,结果以字符串形式返回。如果第2
个参数没有指定,默认转换成二进制。(本次实验,限定参数n取值区间为[2, 36],限定参数x >= 0)
当n = 17,即17进制时,基数是0-9, A-G
当n = 18,即18进制时,基数是0-9, A-H
依次类推:
当n = 36, 即36进制时,基数是0-9, A-Z (即:10 -> A, 11->B, 12->C, …, 35->Z)
在main函数中,输入十进制整数,调用函数 dec2n 得到转换成指定进制的字符串,输出。要求支持多组输入。
#include <iostream> #include <string> #include <algorithm> std::string dec2n(int x, int n = 2); int main() { int x; while(std::cin >> x) { std::cout << "十进制: " << x << '\n' << "二进制: " << dec2n(x) << '\n' << "八进制: " << dec2n(x, 8) << '\n' << "十二进制: " << dec2n(x, 12) << '\n' << "十六进制: " << dec2n(x, 16) << '\n' << "三十二进制: " << dec2n(x, 32) << "\n\n"; } } std::string dec2n(int x, int n) { if (x == 0) { return "0"; } if (n < 2 || n > 36) { return "ERROR"; } std::string res; int t; while (x > 0) { t = x % n; if (t < 10) { res += (char)(t + '0'); } else { res += (char)(t - 10 + 'A'); } x /= n; } std::reverse(res.begin(), res.end()); return res; }

6. 实验任务6
编写程序,在屏幕上打印字母密文对照表。
#include <iostream> #include <iomanip> #include <string> #include <algorithm> using namespace std; int main() { string lowercase = "a b c d e f g h i j k l m n o p q r s t u v w x y z "; string uppercase = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "; cout << " " << lowercase.substr(0, lowercase.length() - 1) << endl; for (int i = 1; i <= 26; i++) { cout << setw(2) << i << " "; rotate(uppercase.begin(), uppercase.begin() + 2, uppercase.end()); cout << uppercase.substr(0, uppercase.length() - 1) << endl; } return 0; }
7. 实验任务7
编写一个程序,实现自动生成小学生算术运算题目并自动评测。具体要求如下:
程序运行后,自动生成10道10以内的算术运算题。
运算式(包括=)在程序运行时自动生成。
运算式=右侧的答案由用户输入
算术运算包括:加、减、乘、除。生成题目时,运算随机。
两个操作数限制在[1, 10]区间内的整数。
题目是减法运算时,要求第一个操作数>=第二个操作数。
题目是除法运算时,要求第一个操作数能整除第二个操作数整除。
用户完成10道测试后,屏幕给出正确率,要求以百分号形式输出,保留小数点后两位。
多次运行程序时,每次生成的题目与上次不同。(Tips: 使用 srand(time(0)) 作为随机种子)
#include <iostream> #include <iomanip> #include <ctime> #include <cstdlib> int main() { std::srand(std::time(0)); using namespace std; enum calculator {ADD = 1, SUB, MUL, DIV}; int correct_count = 0; for (int i = 0; i < 10; i++) { int a = std::rand() % 10 + 1; int b = std::rand() % 10 + 1; int op = std::rand() % 4 + 1; int user_answer; int correct_answer; switch (op) { case ADD: cout << a << " + " << b << " = "; cin >> user_answer; correct_answer = a + b; if (user_answer == correct_answer) { correct_count++; } break; case SUB: if (a < b) { std::swap(a, b); } cout << a << " - " << b << " = "; cin >> user_answer; correct_answer = a - b; if (user_answer == correct_answer) { correct_count++; } break; case MUL: cout << a << " * " << b << " = "; cin >> user_answer; correct_answer = a * b; if (user_answer == correct_answer) { correct_count++; } break; case DIV: if (a % b != 0) { i--; continue; } cout << a << " / " << b << " = "; cin >> user_answer; correct_answer = a / b; if (user_answer == correct_answer) { correct_count++; } break; } } double accuracy = (correct_count / 10.0) * 100; cout << fixed << setprecision(2); cout << "正确率:" << accuracy << "%" << endl; return 0; }
这题我在一开始编写时对小学整除除法编写不太有思路,,因为要满足能整除,导致除法能生成的概率太低了,想不到怎么解决。后面用ai跑了两个版本,一个是优化了除法生成逻辑,一个是预先生成所有可能的除法题目。我将ai给的前者方案也放在这里。看完感觉也不难,主要我自己一开始没想到先随机生成除数和商的做法。
case DIV: // 除法 - 优化版本 // 先随机生成除数b b = std::rand() % 10 + 1; // 随机生成商,然后计算被除数a int quotient; quotient = std::rand() % 10 + 1; a = quotient * b; // 确保被除数不超过10 while (a > 10) { quotient = std::rand() % 10 + 1; a = quotient * b; } cout << a << " / " << b << " = "; cin >> user_answer; correct_answer = quotient; if (user_answer == correct_answer) { correct_count++; } break; } } }
实验总结
(1)C++环境下有更多可用的封装函数,使用时要注意其功能上的差异和局限性,比如“使用 cin >> s 输入时,输入的字符串中不能包含空格。”诸如此类。编写时还要注意某些函数特定的书写习惯。
(2)编写函数处理数据时,有时需要将数据拆成零散的数据元素,有时作为整体,有时拆成数据元素之后又要重新组合或者增删某些元素。因此要格外注意数据对象本身的特质和需要实现的目标又有哪些特征。