实验一 现代C++编程初体验
实验任务一
源代码:

1 #include<iostream> 2 #include<string> 3 #include<vector> 4 #include<algorithm> 5 template<typename T> 6 void output(const T& c); 7 void test1(); 8 void test2(); 9 void test3(); 10 int main() { 11 std::cout << "测试1: \n"; 12 test1(); 13 std::cout << "测试2: \n"; 14 test2(); 15 std::cout << "测试3: \n"; 16 test3(); 17 } 18 19 template<typename T> 20 void output(const T& c) { 21 for (auto& i : c) 22 std::cout << i << ' '; 23 std::cout << '\n' ; 24 } 25 void test1() { 26 using namespace std; 27 28 string s0{ "0123456789" }; 29 cout << "s0 = " << s0 << endl; 30 string s1(s0); 31 reverse(s1.begin(), s1.end()); 32 cout << "s1 = " << s1 << endl; 33 34 string s2(s0.size(), ' '); 35 reverse_copy(s0.begin(), s0.end(), s2.begin()); 36 cout << "s2 = " << s2 << endl; 37 } 38 void test2() { 39 using namespace std; 40 41 vector<int> v0{ 2,0,4,9 }; 42 cout << "v0: "; output(v0); 43 vector<int> v1{ v0 }; 44 reverse(v1.begin(), v1.end()); 45 cout << "v1: "; output(v1); 46 47 vector<int> v2{ v0 }; 48 reverse_copy(v0.begin( ), v0.end( ), v2.begin( )); 49 cout << "v2: "; output(v2); 50 51 } 52 void test3() { 53 using namespace std; 54 55 vector<int> v0{ 0,1,2,3,4,5,6,7,8,9 }; 56 cout << "v0: "; output(v0); 57 58 vector<int> v1{ v0 }; 59 rotate(v1.begin(), v1.begin() + 1, v1.end()); 60 cout << "v1: "; output(v1); 61 62 vector<int> v2{ v0 }; 63 rotate(v2.begin(), v2.begin() + 2, v2.end()); 64 cout << "v2: "; output(v2); 65 66 vector<int> v3{ v0 }; 67 rotate(v3.begin(), v3.end() - 1, v3.end()); 68 cout << "v3: "; output(v3); 69 vector<int> v4{ v0 }; 70 rotate(v4.begin(), v4.end() - 2, v4.end()); 71 cout << "v4: "; output(v4); 72 }
运行测试截图:
观察与思考:
1.reverse 和 reverse_copy 有什么区别?
reverse 会直接在原字符串上直接进行反转操作,改变了原容器内容。
reverse_copy 则会把反转后的结果复制到另一个容器中,原容器保持不变。
2.rotate 算法是如何改变元素顺序的?它的三个参数分别代表什么?
功能:将范围为 [first, last)
的以 middle
为分割点,将 [first, middle)
和 [middle, last)
两部分交换顺序,形成范围为 [middle, last) + [first, middle)的新字符串。
三个参数:
first
:范围的起始迭代器;
middle
:旋转的分割点迭代器,即旋转后此位置的元素将成为首元素;
last
:范围的结束迭代器。
实验任务二
源代码:

1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 #include <numeric> 5 #include <iomanip> 6 #include <cstdlib> 7 #include <ctime> 8 9 template<typename T> 10 void output(const T &c); 11 12 int generate_random_number(); 13 void test1(); 14 void test2(); 15 16 int main() { 17 std::srand(std::time(0)); 18 std::cout << "测试1: \n"; 19 test1(); 20 21 std::cout << "\n测试2: \n"; 22 test2(); 23 } 24 25 template <typename T> 26 void output(const T &c) { 27 for(auto &i : c) 28 std::cout << i << ' '; 29 std::cout << '\n'; 30 } 31 32 int generate_random_number() { 33 return std::rand() % 101; 34 } 35 36 void test1() { 37 using namespace std; 38 39 vector<int> v0(10); 40 generate(v0.begin(), v0.end(), generate_random_number); 41 cout << "v0: "; output(v0); 42 43 vector<int> v1{v0}; 44 sort(v1.begin(), v1.end()); 45 cout << "v1: "; output(v1); 46 vector<int> v2{v0}; 47 sort(v2.begin()+1, v2.end()-1); 48 cout << "v2: "; output(v2); 49 } 50 void test2() { 51 using namespace std; 52 53 vector<int> v0(10); 54 generate(v0.begin(), v0.end(), generate_random_number); 55 cout << "v0: "; output(v0); 56 57 auto min_iter = min_element(v0.begin(), v0.end()); 58 auto max_iter = max_element(v0.begin(), v0.end()); 59 cout << "最小值: " << *min_iter << endl; 60 61 auto ans = minmax_element(v0.begin(), v0.end()); 62 cout << "最小值: " << *(ans.first) << endl; 63 cout << "最大值: " << *(ans.second) << endl; 64 65 double avg1 = accumulate(v0.begin(), v0.end(), 0.0) / v0.size(); 66 cout << "均值: " << fixed << setprecision(2) << avg1 << endl; 67 68 sort(v0.begin(), v0.end()); 69 double avg2 = accumulate(v0.begin()+1, v0.end()-1, 0.0) / (v0.size()-2); 70 cout << "去掉最大值、最小值之后,均值: " << avg2 << endl; 71 }
运行结果截图:
观察与思考:
1.generate 算法的作用是什么?
generate
算法的核心是 “按照某种逻辑或规则创建内容”。在任务二中,用生成指定范围和数据元素的vector容器。
2.minmax_element 和分别调用 min_element 、 max_element 相比,有什么优势?
通过一次遍历,完成了两次操作。减少了时间开销,使代码更加简洁。
3.把代码中函数 generate_random_number 的声明(line13)和定义(line35-37)注释起来,把两处调用改成如 下写法,观察效果是否等同?查阅c++中lambda表达式用法。
效果等同。C++ lambda表达式用法:lambda表达式是一种匿名函数,语法格式为 [捕获列表] (参数列表) -> 返回值类型 { 函数体 } 。其中捕获列表用于指定外部变量的访问方式(如 [] 空捕获、 [&] 引用捕获、 [=] 值捕获等);参数列表和普通函数类似;返回值类型可省略,由编译器自动推导。示例中的lambda [](){return std::rand()%101;} 属于无捕获、无参数、返回 int 类型的简单形式。lambda可以直接在使用的地方定义,避免单独定义命名函数的麻烦。
实验任务三
源代码:

#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 功能分别是什么?
tolower:将大写字母变为小写;toupper:将小写字母变为大写。非字母的字符不变。
3.transform 的4个参数意义分别是什么?如果把第3个参数 s2.begin() 改成 s1.begin() ,有何区别?
分别为:要转换的元素的起始位置;要转换的元素的结束位置,( “左闭右开” );转换后元素的存放起始位置;一元操作函数,用于对输入范围的每个元素执行转换(接收一个输入元素,返回转换后的结果)。
区别:改为s1.begin()后s1中的原始元素将会被覆盖。
实验任务四
源代码:

1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 bool is_palindrome(const std::string &s); 5 bool is_palindrome_ignore_case(const std::string &s); 6 int main() { 7 using namespace std; 8 string s; 9 // 多组输入,直到按下Ctrl+Z结束测试 10 while(cin >> s) { 11 cout << boolalpha 12 << "区分大小写: " << is_palindrome(s) << "\n" 13 << "不区分大小写: " << is_palindrome_ignore_case(s) << "\n\n"; 14 } 15 } 16 // 函数is_palindrome定义 17 bool is_palindrome(const std::string &s){ 18 using namespace std; 19 string ss(s.size(),' '); 20 reverse_copy(s.begin(),s.end(),ss.begin()); 21 if(ss==s){ 22 return true; 23 } 24 else{ 25 return false; 26 } 27 } 28 29 // 函数is_palindrome_ignore_case定义 30 bool is_palindrome_ignore_case(const std::string &s){ 31 using namespace std; 32 string ss; 33 for(auto &i:s){ 34 ss+=tolower(i); 35 } 36 string rev(s.size(),' '); 37 reverse_copy(ss.begin(),ss.end(),rev.begin()); 38 if(rev==ss){ 39 return true; 40 } 41 else{ 42 return false; 43 } 44 }
运行结果截图:
观察与思考: 使用 cin >> s 输入时,输入的字符串中不能包含空格。如果希望测试字符串包含空格(如 hello oop ),代码应如何调整?
可以使用 std::getline
函数。
实验任务五
源代码:

1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 std::string dec2n(int x, int n = 2); 5 int main() { 6 int x; 7 while(std::cin >> x) { 8 std::cout << "十进制: " << x << '\n' 9 << "二进制: " << dec2n(x) << '\n' 10 << "八进制: " << dec2n(x, 8) << '\n' 11 << "十二进制: " << dec2n(x, 12) << '\n' 12 << "十六进制: " << dec2n(x, 16) << '\n' 13 << "三十二进制: " << dec2n(x, 32) << "\n\n"; 14 } 15 } 16 // 函数dec2n定义 17 std::string dec2n(int x, int n){ 18 using namespace std; 19 string ans; 20 if(x==0) return "0"; 21 while(x){ 22 if(x%n<=9){ 23 ans+=static_cast<char>(x%n+'0'); 24 } 25 else{ 26 ans+=static_cast<char>('A'+(x%n-10)); 27 } 28 x/=n; 29 } 30 reverse(ans.begin(),ans.end()); 31 return ans; 32 }
运行测试截图:
实验任务六
源代码:

1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include<bits/stdc++.h> 5 using namespace std; 6 void func(string &s){ 7 auto t=s[0]; 8 for(auto i=0;i<s.size()-1;i++){ 9 s[i]=s[i+1]; 10 } 11 s[s.size()-1]=t; 12 for(auto &i:s){ 13 cout<<setw(2)<<i; 14 } 15 cout<<'\n'; 16 } 17 int main(){ 18 string s1,s2; 19 for(char i='a';i<='z';i++){ 20 s1+=i; 21 } 22 cout<<" "; 23 for(auto i=0;i<s1.size();i++){ 24 cout<<setw(2)<<s1[i]; 25 } 26 cout<<'\n'; 27 for(auto &i:s1){ 28 s2+=toupper(i); 29 } 30 int cnt=1; 31 while(cnt<=26){ 32 cout<<setw(2)<<cnt++; 33 func(s2); 34 } 35 36 37 return 0; 38 }
运行测试截图:
实验任务七
源代码:

1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include<bits/stdc++.h> 5 using namespace std; 6 void func(string &s){ 7 auto t=s[0]; 8 for(auto i=0;i<s.size()-1;i++){ 9 s[i]=s[i+1]; 10 } 11 s[s.size()-1]=t; 12 for(auto &i:s){ 13 cout<<setw(2)<<i; 14 } 15 cout<<'\n'; 16 } 17 int main(){ 18 int ans[11],res[11]; 19 srand(time(0)); 20 int sign,x,y,cnt=1; 21 double r; 22 23 while(cnt<=10){ 24 sign=rand()%4; 25 if(sign==0){ 26 x=rand()%9+1; 27 y=rand()%9+1; 28 cout<<x<<'+'<<y<<'='; 29 ans[cnt]=x+y; 30 cin>>res[cnt]; 31 } 32 else if(sign==1){ 33 do{ 34 x=rand()%9+1; 35 y=rand()%9+1; 36 }while(x<=y); 37 cout<<x<<'-'<<y<<'='; 38 ans[cnt]=x-y; 39 cin>>res[cnt]; 40 } 41 else if(sign==2){ 42 x=rand()%9+1; 43 y=rand()%9+1; 44 cout<<x<<'*'<<y<<'='; 45 ans[cnt]=x*y; 46 cin>>res[cnt]; 47 } 48 else{ 49 do{ 50 x=rand()%9+1; 51 y=rand()%9+1; 52 }while(x%y!=0); 53 cout<<x<<'/'<<y<<'='; 54 ans[cnt]=x/y; 55 cin>>res[cnt]; 56 } 57 cnt++; 58 } 59 int count=0; 60 for(int i=1;i<=10;i++){ 61 if(ans[i]==res[i]) count++; 62 } 63 r=count/10.0; 64 cout<<fixed<<setprecision(2)<<r*100<<'%'<<'\n'; 65 66 67 return 0; 68 }
运行结果截图:
实验总结:
1.C++标准库中有很多非常方便的接口函数,熟练掌握可以减少代码量,节省时间。比如这次实验中的reverse,reverse_copy,tolower,toupper等函数。
2.注意区分cin >>与getline的输入差异,解决包含空格的字符串读取问题。
3.初步了解了 lambda 表达式的语法与场景。