实验1 现代C++编程初体验
一、实验任务1
源代码task1.cpp

#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(); return 0; } 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{"013456789"}; 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有什么区别?
reverse 函数用于将容器(如 string、vector 等)中的元素顺序直接反转,操作会直接修改原容器的内容;而 reverse_copy 函数在实现元素反转的同时,会把反转后的结果复制到另一个容器中,原容器的元素顺序不会发生改变。
问题2:rotate算法是如何改变元素顺序的?它的三个参数分别代表什么?
rotate 算法能对容器中指定范围内的元素进行循环旋转,使得该范围内的元素顺序发生调整,旋转后,原本处于第二个参数所指位置的元素会成为这个范围的新首元素。 它的三个参数均为迭代器,分别代表: 第一个参数:旋转范围的起始位置(包含该位置); 第二个参数:旋转后作为新起始位置的元素所在处(属于原范围内部); 第三个参数:旋转范围的结束位置(不包含该位置)。
二、实验任务2
源代码task2.cpp

#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(); } template <typename T> void output(const T &c) { for(auto &i:c) std::cout << i <<' '; std::cout << '\n'; } int generate_random_number() { return std::rand()%101; } void test1() { using namespace std; vector<int> v0(10); generate(v0.begin(),v0.end(),generate_random_number); cout << "v0: "; output(v0); vector<int> v1(v0); sort(v1.begin(),v1.end()); cout << "v1: "; output(v1); vector<int> v2(v0); sort(v2.begin()+1,v2.end()-1); cout << "v2: "; output(v2); } 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相比,有什么优势?
minmax_element 相较于分别调用 min_element 和 max_element,其优势在于只需只需对容器遍历一次就能就能同时找到最大值和最小值,而后者需要遍历两次。这减少了遍历次数,进而提升了程序的执行效率。
问题 3:把代码中函数generate_random_number的声明和定义注释起来,把两处调用改成如下写法,观察效果是否等同?查阅c++中lambda表达式用法。
效果是等同的,两种方式都能生成 0 到 100 之间的随机数。lambda 表达式由捕获列表、参数列表、可变修饰符、异常说明、返回类型和函数体组成。此处的[]表示不捕获任何外部变量,()说明没有参数,返回类型则由编译器自动推断为 int 类型。
三、实验任务3
源代码task3.cpp

#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功能是什么?
func 函数的功能是,对字符进行处理:若字符是字母(无论大小写),则将其转换为后续的字母(按顺序递进),其中特殊处理‘z’转为‘a’、‘Z’转为‘A’。
问题 2:tolower和toupper功能分别是什么?
tolower 的作用是将大写字母转换为对应的小写字母;toupper 的作用是将小写字母转换为对应的大写字母。
问题 3:transform的4个参数意义分别是什么?如果把第3个参数s2.begin()改成s1.begin(),有何区别?
transform 的 4 个参数中,前两个参数划定了需要进行转换操作的元素范围,第三个参数指定了存储转换后结果的起始位置,第四个参数则是用于处理元素的操作(如函数或函数对象)。 若将第三个参数从 s2.begin () 改为 s1.begin (),区别在于:原写法会将转换结果存入 s2,s1 保持不变;修改后则会直接在 s1 自身上进行替换,转换结果覆盖 s1 原有的元素。
四、实验任务4
源代码task4.cpp

#include<iostream> #include<string> #include<algorithm> 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) { int left=0; int right=s.size()-1; while(left<right) { if(s[left]!=s[right]) return false; left++; right--; } return true; } bool is_palindrome_ignore_case(const std::string &s) { std::string s0; for(auto c:s) s0+=toupper(c) ; int left=0; int right=s0.size()-1; while(left<right) { if(s0[left]!=s0[right]) return false; left++; right--; } return true; }
运行结果截图
问题:使用cin>>s输入时,输入的字符串中不能包含空格。如果希望测试字符串包含空格,代码应如何调整?
使用getline(cin,s)代替cin>>s,可以输入带空格的字符串,但注意需要头文件#include<string>。
五、实验任务5
源代码task5.cpp

#include<iostream> #include<vector> #include<iomanip> #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'; } } std::string dec2n(int x,int n) { std::string result; int rem; if(x==0) return "0"; while(x>0) { rem=x%n; char a; if(rem<10) a='0'+rem; else a='A'+rem-10; result.push_back(a); x=x/n; } reverse(result.begin(),result.end()); return result; }
运行结果截图
六、实验任务6
源代码task6.cpp

#include<iostream> #include<iomanip> using namespace std; int main() { char c; int i; cout << ' ' << ' '; for(c='a';c<='z';c++) cout << ' ' << c ; cout << endl; for(i=2;i<=27;i++) { cout << setw(2) << right << i-1 << ' '; for(c='A'+(i-1);c<'A'+(i-1)+26;c++) { cout << (char)(c>'Z'?c-26:c) << ' '; } cout << endl; } return 0; }
运行结果截图
七、实验任务7
源代码task7.cpp

int number(); char sign(); int func(int x,char a); int calculate(int a,char j,int b); int main(){ using namespace std; srand(std::time(0)); double s; int n=0; int x,y,z; char j; for(int i=1;i<=10;i++){ x=number(); j=sign(); y=func(x,j); cout<<x<<j<<y<<'='; cin>>z; if(z==calculate(x,j,y))n++; cout<<endl; } cout<<"正确率:"<<fixed<<setprecision(2)<<n/10.0*100<<"%"<<endl; } int number(){ return std::rand()%9+1; } char sign(){ using namespace std; vector<char> v0{'+','-','*','/'}; int i=rand()%4; return v0[i]; } int func(int x,char a){ int i; if(a=='+'||a=='*') return std::rand()%9+1; else if(a=='-') return std::rand()%x+1; else{ do{ i=std::rand()%9+1; }while(x%i!=0); return i; } } int calculate(int a,char j,int b){ if(j=='+')return a+b; else if(j=='-')return a-b; else if(j=='*')return a*b; else return a/b; }
运行结果截图
实验总结:
这次实验最直观的感受,就是封装函数带来的便利——之前处理字符大小写,还得自己写判断条件,这次用 tolower 和 toupper,一行代码就能搞定,不仅写得快,回头看代码时,一眼就知道这段是做字符转换的,代码的可读性和编写效率都提了不少。 更让我有收获的是对编程思路的转变。以前用面向过程写代码,总盯着 “第一步做什么、第二步做什么”,比如处理一个数据流程,满脑子都是算法步骤,遇到逻辑绕一点的地方就容易卡壳。但这次尝试面向对象编程后,我开始先找 “这个问题里有哪些对象”,理清对象后再写代码,原本觉得复杂的逻辑突然就顺了,很多问题一下子变得清晰。