《算法笔记》学习记录-第二章 C/C++快速入门
- [2.0 C语言小程序](#2.0 C语言小程序)
- [2.1 基本数据结构](#2.1 基本数据结构)
-
- [2.1.1 变量定义](#2.1.1 变量定义)
- [2.1.2 变量类型](#2.1.2 变量类型)
- [2.1.3 强制类型转换](#2.1.3 强制类型转换)
- [2.1.4 符号常量和const常量](#2.1.4 符号常量和const常量)
- [2.1.5 运算符](#2.1.5 运算符)
-
- [1. 算数运算符](#1. 算数运算符)
- [2. 关系运算符](#2. 关系运算符)
- [3. 逻辑运算符](#3. 逻辑运算符)
- [4. 条件运算符](#4. 条件运算符)
- [5. 位运算符](#5. 位运算符)
- [2.2 顺序结构](#2.2 顺序结构)
-
- [2.2.1 赋值表达式](#2.2.1 赋值表达式)
- [2.2.2 使用 scanf 和 printf 输入/输出](#2.2.2 使用 scanf 和 printf 输入/输出)
-
- [1. scanf 函数的使用](#1. scanf 函数的使用)
- [2. printf 函数的使用](#2. printf 函数的使用)
- [2.2.3 使用 getchar 和 putchar 输入/输出字符](#2.2.3 使用 getchar 和 putchar 输入/输出字符)
- [2.2.4 注释](#2.2.4 注释)
- [2.2.5 typedef](#2.2.5 typedef)
- [2.2.6 常用 math 函数](#2.2.6 常用 math 函数)
-
- [1. fabs(double x)](#1. fabs(double x))
- [2. floor(double x) 和 ceil(double x)](#2. floor(double x) 和 ceil(double x))
- [3. pow(double r, double p)](#3. pow(double r, double p))
- [4. sqrt(double x)](#4. sqrt(double x))
- [5. log(double x)](#5. log(double x))
- [6. sin(double x)、cos(double x) 和 tan(double x)](#6. sin(double x)、cos(double x) 和 tan(double x))
- [7. asin(double x)、acos(double x) 和 atan(double x)](#7. asin(double x)、acos(double x) 和 atan(double x))
- [8. round(double x)](#8. round(double x))
- [2.3 选择结构](#2.3 选择结构)
-
- [2.3.1 if 语句](#2.3.1 if 语句)
- [2.3.2 if 语句的嵌套](#2.3.2 if 语句的嵌套)
- [2.3.3 switch 语句](#2.3.3 switch 语句)
- [2.4 循环结构](#2.4 循环结构)
-
- [2.4.1 while 语句](#2.4.1 while 语句)
- [2.4.2 do...while 语句](#2.4.2 do...while 语句)
- [2.4.3 for 语句](#2.4.3 for 语句)
- [2.4.4 break 和 continue 语句](#2.4.4 break 和 continue 语句)
- [2.5 数组](#2.5 数组)
-
- [2.5.1 一维数组](#2.5.1 一维数组)
- [2.5.2 冒泡排序](#2.5.2 冒泡排序)
- [2.5.3 二维数组](#2.5.3 二维数组)
- [2.5.4 memset--对数组中每一个元素赋相同的值](#2.5.4 memset--对数组中每一个元素赋相同的值)
- [2.5.5 字符数组](#2.5.5 字符数组)
-
- [1. 字符数组的初始化](#1. 字符数组的初始化)
- [2. 字符数组的输入输出](#2. 字符数组的输入输出)
-
- [(1)scanf 输入,printf 输出](#(1)scanf 输入,printf 输出)
- [(2)getchar 输入,putchar 输出](#(2)getchar 输入,putchar 输出)
- [(3)gets 输入,puts 输出](#(3)gets 输入,puts 输出)
- [3. 字符数组的存放方式](#3. 字符数组的存放方式)
- [2.5.6 string.h 头文件](#2.5.6 string.h 头文件)
-
- [1. strlen()](#1. strlen())
- [2. strcmp()](#2. strcmp())
- [3. strcpy()](#3. strcpy())
- [4. strcat()](#4. strcat())
- [2.5.7 sscanf 与 sprintf](#2.5.7 sscanf 与 sprintf)
- [2.6 函数](#2.6 函数)
-
- [2.6.1 函数的定义](#2.6.1 函数的定义)
- [2.6.2 再谈 main 函数](#2.6.2 再谈 main 函数)
- [2.6.3 以数组作为函数参数](#2.6.3 以数组作为函数参数)
- [2.6.4 函数的嵌套调用](#2.6.4 函数的嵌套调用)
- [2.6.5 函数的递归调用](#2.6.5 函数的递归调用)
- [2.7 指针](#2.7 指针)
-
- [2.7.1 什么是指针](#2.7.1 什么是指针)
- [2.7.2 指针变量](#2.7.2 指针变量)
- [2.7.3 指针与数组](#2.7.3 指针与数组)
- [2.7.4 使用指针变量作为函数参数](#2.7.4 使用指针变量作为函数参数)
- [2.7.5 引用](#2.7.5 引用)
-
- [1. 引用的含义](#1. 引用的含义)
- [2. 指针的引用](#2. 指针的引用)
- [2.8 结构体(struct)的使用](#2.8 结构体(struct)的使用)
-
- [2.8.1 结构体的定义](#2.8.1 结构体的定义)
- [2.8.2 访问结构体内的元素](#2.8.2 访问结构体内的元素)
- [2.8.3 结构体的初始化](#2.8.3 结构体的初始化)
- [2.9 补充](#2.9 补充)
-
- [2.9.1 cin 与 cout](#2.9.1 cin 与 cout)
-
- [1. cin](#1. cin)
- [2. cout](#2. cout)
- [2.9.2 浮点数的比较](#2.9.2 浮点数的比较)
-
- [1. 等于运算符(==)](#1. 等于运算符(==))
- [2. 大于运算符(>)](#2. 大于运算符(>))
- [3. 小于运算符(<)](#3. 小于运算符(<))
- [4, 大于等于运算符(>=)](#4, 大于等于运算符(>=))
- [5. 小于等于运算符(<=)](#5. 小于等于运算符(<=))
- [6. 圆周率π](#6. 圆周率π)
- [2.9.3 复杂度](#2.9.3 复杂度)
-
- [1. 时间复杂度](#1. 时间复杂度)
- [2. 空间复杂度](#2. 空间复杂度)
- [3. 编码复杂度](#3. 编码复杂度)
- [2.10 黑盒测试](#2.10 黑盒测试)
-
- [2.10.1 单点测试](#2.10.1 单点测试)
- [2.10.2 多点测试](#2.10.2 多点测试)
2.0 C语言小程序
cpp
#include <stdio.h> // 头文件
// 等价写法:#include <cstdio>
// #include <math.h> 等价 #include <cmath>
// #include <string.h> 等价 #include <cstring>
int main(){ // 主函数,一个程序最多只能有一个主函数
// 定义两个整数型变量a和b
int a, b;
// 读入两个整数
scanf("%d%d", &a, &b);
// 输出两个整数的和
printf("%d", a+b);
return 0;
}
2.1 基本数据结构
2.1.1 变量定义
格式:
变量类型 变量名;
或者
变量类型 变量名 = 初值; // 在定义的时候就赋初值
2.1.2 变量类型
-
整型
int: 绝对值在 10 9 10^9 109范围以内的整数或者32位整数, 输出格式是%dlong long
-
浮点型 : 输出格式是
%f; 不要使用float,碰到浮点型的数据都用double- 单精度
float: 有效精度6~7位 - 双精度
double: 有效精度15~16位
- 单精度
-
字符型
- 字符变量和字符常量(字符本身,例如: 'e'):
char;小写字母比大写字母的ASCII码值大32;字符常量(必须是单个字符)必须用单引号标注 - 转义字符:
\n换行;\tTab键;\0空字符NULL,其ASCII码为0,不是空格 - 字符串常量:双引号标记的字符集,可以作为初值赋给字符数组,并用
%s格式输出;由多个char字符组合而成的字符集合称作字符数组;不能把字符串常量赋值给字符变量
- 字符变量和字符常量(字符本身,例如: 'e'):
-
布尔型
bool:C语言中必须添加stdbool.h头文字才可以使用,C++中可以直接使用;取值只能为true(真)或者false(假);整数常量在赋值给布尔型变量时会自动转换为true(非零,包括正整数和负整数)或者false(零);对于计算机来说,true和false在存储时分别为1和0;用%d格式输出时,true和false会输出1和0
2.1.3 强制类型转换
格式:
(新类型名) 变量名
2.1.4 符号常量和const常量
格式:
#define 标识符 常量 // 注意末尾不加分号
或者
const 数据类型 变量名 = 常量; // 更推荐
此外,define除了可以定义常量外,其实可以定义任何语句或片段,格式:
#define 标识符 任何语句或片段
注意:宏定义是直接将对应的部分替换,然后才进行编译和运行;它把替换的部分直接原封不动替换进去;尽量不要使用宏定义来做除了定义常量以外的事情,除非给能加的地方都加上括号
例如:
cpp
#include <stdio.h>
#define ADD(a, b) ((a)+(b)) // 正确的
// #define ADD(a, b) (a+b) // 错误
// #define ADD(a,b) a+b // 错误
int main(){
int num1 = 3, num2 = 5;
printf("%d", ADD(num1, num2));
return 0;
}
例如:
cpp
#include <stdio.h>
#define CAL(x) (x * 2 + 1)
int main(){
int a = 1;
printf("%d\n", CAL(a + 1)); // 错误,宏定义替换为CAL(a + 1 * 2 + 1)
return 0;
}
2.1.5 运算符
1. 算数运算符
+加法运算符:两数相加-减法运算符:两数相减*乘法运算符:两数相乘/除法运算符:取前面的数除以后面的数得到的商 (注意:当被除数跟除数都是整型时,并不会得到一个double浮点型的数,而是直接舍去小数部分;除数如果为0,会导致程序异常退出或是得到错误输出"1.#INF00")%取模运算符:取前面的数除以后面的数得到的余数++自增运算符:令一个整型变量增加1i++:先使用i再将i加1++i:先将i加1再使用i
--自减运算符:令一个整型变量减少1;i--和--i
加减乘除四种运算的优先级顺序和四则运算的优先级相同;
取模运算符的优先级和除法运算符相同;
2. 关系运算符
六种:<小于、>大于、<=小于等于、>=大于等于、==等于、!=不等于
3. 逻辑运算符
三种:&&与、||或、!非
4. 条件运算符
格式:
A ? B : C; // C语言中唯一的三目运算符,需要三个参数的运算符
含义:如果A为真,那么执行并返回B的结果;如果A为假,那么执行并返回C的结果。
5. 位运算符
六种:<<左移、>>右移、&位与、|位或、^位异或、~位取反
优先级:位运算符<算术运算符
无穷大的数INF(两种形式等价):
const int INF = (1 << 30) - 1;
const int INF = 0x3fffffff;
2.2 顺序结构
2.2.1 赋值表达式
用"="实现赋值
赋值运算符可以通过将其他运算符放在前面来实现赋值操作的简化。
例如:
cpp
n = n + 2 等价于 n += 2
n = n * 3 等价于 n *= 3
n = n / m 等价于 n /= m
2.2.2 使用 scanf 和 printf 输入/输出
1. scanf 函数的使用
格式:
scanf("格式控制", 变量地址);
例如:scanf("%d", &n);
其中,变量地址的写法是"&变量名",&称为取地址运算符
常见数据类型变量的 scanf 格式符:
int:%dlong long:%lldfloat:%fdouble:%lfchar:%c字符串(char数组):%s注意:数组名称本身就代表了这个数组第一个元素的地址,所以不需要再加取地址运算符
注意:
- 在 scanf 中,除了 char 数组整个输入的情况不加&之外,其他变量类型都需要加&;
- scanf 的双引号内的内容其实就是整个输入,只不过把数据换成它们对应的格式符并把变量的地址按次序写在后面而已;
- 除了%c以外,scanf 对其他格式符的输入是以空白符(即空格、换行等)为结束判断标志的;
- 字符数组使用%s读入的时候以空格跟换行为读入结束的标志;
- scanf 的%c格式是可以读入空格跟换行的;
2. printf 函数的使用
格式:
printf("格式控制", 变量名称);
常见数据类型变量的 printf 格式符:
int:%dlong long:%lldfloat:%fdouble:%f// 只有这个和 scanf 不一样char:%c字符串(char数组):%s
三种实用的输出格式:
%md: 可以使不足m位的int型变量以m位进行右对齐输出,其中高位用空格补齐;如果变量本身超过m位,则保持原样;%0md: 可以使不足m位的int型变量以m位进行右对齐输出,其中高位用0补齐;如果变量本身超过m位,则保持原样;%.mf: 可以让浮点数保留m位小数输出,精度的规则是"四舍六入五成双";题目要求"浮点数的输出保留XX位小数(或是精确到小数点后XX位)"时使用;如果是四舍五入要用到 round 函数
2.2.3 使用 getchar 和 putchar 输入/输出字符
getchar 用来输入单个字符,putchar 用来输出单个字符;
getchar 可以识别换行符;
2.2.4 注释
/**/: 注释若干连续行的内容//: 注释单行
2.2.5 typedef
给复杂的数据类型起一个别名,提高编码效率
例如:
cpp
#include <cstdio>
typedef long long LL; // 给 long long 起个别名LL
int main(){
LL a = 123456789012345LL, b = 234567890123456LL; // 直接使用LL
printf("%lld\n", a+b);
return 0;
}
输出结果是:
cpp
358024679135801
2.2.6 常用 math 函数
1. fabs(double x)
用于对 double 型变量取绝对值
例如:
cpp
#include <stdio.h>
#include <math.h>
int main(){
double db = -12.56;
printf("%.2f\n", fabs(db));
return 0;
}
输出结果:
cpp
12.56
2. floor(double x) 和 ceil(double x)
分别用于 double 型变量的向下取整和向上取整,返回类型为 double 型
例如:
cpp
#include <stdio.h>
#include <math.h>
int main(){
double db1 = -5.2, db = 5.2;
printf("%.0f %.0f\n", floor(db1), ceil(db1));
printf("%.0f %.0f\n", floor(db2), ceil(db2));
return 0;
}
输出结果:
cpp
-6 -5
5 6
3. pow(double r, double p)
该函数用于返回 r p r^p rp,要求r和p都是 double 型
例如:
cpp
#include <stdio.h>
#include <math.h>
int main(){
double db = pow(2.0, 3.0);
printf("%f\n", db);
return 0;
}
输出结果:
cpp
8.000000
4. sqrt(double x)
用于返回 double 型变量的算术平方根
例如:
cpp
#include <stdio.h>
#include <math.h>
int main(){
double db = sqrt(2.0);
printf("%f\n", db);
return 0;
}
输出结果:
cpp
1.414214
5. log(double x)
用于返回 double 型变量的 以自然对数为底 的对数
例如:
cpp
#include <stdio.h>
#include <math.h>
int main(){
double db = log(1.0);
pringf("%f\n", db);
return 0;
}
输出结果:
cpp
0.000000
C语言中没有对任意底数求对数的函数,因此必须使用换底公式来将不是以自然对数为底的对数转换为以e为底的对数,即 l o g a b = l o g e b / l o g e a log_ab = log_eb / log_ea logab=logeb/logea
6. sin(double x)、cos(double x) 和 tan(double x)
分别返回 double 型变量的正弦值、余弦值和正切值。参数要求是弧度制
例如:
cpp
#include <stdio.h>
#include <math.h>
const double pi = acos(-1.0); // 把pi定义为精确值acos(-1.0)(因为cos(pi)=-1)
int main(){
double db1 = sin(pi * 45 / 180);
double db2 = cos(pi *45 / 180);
double db3 = tan(pi * 45 / 180);
printf("%f, %f, %f\n", db1, db2, db3);
return 0;
}
输出结果:
cpp
0.707107, 0.707107, 1.000000
7. asin(double x)、acos(double x) 和 atan(double x)
分别返回 double 型变量的反正弦值、反余弦值和反正切值
例如:
cpp
#include <stdio.h>
#include <math.h>
int main(){
double db1 = asin(1);
double db2 = acos(-1.0);
double db3 = atan(0);
printf("%f, %f, %f\n", db1, db2, db3);
return 0;
}
输出结果:
cpp
1.570796, 3.141593, 0.000000
8. round(double x)
用于将 double 型变量x四舍五入,返回类型也是 double 型,需进行取整
例如:
cpp
#include <stdio.h>
#include <math.h>
inr main(){
double db1 = round(3.40);
double db2 = round(3.45);
double db3 = round(3.50);
double db4 = round(3.55);
double db5 = round(3.60);
printf("%d, %d, %d, %d, %d\n", (int)db1, (int)db2, (int)db3, (int)db4, (int)db5);
return 0;
}
输出结果:
cpp
3, 3, 4, 4, 4
2.3 选择结构
2.3.1 if 语句
if 语句 格式:
if(条件A){
...
}
if-else 语句 格式:
if(条件A){
...
} else {
...
}
一般只有在明确不会出错的情况下才可以将大括号去掉
else if 写法格式:
if(条件A){
...
} else if(条件B){
...
} else {
...
}
技巧:如果表达式是"!=0"或"==0",可以采用比较简单的写法
-
如果表达式是"!=0",则可以省略"!=0";
if(n) 等价于 if(n!=0)
-
如果表达式是"==0",则可以省略"==0",并在表达式 前添加非运算符"!";
if(!n) 等价于 if(n==0)
2.3.2 if 语句的嵌套
格式:
if(条件A){
...
if(条件B){
...
} else {
...
}
...
} else {
...
}
2.3.3 switch 语句
分支条件较多时会显得比较精练,但在分支条件较少时用得并不多
格式:
switch(表达式){
case 常量表达式1:
...
break;
case 常量表达式2:
...
break;
case 常量表达式n:
...
break;
default;
...
}
2.4 循环结构
2.4.1 while 语句
格式:
while(条件A){
...
}
技巧:
- 如果表达式是"!=0",则可以省略"!=0";
- 如果表达式是"==0",则可以省略"==0",并在表达式 前添加非运算符"!";
2.4.2 do...while 语句
格式:
do{
...
}while(条件A); // 注意有分号
先执行省略号中的内容一次,然后才判断条件A是否成立。如果条件A成立,继续反复执行省略号的内容,知道某一次条件A不再成立,则退出循环
2.4.3 for 语句
格式:
for(表达式A; 表达式B; 表达式C){
...
}
意思:
- ① 在 for 循环开始前,首先执行表达式A;
- ② 判断表达式B是否成立:若成立,执行省略号内容;否则,退出循环;
- ③ 在省略号内容执行完毕后,执行表达式C,之后回到②
常用特例:
for(循环变量赋初值; 循环条件; 循环变量改变){
...
}
2.4.4 break 和 continue 语句
break 可以强制退出 switch 语句
示例:
cpp
#include <stdio.h>
int main(){
int n, sum = 0;
for(int i = 1; i <= 100; i++){
sum = sum + i;
if(sum >= 2000) break;
}
printf("sum = %d\n", sum);
return 0;
}
continue 可以在需要的地方临时结束循环的当前轮回,然后进入下一个轮回
示例:
cpp
#include <stdio.h>
int main(){
int sum = 0;
for(int i = 1; i <= 5; i++){
if(i% 2 == 1) continue;
sum = sum + i;
}
printf("sum = %d\n", sum);
return 0;
}
2.5 数组
2.5.1 一维数组
定义格式:
数据类型 数据名[数据大小];
注意:数组大小必须是整数常量,不可以是变量
访问格式:
数组名称[下标]
在定义了长度为 size 的一维数组后,只能访问下标为 0 ~ size-1 的元素
一维数组的初始化,需要给出用逗号隔开的从第一个元素开始的若干个元素的初值,并用大括号括住。后面未被赋初值的元素将会由不同编译器内部实现的不同而被赋以不同的初值(可能是很大的随机数),而一般情况默认初值为0。
示例:
cpp
#include <stdio.h>
int main(){
int a[10] = {5, 3, 2, 6, 8, 4};
for(int i = 0; i < 10; i++){
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
输出结果:
cpp
a[0] = 5
a[1] = 3
a[2] = 2
a[3] = 6
a[4] = 8
a[5] = 4
a[6] = 0
a[7] = 0
a[8] = 0
a[9] = 0
数组中每个元素都可以被赋值、被运算,可以被当作普通变量进行相同的操作。
递推:根据一些条件,可以不断让后一位的结果由前一位或前若干位计算得来
- 顺推
- 逆推
2.5.2 冒泡排序
排序:指将一个无序序列按某个规则进行有序排列。
冒泡排序的本质:交换,即每次通过交换的方式把当前剩余元素的最大值移动到一端,而当剩余元素减少为0时,排序结束。
交换两个数:
cpp
#include <stdio.h>
int main(){
int s = 1, b = 2;
int temp = a;
a = b;
b = temp;
printf("a = %d, b = %d\n", a, b);
return 0;
}
输出结果:
cpp
a = 2, b = 1
冒泡排序举例:
现有一个数组a,其中有5个元素,分别为a[0]=3、a[1]=4、a[2]=1、a[3]=5、a[4]=2,要求把它们按照从小到大的顺序排序。
(1) 第一趟:
a[0] 和 a[1] 比较(3 和 4 比较),a[1] 大,因此不动,此时序列为 {3, 4, 1, 5, 2};
| ++3++ | ++4++ | 1 | 5 | 2 |
|---|
a[1] 和 a[2] 比较(4 和 1 比较),a[1] 大,因此把 a[1] 和 a[2] 交换,此时序列为 {3, 1, 4, 5, 2};
| 3 | ++1++ | ++4++ | 5 | 2 |
|---|
a[2] 和 a[3] 比较(4 和 5 比较),a[3] 大,因此不动,此时序列为 {3, 1, 4, 5, 2};
| 3 | 1 | ++4++ | ++5++ | 2 |
|---|
a[3] 和 a[4] 比较(5 和 2 比较),a[4] 大,因此把 a[3] 和 a[4] 交换,此时序列为 {3, 1, 4, 2, 5};
| 3 | 1 | 4 | ++2++ | ++5++ |
|---|
第一趟排序结束,共进行了 4 次排序
(2) 第二趟:
a[0] 和 a[1] 比较(3 和 1 比较),a[0] 大,因此把 a[0] 和 a[1] 交换,此时序列为 {1, 3, 4, 2, 5};
| ++1++ | ++3++ | 4 | 2 | 5 |
|---|
a[1] 和 a[2] 比较(3 和 4 比较),a[2] 大,因此不动,此时序列为 {1, 3, 4, 2, 5};
| 1 | ++3++ | ++4++ | 2 | 5 |
|---|
a[2] 和 a[3] 比较(4 和 2 比较),a[2] 大,因此把 a[2] 和 a[3] 交换,此时序列为 {1, 3, 2, 4, 5};
| 1 | 3 | ++2++ | ++4++ | 5 |
|---|
第二趟排序结束,共进行了 3 次排序
(3) 第三趟:
a[0] 和 a[1] 比较(1 和 3 比较),a[1] 大,因此不动,此时序列为 {1, 3, 2, 4, 5};
| ++1++ | ++3++ | 2 | 4 |
5 |
|---|
a[1] 和 a[2] 比较(3 和 2 比较),a[1] 大,因此把 a[1] 和 a[2] 交换,此时序列为 {1, 2, 3, 4, 5};
| 1 | ++2++ | ++3++ | 4 |
5 |
|---|
第三趟排序结束,共进行了 2 次排序
(4) 第四趟:
a[0] 和 a[1] 比较(1 和 2 比较),a[1] 大,因此不动,此时序列为 {1, 2, 3, 4, 5};
| ++1++ | ++2++ | 3 |
4 |
5 |
|---|
第四趟排序结束,共进行了 1 次排序
冒泡排序结束,对 n 个数进行排序,整个过程执行 n-1 趟;当第 i 趟时,从 a[0] 到 a[n-i-1] 都需要与下一个数比较。
代码实现:
cpp
#include <stdio.h>
int main(){
int a[10] = {3, 1, 4, 5, 2};
for(int i = 1; i <= 4; i++){ // 进行 n - 1 趟
// 第 i 趟时从 a[0] 到 a[n - i - 1]都与它们下一个数比较
for(int j = 0; j < 5 - i; j++){
if(a[j] > a[j + 1]){ // 如果左边的数更大,则交换 a[j] 和 a[j + 1]
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
for(int i = 0; i < 5; i++){
printf("%d ", a[i]);
}
return 0;
}
输出结果:
cpp
1 2 3 4 5
2.5.3 二维数组
格式:
数据类型 数组名[第一维大小][第二维大小]
访问格式:
数组名[下标1][下标2]
例如:a[3][4]
| 行标签 | 列0 | 列1 | 列2 | 列3 |
|---|---|---|---|---|
| 行0 a[0]: | a[0][0] | a[0][1] | a[0][2] | a[0][3] |
| 行1 a[1]: | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
| 行2 a[2]: | a[2][0] | a[2][1] | a[2][2] | a[2][3] |
初始化时,需要按照第一维的顺序依次用大括号给出第二维初始化情况,然后将它们用逗号分隔,并用大括号全部括住,而在这些被赋初值的元素以外的部分将被默认赋值为0。
举例:
cpp
#include <stdio.h>
int main(){
int a[5][6] = {{3, 1, 2}, {8, 4}, {}, {1, 2, 3, 4, 5}};
for(int i = 0; i < 5; i++){
for(int j = 0; j < 6; j++){
printf("%d", a[i][j]);
}
printf("\n");
}
return 0;
}
输出结果:
cpp
3 1 2 0 0 0
8 4 0 0 0 0
0 0 0 0 0 0
1 2 3 4 5 0
0 0 0 0 0 0
特别提醒:如果数组大小较大(大概 10 6 10^6 106级别),则需要将其定义在主函数之外,否则会使程序异常退出,原因是函数内部申请的局部变量来自系统栈,允许申请的空间较小;而函数外部申请的全局变量来自静态存储区,允许申请的空间较大。
例如:
cpp
#include <stdio.h>
int a[1000000];
int main(){
for(int i = 0; i < 1000000; i++){
a[i] = i;
}
return 0;
}
2.5.4 memset--对数组中每一个元素赋相同的值
格式:
memset(数组名, 值, sizeof(数组名));
需要再开头添加 string.h 头文件。
只建议使用 memset 赋 0 或 -1 。
memset 使用的是按字节赋值,0 的二进制补码为全 0 ,-1 的二进制补码为全 1。
如果要对数组赋其他数字(例如 1),那么请使用 fill 函数。
示例:
cpp
#include <stdio.h>
#include <string .h>
int main(){
int a[5] = {1, 2, 3, 4, 5};
// 赋初值 0
memset(a, 0, sizeof(a));
for(int i = 0; i < 5; i ++){
printf("%d ", a[i]);
}
printf("\n");
// 赋初值 -1
memset(a, -1, sizeof(a));
for(int i = 0; i < 5; i++){
printf("%d ", a[i]);
}
pritnf("\n");
return 0;
}
输出结果:
cpp
0 0 0 0 0
-1 -1 -1 -1 -1
2.5.5 字符数组
1. 字符数组的初始化
例如:
cpp
char str[15] = {'G', 'o', 'o', 'd', ' ', 's', 't', 'o', 'r', 'y', '!'};
字符数组也可以通过直接赋值字符串来初始化(仅限于初始化,程序其他位置不允许这样直接赋值整个字符串),例如:
cpp
char str[15] = "Good Story!";
2. 字符数组的输入输出
(1)scanf 输入,printf 输出
两种格式:
%c: 用来输入单个字符,能够识别空格跟换行并将其输入;
%s: 用来输入一个字符串并存在字符数组中,通过空格或换行来识别一个字符串的结束,scanf 在使用 %s 时,后面对应数组名前面不需要加 & 取地址运算符。
(2)getchar 输入,putchar 输出
分别用来输入和输出单个字符,例如:
cpp
#include <stdio.h>
int main(){
char str[5][5];
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
str[i][j] = getchar();
}
getchar(); // 这句是为了把输入中每行末尾的换行符吸收掉
}
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
putchar(str[i][j]);
}
putchar('\n');
}
return 0;
}
(3)gets 输入,puts 输出
gets 用来输入一行字符串(注意:gets 识别换行符 \n 作为输入结束,因此 scanf 完一个整数后,如果要使用 gets,需要先用 getchar 接收整数后的换行符),并将其存放于一维数组(或二维数组的一维)中;puts 用来输出一行字符串,即将一维数组(或二维数组的一维)在界面上输出,并紧跟一个换行。例如:
cpp
#include <stdio.h>
int main(){
char str1[20];
char str2[5][10];
gets(str1);
for(int i = 0; i < 3; i++){
gets(str2[i]);
}
puts(str1);
for(int i = 0; i < 3; i++){
puts(str2[i]);
}
return 0;
}
输入下面四个字符串:
cpp
WoAiDeRenBuAiWo
QAQ
T_T
WoAiNi
这段代码通过 gets(str1) 将第一个字符串存入字符数组 str1 中,然后通过 for 循环将后三个字符串分别存于 str2[0]、str2[1]和str2[2] 中。之后使用 puts 来将这些字符串原样输出。
3. 字符数组的存放方式
字符数组的每一位都是一个char字符。
在一维字符数组(或是二维字符数组的第二维)的末尾都有一个空字符\0,以表示存放的字符串的结尾。
空字符\0 在使用 gets 或 scanf 输入字符串时会自动添加在输入的字符串后面,并占用一个字符位,而 puts 与 printf 就是通过识别 \0 作为字符串的结尾来输出的。
特别提醒1:结束符\0 的ASCII码为0,即空字符NULL,占用一个字符位,因此开字符数组的时候千万要记得字符数组的长度一定要比实际存储字符串的长度至少多1。注意:int型数组的末尾不需要加\0,只有char型数组需要。注意:\0和空格不是一个东西。
特别提醒2:如果不是使用 scanf 函数的 %s 格式或 gets 函数输入字符串(例如使用 getchar),一定要在输入的每个字符串后加入"\0",否则 printf 和 puts 输出字符串会因无法识别字符串末尾而输出一大堆乱码。
2.5.6 string.h 头文件
1. strlen()
可以得到字符数组中第一个\0前的字符的个数
格式:
strlen(字符数组);
例如:
cpp
int len = strlen(str);
2. strcmp()
返回两个字符串大小的比较结果,比较原则是按字典序
格式:
strcmp(字符数组1, 字符数组2)
字典序是字符串在字典中的顺序,因此如果有两个字符数组 str1 和 str2,且满足 str1[0...k-1] == str2[0...k-1]、str1[k]<str2[k],那么就说 str1 的字典序小于 str2。
strcmp 的返回结果:
① 如果字符数组1 < 字符数组2,则返回一个负整数(不同编译器处理不同,不一定是-1);
② 如果字符数组1 == 字符数组2,则返回0;
③ 如果字符数组1 > 字符数组2,则返回一个正整数(不同编译器处理不同,不一定是+1)。
例如:
cpp
#include <stdio.h>
#include <string.h>
int main(){
char str1[50], str2[50];
gets(str1);
gets(str2);
int cmp = strcmp(str1, str2);
if(cmp < 0) printf("str1 < str2\n");
else if(cmp > 0) printf("str1 > str2\n");
else printf("str1 == str2\n");
return 0;
}
输入字符串:
cpp
Dear Mozart
Canon
输出结果:
cpp
str1 > str2
3. strcpy()
把一个字符串复制给另一个字符串
格式:
strcpy(字符数组1, 字符数组2)
注意:是把字符数组2复制给字符数组1,这里的"复制"包括了结束符\0。
例如:
cpp
strcpy(str1, str2);
4. strcat()
把一个字符串接到另一个字符串后面
格式:
strcat(字符数组1, 字符数组2)
注意:是把字符数组2 接到字符数组1 后面
例如:
cpp
#include <stdio.h>
#include <string.h>
int main(){
char str1[50], str2[50];
gets(str1);
gets(str2);
strcat(str1, str2);
puts(str1);
return 0;
}
输入字符串:
cpp
ArkLight
Through the Fire and Flames
输出结果:
cpp
ArkLightThrough the Fire and Flames
2.5.7 sscanf 与 sprintf
理解:
sscanf : string + scanf
sprintf : string + printf
写法:
cpp
scanf("%d", &n);
printf("%d", n);
// 同理,其中 screen 表示屏幕
scanf(screen, "%d", &n); // 把screen的内容以"%d"的格式传输到n中(即从左至右)
printf(screen, "%d", n); // 把n以"%d"的格式传输到screen上(即从右至左)
// 格式相同,只是把screen换成了字符数组(假设定义了一个char数组str[100])
sscanf(str, "%d", &n); // 把字符数组str中的内容以"%d"的格式写到n中(从左至右)
sprintf(str, "%d", n); // 把n以"%d"的格式写到str字符数组中(从右至左)
示例:
cpp
// 使用 sscanf 将字符数组str中的内容按"%d:%lf,%s"的格式写到int型变量n、double型变量db、char型数组str2中
#include <stdio.h>
int main(){
int n;
double db;
char str[100] = "2048:3.14,hello", str2[100];
sscanf(str, "%d:%lf,%s", &n, &db, str2);
printf("n = %d, db = %.2f, str2 = %s\n", n, db, str2);
return 0;
}
输出结果:
cpp
n = 2048, db = 3.14, str2 = hello
示例:
cpp
// 使用 sprintf 将int型变量n、double型变量db、char型数组str2按"%d:%.2f,%s"的格式写到字符数组str中
#include <stdio.h>
int main(){
int n = 12;
double db = 3.1415;
char str[100], str2[100] = "good";
sprintf(str, "%d:%.2f,%s", n, db, str2);
printf("str = %s\n",str);
return 0;
}
输出结果:
cpp
str = 12:3.14,good
2.6 函数
2.6.1 函数的定义
基本语法格式:
返回类型 函数名称(参数类型 参数){
函数主体
}
分类:
- 无参函数:不需要提供参数就可以执行的函数,返回类型是void(表示为空),只实现语句不返回变量
- 有参函数:需要填写参数的函数
概念:全局变量和局部变量
- 全局变量:在定义之后的所有程序段内都有效的变量(即定义在其之后所有函数之前)
- 局部变量:定义在函数内部,且只在函数内部生效,函数结束时局部变量销毁
- 值传递
- 形式参数或形参:函数定义的小括号内的参数
- 实际参数或实参:实际调用时小括号内的参数
- 值传递
2.6.2 再谈 main 函数
结构:
int main(){
...
return 0; // 返回0->告知系统程序正常结束
}
2.6.3 以数组作为函数参数
参数中数组的第一维不需要填写长度(如果是二维数组,那么第二维需要填写长度),实际调用时也只需要填写数组名。
数组作为参数时,在函数中对数组元素的修改就等同于是对原数组元素的修改(这与普通的局部变量不同)
2.6.4 函数的嵌套调用
指在一个函数中调用另一个函数。
2.6.5 函数的递归调用
指一个函数调用该函数自身。函数自己调用自己
2.7 指针
2.7.1 什么是指针
计算机中,每个变量都会存放在内存中分配的一个空间,而每种类型的变量所占的空间不一样,例如:int型变量占用4Byte,long long型变量占用8Byte。
可以把一个字节理解成一个"房间",每个房间有一个"房间号"。
对应在计算机中,每个字节(即房间)都会有一个地址(即房间号),这里的地址就起房间号的作用,即变量存放的位置,而计算机就是通过地址找到某个变量的。变量的地址一般指它占用的字节中第一个字节的地址。
一个房间号"指向"一个房间,对应到计算机上就是一个地址"指向"一个变量,可以通过地址来找到变量。
在C语言中用"指针"来表示内存地址(或者称指针指向了内存地址),而如果这个内存地址恰好是某个变量的地址,那么又称"这个指针指向该变量"。
可以简单理解为指针就是变量的地址(不太严谨)。
只要在变量前面加上&(取地址运算符),就表示变量的地址。
指针是一个 unsigned 类型的整数。
2.7.2 指针变量
指针变量用来存放指针 (或者可以理解成地址)。
定义方式:
// C++ 常把*写在数据类型后
int* p;
double* p;
char* p;
// C常把*写在变量名前
int *p;
double *p;
char *p;
注意:同时定义好几个同种类型的指针变量时
int* p1, p2; // p1是int*型,p2是int型
int* p1, *p2; // p1和p2都是int*型
int *p1, *p2; // 为了美观,把第一个*放在变量名p1前
给指针变量赋值的方式一般是把变量的地址取出来,然后赋给对应类型的指针变量:
cpp
int a;
int* p = &a;
// 也可以写成
int a;
int* p;
p = &a;
// 给多个指针变量初始化
int a, b;
int *p1 = &a, *p2 = &b;
注意:int*是指针变量的类型,p是变量名,用来存储地址;因此,地址&a是赋值给p而不是*p的;星号是类型的一部分。
对一个指针变量存放的地址,如何得到这个地址所指的元素?-> 把星号*视为一把开启房间的钥匙,将其加在p的前面,示例:
cpp
#include <stdio.h>
int main(){
int a; // 定义int型变量a,但是没有对其进行初始化
int* p = &a; // 定义指针变量p,并将a的地址赋值给p。这时,指针变量p存放了a的地址
a = 233; // a被赋值为233,a所在地址的房间内的东西被改变了,但这并不影响它的地址
printf("%d\n", *p):
return 0;
}
输出结果:
cpp
233
p保存的是地址,*p是这个地址中存放的元素,如果直接对*p进行赋值,也可以起到改变那个保存的元素的功能。例如:
cpp
#include <stdio.h>
int main(){
int a;
int* p = &a;
*p = 233;
printf("%d, %d\n", *p, a);
return 0;
}
输出结果:
cpp
233, 233
指针可以进行加减法,其中减法的结果就是两个地址偏移的距离。
对一个int*型的指针变量p来说,p+1是指p所指的int型变量的下一个int型变量地址。这个所谓的"下一个"是跨越了一整个int型(即4Byte)
指针变量支持自增和自减操作,因此p++等同于p=p+1使用。
对指针变量来说,把其存储的地址的类型称为基类型。基类型必须和指针变量存储的地址类型相同。
2.7.3 指针与数组
数组是由地址上连续的若干个相同类型的数据组合而成。
数组名称也作为数组的首地址使用。
指针变量可以进行加减法 -> a+i 等同于 &a[i],这是因为 a+i 就是指数组a的首地址偏移i个int型变量的位置。但是注意,a+i 只是地址,如果想要访问其中的元素 a[i],需要加上星号,使其变成 *(a+i) 后才和 a[i] 等价。得到一种输入数据元素的新颖写法:
scanf("%d", a + i);
指针变量可以使用自增操作,用于枚举数组中的元素:
cpp
#include <stdio.h>
int main(){
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for(int* p = a; p < a + 10; p++){
printf("%d ", *p);
}
return 0;
}
输出结果:
cpp
1 2 3 4 5 6 7 8 9 10
指针的减法:
cpp
#include <stdio.h>
int main(){
int a[10] = {1, 4, 9, 16, 25, 36, 49};
int* p = a; // 其实是&a[0]
int* q = &a[5]; // 其实是&a[5]
printf("q = %d\n", q);
printf("p = %d\n", p);
printf("q - p = %d\n", q - p); // 两个地址之间的距离,这个距离以int为单位
return 0;
}
输出结果:
cpp
q = 2686708
p = 2686688
q - p = 5
两个 int 型的指针相减,等价于在求两个指针之间相差了几个 int。
2.7.4 使用指针变量作为函数参数
指针类型也可以作为函数参数的类型,这是视为把变量的地址传入函数。如果在函数中对这个地址中的元素进行改变,原先的数据就会确实的被改变。示例:
cpp
#include <stdio.h>
void change(int* p){
*p = 233;
}
int main(){
int a = 1;
int* p = &a; // 把int*型的指针变量p赋值为a的地址
change(p); // 通过change函数把指针变量p作为参数传入。此时传入的其实是a的地址。在change函数中,使用*p修改地址中存放的数据,也就是改变了a本身。
printf("%d\n", a); // 输出的是已经改变后的值
return 0;
}
// 这种传递方式被称为地址传递
经典例子:使用指针作为参数,交换两个数
cpp
#include <stdio.h>
int main(){
int a = 1, b = 2;
int temp = a;
a = b;
b = temp;
printf("a = %d, b = %d\n", a, b);
return 0;
}
输出结果:
cpp
a = 2, b = 1
把交换过程写成函数时函数在接收参数的过程中是单向一次性的值传递。也就是说,在调用swap(a, b)时只是把a和b的值传进去了,这样相当于产生了一个副本,对这个副本的操作不会影响main函数中a、b的值。
main函数传给swap函数的"地址"其实是一个"无符号整型"的数,其本身也跟普通变量一样只是"值传递"。
要使用指针的方法。
指针变量存放的是地址,那么使用指针变量作为参数时传进来的也是地址。只有在获取地址的情况下对元素进行操作,才能真正的修改变量。
代码:
cpp
#include <stdio.h>
void swap(int* a, int* b){ // swap函数中的a和b都是地址
int temp = *a; // *a和*b就是地址中存放的数据,可以"看成"是int型变量
*a = *b;
*b = temp;
}
int main(){
int a = 1, b = 2;
int *p1 = &a, *p2 = &b; // 把a和b的地址作为参数传入
swap(p1, p2);
printf("a = %d, b = %d\n", *p1, *p2);
return 0;
}
输出结果:
cpp
a = 2, b = 1
2.7.5 引用
1. 引用的含义
引用不产生副本,而是给原变量起了一个别名。对引用变量的操作就是对原变量的操作。
引用的使用方法:只需要在函数的参数类型后面加一个&就可以了(& 加在 int 后面或者变量名前面都可以,考虑到引用时别名的意思,因此一般写在变量名面前)。示例:
cpp
#include <stdio.h>
void change(int &x){
x = 1;
}
int main(){
int x = 10;
change(x);
printf("%d\n", x);
return 0;
}
输出结果:
cpp
1
注意:不管是否使用引用,函数的参数名和实际传入的参数名可以不同。
注意:要把引用的 & 跟取地址运算符 & 区分开来,引用并不是取地址的意思。
2. 指针的引用
示例:
cpp
#include <stdio.h>
void swap(int* &p1, int* &p2){
int* temp = p1;
p1 = p2;
p2 = temp;
}
int main(){
int a = 1, b = 2;
int *p1 = &a, *p2 = &b;
swap(p1, p2);
printf("a = %d, b = %d\n", *p1, *p2);
return 0;
}
注意:由于引用时产生变量 的别名,因此常量不可使用引号。
2.8 结构体(struct)的使用
2.8.1 结构体的定义
格式:
struct Name{
// 一些基本的数据结构或者自定义的数据类型
};
例如:
cpp
struct studentInfo{
int id;
char gender; // 'F' or 'M'
char name[20];
char major[20];
}Alice, Bob, stu[1000]; //两个结构体变量和一个结构体数组(如果不在此处定义变量或数组,则大括号外直接跟上分号)
注意:结构体里面能定义除了自己本身(这样会引起循环定义问题)之外的任何数据类型。不过虽然不能定义自己本身,但是可以定义自身类型的指针变量。例如:
cpp
struct node{
node n; // 不能定义node型变量
node* next; // 可以定义node*型指针变量
}
2.8.2 访问结构体内的元素
两种方法:"."操作和"->"操作
定义studentInfo类型:
cpp
struct studentInfo{
int id;
char name[20];
studentInfo* next; // 多了一个指针next用来指向下一个学生的地址
}stu, *p; // 结构体变量中定义了普通变量stu和指针变量p
于是访问stu中变量的写法:
cpp
stu.id
stu.name
stu.next
访问指针变量p中元素的写法:
cpp
(*p).id
(*P).name
(*p).next
或者C语言中有一种简洁写法:
cpp
p->id
p->name
p->next
2.8.3 结构体的初始化
构造函数:用来初始化结构体的一种函数,直接定义在结构体中。
一个特点是:它不需要写返回类型,且函数名与结构体名相同。
一般来说,对一个普通定义的结构体,其内部会生成一个默认的构造函数(但不可见):
cpp
struct studentInfo{
int id;
char gender;
// 默认生成的构造函数
studentInfo(){}
};
如果要自己手动提供id和gender的初始化参数,就提供初始化参数来对结构体内的变量进行赋值,其中_id和_gender都是变量名,只要不和已有的变量冲突,用其他变量名也可以。
cpp
struct studentInfo{
int id;
char gender;
// 下面的参数用以对结构体内部变量进行赋值
studentInfo(int _id, char _gender){
// 赋值
id = _id;
gender = _gender;
}
};
构造函数也可以简化成一行:
cpp
struct studentInfo{
int id;
char gender;
studentInfo(int _id, char _gender) : id(_id), gender(_gender) {}
};
这样就可以在需要时直接对结构体变量进行赋值了:
cpp
studentInfo stu = studentInfo(10086, 'M');
注意:如果自己重新定义了构造函数,则不能不经初始化就定义结构体变量。
为了既能不初始化就定义结构体变量,又能享受初始化带来的便捷,可以把"studentInfo(){}"手动加上。这意味着,只要参数个数和类型不完全相同,就可以定义任意多个构造函数,以适应不同的初始化场合。示例:
cpp
struct studentInfo{
int id;
char gender;
// 用以不初始化就定义结构体变量
studentInfo(){}
// 只初始化gender
studentInfo(char _gender){
gender = _gender;
}
// 同时初始化id和gender
studentInfo(int _id, char _gender){
id = _id;
gender = _gender;
}
};
示例,其中结构体Point用于存放平面点的坐标x、y:
cpp
#include <stdio.h>
struct Point{
int x, y;
Point(){} // 用以不经初始化地定义pt[10]
Point(int _x, int _y) : x(_x), y(_y) {} // 用以提供x和y的初始化
}pt[10];
int main(){
int num = 0;
for(int i = 1; i <= 3; i++){
for(int j = 1; j <= 3; j++){
pt[num++] = Point(i, j); // 直接使用构造函数
}
}
for(int i = 0; i < num; i++){
printf("%d,%d\n", pt[i].x, pt[i].y);
}
return 0;
}
构造函数在结构体内元素比较多时会使代码显得精炼,因为可以不需要临时变量就初始化一个结构体,而且代码更加工整,推荐使用。
2.9 补充
2.9.1 cin 与 cout
C++中的输入和输出函数,需要添加头文字 "#include <iostream>" 和 "using namespace std;" 才能使用。不需要指定输入输出的格式,也不需要使用取地址运算符&,而可以直接进行输入/输出。
1. cin
c和in的合成词,采用输入运算符 ">>" 来进行输入。
如果想要输入一个整数n,则可以按下面的写法输入:
cpp
#include <iostream>
using namespace std;
int main(){
int n;
cin >> n; // 不指定格式,不需要加取地址运算符&,直接写变量名就行
return 0;
}
同时读入多个变量,只需要往后面使用>>进行扩展:
cpp
cin >> n >> db >> c >> str; // int型 int,double型 db,char型 c,char型数组 str[]
如果想要读入一整行,则需要使用 getline 函数,例如把一整行都读入char型数组str[100]中:
cpp
char str[100];
cin.getline(str, 100);
而如果是string容器,则需要用下面的方式输入:
cpp
string str;
getline(cin, str);
2. cout
c和out的合成词,使用输出运算符 "<<",使用方法和cin一致。
cpp
cout << n << db << c << str; // int型 int,double型 db,char型 c,char型数组 str[]
但要注意,输出时中间没有加空格,因此可以在每个变量之间加上空格:
cpp
cout << n <<" "<< db <<" "<< c <<" "<< str;
在中间输入字符串也可以:
cpp
cout << n <<"haha"<< db <<"heihei"<< c <<"wawa"<< str;
cout中,换行有两种方式:
- 使用
\n; - 使用
endl(是end line的缩写)
cpp
cout << n << "\n"<< db << endl;
如果想要控制double型的精度,例如输出小数点后两位,那么需要在输出之前加上一些东西,并且要加上 "#include <iomanip>" 头文件:
cpp
cout << setiosflags(ios::fixed) << setprecision(2) << 123.4567 << endl; // 输出123.46
2.9.2 浮点数的比较
浮点数在计算机中的存储并不总是精确的。经过大量计算后,浮点型的数可能会存在误差,但是C/C++中的"=="操作是完全相同才能判定为true,于是需要引入一个极小数eps来修正误差。
1. 等于运算符(==)
a 落在 [b-eps, b+eps] 区间中时,应当判断为 a==b 成立。
eps 取 10 − 8 10^{-8} 10−8是合适的,因此可以将eps定义为常量 1e-8:
cpp
const double eps = 1e-8;
为了使比较更加方便,把比较操作写成宏定义的形式:
cpp
#define Equ(a, b) ((fabs((a)-(b)))<(eps)) // 将a和b相减,如果差的绝对值小于及销量eps,那么就返回true
注意:如果想要使用不等于,只需要在使用时的Equ前面加一个非运算符"!"即可(!Equ(a, b))。
cpp
#include <stdio.h>
#include <math.h>
const double eps = 1e-8;
#define Equ(a, b) ((fabs((a)-(b)))<(eps))
int main(){
double db = 1.23;
if(Equ(db, 1.23)){
printf("true");
}else{
printf("false");
}
return 0;
}
2. 大于运算符(>)
a 的范围是 (b+eps, +∞),也即a减b大于eps。
cpp
#define More(a, b) (((a)-(b))>(eps))
3. 小于运算符(<)
a 的范围是 (-∞, b-eps),也即a减b小于-eps。
cpp
#define Less(a, b) (((a)-(b))<(-eps))
4, 大于等于运算符(>=)
可以理解为大于运算符和等于运算符的结合。
a 的范围是 (b-eps, +∞),也即a减b大于-eps。
cpp
#define MoreEqu(a, b) (((a)-(b))>(-eps))
5. 小于等于运算符(<=)
可以理解为小于运算符和等于运算符的结合。
a 的范围是 (-∞, b+eps),也即a减b小于eps。
cpp
#define LessEqu(a, b) (((a)-(b))<(eps))
6. 圆周率π
因为由 cos(π)=-1 可知 π = arccos(-1)。因此只需要把π写成常量 acos(-1.0)。
cpp
const double Pi=acos(-1.0);
2.9.3 复杂度
1. 时间复杂度
时间复杂度是算法需要执行基本运算的次数所处的等级,其中基本运算就是类似加减乘除这种计算机可以直接实现的运算。时间复杂度是评判算法时间效率的有效标准。
在时间复杂度中,高等级的幂次会覆盖低等级的幂次。
趋近于O( c n 2 cn^2 cn2),其中c是一个常数,把这个常数称为算法时间复杂度的常数。
对数复杂度书写时一般省略底数:O( l o g n logn logn)。
常数复杂度O( 1 1 1)表示算法消耗的时间不随规模的增长而增长。
O( 1 1 1) < O( l o g n logn logn) < O( n n n) < O( n 2 n^2 n2)
对一般的OJ系统来说,一秒能承受的运算次数大概是 10 7 10^7 107 ~ 10 8 10^8 108。
2. 空间复杂度
空间复杂度表示算法需要消耗的最大数据空间。
O( 1 1 1)的空间复杂度是指算法消耗的空间不随数据规模的增大而增大。
3. 编码复杂度
编码复杂度是一个定性的概念,并没有什么量化的标准。
2.10 黑盒测试
系统后台会准备若干组输入数据,然后让提交的程序去运行这些数据,如果输出的结果与正确答案完全相同(字符串意义上的比较),那么就称通过了这道题的黑盒测试,否则会根据错误类型而返回不同的结果。
2.10.1 单点测试
系统会判断每组数据的输出结果是否正确。如果输出正确,那么对该组数据来说就通过了测试,并获得了这组数据的分值。
2.10.2 多点测试
要求程序能一次运行所有数据,并要求所有输出结果都必须完全正确,才能算作这题通过;而只要有其中一组数据的输出错误,本题就只能得0分。
3种输入方式:
(1) while...EOF 型
如果题目中没有指定何时结束输入,一般都是指输入完所有数据(即到达文件末尾)为止。
scanf 函数的返回值为其成功读入的参数的个数。
只有在读取文件时到达文件末尾导致的无法读取现象才会产生读入失败。这个时候,scanf 函数会返回 -1 而不是0,且C语言中使用 EOF(即End Of File)来表示 -1。
启发:当题目没有说明多少数据需要读入时,就可以利用 scanf 的返回值是否为 EOF 来判断输入是否结束。
cpp
while(scanf("%d", &n) != EOF){
...
}
只要 scanf 的返回值不为 EOF(即文件中的数据没有读完),就反复读入n,执行while函数体的内容;当读入失败(到达文件末尾)时,结束while循环。
如果读入字符串,则有 scanf("%s", str) 和 gets(str) 两种方式可用,对应的输入写法:
cpp
while(scanf("%s", str) != EOF){
...
}
while(gets(str) != NULL){
...
}
(2) while...break 型
题目要求当输入的数据满足某个条件时停止输入。
两种写法:
- 一种是在 while...EOF 的内部进行判断,当满足条件时中断(break)当前 while 循环
cpp
#include <stdio.h>
int main(){
int a, b;
while(scanf("%d%d", &a, &b) != EOF){
if(a == 0 && b == 0) break;
printf("%d\n", a + b);
}
return 0;
}
- 把退出条件的判断放在 while 语句中,令其与 scanf 用逗号隔开
cpp
#include <stdio.h>
int main(){
int a, b;
while(scanf("%d%d", &a, &b), a || b){ // 全写为:a != 0 || b != 0。
// 含义是:当a和b中有一个不为0时就进行循环
printf("%d\n", a + b);
}
return 0;
}
(3) while(T--) 型
题目会给出测试数据的组数,然后才给出相应数量组数的输入数据。
由于给定了测试数据的组数,因此需要用一个变量T来存储,并在程序开始时读入。在读入T后,下面就可以进行T次循环,每次循环解决一组数据的输入与输出,while(T--) 就是循环执行T次的含义。
cpp
#include <stdio.h>
int main(){
int T, a, b;
scanf("%d", &T);
while(T--){
scanf("%d%d", &a, &b);
printf("%d\n", a + b);
}
return 0;
}
三种输出类型
(1) 正常输出
要求需要每两组输出数据中间没有额外的空行,即输出数据是连续的多行。
(2) 每组数据输出之后都额外加一个空行
只需要在每组输出结束之后额外加一个换行符\n即可。
(3) 两组输出数据之间有一个空行,最后一组数据后面没有空行
一般是在第三种输入类型 while(T--) 的情况下,只需要通过判断T是否已经减小到0来判断是否应当输出额外的换行。
注意:在多点测试中,每一次循环都要重置一下变量和数组,否则下一组数据来临的时候变量和数组的状态就不是初始状态了。重置数组一般使用 memset 函数或 fill 函数。