备战蓝桥杯----C/C++组 (一)所需C++基础知识(下)
个人主页:
✨永远在路上,永远向前走
个人专栏:
文章目录
- C++竞赛语法基础(下篇):进阶语法与实用技巧前言
C++竞赛语法基础(下篇):进阶语法与实用技巧前言
书接上文,本文继续讲解蓝桥杯编程竞赛所需的基本C++基础语法知识。
在掌握基础语法后,进一步学习数组、字符串、函数、结构体等进阶内容,为算法竞赛打下坚实基础
10. 逻辑操作符
| 操作符 | 含义 | 说明 |
|---|---|---|
| ! | 逻辑非 | 取反 |
| && | 逻辑与 | 两边都为真才为真 |
短路特性:
a && b:如果a为假,b不会被执行a || b:如果a为真,b不会被执行
11. 条件操作符(三目操作符)
表达式1? 表达式2: 表达式3// 如果表达式1为真,执行表达式2,否则执行表达式3int max =(a > b)? a : b;// 求a和b的最大值12. switch语句
switch(表达式){case 常量1:// 代码break;case 常量2:// 代码break;default:// 默认代码break;}特点:
- 表达式必须是整型
- case后必须是整型常量
- 每个case后要加break,否则会继续执行下一个case
13. 循环结构
13.1 while循环
while(条件){// 循环体}// 示例:打印1~10int i =1;while(i <=10){ cout << i <<" "; i++;}13.2 for循环
for(初始化; 条件; 调整){// 循环体}// 示例:打印1~10for(int i =1; i <=10; i++){ cout << i <<" ";}13.3 do-while循环
do{// 循环体}while(条件);// 特点:至少执行一次循环体13.4 break和continue
break:跳出整个循环continue:跳过本次循环的剩余部分,进入下一次循环
for(int i =1; i <=10; i++){if(i ==5)break;// i=5时跳出循环if(i ==3)continue;// i=3时跳过打印 cout << i <<" ";}// 输出:1 2 413.5 循环嵌套
// 打印乘法口诀表for(int i =1; i <=9; i++){for(int j =1; j <= i; j++){printf("%d*%d=%2d ", j, i, i*j);} cout << endl;}14. 数组
数组是一组相同类型元素的集合,在内存中连续存储。
14.1 一维数组
数组创建:
int arr[10];// 创建包含10个int的数组constint N =100;int a[N];// 使用常量定义大小数组初始化:
int arr1[5]={1,2,3,4,5};// 完全初始化int arr2[5]={1,2};// 不完全初始化,剩余为0int arr3[]={1,2,3,4,5};// 省略大小,编译器自动计算数组下标:
- 下标从0开始
- 最后一个元素的下标是
n-1 - 使用
[]访问元素:arr[下标]
数组遍历:
int arr[10]={1,2,3,4,5,6,7,8,9,10};// 方法1:for循环for(int i =0; i <10; i++){ cout << arr[i]<<" ";}// 方法2:范围for(C++11)for(int x : arr){ cout << x <<" ";}// 方法3:auto自动推导类型for(auto x : arr){ cout << x <<" ";}数组和sizeof:
int arr[10]; cout <<sizeof(arr);// 40(总字节数) cout <<sizeof(arr)/sizeof(arr[0]);// 10(元素个数)14.2 二维数组
创建:
int arr[3][5];// 3行5列的二维数组初始化:
int arr1[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};int arr2[3][5]={{1,2},{3,4},{5,6}};// 按行初始化int arr3[][5]={1,2,3,4,5,6,7,8,9,10};// 可以省略行数,不能省略列数访问和遍历:
int arr[3][5]={...};for(int i =0; i <3; i++){for(int j =0; j <5; j++){ cout << arr[i][j]<<" ";} cout << endl;}14.3 数组常用函数
memset - 设置数组内容(按字节设置):
#include<cstring>int arr[10];memset(arr,0,sizeof(arr));// 全部设为0(正确用法)// 注意:memset(arr, 1, sizeof(arr)) 不会将每个元素设为1!// 因为memset是按字节设置,每个字节设为1,int有4字节,值为0x01010101=16843009memcpy - 拷贝数组:
int a[10]={1,2,3,4,5,6,7,8,9,10};int b[10];memcpy(b, a,sizeof(a));// 将a的内容拷贝到b14.4 竞赛技巧 - 数组大小
constint N =10010;int arr[N];// 通常开大一些,防止越界全局数组的优势:
- 自动初始化为0
- 空间更大(全局区 vs 栈区)
- 避免传参麻烦
14.5 字符数组
初始化:
char str1[10]="hello";// 字符串方式char str2[]="hello";// 自动分配大小,包含'\0'char str3[10]={'h','e','l','l','o','\0'};// 字符方式字符串长度:
#include<cstring>char str[]="hello"; cout <<sizeof(str);// 6(包含'\0') cout <<strlen(str);// 5(不包含'\0')字符数组输入:
// 无空格字符串char s[100]; cin >> s;// C++方式scanf("%s", s);// C方式// 有空格字符串char s[100];fgets(s,100,stdin);// 会读取换行符scanf("%[^\n]s", s);// 读取到换行前字符串函数:
#include<cstring>char s1[20]="hello";char s2[20]="world";strcpy(s1, s2);// 拷贝:s1变为"world"strcat(s1, s2);// 拼接:s1变为"helloworld"strcmp(s1, s2);// 比较:相等返回0,s1<s2返回负数,s1>s2返回正数15. string字符串
string是C++提供的字符串类型,使用更加方便。
15.1 创建和初始化
#include<string> string s1;// 空字符串 string s2 ="hello";// 直接初始化 string s3("world");// 构造函数方式 string s4 = s2;// 拷贝初始化15.2 输入输出
string s;// 无空格输入 cin >> s;// 有空格输入getline(cin, s);// 读取一行,包含空格getline(cin, s,'q');// 读取直到遇到'q'15.3 常用操作
长度:
string s ="hello"; cout << s.size();// 5 cout << s.length();// 5(效果相同)访问元素:
string s ="hello"; cout << s[0];// 'h' s[1]='a';// 修改为"hallo"拼接:
string s1 ="hello"; string s2 ="world"; string s3 = s1 + s2;// "helloworld" s1 += s2;// s1变为"helloworld" s1 +='!';// 追加字符插入和删除:
string s ="hello"; s.push_back('!');// 末尾添加字符:"hello!" s.pop_back();// 删除末尾字符:"hello" s.insert(2,"xxx");// 在位置2插入:"hexxxllo"查找:
string s ="hello world"; size_t pos = s.find("world");// 返回第一次出现的位置(6)if(pos != string::npos){ cout <<"找到了,位置:"<< pos;}截取子串:
string s ="hello world"; string s1 = s.substr(6);// 从位置6到结尾:"world" string s2 = s.substr(6,5);// 从位置6开始取5个字符:"world"比较:
string s1 ="abc"; string s2 ="abd";if(s1 < s2){// 按字典序比较 cout <<"s1小于s2";}15.4 字符串和数字转换
#include<string>// 字符串转数字 string s ="123";int n =stoi(s);// 转intlong m =stol(s);// 转longdouble d =stod(s);// 转double// 数字转字符串int num =123; string str =to_string(num);// "123"16. 函数
函数是将一段代码封装起来,实现特定功能。
16.1 函数定义
返回类型 函数名(参数列表){// 函数体return 返回值;// 如果返回类型不是void}// 示例:加法函数intAdd(int x,int y){return x + y;}16.2 函数调用
intmain(){int a =10, b =20;int c =Add(a, b);// 调用函数 cout << c << endl;return0;}16.3 形参和实参
- 实参:调用函数时实际传递的值
- 形参:函数定义中的参数,接收实参的值
重要:形参是实参的一份临时拷贝,修改形参不影响实参。
16.4 传值vs传引用
传值调用:
voidSwap(int x,int y){int tmp = x; x = y; y = tmp;}// 调用后,实参不会改变传引用调用(C++特有):
voidSwap(int& x,int& y){// 使用引用int tmp = x; x = y; y = tmp;}// 调用后,实参会改变引用就是给变量起别名:
int a =10;int& ra = a;// ra是a的引用(别名) ra =20;// a变为20引用传参的优势:
- 避免拷贝,提高效率
- 可以在函数内修改实参
16.5 数组作为函数参数
voidprint_arr(int arr[],int size){for(int i =0; i < size; i++){ cout << arr[i]<<" ";}}intmain(){int arr[10]={1,2,3,4,5,6,7,8,9,10};print_arr(arr,10);// 数组传参return0;}注意:数组传参时,形参和实参指向同一块内存,修改形参会影响实参。
16.6 函数重载
函数重载允许同一个函数名有不同的参数列表:
intAdd(int a,int b){return a + b;}doubleAdd(double a,double b){return a + b;}intmain(){ cout <<Add(1,2);// 调用int版本 cout <<Add(1.5,2.5);// 调用double版本return0;}16.7 常用库函数
max/min:
#include<algorithm>int a =10, b =20; cout <<max(a, b);// 20 cout <<min(a, b);// 10swap:
#include<utility>// 或 <algorithm>int a =10, b =20;swap(a, b);// 现在a=20, b=1017. 递归
递归是函数调用自身的编程技巧。
17.1 递归的必要条件
- 存在限制条件(递归出口)
- 每次递归调用后越来越接近限制条件
17.2 阶乘 - 递归示例
intFact(int n){if(n ==0)// 递归出口return1;elsereturn n *Fact(n -1);// 递归调用}17.3 递归执行过程
以Fact(3)为例:
- Fact(3)调用:3 * Fact(2)
- Fact(2)调用:2 * Fact(1)
- Fact(1)调用:1 * Fact(0)
- Fact(0)返回:1
- Fact(1)返回:1 * 1 = 1
- Fact(2)返回:2 * 1 = 2
- Fact(3)返回:3 * 2 = 6
17.4 递归vs迭代
递归的优点:代码简洁,逻辑清晰
递归的缺点:效率低(有函数调用开销),可能栈溢出
斐波那契数列 - 递归版本(效率低):
intFib(int n){if(n <=2)return1;returnFib(n-1)+Fib(n-2);}斐波那契数列 - 迭代版本(效率高):
intFib(int n){int a =1, b =1, c =1;for(int i =3; i <= n; i++){ c = a + b; a = b; b = c;}return c;}结论:能用迭代解决的问题尽量用迭代,递归适合解决天然具有递归结构的问题(如树的遍历)。
18. 位运算
位运算直接操作整数的二进制位,效率非常高。
18.1 二进制基础
进制转换:
- 10进制转2进制:除2取余,倒序排列
- 2进制转10进制:按权展开
原码、反码、补码:
- 正数:原码=反码=补码
- 负数:补码=反码+1(符号位不变)
- 整数在内存中以补码形式存储
18.2 位运算符
| 运算符 | 名称 | 规则 |
|---|---|---|
| << | 左移 | 左边丢弃,右边补0 |
| >> | 右移 | 算术右移(左边补符号位) |
| & | 按位与 | 同为1才为1 |
| | | 按位或 | 有1则为1 |
| ^ | 按位异或 | 相同为0,不同为1 |
| ~ | 按位取反 | 0变1,1变0 |
18.3 位运算技巧
判断奇偶:
if(n &1)// 奇数else// 偶数获取第i位:
intget_bit(int x,int i){return(x >> i)&1;}将第i位置为1:
x |=(1<< i);将第i位置为0:
x &=~(1<< i);反转第i位:
x ^=(1<< i);去掉最右边的1:
x &=(x -1);这个操作常用于统计二进制中1的个数:
intcount_ones(int x){int cnt =0;while(x){ x &=(x -1); cnt++;}return cnt;}保留最右边的1:
int lowbit = x &(-x);18.4 异或的巧妙应用
性质:
- a ^ a = 0
- a ^ 0 = a
- a ^ b ^ a = b
交换两个数:
a = a ^ b; b = a ^ b; a = a ^ b;找单身狗(数组中只出现一次的数):
intsingleNumber(int* nums,int n){int ans =0;for(int i =0; i < n; i++){ ans ^= nums[i];}return ans;}19. 结构体
结构体可以将不同类型的数据组合成一个整体。
19.1 结构体定义
structStudent{ string name;int chinese;int math;int total;};// 注意分号19.2 结构体变量创建
// 方式1:直接创建structStudent s1;// C风格(struct可省略) Student s2;// C++风格// 方式2:定义时创建structStudent{ string name;int score;} s1, s2;// 方式3:初始化 Student s ={"张三",90,95,185};19.3 访问成员
Student s; s.name ="李四"; s.math =100; cin >> s.chinese; s.total = s.chinese + s.math;19.4 结构体整体操作
Student s1 ={"张三",90,95,185}; Student s2 = s1;// 整体赋值(成员逐一拷贝)19.5 结构体数组
Student stu[50];// 50个学生的数组// 遍历for(int i =0; i < n; i++){ cout << stu[i].name <<" "<< stu[i].total << endl;}19.6 结构体成员函数
C++的结构体可以包含函数:
structStudent{ string name;int chinese;int math;voidcalc_total(){ total = chinese + math;}voidprint(){ cout << name <<": "<< total << endl;}int total;};19.7 运算符重载
让结构体支持自定义的比较或运算:
structTime{int hours;int minutes;// 重载小于号,用于排序booloperator<(const Time& t)const{if(hours != t.hours)return hours < t.hours;return minutes < t.minutes;}};// 重载输出运算符 ostream&operator<<(ostream& os,const Time& t){ os << t.hours <<":"<< t.minutes;return os;}20. sort排序
sort是C++标准库中最常用的排序函数。
20.1 基本用法
#include<algorithm>int arr[]={5,2,8,1,9};int n =sizeof(arr)/sizeof(arr[0]);sort(arr, arr + n);// 升序排序// 输出:1 2 5 8 920.2 自定义排序规则
使用比较函数:
boolcmp(int a,int b){return a > b;// 降序}sort(arr, arr + n, cmp);排序结构体:
structStudent{ string name;int score;};boolcmp_by_score(Student a, Student b){return a.score > b.score;// 按成绩降序}sort(stu, stu + n, cmp_by_score);多关键字排序:
boolcmp(Student a, Student b){if(a.total != b.total)return a.total > b.total;// 先按总分降序elseif(a.chinese != b.chinese)return a.chinese > b.chinese;// 再按语文降序elsereturn a.id < b.id;// 最后按学号升序}20.3 字符串排序
string s ="cba";sort(s.begin(), s.end());// s变为"abc"21. 类(选学)
类是C++面向对象编程的核心,竞赛中了解基本用法即可。
21.1 类的定义
classStudent{public:// 公有成员,可以在类外访问voidinit(string n,int s){ name = n; score = s;}voidprint(){ cout << name <<": "<< score << endl;}private:// 私有成员,只能在类内访问 string name;int score;};21.2 类的使用
intmain(){ Student stu; stu.init("张三",95); stu.print();// stu.name = "李四"; // 错误!name是私有的return0;}21.3 struct和class的区别
- struct的成员默认是public
- class的成员默认是private
- 其他方面基本相同
竞赛中常用struct来组织数据,用class来封装复杂对象。
总结
经过上下两篇的学习,我们已经掌握了C++竞赛所需的核心语法知识:
基础篇:开发环境、数据类型、输入输出、条件判断、循环结构
进阶篇:数组、字符串、函数、递归、位运算、结构体、排序
这些知识是参加算法竞赛的基石。熟练掌握后,就可以开始学习算法了,包括枚举、搜索、贪心、动态规划等核心算法。
记住:语法是工具,算法是灵魂。只有熟练掌握语法,才能更好地实现算法,在竞赛中取得好成绩。
【后续学习建议】
- 多做练习,巩固语法知识
- 学习常用STL容器(vector、set、map、stack、queue等)
- 开始接触基础算法:枚举、二分、贪心、搜索
- 刷题平台推荐:洛谷、AcWing、Codeforces
祝大家在蓝桥杯中取得好成绩!感谢各位大佬的观看!