文章目录
展开1.0 第一个C++程序
1 2 3 4 5 6 7 8 9 |
#include <iostream> // 引用头文件 using namespace std; // 引入命名空间 int main() // 定义 main 函数 { cout<<"Hello,World!"<<endl; // 输出 Hello World system("pause"); // 控制台暂留 return 0; // 返回 0,结束 main 函数 } |
1 2 3 4 5 6 |
public class HelloWorld{ public static void main(String args[]){ System.out.println("Hello World!"); } } |
1.1 注释
1.1.1 单行注释(行内注释)
1 2 |
// 注释内容 |
1.1.2 多行注释(注释块)
1 2 3 4 5 6 |
/* 多行 注释 内容 */ |
1.1.3 注释用途
- 解释程序的意思
- 让某段代码不执行(调试)
- 便于日后维护、他人阅读
1.2 输入与输出
1.2.1 cin 与 cout
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> using namespace std; int main() { int x, y; // 声明变量 cin >> x >> y; // 读入 x 和 y cout << y << endl << x; // 输出 y,换行,再输出 x system("pause"); return 0; // 结束主函数 } |
变量 见后文。
1.2.2 scanf 与 printf
- 是 C 语言提供的,C++ 向下兼容的。
- 大多数情况下速度比 cin 和 cout 快。
- 可以方便地控制输入输出格式。
1 2 3 4 5 6 7 8 9 |
#include <cstdio> int main() { int x, y; scanf("%d%d", &x, &y); // 读入 x 和 y printf("%d\n%d", y, x); // 输出 y,换行,再输出 x return 0; } |
其中, %d
表示读入/输出的变量是一个有符号整型(int
型)的变量。
类似地:
%s
表示字符串(string)。%c
表示字符(char)。-
%lf
表示双精度浮点数(double
)。 %lld
表示长整型(long long
)。%u
表示无符号整型(unsigned int
)。%llu
表示无符号长整型(unsigned long long
)。
除了类型标识符以外,还有一些控制格式的方式。
%1d
表示长度为1的整型。在读入时,即使没有空格也可以逐位读入数字。在输出时,若指定的长度大于数字的位数,就会在数字前用空格填充。若指定的长度小于数字的位数,就没有效果。12345678910#include <iostream>using namespace std;int main(){int a,b,c;scanf("%1d%1d%1d",&a,&b,&c);printf("%2d %3d %4d\n",a,b,c);return 0;}%.6lf
,用于输出,保留六位小数。123456789#include <iostream>using namespace std;int main(){double pi=3.1415926;printf("pi的值为: %.2lf(保留两位小数),%.4lf(保留四位小数)。", pi, pi);return 0;}
1.2.3 转义字符
转义字符用来表示一些无法直接输入的字符,如由于字符串字面量中无法换行而无法直接输入的换行符,由于有特殊含义而无法输入的引号,由于表示转义字符而无法输入的反斜杠等。
常见的转义字符有:
\t
表示制表符,即一个tab,通常为4个空格大小。\\
表示\
。\"
表示"
。\0
表示空字符,用来表示C风格字符串的结尾。\r
表示回车。Linux系统换行符为\n
,Windows系统换行符为\r\n
。在 OI 中,如果输出需要换行,使用\n
即可。但读入时,如果使用逐字符读入,可能会由于换行符造成一些问题,需要注意。例如,gets
将\n
作为字符串结尾,这时候如果换行符是\r\n
,\r
就会留在字符串结尾。- 特殊地,
%%
表示%
,只能用在printf
或scanf
中,在其他字符串字面量中只需要简单使用%
就好了。
1.3 变量
1.3.1 数据类型
C++ 的类型系统由如下几部分组成:
- 基础类型
a. 无类型/
void
型b. 空指针类型
c. 算数类型
i. 整数类型(
int
) ii. 布尔类型(
bool
) iii. 字符类型(
char
) iv. 浮点类型(
float
,double
) -
复合类型
1.3.1.1 布尔类型(bool)
值只有两种:true
和 false
。
一般情况下占 1 字节的空间。
1.3.1.2 整数类型
5 种:char
,short
,int
,long
,long long
。
区别:
- 存储所占空间不同
- 表示的范围不同
short < int < long long
。
符号性:
signed
:表示带符号整数(默认);unsigned
:表示无符号整数。
1.3.1.3 字符类型
char
,底层存储方式仍然是整数,一般通过 ASCII 编码实现字符和整数的一一对应。
‘a’:97 ‘A’:65
1.3.1.4 浮点类型(小数)
float
,double
。区别是位宽不同,进而能表示的范围和精度不同。
1.3.1.5 无类型
void
,只能用于声明函数,表示函数无返回值。
1.3.2 类型转换
小类型可以转换到大类型。
例如:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> using namespace std; int main() { int a = 10; long long b = a; cout << b << endl; return 0; } |
大类型转换到小类型可能丢失精度。
例如:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> using namespace std; int main() { int a = 2147483648; short b = a; cout << b << endl; return 0; } |
1.3.3 定义变量
1 2 3 4 |
int oi; double wiki; char org = 'c'; |
区分全局变量(花括号外的)和局部变量(花括号内的),前者会被初始化成0,后者没有这种特性。
变量标识符:数字、字母和下划线,不能以数字开头,不能是关键字。
- 合法:abc, abc123, _abc123
- 不合法:123abc, *abc, int
1.3.3.1 变量作用域
全局变量的作用域,自其定义之处开始,至文件结束为止。
局部变量的作用域,自其定义之处开始,至代码块结束为止。
由花括号括起的若干语句就是一个代码块。
1 2 3 4 5 6 7 8 |
int g = 20; int main() { int g = 10; printf("%d\n", g); return 0; } |
如果一个代码块的内嵌块中定义了相同变量名的变量,则内层块中将无法访问外层块中相同变量名的变量。
为了防止出现意料之外的错误,请尽量避免局部变量与全局变量重名的情况。
1.3.4 常量
1 2 3 |
const int a = 2; a = 3; |
常量是固定值,在程序执行期间不会改变。
常量的值在定义后不能被修改。定义时加一个 const
关键字即可。
如果修改了常量的值,在编译环节就会报错:error: assignment of read-only variable‘a’
。
1.4 运算
1.4.1 算术运算符
运算符 | 功能 |
---|---|
+ (单目) |
正 |
- (单目) |
负 |
* (双目) |
乘法 |
/ |
除法 |
% |
取模 |
+ (双目) |
加法 |
- (双目) |
减法 |
算术运算符中有两个单目运算符(正、负)以及五个双目运算符(乘法、除法、取模、加法、减法),其中单目运算符的优先级最高。
其中取模运算符 %
意为计算两个整数相除得到的余数,即求余数。
而 -
为双目运算符时做减法运算符,如 2-1
;为单目运算符时做负值运算符,如 -1
。
遵循数学中加减乘除的优先规律,首先进行优先级高的运算,同优先级自左向右运算,括号提高优先级。
1.4.1.1 算术运算中的类型转换
对于双目算术运算符,当参与运算的两个变量类型相同时,不发生类型转换 ,运算结果将会用参与运算的变量的类型容纳,否则会发生类型转换,以使两个变量的类型一致。
转换的规则如下:
- 先将
char
,bool
,short
等类型提升至int
(或unsigned int
,取决于原类型的符号性)类型; - 若存在一个变量类型为
long double
,会将另一变量转换为long double
类型; - 否则,若存在一个变量类型为
double
,会将另一变量转换为double
类型; - 否则,若存在一个变量类型为
float
,会将另一变量转换为float
类型;
例如,对于一个整型( int
)变量 x
和另一个双精度浮点型( double
)类型变量 y
:
x/3
的结果将会是整型;x/3.0
的结果将会是双精度浮点型;x/y
的结果将会是双精度浮点型;x*1/3
的结果将会是整型;x*1.0/3
的结果将会是双精度浮点型;
1.4.2 位运算符
1.4.2.1 进位制
二进制(0b
)、八进制(0
)、十六进制(0x
)、十进制。
进制间的相互转化:以十进制为桥梁。
0~9,逢十进一
X进制(表示数字的方法,记数符号):0~x-1,逢x进一
二进制:10,111,101,1111,11010101
八进制:6743547
十六进制:A(10)B(11)C(12)D(13)E(14)F(15),63AF
二进制(X进制)转十进制,按位权法
(111010)_ 2=(58)_ {10}
(136)_ 8=(94)_ {10}
(A6F)_ {16}=(2671)_ {10}
十进制转二进制(X进制),除法取余法
(58)_{10}=(111010)_2
二进制转十六进制
01 1101 0010 1010
1 D 2 A
1.4.2.2 位运算(二进制上每一位的运算)
运算 | 运算符 | 解释 |
---|---|---|
与 | & |
只有两个对应位都为 1 时才为 1 |
或 | | |
只要两个对应位中有一个 1 时就为 1 |
异或 | ^ |
只有两个对应位不同时才为 1 |
取反 | ~ |
1 变 0,0 变 1 |
左移 | << |
将 num 的二进制表示向左移动 i 位所得的值 |
右移 | >> |
将 num 的二进制表示向右移动 i 位所得的值 |
与运算(交集):101&110=100,有0即0,没0即1。
或运算(并集):101|100=101,有1即1,没1即0。
异或运算:相同为0,不同为1。1010^1101=0111。
取反:按位取反。~1010=0101。
左移:(舍位)1011001<<3=1011。
右移:(补零)1011001>>3=1011001000。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> using namespace std; int main() { // 1.位运算通常会快于乘除法运算 int x = 100; x>>=1;// x = x >> 1; 右移一位,等价于除2下取整 int y = 100; y<<=1;// y = y << 1; 左移一位,等价于乘2 cout<<x<<" "<<y<<endl; // 2.异或(相同为0,不同为1),无先后顺序的. // 一个数异或自己为0,一个数异或0为本身 // 一个数异或同一个数两次,还是本身 int a = 10, b = 20; cout<<(a^(b^b))<<endl; // 给定一个数组,有奇数个数,除一个数外,其它数两两相同,问如何找出这个数。 // 循环第一层,遍历所有数 // 循环第二层,找跟第一层遍历的数相同的数,让二者配对 return 0; } |
1.4.3 自增/自减运算符
有时我们需要让变量进行增加 1(自增)或者减少 1(自减),这时自增运算符 ++
和自减运算符 --
就派上用场了。
自增/自减运算符可放在变量前或变量后面,在变量前称为前缀,在变量后称为后缀,单独使用时前缀后缀无需特别区别,如果需要用到表达式的值则需注意,具体可看下面的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
i = 100; op1 = i++; // op1 = 100,先 op1 = i,然后 i = i + 1 i = 100; op2 = ++i; // op2 = 101,先 i = i + 1,然后赋值 op2 i = 100; op3 = i--; // op3 = 100,先赋值 op3,然后 i = i - 1 i = 100; op4 = --i; // op4 = 99,先 i = i - 1,然后赋值 op4 |
1.4.4 复合赋值运算符
复合赋值运算符实际上是表达式的缩写形式。
可分为复合算术运算符 +=
、-=
、*=
、/=
、%=
和复合位运算符 &=
、|=
、^=
、<<=
、>>=
。
例如,op = op + 2
可写为 op += 2
,op = op - 2
可写为 op -= 2
,op= op * 2
可写为 op *= 2
。
1.4.5 比较运算符(得到一个bool值)
运算符 | 功能 |
---|---|
> |
大于 |
>= |
大于等于 |
< |
小于 |
<= |
小于等于 |
== |
等于 |
!= |
不等于 |
1.4.6 条件运算符(三目运算符)
a ? b : c
中如果表达式 a
成立,那么这个条件表达式的结果是 b
,否则条件表达式的结果是 c
。
1.4.7 逻辑运算符
运算符 | 功能 |
---|---|
&& |
逻辑与 |
|| |
逻辑或 |
! |
逻辑非 |
1 2 3 4 5 6 |
Result = op1 && op2; // 当 op1 与 op2 都为真时则 Result 为真 Result = op1 || op2; // 当 op1 或 op2 其中一个为真时则 Result 为真 Result = !op1; // 当 op1 为假时则 Result 为真 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> // 引用头文件 using namespace std; // 引入命名空间 int main() // 定义 main 函数 { int a, b, c; cin>>a>>b>>c; // 找出 a,b,c中较大的值 // 1. a是最大的,a>b && a>c // 2. b是最大的,b>a && b>c // 3. c是最大的,c>a && c>b int maxx = (a>b&&a>c) ? a : (b>c?b:c); //int maxx = a>b?(a>c?a:c):(b>c?b:c); cout<<maxx<<endl; return 0; // 返回 0,结束 main 函数 } |
1.4.8 运算符优先级
原则:不清楚优先级就套括号。
1.5 流程控制语句
1.5.1 分支
1.5.1.1 if 语句
基本 if 语句
1 2 3 4 |
if (条件) { 主体; } |
if…else 语句
1 2 3 4 5 6 |
if (条件) { 主体1; } else { 主体2; } |
if…else if…else 语句
1 2 3 4 5 6 7 8 9 10 |
if (条件1) { 主体1; } else if (条件2) { 主体2; } else if (条件3) { 主体3; } else { 主体4; } |
1.5.1.2 switch语句
1 2 3 4 5 6 7 8 9 |
switch (选择句) { case 标签1: 主体1; case 标签2: 主体2; default: 主体3; } |
选择句可以是算术值,可以是数值(变量),可以是字符。
switch 语句执行时,先求出选择句的值,然后根据选择句的值选择相应的标签,从标签处开始执行。
其中,选择句必须是一个整数类型表达式,而标签都必须是整数类型的常量。例如:
1 2 3 4 5 6 7 |
int i = 1; // 这里的 i 的数据类型是整型 ,满足整数类型的表达式的要求 switch (i) { case 1: cout << "OI WIKI" << endl; } |
1 2 3 4 5 6 7 8 9 |
char i = 'A'; // 这里的 i 的数据类型是字符型 ,但 char // 也是属于整数的类型,满足整数类型的表达式的要求 switch (i) { case 'A': cout << "OI WIKI" << endl; } |
switch 语句中还要根据需求加入 break 语句进行中断,否则在对应的 case 被选择之后接下来的所有 case 里的语句和 default 里的语句都会被运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
char i = 'B'; switch (i) { case 'A': cout << "OI" << endl; break; case 'B': cout << "WIKI" << endl; default: cout << "Hello World" << endl; } |
以上代码运行后输出的结果为 WIKI
和 Hello World
,如果不想让下面分支的语句被运行就需要 break 了,具体例子可看下面的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
char i = 'B'; switch (i) { case 'A': cout << "OI" << endl; break; case 'B': cout << "WIKI" << endl; break; default: cout << "Hello World" << endl; } |
以上代码运行后输出的结果为 WIKI,因为 break 的存在,接下来的语句就不会继续被执行了。最后一个语句不需要 break,因为下面没有语句了。
处理入口编号不能重复,但可以颠倒。也就是说,入口编号的顺序不重要。各个 case(包括 default)的出现次序可任意。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
char i = 'B'; switch (i) { case 'B': cout << "WIKI" << endl; break; default: cout << "Hello World" << endl; break; case 'A': cout << "OI" << endl; } |
1.5.2 循环
1.5.2.1 for 语句
1 2 3 4 |
for (初始化; 判断条件; 更新) { 循环体; } |
for 语句的三个部分中,任何一个部分都可以省略。其中,若省略了判断条件,相当于判断条件永远为真。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
for(A;B;C) { D; E; F; .... } A; for(;;) { D; E; F; .... C; if(!B)break; } |
1.5.2.2 while 语句
1 2 3 4 |
while (判断条件) { 循环体; } |
for和while用法的区别
for适用于已知循环次数的情况
while相反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> using namespace std; int main() { // 从1加到n的结果 int ans = 0; int n; cin>>n; for(int i=1;i<=n;i++) { ans += i; } cout<<ans<<endl; return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> using namespace std; int main() { //从1开始加,和大于等于4000,问加到多少 int i = 1; int ans = 0; while(1){ ans += i; if(ans>=10)break; i++; } cout<<i<<endl; return 0; } |
1.5.2.3 do…while 语句
1 2 3 4 |
do { 循环体; } while (判断条件); |
与 while 语句的区别在于,do…while 语句是先执行循环体再进行判断的。
三种语句可以彼此代替,但一般来说,语句的选用遵守以下原则:
- 循环过程中有个固定的增加步骤(最常见的是枚举/遍历)时,使用 for 语句;
- 只确定循环的终止条件时,使用 while 语句;
- 使用 while 语句时,若要先执行循环体再进行判断,使用 do…while 语句。一般很少用到,常用场景是用户输入。
例题P5718
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> using namespace std; /* 8 1 9 2 6 0 8 1 7 */ int main() { int n; cin>>n; // 读玉米地的长度 int minn = 1001; // 最开始手里的玉米(玉米一定是最大的玉米) for(int i=1;i<=n;i++) // 走到玉米地里去,i当前处于第几块地 { int x; cin>>x; // 玉米的大小读进来 if(x<minn){ // 判断:如果读进来的的玉米比手里的玉米小 minn=x; // 把手里的玉米换成读进来的玉米 } } cout<<minn<<endl; // 得到的是田地里最xiao的玉米 return 0; } |
例题T357607
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> using namespace std; int main() { int n; cin>>n; long long sum=0; for(int i=1;i<=n;i++){ // 第一重循环:枚举1到n,用于求总和 long long x=1;// 乘法和加法的性质。乘法:1基元的,乘1式子不变;加法:0基元的,加0式子不变。 for(int j=1;j<=i;j++){ // 第二重循环:枚举1到i,用于求i的阶乘 x=x*j; } sum=sum+x; } cout<<sum<<endl; return 0; } |
1.5.2.4 break 和 continue 语句
break 语句的作用是退出循环。
continue 语句的作用是跳过循环体的余下部分。
1.5.2.5 循环嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> using namespace std; int main() { int n; cin>>n; for(int i=1;i<=n;i++) //行 { for(int j=1;j<=n;j++) //列 { cout<<"("<<i<<", "<<j<<")"<<" "; } cout<<endl; } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> using namespace std; int main() { // 找出2~n中间的所有质数 // 2,3,5,7,11,13,17,19,23,29.... // 第一层循环:i从2遍历到n(判断每个数是否是质数/素数) // 第二层循环:j从2枚举到i-1,判断j是否能整除i。如果j可以整除i,说明i是合数; // 如果循环结束,所有j都不能整除i,说明i是质数 int n; cin>>n; for(int i=2;i<=n;i++) { // 判断i是否是质数 bool flag=true;//表示i是质数 for(int j=2;j<=i-1;j++){ if(i%j==0){ flag=false;//表示i是合数 break;// 只能跳出一层循环 } } if(flag){ cout<<i<<" "; } } return 0; } |
1.6 高级数据类型
1.6.1 数组
数组是存放相同类型对象的容器,数组中存放的对象没有名字,而是要通过其所在的位置访问。
数组的大小是固定的,不能随意改变数组的长度。
1.6.1.1 定义数组
数组的声明形如 a[d]
,其中,a
是数组的名字,d
是数组中元素的个数。
在编译时,d
应该是已知的,也就是说,d
应该是一个整型的常量表达式。
1 2 3 4 5 |
unsigned int d1 = 42; const int d2 = 42; int arr1[d1]; // 错误:d1 不是常量表达式 int arr2[d2]; // 正确:arr2 是一个长度为 42 的数组 |
不能将一个数组直接赋值给另一个数组:
1 2 3 4 |
int arr1[3]; int arr2 = arr1; // 错误 arr2 = arr1; // 错误 |
1.6.1.2 访问数组元素
可以通过下标运算符 []
来访问数组内元素,数组的索引(即方括号中的值)从 0 开始。
以一个包含 10 个元素的数组为例,它的索引为 0 到 9,而非 1 到 10。
但在 OI 中,为了使用方便,我们通常会将数组开大一点,不使用数组的第一个元素,从下标 1 开始访问数组元素。
例 1:从标准输入中读取一个整数n
,再读取n
个数,存入数组中。其中,n\leq1000。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> using namespace std; int arr[1001]; // 数组 arr 的下标范围是 [0, 1001) int main() { int n; cin >> n; for (int i = 1; i <= n; ++i) { cin >> arr[i]; } } |
例 2:(接例 1)求和数组 arr
中的元素,并输出和。满足数组中所有元素的和小于等于 2^{31}-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> using namespace std; int arr[1001]; int main() { int n; cin >> n; for (int i = 1; i <= n; ++i) { cin >> arr[i]; } int sum = 0; for (int i = 1; i <= n; ++i) { sum += arr[i]; } printf("%d\n", sum); return 0; } |
数组的下标idx 应当满足 0\leq{idx}<size ,如果下标越界,则会产生不可预料的后果,如段错误(Segmentation Fault),或者修改预期以外的变量。
例题P1427 小鱼的数字游戏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <iostream> using namespace std; int a[110]; int main() { int n=0; // a 0 1 2 3 4 5 6 7 // 3 65 23 5 34 1 30 0 while(1) { cin>>a[n]; if(a[n]==0) { break; } n++; } // a[n]=0 , n=7 //a[n-1]是读入的最后一个非0数,也是第一个输出的数 for(int i=n-1;i>=0;i--) { cout<<a[i]<<" "; } /* do{ n++; cin>>a[n]; }while(a[n]!=0); for(int i=n-1;i>=1;i--) { cout<<a[i]<<" "; } */ return 0; } |
1.6.1.3 多维数组
多维数组的实质是「数组的数组」,即外层数组的元素是数组。
一个二维数组需要两个维度来定义:数组的长度和数组内元素的长度。访问二维数组时需要写出两个索引:
1 2 3 |
int arr[3][4]; // 一个长度为 3 的数组,它的元素是「元素为 int 的长度为的 4 的数组」 arr[2][1] = 1; // 访问二维数组 |
我们经常使用嵌套的 for 循环来处理二维数组。
例:从标准输入中读取两个数 n 和 m,分别表示黑白图片的高与宽,满足 n,m\leq1000 。
对于接下来的 n 行数据,每行有用空格分隔开的 m 个数,代表这一位置的亮度值。现在我们读取这张图片,并将其存入二维数组中。
1 2 3 4 5 6 7 8 9 |
const int maxn = 1001; int pic[maxn][maxn]; int n, m; cin >> n >> m; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) cin >> pic[i][j]; |
同样地,你可以定义三维、四维,以及更高维的数组。
1.6.2 结构体
结构体(struct),可以看做是一系列称为成员元素的组合体。
可以看做是自定义的数据类型。
1.6.2.1 定义结构体
1 2 3 4 5 6 7 8 |
struct Object { int weight; int value; } e[array_length]; const Object a; Object b, B[array_length], tmp; |
上例中定义了一个名为 Object
的结构体,两个成员元素 value, weight
,类型都为 int
。
在 }
后,定义了数据类型为 Object
的常量 a
,变量 b
,变量 tmp
,数组 B
。
对于某种已经存在的类型,都可以使用这里的方法进行定义常量、变量、数组等。
1.6.2.2 访问/修改成员元素
可以使用 变量名.成员元素名
进行 访问。
如 : 输出 var
的 v
成员:cout << var.v
。
如 : 将结构体指针 ptr
指向的结构体的成员元素 v
赋值为 tmp
:(*ptr).v = tmp
或者 ptr->v = tmp
。
1.6.2.3 为什么需要结构体
首先,条条大路通罗马,可以不使用结构体达到相同的效果。但是结构体能够显式地将成员元素(在算法竞赛中通常是变量)捆绑在一起,如本例中的 Object
结构体,便将 value,weight
放在了一起(定义这个结构体的实际意义是表示一件物品的重量与价值)。这样的好处边是限制了成员元素的使用。
想象一下,如果不使用结构体而且有两个数组 value[], weight[]
,很容易写混淆。但如果使用结构体,能够减轻出现使用变量错误的几率。
并且不同的结构体(结构体类型,如 Object
这个结构体)或者不同的结构体变量(结构体的实例,如上方的 e
数组)可以拥有相同名字的成员元素(如 tmp.value,b.value
),同名的成员元素相互独立(拥有独自的内存,比如说修改 tmp.value
不会影响 b.value
的值)。
这样的好处是可以使用尽可能相同或者相近的变量去描述一个物品。比如说 Object
里有 value
这个成员变量;我们还可以定义一个 Car
结构体,同时也拥有 value
这个成员;如果不使用结构体,或许我们就需要定义 valueOfObject[],valueOfCar[]
等不同名称的数组来区分。
例题P5744【深基7.习9】培训
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <iostream> using namespace std; struct Stu{ char name[1000]; int age; int score; }; Stu stu[10]; int main() { int n; cin>>n; for(int i=1;i<=n;i++) { cin>>stu[i].name>>stu[i].age>>stu[i].score; } for(int i=1;i<=n;i++) { if(stu[i].score*1.2>=600) { stu[i].score=600; } else { stu[i].score*=1.2; } stu[i].age++; cout<<stu[i].name<<" "<<stu[i].age<<" "<<stu[i].score<<endl; } return 0; } |
1.6.3 字符串
字符串是一种特殊的序列,它是由字符构成的一串序列。
1.6.3.1 概念
设有字符串 S="abcdefg"
。
子串:表示从母串(源串)中截取的一段连续的字符串,如S1="cde"
。
子序列:表示从母串(源串)中截取的一段不一定连续的、但相对顺序不变的字符串,如S2="aceg"
。
后缀:表示母串(源串)的末尾若干个字符,如S3="efg"
。特别的,除了母串本身以外的其他后缀叫真后缀。
前缀:表示母串(源串)的前面若干个字符,如S4="abc"
。特别的,除了母串本身以外的其他前缀叫真前缀。
字典序:以第 i 个字符作为第 i 关键字进行大小比较。如"abc">"bb"
,"efg">"egfd"
等等。
回文串:正着写和倒着写相同的字符串,如S5="abcba"
。
1.6.3.2 字符串的存储
- 我们学过数组,就可以用数组的方式来存储字符串,即
char s[200]="abcdef";
表示声明一个长度空间为200的字符串。 - C++中可以用string关键字声明字符串,如
string s="abcdef";
。
1.6.3.2 C++标准库
C++、Java、Python等语言火爆的原因除了性能好,更突出的一点是它们有良好的社区,这导致我们可以使用前人写好的代码。
当我们使用string s="abcdef";
声明字符串时,有如下函数可以使用:
s.size()
或s.length()
返回字符串中'\0'
之前的字符个数,如果字符串为"abc\0abc"
,则返回3。s.find(ch, start = 0)
查找并返回从start
开始的字符ch
的位置;s.rfind(ch)
从末尾开始,查找并返回第一个找到的字符ch
的位置(皆从0
开始)(如果查找不到,返回-1
)。s.substr(start, len)
可以从字符串的start
(从0
开始)截取一个长度为len
的字符串(缺省len
时代码截取到字符串末尾)。s.append(s)
将s
添加到字符串末尾。s.append(s, pos, n)
将字符串s
中,从pos
开始的n
个字符连接到当前字符串结尾。s.replace(pos, n, s)
删除从pos
开始的n
个字符,然后在pos
处插入串s
。s.erase(pos, n)
删除从pos
开始的n
个字符。s.insert(pos, s)
在pos
位置插入字符串s
。
1.7 函数
1.7.1 函数的声明
编程中的函数(function)一般是若干语句的集合。我们也可以将其称作「子过程(subroutine)」。在编程中,如果有一些重复的过程,我们可以将其提取出来,形成一个函数。函数可以接收若干值,这叫做函数的参数。函数也可以返回某个值,这叫做函数的返回值。
声明一个函数,我们需要返回值类型、函数的名称,以及参数列表。
1 2 3 4 5 |
// 返回值类型 int // 函数的名称 some_function // 参数列表 int, int int some_function(int, int); |
如上,我们声明了一个名为 some_function
的函数,它需要接收两个 int
类型的参数,返回值类型也为 int
。可以认为,这个函数将会对传入的两个整数进行一些操作,并且返回一个同样类型的结果。
1.7.2 实现函数:编写函数的定义
只有函数的声明(declaration)还不够,他只能让我们在调用时能够得知函数的 接口 类型(即接收什么数据、返回什么数据),但其缺乏具体的内部实现,也就是函数的 定义(definition)。我们可以在 声明之后的其他地方 编写代码 实现(implement)这个函数(也可以在另外的文件中实现,但是需要将分别编译后的文件在链接时一并给出)。
1 2 3 4 |
返回类型 函数名(形参列表){ } |
如果函数有返回值,则需要通过 return
语句,将值返回给调用方。函数一旦执行到 return
语句,则直接结束当前函数,不再执行后续的语句。
1 2 3 4 5 6 7 8 9 10 11 |
int some_function(int, int); // 声明 /* some other code here... */ int some_function(int x, int y) { // 定义 int result = 2 * x + y; return result; result = 3; // 这条语句不会被执行 } |
在定义时,我们给函数的参数列表的变量起了名字。这样,我们便可以在函数定义中使用这些变量了。
如果是同一个文件中,我们也可以直接将 声明和定义合并在一起,换句话说,也就是在声明时就完成定义。
1 2 |
int some_function(int x, int y) { return 2 * x + y; } |
如果函数不需要有返回值,则将函数的返回值类型标为 void
;如果函数不需要参数,则可以将参数列表置空。同样,无返回值的函数执行到 return;
语句也会结束执行。
1 2 3 4 5 6 7 8 9 |
void say_hello() { cout << "hello!\n"; cout << "hello!\n"; cout << "hello!\n"; return; cout << "hello!\n"; // 这条语句不会被执行 } |
1.7.3 函数的调用
和变量一样,函数需要先被声明,才能使用。使用函数的行为,叫做「调用(call)」。我们可以在任何函数内部调用其他函数,包括这个函数自身。函数调用自身的行为,称为 递归(recursion)。
在大多数语言中,调用函数的写法,是 函数名称加上一对括号 ()
,如 foo()
。如果函数需要参数,则我们将其需要的参数按顺序填写在括号中,以逗号间隔,如 foo(1, 2)
。函数的调用也是一个表达式,函数的返回值 就是 表达式的值。
函数声明时候写出的参数,可以理解为在函数 当前次调用的内部 可以使用的变量,这些变量的值由调用处传入的值初始化。看下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void foo(int x, int y) { x = x * 2; y = y + 3; } /* ... */ a = 1; b = 1; // 调用前:a = 1, b = 1 foo(a, b); // 调用 foo // 调用后:a = 1, b = 1 |
在上面的例子中,foo(a, b)
是一次对 foo
的调用。调用时,foo
中的 x
和 y
变量,分别由调用处 a
和 b
的值初始化。因此,在 foo
中对变量 x
和 y
的修改,并不会影响到调用处的变量的值。
如果我们需要在函数(子过程)中修改变量的值,则需要采用「传引用」的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void foo(int& x, int& y) { x = x * 2; y = y + 3; } /* ... */ a = 1; b = 1; // 调用前:a = 1, b = 1 foo(a, b); // 调用 foo // 调用后:a = 2, b = 4 |
上述代码中,我们看到函数参数列表中的「int
」后面添加了一个「&
(and 符号)」,这表示对于 int
类型的 引用(reference)。在调用 foo
时,调用处 a
和 b
变量分别初始化了 foo
中两个对 int
类型的引用 x
和 y
。在 foo
中的 x
和 y
,可以理解为调用处 a
和 b
变量的「别名」,即 foo
中对 x
和 y
的操作,就是对调用处 a
和 b
的操作。
例题B2128
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> using namespace std; /* 功能:判断一个数是否是素数/质数 入参:int n,表示判断n是否是素数 出参:bool */ bool isprime(int n){ for(int i=2;i<=n-1;i++){ if(n%i==0)return false; } return true; } int main() { int n; cin>>n; int cnt=0; for(int i=2;i<=n;i++){ if(isprime(i))cnt++; } cout<<cnt<<endl; return 0; } |