👂 无论你多怪异我还是会喜欢你(《刺客伍六七》动画推广版片尾曲) - 周子琰 - 单曲 - 网易云音乐
一起补基础! φ(゜▽゜*)♪
👂 My Nam's Suzie - Susie/Farfashah - 单曲 - 网易云音乐
算法训练营的东西,都会放到《蓝桥杯2024备赛》专栏里
花了我68块买的书,第一章耗时5小时,12032字
目录
🌳基础语法
🌼cout与指针
🌼(浮点)精度,域宽,填充
🌼输出格式
🌳轻松写函数
🌼1,标准函数
🌼2,无返回值函数
🌼3,无参数函数
🌼4,传值参数函数
🌼5,引用参数函数
🌼6,数组参数函数
🌼7,字符串参数函数
🌼8,函数嵌套
🌼9,函数重载
🌼10,函数模板
🌳递归
🌳结构体 -- 信息携带者
🌳数组
🌼先说静态数组
🌼再说动态数组
🌼2,二维数组
🌳字符串
🌼1,C-风格字符串
🌼2,C++ string类字符串
🌳基础语法
🌼cout与指针
利用cout对象输出指针,引用类型的数据。当输出数据为指针或引用类型,与printf()函数用法一致,不带 * 输出的是指针的值,即变量地址;带 * 输出的是指针指向的变量的值。
它比printf()函数简便之处,在于,不必设置数据的输出格式
#include<iostream>
using namespace std;
int main()
{
int a = 10, *p;
int &b = a; //引用, 变量b和a指向同一个空间
p = &a; //指针p存储变量a地址
string s = "C++";
string *ps = &s;
cout<<p<<endl; //地址
cout<<b<<endl; //值
cout<<*p<<endl; //值
cout<<endl;
cout<<ps<<endl; //地址
cout<<*ps; //值
return 0;
}
0x6dfecc
10
10
0x6dfeb4
C++
🌼(浮点)精度,域宽,填充
操作符 | 功能 |
---|---|
setprecision(int n) | 精度为n |
setw(int n) | 域宽为n |
setfill(char c) | 填充的字符为c |
头文件:#include<iomanip>
训练1-2:将2.0开平方后,设置不同精度和宽度,输出
#include<iostream>
#include<cmath> //sqrt
#include<iomanip> //setw(), setprecision(), setfill()
using namespace std;
int main()
{
double d = sqrt(2.0);
cout<<"精度设置:"<<endl;
for(int i = 0; i < 5; ++i)
cout<<setprecision(i)<<d<<endl; //设置不同精度
cout<<"当前精度为:"<<cout.precision()<<endl;
cout<<"当前域宽:"<<cout.width()<<endl;
cout<<setw(6)<<d<<endl; //默认右对齐
cout<<"当前填充字符:"<<endl;
cout<<setfill('*')<<setw(10)<<d<<endl; //setfill()函数可直接插入流
return 0;
}
精度设置:
1
1
1.4
1.41
1.414
当前精度为:4
当前域宽:0
1.414
当前填充字符:
*****1.414
🌼输出格式
操作符 | 功能 |
---|---|
oct | 八进制输出 |
dec | 十进制输出 |
hex | 十六进制输出 |
训练1-3:输入一个三位数,输出个位,十位,百位上数字
#include<iostream>
#include<iomanip> //setw()
using namespace std;
int main()
{
int n;
cin>>n;
int ge, shi, bai;
ge = n % 10, shi = n / 10 % 10, bai = n / 100 % 10;
cout<<ge<<setw(2)<<shi<<setw(2)<<bai;
return 0;
}
647
7 4 6
🌳轻松写函数
函数是...实现某一功能代码的...模块化封装,定义如下
返回值类型 函数名(参数类型 参数名1, 参数类型 参数名2...)
{
执行语句
...return 返回值;
}
下面介绍10种函数类型,分别是
1,标准函数 2,无返回值函数 3,无参数函数 4,传值参数函数
5,引用参数函数 6,数组参数函数 7,字符串参数函数 8,函数嵌套
9,函数重载 10,函数模板
🌼1,标准函数
训练1-22:输入n对整数a, b,输出它们的和
#include<iostream>
using namespace std;
//int add(int a, int b); //函数原型声明
int add(int a, int b) //函数定义
{
return a + b;
}
int main()
{
int n, a, b;
cin>>n;
int C[n];
for(int i = 0; i < n; ++i) {
cin>>a>>b;
C[i] = add(a, b); //调用函数
}
for(int i = 0; i < n; ++i)
cout<<C[i]<<endl;
return 0;
}
4
1 2
6 8
11 -5
3 3
3
14
6
6
🌼2,无返回值函数
如果没有返回值,则返回值类型为void
训练1-23:输入n,输出1~n的所有整数(无返回值)
#include<iostream>
using namespace std;
void print(int n) { //无返回值
for(int i = 1; i <= n; ++i)
cout<<i<<endl;
}
int main()
{
int n;
cin>>n;
print(n);
return 0;
}
5
1
2
3
4
5
🌼3,无参数函数
训练1-24:输入n,如果n为10的倍数,输出3个“very good!”
#include<iostream>
using namespace std;
void print() //无参数
{
for(int i = 0; i < 3; ++i)
cout<<"very good!"<<endl;
}
int main()
{
int n;
cin>>n;
if(n % 10 == 0)
print();
return 0;
}
20
very good!
very good!
very good!
🌼4,传值参数函数
传值参数在函数内部的改变,出了函数后无效
训练1-25:输入两个整数a, b,交换后输出
#include<iostream>
using namespace std;
void swap(int x, int y) //传值参数
{
int temp;
temp = x;
x = y;
y = temp;
cout<<"交换中"<<x<<"\t"<<y<<endl;
}
int main()
{
int a, b;
cin>>a>>b;
cout<<endl;
cout<<"交换前"<<a<<"\t"<<b<<endl;
swap(a, b);
cout<<"交换后"<<a<<"\t"<<b<<endl;
return 0;
}
-2 666
交换前-2 666
交换中666 -2
交换后-2 666
🌼5,引用参数函数
引用参数在参数前加“&”符号,引用参数在函数内部的改变,除了函数后仍然有效
训练1-26:输入两个整数a和b,交换后输出
#include<iostream>
using namespace std;
void swap(int &x, int &y) //引用参数
{
int temp;
temp = x;
x = y;
y = temp;
cout<<"交换中"<<x<<"\t"<<y<<endl;
}
int main()
{
int a, b;
cin>>a>>b;
cout<<endl;
cout<<"交换前"<<a<<"\t"<<b<<endl;
swap(a, b);
cout<<"交换后"<<a<<"\t"<<b<<endl;
return 0;
}
对比传值参数,只是在参数前,加了取地址符“&”
-3 666
交换前-3 666
交换中666 -3
交换后666 -3
🌼6,数组参数函数
此部分较为陌生
训练1-27:输入n个整数并将其存入a[]数组,求和然后输出和
#include<iostream>
using namespace std;
int arrayadd(int a[], int n) //a[n]作为参数时, 要分开写, a[]也可用*a
{
int sum = 0;
for(int i = 0; i < n; ++i)
sum += a[i];
return sum;
}
int main()
{
int n, s;
//静态定义长度1000的数组, 静态定义空间数是具体的数值或常量
int a[1000];
cin>>n;
//int *a = new int [n]; //动态定义, 此时n可以为变量
for(int i = 0; i < n; ++i)
cin>>a[i];
s = arrayadd(a, n);
cout<<s<<endl;
return 0;
}
4
2 7 -5 11
15
🌼7,字符串参数函数
训练1-28:输入n个字母,如果是小写字母,转换为大写字母,输出转换后的字符串
#include<iostream>
//#include<string>
using namespace std;
void strconvert(string &s) //char *s字符型数组
{
for(int i = 0; i < s.length(); ++i) //strlen(s)
if(s[i] >= 'a' && s[i] < 'z')
s[i] -= 32;
cout<<s<<endl;
}
int main()
{
string str; //char str[10]字符型数组
cin>>str;
strconvert(str);
cout<<str<<endl;
return 0;
}
What'sYourName
WHAT'SYOURNAME
WHAT'SYOURNAME
🌼8,函数嵌套
训练1-29:输入两个整数a和b,求两个整数的最大公约数和最小公倍数
#include<iostream>
using namespace std;
int gcd(int x, int y) //辗转相除求最大公约数
{
int t;
while(x % y) {
t = y;
y = x % y;
x = t;
}
return y;
}
int lcm(int x, int y) //最小公倍数
{
int g;
g = gcd(x, y); //嵌套
return (x * y / g);
}
int main()
{
int a, b, c, d;
cin>>a>>b;
c = gcd(a, b);
d = lcm(a, b);
cout<<c<<"\t"<<d;
return 0;
}
80 36
4 720
🌼9,函数重载
函数重载(多态)指的是,多个同名函数,但是每个函数的,参数数量,类型,顺序不同
它可以提高代码的可读性和复用性
训练1-30:写一个函数,对于字符串类型的数据,取其长度一半;对于浮点类型的数据,取其值二分之一
#include<iostream>
//#include<string>
using namespace std;
float half(float x)
{
return x / 2;
}
char *half(string s) //返回一个char型指针, 表示字符串地址
{
int n = s.length() / 2;
char *str = new char[n + 1]; //new分配的是地址
for(int i = 0; i < n; ++i)
str[i] = s[i];
str[n] = '\0';
return str;
}
int main()
{
float n;
string st;
cin>>n>>st;
cout<<half(n)<<endl;
cout<<half(st);
return 0;
}
3.22345
HeyGirl
1.61172
Hey
详细解释下代码中的
char *half(string s) //返回一个char型指针, 表示字符串地址
char *str = new char[n + 1]; //new分配的是地址
1,函数定义中,使用了指针类型char*来存储字符串类型的返回值,因为字符数组可以使用指针来访问,并且使用动态内存分配函数new来动态创建一个长度为n + 1的字符数组
2,'\0'是表示字符串结束的空字符
3,C++中,实现带有字符串类型返回值的函数时,通常使用指针类型char *来存储返回值,并使用new动态分配内存来保证内存的正确分配和释放
4,使用指针类型存储分配的内存地址,* 运算符获取该指针指向的值
🌼10,函数模板
在C++中,template
是一种用于定义通用代码的机制,可以用于定义类模板、函数模板和变量模板等。其中,函数模板是一种通用的函数定义方式,使得可以声明和定义多个具有相同结构但参数类型不同的函数,具体的语法格式如下:
template <typename Type1, typename Type2, ...>
ReturnType FunctionName(Type1 arg1, Type2 arg2, ...)
{
// function body
}
函数模板(Function Template)使得代码更加灵活,可以避免重复编写相似的代码,同时也更方便管理和维护代码
训练1-31:输入两个数a和b(整数或浮点数),求两个数的和值
#include<iostream>
using namespace std;
template<typename T> //模板
T add(T x, T y) //相当于把int, float等替换成自定义名字
{
return x + y;
}
int main()
{
int a, b;
double c, d;
cin>>a>>b>>c>>d;
cout<<add(a, b)<<"\t"<<add(c, d);
return 0;
}
18 -22 17.22 1.33
-4 18.55
🌳递归
递归调用是函数内部调用自身的过程,需要结束条件,否则会进入无限递归状态,永远无法结束
1,递归函数
训练1-32:输入n个整数,倒序输出所有整数
#include<iostream>
using namespace std;
int a[100];
void print(int i)
{
cout<<a[i]<<endl;
if(i > 0)
print(i - 1);
//cout<<a[i]<<endl;
}
int main()
{
int n;
cin>>n;
for(int i = 0; i < n; ++i)
cin>>a[i];
print(n - 1);
return 0;
}
4
-1 5 -9 11
11
-9
5
-1
2,递归原理
递归 = 递推 + 回归
递推指的是,将原问题不断分解为子问题直到达到结束条件,返回最近子问题的解
然后逆向逐一回归,最终达到递推开始时的原问题,返回原问题的解
阶乘是典型的递归调用问题
先看代码
#include<iostream>
using namespace std;
long long fac(int n)
{
if(n == 0 || n == 1)
return 1;
else
return n * fac(n - 1);
}
int main()
{
int n;
cin>>n;
cout<<fac(n);
return 0;
}
5
120
再看原理图
注意
递归中,每一次递推都需要一个栈空间来保存调用记录,so计算空间复杂度时,需要计算递归栈的辅助空间
递归在计算机内部的处理,使用了一种被成为“栈”的数据结构,类似步枪弹匣的装子弹和退子弹
只能从顶端插入和抽取,被称为“后进先出”(Last In First Out, LIFO)
原理图如下(实际递归中传递的是参数的地址)
进栈
出栈
先一步一步把子问题压入栈,直到得到返回值,再一步一步出栈,最终得到递归结果,运算过程使用了n个栈空间作为辅助空间
训练1-34:输入一个整数n,输出斐波那契数列第n项
🌳结构体 -- 信息携带者
多个数据项组合在一起作为一个数据元素
struct student //学生信息结构体
{
string name, number, sex;
int age;
float score;
};
student a; //定义一个结构体类型变量a
有时为了方便,会使用typedef给结构体起个小名
typedef struct student //学生信息结构体
{
string name, number, sex;
int age;
float score;
}stu;
stu a; //定义一个结构体变量a, 与student a等效
typedef语法规则
typedef 类型名称 类型标识符;
使用typedef好处
1,简化复杂的类型声明
2,提高程序可移植性
比如
typedef in ElemType; //给int起个小名ElemType
在程序中就可以直接定义
ElemType a; //等价于int a;
所以,如果由1000个地方用到了ElemType类型,但是现在处理的数据变为字符类型了
可以将类型定义中的int改为char
typedef char ElemType;
这样只需修改类型定义,无需在1000个位置改动,否则容易漏了某处导致错误
使用typedef提高算法通用性,因为很多时候结构体定义并不指定处理的数据是什么类型,不能简单写成某种类型
🌳数组
1,一维数组
🌼先说静态数组
常规用法,懂得都懂,注意数组过大时,考虑声明为全局变量或者vector
数组名表示地址
训练1-38:现在有n盏灯,编号为1~n,开始时所有灯都是关的,编号为1的人把1的倍数的灯开关按下(开的关上,关的打开),编号为2的人把2的倍数的灯开关按下...直到第k个人为止
给定n和k(0 < n, k <= 1000),输出哪几盏灯是开着的
#include<iostream>
#include<cstring> //memset()
using namespace std;
int main()
{
int a[1010];
memset(a, 0, sizeof(a)); //初始化每个元素为0
int n, k;
cin>>n>>k;
//按下开关
for(int i = 1; i <= k; ++i) //k的倍数
for(int j = 1; j <= n; ++j) //n盏灯
if(j % i == 0)
a[j] = !a[j]; //0的取1, 1的取0
//输出结果
for(int i = 1; i <= n; ++i) {
if(a[i]) {
if(i != 1)
cout<<" ";
cout<<i;
}
}
return 0;
}
关键是代码第16行,a[j] = !a[j]
33 9
1 4 9 10 11 12 13 14 15 17 18 19 21 23 27 29 30 31
训练1-39:输入n个学生成绩,存入数组,求总成绩和平均成绩(浮点数)
为了练习数组参数函数
#include<iostream>
using namespace std;
int a[100];
//(int a[], int n)等价于(int *a, int n)
int add(int a[], int n) { //数组作为参数, 不可以直接写a[n]
int sum = 0;
for(int i = 0; i < n; ++i)
sum += a[i];
return sum;
}
int main()
{
int n, s;
float avg;
cin>>n;
for(int i = 0; i < n; ++i)
cin>>a[i];
s = add(a, n);
avg = float(s) / n;
cout<<s<<"\t"<<avg;
return 0;
}
4
2 3 4 5
14 3.5
🌼再说动态数组
2)动态定义
在程序运行过程中,动态分配空间定义数组,一维数组动态定义:
类型说明符 * 数组名 = new[常量或变量表达式];
int *a = new int[n];
类型说明符 --> 元素类型; 常量或变量表达式 --> 数组长度
使用new分配的数组,使用完毕后要用delete释放内存空间
delete[] 数组名
delete[] a;
注意
1,delete释放的是new分配的内存(不要释放其他的)
2,delete只能释放同一个内存块1次
3,new给实体分配内存,delete释放
4,new给数组分配内存,delete[]释放
5,对空指针使用delete是安全的
训练1-41:输入n个学生的成绩,并存入动态数组a[],统计不及格人数
#include<iostream>
using namespace std;
int count(int a[], int n) { //数组作为参数, 不可以直接写a[n]
int sum = 0;
for(int i = 0; i < n; ++i)
if(a[i] < 60)
sum++;
return sum;
}
int main()
{
int n;
cin>>n;
int *a = new int[n]; //动态数组
for(int i = 0; i < n; ++i)
cin>>a[i];
cout<<"no pass: "<<count(a, n);
delete[] a; //释放内存
return 0;
}
5
55 60 12 100 61
no pass: 2
🌼2,二维数组
1)静态定义
int a[2][4] = {{0,1,2,3},{7,2,9,5}};
int a[2][4] = {0,1,2,3,7,2,9,5};
int a[2][4] = {{0,1,2},{0}};
示例
#include<iostream>
using namespace std;
void print(int a[][4])
{
for(int i = 0; i < 2; ++i) {
for(int j = 0; j < 4; ++j)
cout<<a[i][j]<<" ";
cout<<endl;
}
}
int main()
{
int a[2][4] = {{0,1,2,3},{7,2,9,5}};
print(a);
cout<<endl;
int b[2][4] = {0,1,2,3,7,2,9,5};
print(b);
cout<<endl;
int c[2][4] = {{0,1,2},{0}};
print(c);
return 0;
}
0 1 2 3
7 2 9 5
0 1 2 3
7 2 9 5
0 1 2 0
0 0 0 0
⚪注意
将二维数组作为参数时,可以省略第1维长度,但必须指定第2维长度
int sum(int a[][5], int n);
2)动态定义
一个 m行n列的二维数组相当于m个长度为n的一维数组
int **array = new int*[m];
for(int i = 0; i < m; ++i) {
array[i] = new int[n]; //按行分配空间
}
for(int i = 0; i < m; ++i) {
delete[] array[i]; //按行释放空间
}
delete[] array;
对比下一维和二维动态数组的声明
int *a = new int[n]; //一维
int **a = new int*[n]; //二维
训练1-42:蛇形填数,输入一个整数n,按照蛇形填写n * n矩阵
#include<iostream>
#include<cstring>
#include<iomanip>
using namespace std;
//int a[100][100];
int main()
{
int n, x, y, total;
cin>>n;
int **a = new int*[n]; //指向指针的指针a
for(int i = 0; i < n; ++i) {
a[i] = new int[n]; //按行分配空间
memset(a[i], 0, n*sizeof(int));
}
//输出初始化后的数组
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j)
cout<<setw(5)<<a[i][j];
cout<<endl;
}
cout<<endl;
x = y = 0;
total = a[0][0] = 1;
//预处理
while(total < n*n) {
while(y + 1 < n && !a[x][y + 1]) //向右
a[x][++y] = ++total;
while(x + 1 < n && !a[x + 1][y]) //向下
a[++x][y] = ++total;
while(y - 1 >= 0 && !a[x][y - 1]) //向左
a[x][--y] = ++total;
while(x - 1 >= 0 && !a[x - 1][y]) //向上
a[--x][y] = ++total;
}
//输出蛇形矩阵
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j)
cout<<setw(5)<<a[i][j];
cout<<endl;
}
//释放内存
for(int i = 0; i < n; ++i)
delete[] a[i]; //按行释放空间
delete[] a;
return 0;
}
具体解释下第11行和第14行
int **a = new int*[n]; //指向指针的指针a
memset(a[i], 0, n*sizeof(int));
第11行:定义一个指向指针的指针a,该指针指向一个有n个元素的指针数组,每个指针指向一个int类型数组
第14行:memset()函数,对一段内存空间置0,第1个参数是地址,第3个参数要赋值的字节数,所以是n*sizeof(int)
6
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
1 2 3 4 5 6
20 21 22 23 24 7
19 32 33 34 25 8
18 31 36 35 26 9
17 30 29 28 27 10
16 15 14 13 12 11
🌳字符串
字符串,指存储在内存的,连续字节中的一系列字符
C++中字符串分为两种形式:C-风格字符串,C++string类字符串
🌼1,C-风格字符串
C-风格头文件#include<cstring>,默认以'\0'结束,在存储空间中不要忘了'\0'
定义方式1
字符数组
char a[8] = {'a','b','c','d','e','f','g','h'};
字符串
char a[8] = {'a','b','c','d','e','f','g','\0'};
定义方式2
字符串
char a[8] = "abcdefg";
char a[] = "affsdjkl;sd";
求长度
1,sizeof:
返回所占总空间字节数,整型/字符型的数组/指针均可
在编译时计算,因此sizeof不能返回动态分配的内存空间大小
2,strlen:
返回字符数组或字符串所占字节数,针对字符数组/指针
区分
cin:空格,制表符,换行符作为结束,因此只能接受一个单词(换行符保留)
getline:读取一行,直到遇到换行符
get:读取一行,直到换行符,但是换行符保留在输入序列中
#include<iostream>
using namespace std;
int main()
{
char s[100];
cin.getline(s, 10); //读入9个字符, 最后一个默认'\0'
cout<<s<<endl;
//cin.getline(s, 10, ':'); //读到冒号停止
//cout<<s<<endl;
return 0;
}
(**sjd a221
(**sjd a2
🌼2,C++ string类字符串
C++ string类字符串的长度没有限制,头文件为#include<string>
它隐藏了字符串的数组性质,使用户可以像处理普通变量一样处理字符串
注意
1,string类字符串没有'\0'的概念
2,char数组使用了一组用于存储一个字符串的存储单元,而string变量使用了一个表示字符串的实体
关于长度:.length(), .size()
关于输入
string s;
cin>>s;
getline(cin, s);
getline(cin, s, ':');
训练1-45:输入一些字符串,对其进行复制,拼接,比较等操作
#include<iostream>
#include<cstring> //C 风格
#include<string> //C++风格
using namespace std;
/* C-风格
strlen(): 长度
strcpy(): 复制
strcat(): 拼接
strcmp(): 比较
strchr(): 查找字符
strlwr(): 转小写
strupr(): 转大写
*/
/* string类
.size(), .length(),=,+,==,!=,>=,<=,find
*/
int main()
{
char s1[100];
char s2[20] = "hello!";
char s3[] = "a";
char s4 = 'a';
char s5[3] = {'a','b','c'};
char s6[3] = {'a','b','\0'};
cin>>s1;
cout<<strlen(s1)<<endl;
cout<<s1<<" "<<s2<<" "<<s3<<" "<<s4<<" "<<endl;
cout<<s5<<" "<<s6<<" "<<endl;
cout<<"jasskfjalsjdl" "123"<<endl;
cout<<"aslkjdalksjdl"
"123"<<endl;
cout<<"asdkjasldkjal 123"<<endl;
return 0;
}
HelloBoy
8
HelloBoy hello! a a
abca ab
jasskfjalsjdl123
aslkjdalksjdl123
asdkjasldkjal 123
解释下输出第4行为什么是abca ab而不是abc ab呢
因为先输出s5中存储的abc后,由于s5长度只有3,而其中字符数组abc长度为3,没有以空字符'\0'结尾,所以会输出s5后面的内存数据,直到误认为遇到了空字符'\0',此时是a
所以最终输出abca ab
训练1-46:输入一行字符,统计单词个数
#include<iostream>
using namespace std;
int countword(string s)
{
int len = s.size(), i = 0, num = 0;
while(i < len) {
while(s[i] == ' ') //跳过连续空格
i++;
if(i < len) //单词数+1, 是i < len
num++;
while(s[i] != ' ' && i < len) //跳过当前单词
i++;
}
return num; //得到单词数
}
int main()
{
string s;
getline(cin, s);
cout<<countword(s);
return 0;
}
I love you forever my son
6
(一行空格)
0
训练1-47:输入3个字符串,找出其中最小的字符串
#include<iostream>
using namespace std;
string minstr(string s1, string s2)
{
if(s1 < s2)
return s1;
else
return s2;
}
int main()
{
string s1, s2, s3, Min;
cin>>s1>>s2>>s3;
Min = minstr(s1, minstr(s2, s3));
cout<<Min;
return 0;
}
whup whuz whupa
whup