信息在计算机中的表示
用0和1表示各种信息
- 计算机的电路由逻辑门电路组成,一个逻辑门电路可以表示0或1。
- 一个二进制位,取值只能是0或1,称为一个比特(bit),简写:b
- 八个二进制位,称为一个字节(byte),简写:B
- 1024 B=1 KB,1024 KB=1 MB,1024 MB=1 GB,1024 GB=1 TB
- 由8个0或1组成的串,一共256种不同的组合,足以表示阿拉伯数字以及英语中用到的所有字母和标点符号。这就是ASCII编码方案。
十进制到二进制的互相转换
- K进制数到十进制数的转换:按权相加法
int AnyToTen(string num, int K) // num是要转换的字符串,K是进制(2<=K<=36)
{
int ans = 0; // ans是转换的结果
for(int i = 0; i < num.size(); i++) // 从高位开始循环遍历
{
char ch = num[i]; // ch是num各位的字符
int base; // base是该位本来的数
if(ch >= '0' && ch <= '9') // 如果ch是数字
base = ch - '0';
else if(ch >= 'a' && ch <= 'z') // 如果ch是小写字母
base = ch - 'a' + 10;
else if(ch >= 'A' && ch <= 'Z') // 如果ch是大写字母
base = ch - 'A' + 10;
else // 如果ch是非法字符
break;
ans = ans * K + base;
}
return ans;
}
- 十进制数到K进制数的转换:除留余数法
string TenToAny(int num, int K) // num是要转换的数,K是转换结果的进制(2<=K<=36)
{
string ans = ""; // ans是转换的结果
int remain; // remain是余数
do {
remain = num % K; // 取余
num /= K;
if(remain >= 10) // 如果超出十进制,需要用大写字母表示
ans += remain - 10 + 'A';
else // 如果没超出十进制,用数字表示
ans += remain + '0';
} while(num); // 循环遍历取余数
reverse(ans.begin(), ans.end()); // 字符串逆序
return ans;
}
为什么学习C++而不是C语言
- C语言功能弱,C++优势在于大规模程序。
- 课程学习的内容是C++的一部分,即C语言+STL(Standard Template Library,标准模板库)。
第一个C++程序
- 输出:Hello, world!
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
printf("Hello, world!\n");
return 0;
}
变量
什么是变量
- 变量就是一个代号,程序运行时系统会自动为变量分配内存空间,对变量的访问就是对其代表的内存空间的访问。
- 变量有名字和类型两种属性,不同变量的名字对应了内存中的不同地址,变量的类型决定了一个变量占用多少个字节。
- 在C++语言中,变量必须先定义才能使用,一个变量不能定义两次。
变量的命名规则
- 变量由大小写字母、数字和下划线构成,中间不能有空格,长度不限,不能以数字开头。
- 变量名是大小写相关的,name和Name是不同的两个变量。
- 变量名不能和C++的保留字重复。
数据类型
C++的数据类型
- C++的基本数据类型
- (有符号)整型:int(4), long(4), short(2), long long(8)
- 无符号整型:unsigned int(4), unsigned long(4), unsigned short(2), unsigned long long(8)
- 字符型:char(1), unsigned char(1)
- 浮点型:float(4), double(8)
- 布尔型:bool(一般是1)
- 除基本数据类型外,C++还允许程序员自定义数据类型。
有符号整数的表示方式
- 将最左位(最高位)看作符号位。
- 非负数:符号位为0,其余位为绝对值。
- 负数:符号位为1,其余位为绝对值取反再加1。
整数 | 二进制形式 | 十六进制形式 |
---|---|---|
0 | 0000 0000 0000 0000 | 0000 |
1 | 0000 0000 0000 0001 | 0001 |
257 | 0000 0001 0000 0001 | 0101 |
32767 | 0111 1111 1111 1111 | 7FFF |
-32768 | 1000 0000 0000 0000 | 8000 |
-1 | 1111 1111 1111 1111 | FFFF |
-2 | 1111 1111 1111 1110 | FFFE |
常量
什么是常量
- 常量就是在程序运行过程中值不会发生改变,而且一眼就能看出其值的量。
- 常量也可以分成多种:整型,浮点型,字符型,字符串,符号常量。
整型常量
- 十六进制整型常量:以
0x
开头,如0xFFA,-0x1a。 - 八进制整型常量:以
0
开头,如01,-0456。
字符型常量
- 字符型常量:用
单引号('')
括起来,如’a’,’:’。 - 字母和数字的ASCII编码:
- ‘0’-‘9’:48-57
- ‘A’-‘Z’:65-90
- ‘a’-‘z’:97-122
- 转义字符:以
反斜杠(\)
开头,如’\n’, ‘\r’。
转义字符 | 含义 | ASCII值 |
---|---|---|
\0 | 空字符(NULL) | 000 |
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,输出位置回退一个字符 | 008 |
\t | 水平制表(HT) ,输出位置跳到下一个Tab位置 | 009 |
\n | 换行(LF) ,输出位置移到下一行开头 | 010 |
\r | 回车(CR) ,输出位置移到本行开头 | 013 |
字符串常量
- 字符串常量:用
双引号("")
括起来,如"a",“abc”。 - ""表示空串,即不包含任何字符的字符串。
输入输出
输入输出控制符
- 在printf和scanf中可以使用以
百分号(%)
开头的控制符,指明要输入或输出的数据的类型以及格式。 - 测试:用scanf读入不同类型的变量
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n; char c; float m;
scanf("%d%c%f", &n, &c, &m); // 输入:34k 234.45
printf("%d %c %f", n, c, m); // 输出:34 k 234.449997
return 0;
}
常用格式控制符 | 作用 |
---|---|
%c | 读入或输出char变量 |
%d | 读入或输出int或short变量 |
%lld | 读入或输出long long变量(64位整数) |
%nd | 输出整数,宽度不足n字符时用空格填充 |
%0nd | 输出整数,宽度不足n字符时用0填充 |
%f | 读入或输出float变量,输出时保留小数点后6位 |
%lf | 读入或输出double变量,输出时保留小数点后6位 |
%.nf | 输出浮点数,精确到小数点后n位 |
%o | 以八进制形式读入或输出整型变量 |
%x | 以十六进制形式读入或输出整型变量 |
%u | 以无符号形式读入或输出整型变量 |
赋值运算符、算术运算符和算术表达式
赋值运算符
- 赋值运算符用于给变量赋值:=, +=, -=, *=, /=, %=
- a += b 等效于 a = a + b,但是执行速度更快,其他类似。
算术运算符
- 算术运算符用于数值运算:+, -, *, /, %, ++, –
- 运算符和操作数构成表达式。表达式的值的类型,以操作数中精度高的类型为准。
- 精度:double > long long > int > short > char
加、减、乘运算的溢出
- 整数进行加、减、乘运算可能导致计算结果超出结果的类型所能表示的范围,这种情况称为溢出。整数类型的溢出部分直接被丢弃。
- 实数(浮点数)也可能溢出,结果不易预测。
- 测试:整数相加导致溢出
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
unsigned int n1 = 0xffffffff;
cout << n1 << endl; // 输出:4294967295
unsigned int n2 = n1 + 3; // 0xffffffff + 3 = 0x100000002,导致溢出
cout << n2 << endl; // 输出:2
return 0;
}
关系运算符、逻辑运算符和逻辑表达式
关系运算符
- 关系运算符用于数值的比较:==, !=, >, <, >=, <=
- 比较的结果是bool类型。bool类型变量只有两种取值,true或false,false等价于0,true等价于非0整型值。
逻辑运算符
- 逻辑运算符用于表达式的逻辑操作:&&, ||, !
强制类型转换运算符以及运算符优先级
强制类型转换运算符
- 强制类型转换运算符用于将操作数转换为指定类型:类型名
运算符优先级(从高到低)
- 自增(++),自减(–),非(!)
- 其他算术运算符(+, -, *, /, %)
- 关系运算符(==, !=, >, <, >=, <=)
- 其他逻辑运算符(&&, ||)
- 赋值运算符(=, +=, -=, *=, /=, %=)
if语句和switch语句
if语句
- 例题:输入年份,判断该年份是否是建国整十周年、建党整十周年以及是否是闰年。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int year; // 年份
scanf("%d", &year); // 输入年份
if(year < 0) { // 如果year是负数
printf("Illegal year.\n"); // 年份不合法
}
else {
printf("Legal year.\n"); // 年份合法
if((year > 1949) && ((year - 1949) % 10 == 0)) // 如果year是建国整十周年
printf("Lucy year.\n");
else if((year > 1921) && ((year - 1921) % 10 == 0)) // 如果year是建党整十周年
printf("Good yaer.\n");
else if((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)) // 如果year是闰年
printf("Leap year.\n");
else // 如果year是普通年份
printf("Common year.\n");
}
return 0;
}
switch语句
- 例题:输入整数,输出对应的星期的英文单词。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n; // 整数n
scanf("%d", &n); // 输入整数n
switch(n) { // 表达式的值必须是整型
case 1: // 常量表达式必须是整型的常量
printf("Monday"); break; // 进入分支后会一直执行语句,直到碰到break
case 2:
printf("Tuesday"); break;
case 3:
printf("Wednesday"); break;
case 4:
printf("Thursday"); break;
case 5:
printf("Friday"); break;
case 6:
printf("Saturday"); break;
case 7:
printf("Sunday"); break;
default:
printf("Illegal");
}
return 0;
}
for循环
for循环
- 例题:连续输出26个字母。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int i; // 循环控制变量
for(i = 0; i < 26; i++) {
cout << char('a' + i); // 强制转换成char类型
// printf("%c", 'a' + i);
}
return 0;
}
- 例题:输入正整数n和m,在1~n的数中取两个不同的数,使其和是m的因子,计算共有多少种不同的取法。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n, m; // 正整数n和m
cin >> n >> m; // 输入正整数n和m
int total = 0; // 不同的取法总数
for(int i = 1; i < n; i++) { // 取第一个数,共有n-1种取法
for(int j = i + 1; j <= n; j++) { // 取第二个数,第二个数要比第一个数大,避免重复
if(m % (i + j) == 0){ // 如果i和j的和是m的因子
total++; // 添加该取法
}
}
}
cout << total; // 输出不同的取法总数
return 0;
}
while循环和do while循环
while循环
- 例题:输入若干不超过100的正整数,输入0标志输入结束,输出其中的最大值、最小值以及所有数的和。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int num; // 正整数num
int max = 0, min = 100, sum = 0; // 最大值、最小值、所有数的和
cin >> num; // 输入正整数num,num不大于100
while(num) { // 如果num为0则结束循环
if(num > max) { // 如果num大于max,更新max
max = num;
}
if(num < min) { // 如果num小于min,更新min
min = num;
}
sum += num; // 更新sum
cin >> num; // 继续输入正整数num
}
cout << max << " " << min << " " << sum; // 输出最大值、最小值、所有数的和
return 0;
}
- 例题:用牛顿迭代法求输入的数的平方根。
#include <iostream>
#include <cstdio>
using namespace std;
double EPS = 0.001; // 控制计算精度
int main()
{
double a;
cin >> a; // 输入a,求a的平方根
if(a >= 0) {
double X = a / 2; // 猜测一个值X
double lastX = X + EPS + 1; // 确保能够进行至少一次迭代
while((X - lastX > EPS) || (lastX - X > EPS)) { // 如果精度未达到要求,则继续迭代
lastX = X; // 保存当前近似值
X = (X + a / X) / 2; // 根据迭代公式求下一个近似值
}
cout << X; // 输出满足精度的近似值
}
else {
cout << "It can't be nagitive."; // 输入的数不能是负数
}
return 0;
}
do while循环
- 例题:输出1~10000以内所有2的整数次幂。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n = 1; // 2的0次幂
do { // 至少执行一次循环
cout << n << " "; // 输出2的整数次幂
n *= 2; // 计算下一个2的整数次幂
} while(n < 10000); // 如果n超出范围,则结束循环
return 0;
}
break语句和continue语句
break语句
- 可以出现在循环体中,其作用是跳出直接包含break语句的那一重循环。
- 例题:输入正整数n和m,在n~m的数中找出一对和最小的兄弟数,如果有多对,就找出弟数最小的那一对。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n, m; // 正整数n和m
cin >> n >> m; // 输入正整数n和m
int num1 = m + 1, num2 = m + 1; // 正整数num1和num2为当前已经找到的最佳兄弟数
for(int i = n; i < m; i++) { // 取弟数,共有m-n种取法
if(i > ((num1 + num2) / 2 + 1)) { // 如果弟数大于最佳兄弟数的和的一半,则之后的循环无意义
break; // 跳出该循环,减少消耗
}
for(int j = i + 1; j <= m; j++) { // 取兄数,兄数要比弟数大,避免重复
if((i + j) > (num1 + num2)){ // 如果该兄弟数的和大于最佳兄弟数的和,则之后的循环无意义
break; // 跳出该循环,减少消耗
}
if((i * j) % (i + j) == 0) { // 如果i和j为兄弟数
if((i + j) < (num1 + num2)) { // 如果该兄弟数的和小于最佳兄弟数的和
num1 = i; num2 = j; // 更新最佳兄弟数
}
else if(((i + j) == (num1 + num2)) && (i < num1)) { // 如果弟数小于最佳兄弟数的弟数
num1 = i; num2 = j; // 更新最佳兄弟数
}
}
}
}
if(num1 == (m + 1)) { // 如果没找到兄弟数
cout << "No solution."; // 输出:No solution.
}
else { // 如果找到兄弟数
cout << num1 << ", " << num2; // 输出兄弟数
}
return 0;
}
continue语句
- 可以出现在循环体中,其作用是立即结束本次循环,并回到循环开头判断是否要进行下一次循环。
OJ编程题输入数据的处理
输入数据的处理
- scanf表达式的值为int,表示成功读入的变量个数。
- scanf表达式的值为EOF(-1),说明输入数据已经结束。
- cin表达式的值为bool类型,表示是否成功读入所有变量。
- 测试:输入数据的处理
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int num1, num2;
// scanf表达式的值为int类型,表示成功读入的变量个数。
int count = scanf("%d%d", &num1, &num2); // 输入两个整数或其他
printf("%d", count); // 输出成功读入的变量个数
int num3, num4;
// scanf表达式的值为EOF(-1),说明输入数据已经结束。
while(scanf("%d%d", &num3, &num4) != EOF) { // 不停输入两个整数,直到输入【Ctrl+Z】,然后输入【Enter】
// 或:while(scanf("%d%d", &num3, &num4) == 2) {
printf("%d", num3 + num4); // 输出它们的和
}
int num5, num6;
// cin表达式的值为bool类型,表示是否成功读入所有变量。
while(cin >> num5 >> num6) { // 不停输入两个整数,直到输入【Ctrl+Z】,然后输入【Enter】
printf("%d", num5 + num6); // 输出它们的和
}
return 0;
}
处理无结束标记的OJ编程题输入
- 例题:输入若干正整数,输出其中的最大值。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int num, max = 0;
while(scanf("%d", &num) != EOF) { // 不停输入正整数num,直到输入【Ctrl+Z】,然后输入【Enter】
// 或:while(scanf("d", &num) == 1) {
// 或:while(cin >> num) {
if(num > max) { // 如果num大于当前最大值
max = num; // 更新当前最大值
}
}
printf("%d", max); // 输出最大值
return 0;
}
用freopen重定向输入
- 测试:将输入由键盘重定向为文件
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
freopen("D:\\WorkSpace\\VSCode\\CodeDemo\\input.txt", "r", stdin);
// 之后所有输入都来自文件:D:\\WorkSpace\\VSCode\\CodeDemo\\input.txt
int num, max = 0;
while(cin >> num) { // 不停输入正整数num,直到输入【Ctrl+Z】,然后输入【Enter】
if(num > max) { // 如果num大于当前最大值
max = num; // 更新当前最大值
}
}
printf("%d", max); // 输出最大值
return 0;
}
循环例题选讲
循环例题选讲
- 例题:输入整数a和正整数n,输出乘方a的n次。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a, n;
cin >> a >> n; // 输入整数a和正整数n
int result = a; // result为乘方计算的结果
for(int i = 1; i < n; i++) { // 循环n-1次
result *= a; // 不断与a相乘
}
cout << result; // 输出结果
return 0;
}
- 例题:输入正整数k,输出斐波那契数列中的第k个数。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int k;
cin >> k; // 输入正整数k
int a1 = 1, a2 = 1; // a1和a2是斐波那契数列中的前两个数
if((k == 1) || (k == 2)) { // 如果k是1或2
cout << 1 << endl; // 直接输出结果1
}
else { // 如果k大于2
int sum; // sum为斐波那契数列中的第k个数
for(int i = 3; i <= k; i++) { // 循环k-2次,直到i==k
sum = a1 + a2; // 计算斐波那契数列中的第i个数
a1 = a2; a2 = sum; // 更新a1和a2,即往后挪一个数
}
cout << sum << endl; // 输出结果sum
}
return 0;
}
- 例题:输入正整数n,输出不大于n的正整数的阶乘的和。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n;
cin >> n; // 输入正整数n
int sum = 0; // sum为阶乘的和
int factorial = 1; // factorial为阶乘
for(int i = 1; i <= n; i++) { // 循环n次
factorial *= i; // 计算i的阶乘
sum += factorial; // 计算1!+2!+3!+...+n!
}
cout << sum; // 输出阶乘的和sum
return 0;
}
- 例题:输入正整数n(n>=2),输出不大于n的全部质数。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n;
cin >> n; // 输入正整数n
cout << 2 << endl; // 输出第一个质数2
for(int i = 3; i <= n; i += 2) { // 循环遍历3~n,跳过所有偶数,每次判断i是否是质数
int factor;
for(factor = 3; factor < i; factor += 2) { // 循环遍历3~i,跳过所有偶数,每次判断factor是否是i的因子
if(i % factor == 0) { // 如果factor是i的因子
break; // i不是质数,跳出循环
}
if(factor * factor > i) { // 如果factor大于i的平方根,则之后的循环无意义
break; // 不需要重复考虑之后的因子,跳出循环
}
}
if(factor * factor > i) { // 如果没有执行过第一个if语句的break语句,表示i没有因子,即i是质数
cout << i << endl; // 输出i
}
}
return 0;
}
数组
数组
- 可以用来表达类型相同的元素的集合。元素的编号称为数组下标,元素的个数称为数组长度,数组名表示数组的地址。
- 例题:输入100个正整数,按逆序输出。
#include <iostream>
#include <cstdio>
using namespace std;
// 使用符号常量,便于修改
#define NUM 100 // 数组长度
// 数组一般不定义在main函数中,尤其是大数组
int a[NUM]; // 定义数组a,有NUM个元素,每个元素都是int类型
int main()
{
for(int i = 0; i < NUM; i++) { // 循环输入正整数,储存到数组a[0]~a[99]
cin >> a[i];
}
for(int i = NUM - 1; i >= 0; i--) { // 循环输出正整数,按逆序输出a[99]~a[0]
cout << a[i] << " ";
}
return 0;
}
- 例题:输入正整数n(n>=2),输出不大于n的全部质数,用筛法(挨拉托色尼筛法)求解。
#include <iostream>
#include <cstdio>
using namespace std;
#define MAX_NUM 100 // 最大数组长度
int isPrime[MAX_NUM]; // 定义数组isPrime,isPrime[i]的值为1,则说明i是质数
int main()
{
for(int i = 2; i <= MAX_NUM; i++) { // 开始假设所有数都是质数
isPrime[i] = 1; // 初始值设为1
}
for(int i = 2; i <= MAX_NUM; i++) { // 循环遍历所有数,逐步划掉其中的合数
if(isPrime[i]) { // 只需考虑质数作为因子的情况,合数作为因子的情况已经包含在内
for(int j = 2*i; j <= MAX_NUM; j += i) { // 循环遍历所有i的倍数
isPrime[j] = 0; // 将i的倍数设置为0,即划掉该合数
}
}
}
for(int i = 2; i <= MAX_NUM; i++) { // 循环遍历数组isPrime
if(isPrime[i]) { // 如果值为1
cout << i << endl; // 输出该质数
}
}
return 0;
}
数组初始化
数组初始化
- 在定义数组时,可以给数组中的元素赋初值,没有赋值的元素自动赋0值。
- 如:int a[10] = {0, 1, 2, 3, 4};
- 在定义数组时,如果给所有元素赋值,则可以不给出元素的个数。
- 如:int a[] = {1, 2, 3};
用数组取代复杂分支结构
- 例题:输入整数1~7,输出对应星期的英文单词。
#include <iostream>
#include <cstdio>
#include <string> // 使用string必须包含该头文件
using namespace std;
string weekdays[] = { // 定义数组weekdays,存放字符串常量
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
int main()
{
int n;
cin >> n; // 输入整数n
if((n >= 1) && (n <= 7)) { // 如果输入合法
cout << weekdays[n-1]; // 输出对应的字符串
}
else { // 如果输入非法
cout << "Illegal."; // 输出错误提示信息
}
return 0;
}
- 例题:已知2012年1月25日是星期三,输入包含年、月、日的日期,输出该日期对应的星期。
#include <iostream>
#include <cstdio>
using namespace std;
int monthdays[13] = { // 定义数组monthdays,存放各月份的天数
-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int main()
{
int year, month, date;
cin >> year >> month >> date; // 输入包含年、月、日的日期
int days = 0; // days为输入的日期从该天起过的天数
for(int y = 2012; y < year; y++) { // 循环遍历经过的年份
if((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) { // 如果y是闰年
days += 1; // 额外加1天
}
days += 365; // 计算相差年份的天数
}
if((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)) { // 如果year是闰年
monthdays[2] = 29; // 修改数组中的2月天数
}
for(int m = 1; m < month; m++) { // 循环遍历经过的月份
days += monthdays[m]; // 计算相差月份的天数
}
days += date - 22; // 计算相差日期的天数
cout << days % 7 << endl; // 输出该日期对应的星期
return 0;
}
数组越界
数组越界
- 数组下标可以是任何整数,可以是负数,也可以大于数组长度,不会导致编译错误。
- 如:int a[10]; a[-2] = 5; a[20] = 10;
- 当数组下标不在数组长度范围内,可能写入别的变量或指令的内存空间,会导致程序运行错误或程序崩溃。
- 用变量作为数组下标时,变量为负数或太大,会导致数组越界。
二维数组
二维数组
- 定义N行M列的二维数组:T a[N][M];
- 数组占用了一片连续的存储空间,大小为sizeof(a)或N×M×sizeof(T)
- 二维数组的每一行,可以直接当作一维数组使用,如a[0],a[1]。
- 在定义二维数组时,如果对每行初始化,则可以不给出行数。
- 如:int a[][3] = {{80, 75, 92}, {61, 65}};
- 例题:输入正整数m和n,再输入m×n的矩阵,输入正整数p和q,在输入p×q的矩阵,输出矩阵相乘的结果矩阵。
#include <iostream>
#include <cstdio>
using namespace std;
#define ROWS 8 // 最大行数
#define COLS 8 // 最大列数
int a[ROWS][COLS]; // 定义m行n列的矩阵
int b[ROWS][COLS]; // 定义p行q列的矩阵
int c[ROWS][COLS]; // 定义m行q列的结果矩阵
int main()
{
int m, n;
cin >> m >> n; // 输入正整数m和n
for(int i = 0; i < m; i++) { // 循环遍历矩阵a每行
for(int j = 0; j < n; j++) { // 循环遍历矩阵a每列
cin >> a[i][j]; // 输入矩阵a的元素
}
}
int p, q;
cin >> p >> q; // 输入正整数p和q
for(int i = 0; i < p; i++) { // 循环遍历矩阵b每行
for(int j = 0; j < q; j++) { // 循环遍历矩阵b每列
cin >> b[i][j]; // 输入矩阵b的元素
}
}
for(int i = 0; i < m; i++) { // 循环遍历矩阵c每行
for(int j = 0; j < q; j++) { // 循环遍历矩阵c每列
c[i][j] = 0; // 对矩阵c初始化
for(int k = 0; k < n; k ++) { // 循环遍历矩阵a的每列或矩阵b的每行
c[i][j] += a[i][k] * b[k][j]; // 计算矩阵c的元素
}
}
}
for(int i = 0; i < m; i++) { // 循环输出矩阵c每行
for(int j = 0; j < q; j++) { // 循环输出矩阵c每列
cout << c[i][j] << " ";
}
cout << endl;
}
return 0;
}
函数
函数
- 函数可以将实现某一功能,并需要反复使用的代码包装起来形成一个功能模块。
- 测试:Max函数
// 求两个整型变量中的较大值
int Max(int x, int y) // x和y为形参
{
if(x > y)
return x;
else
return y;
}
- 测试:isPrime函数
// 判断n是否是质数
bool IsPrime(unsigned int n)
{
if(n <= 1) { // 特殊情况说明
return false;
}
for(int i = 2; i < n; i++) { // 循环遍历2~n-1
if(n % i == 0) { // 如果i为n的因子
return false; // n不是质数
}
}
return true; // 如果n没有因子,n是质数
}
- 例题:输入三角形的三个顶点坐标,输出三条边的长度。
#include <iostream>
#include <cstdio>
using namespace std;
#define EPS 0.001 // 控制计算精度
// 求a的平方根,参考:1-C语言程序设计\week 3\6-NewtonSquare.cpp
double Sqrt(double a)
{
double X = a / 2; // 猜测一个值X
double lastX = X + EPS + 1; // 确保能够进行至少一次迭代
while((X - lastX > EPS) || (lastX - X > EPS)) { // 如果精度未达到要求,则继续迭代
lastX = X; // 保存当前近似值
X = (X + a / X) / 2; // 根据迭代公式求下一个近似值
}
return X; // 输出满足精度的近似值
}
// 求点(x1, y1)和点(x2, y2)的距离
double Distance(double x1, double y1, double x2, double y2)
{
return Sqrt((x1-x2) * (x1-x2) + (y1-y2) * (y1-y2));
}
int main()
{
int x1, y1, x2, y2, x3, y3; // 三个顶点坐标:(x1, y1), (x2, y2), (x3, y3)
cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3; // 输入三角形的三个顶点坐标
double length1 = Distance(x1, y1, x2, y2);
double length2 = Distance(x2, y2, x3, y3);
double length3 = Distance(x1, y1, x3, y3);
cout << length1 << ", " << length2 << ", " << length3 << endl; // 输出三条边的长度
return 0;
}
函数的声明
- 一般来说,函数的定义出现在函数调用语句之前,否则会导致编译错误。
- 如果函数调用语句之前有函数声明,则不一定要有定义。
- 函数声明也称为函数的原型,参数名称可以省略。
- 如:double Distance(double, double, double, double);
main函数
- C/C++程序从main函数开始执行,到main函数中的return语句结束。
函数参数的传递
- 函数的形参是实参的拷贝,且形参的改变不会影响到实参(除非形参类型是数组、引用或对象)。
- 测试:Swap函数
// 交换形参a和b
void Swap(int a, int b)
{
int tmp; // tmp是暂时保存数据的中间值
tmp = a; a = b; b = tmp; // 交换a和b的数据
cout << "Swap: a = " << a << ", b = " << b << endl; // 输出形参
return;
}
数组作为函数参数
- 传递引用,形参数组改变后,实参数组也会改变。形参数组的地址就是实参数组的地址。
- 测试:ArrayMax函数
// 求数组a中的最大值
int ArrayMax(int a[], int length) // length为数组长度
{
int max = a[0];
for(int i = 0; i < length; i++) { // 循环遍历数组下标
if(max < a[i]) { // 如果max小于a[i]
max = a[i]; // 更新max
}
}
return max;
}
- 一维数组作为形参时,不用写出数组的元素个数。
- 如:void PrintArray(int a[]) { }
- 二维数组作为形参时,必须写出数组的列数,不用写出行数,编译器必须知道列数才能根据下标算出元素的地址。
- 如:void PrintArray(int a[][5]) { }
递归
递归
- 一个函数,自己调用自己,就称为递归。
- 递归函数必须有终止条件,否则就会无穷递归导致程序无法终止甚至崩溃。
- 测试:Factorial函数
// 求n的阶乘
int Factorial(int n)
{
if(n < 2) // 终止条件
return 1;
else
return n * Factorial(n-1); // 递归
}
- 测试:Fibonacci函数
// 求斐波那契数列中的第n项
int Fibonacci(int n)
{
if((n == 1) || (n == 2)) // 终止条件
return 1;
else
return Fibonacci(n-1) + Fibonacci(n-2);
}
库函数和头文件
库函数和头文件
- 库函数:C/C++标准规定的、编译器自带的函数,如cin,cout。
- 头文件:C++编译器提供许多头文件,如iostream,cmath。
- 头文件内部包含许多库函数的声明以及其他信息。
位运算
位运算
- 位运算用于对整数类型(int、char、long等)变量中的某一位(bit)或若干位进行操作。
位运算符
- 按位与(&):可以将变量中的某些位清0并保留其他位不变,也可以获取变量中的某一位。
- 例如,将int型变量n的低8位清0,其余位不变:n &= 0xffffff00;
- 例如,判断int型变量n的第7位(从右往左,从0开始数)是否为1:n & 0x80 == 0x80
- 按位或(|):可以将变量中的某些位置1并保留其他位不变。
- 例如,将int型变量n的低8位置1,其余位不变:n |= 0xff;
- 按位异或(^):可以将变量中的某些位取反并保留其他位不变,也可以交换两个变量的值。
- 例如,将int型变量n的低8位取反,其余位不变:n ^= 0xff;
- 例如,交换a和b的值:a ^= b; b ^= a; a ^= b;
- 按位非(~):可以将操作数中的二进制位0变成1,1变成0。
- 左移(<<):左移时,高位丢弃,低位补0。左移1位,相当于乘以2。
- 例如,将操作数a左移n位:a << n
- 右移(>>):右移时,低位丢弃,高位补与符号位相同的数。右移1位,相当于除以2,并且结果相小取整。
- 例如,将操作数a右移n位:a >> n
- 测试:位操作符
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n1 = 15; // n1:0000 0000 0000 0000 0000 0000 0000 1111
n1 >>= 2; // n1右移2位:0000 0000 0000 0000 0000 0000 0000 0011
printf("n1 = %d\n", n1); // 输出:3
short n2 = -15; // n2:1111 1111 1111 0001
n2 >>= 3; // n2右移3位:1111 1111 1111 1110
printf("n2 = %d\n", n2); // 输出:-2
unsigned short n3 = 0xffe0; // n3:1111 1111 1110 0000
n3 >>= 4; // n3右移4位:1111 1111 1111 1110
printf("n3 = %d\n", n3); // 输出:0xffe
char n4 = 15; // n4:0000 1111
n4 >>= 3; // n4右移3位:0000 0001
printf("n4 = %d\n", n4); // 输出:1
return 0;
}
字符串
字符串的形式
- 字符串常量
- 用双引号("")括起来,如"CHINA",“C++ program”。
- 占据内存的字节数为字符串长度加1,多出来的是结尾字符’\0’。
- 空串("")也是合法的字符串常量,占据1字节的内存。
- 用char数组存放字符串
- 数组存放的字符串为’\0’前面的字符组成。
- 数组的内容可以在初始化时设定,也可以用C++库函数修改,也可以用对元素赋值的方式修改。
- string对象
- string是C++标准模板库里的一个类,专门用于处理字符串。
用scanf读入字符串
- 用scanf可以将字符串读入字符数组,直到读入空格为止,并自动添加结尾字符’\0’。
- 如:scanf("%s", word);
- 在数组长度不足的情况下,scanf可能导致数组越界。
读入一行到字符数组
- 读入一行(行长度不超过bufSize-1)或bufSize-1个字符到字符数组,并自动添加结尾字符’\0’。
- 如:cin.getline(buf, bufSize);
- 读入一行(行长度没有限制)到字符数组,并自动添加结尾字符’\0’。
- 如:gets(buf);
- 回车换行符不会写入buf,但是会从输入流中去掉。
字符串库函数
- 字符串复制:strcpy
- strcpy(dest, src):把src指向的字符串复制到dest,返回dest。
- 字符串拼接:strcat
- strcat(dest, src):把src指向的字符串追加到dest指向的字符串的结尾,返回dest。
- 字符串比较:strcmp
- strcmp(str1, str2):比较str1和str2,若相等则返回0,若str1大则返回正数,若str1大则返回负数。
- 求字符串长度:strlen
- strlen(str):计算str的长度,不包括’\0’在内,返回str的长度。
- 字符串转换为大写:strupr
- strupr(str):将str中的字符转换为大写,返回str。
- 字符串转换为小写:strlwr
- strlwr(str):将str中的字符转换为小写,返回str。
- 测试:字符串库函数的使用
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
// 输出词典序小的字符串
void PrintSmall(char str1[], char str2[])
{
if(strcmp(str1, str2) <= 0) // 如果str1小于等于str2
cout << str1;
else // 如果str1大于str2
cout << str2;
}
int main()
{
char str1[30], str2[40];
strcpy(str1, "Hello"); // 字符串复制:str1 = "Hello"
strcpy(str2, str1); // 字符串复制:str2 = str1 = "Hello"
cout << "(1) " << str2 << endl; // 输出:(1) Hello
strcat(str1, ", world"); // 字符串拼接:str1 = "Hello, world"
cout << "(2) " << str1 << endl; // 输出:(2) Hello, world
cout << "(3) "; PrintSmall("abc", str2); cout << endl; // 输出:(3) Hello
cout << "(4) "; PrintSmall("abc", "aaa"); cout << endl; // 输出:(4) aaa
int len = strlen(str1); // 求str1的长度len
cout << "(5) " << len << endl; // 输出:(5) 12
strupr(str1); // 字符串转换为大写:str1 = "HELLO, WORLD"
cout << "(6) " << str1 << endl; // 输出:(6) HELLO, WORLD
char s[100] = "test";
for(int i = 0; i < strlen(s); i++) { // 每次循环都调用strlen函数,造成浪费
s[i] = s[i] + 1; // 此句并不重要,只是为了说明要访问s[i]
}
// 修改1
int length = strlen(s); // 调用一次strlen函数
for(int i = 0; i < length; i++) {
s[i] = s[i] + 1;
}
// 修改2
for(int i = 0; s[i]; i++) { // s[i]='\0'时结束循环
s[i] = s[i] + 1;
}
return 0;
}
字符串
- 例题:输入两个字符串,判断后者是否为前者的子串,输出子串第一次出现的位置。
// 判断str2是否是str1的子串,求子串第一次出现的位置
int Strstr(char str1[], char str2[])
{
if(str2[0] == 0) { // 空串是任何串的子串,且出现位置为0
return 0;
}
else { // 如果str2不是空串
for(int i = 0; str1[i]; i++) { // 循环遍历str1的每个字符
int j = 0, k = i; // j为str2的下标,k为str1的假设子串的下标
for( ; str2[j]; j++, k++) { // 循环遍历str2的每个字符
if(str2[j] != str1[k]) { // 如果str2不等于str1的假设子串
break; // 跳出循环,继续尝试下一个
}
}
if(str2[j] == 0) { // 如果str2[j]='\0',即str2是str1的子串
return i; // 返回子串第一次出现的位置
}
}
return -1; // 如果str2不是str1的子串,返回-1
}
}
指针的基本概念和用法
指针的基本概念
- 指针(指针变量)是大小为4个字节(或8个字节)的变量,其内容代表一个内存地址。
- 通过指针,能够对该指针指向的内存区域进行读写。
指针的用法
- *:间接引用运算符
- p:T类型的指针,存放变量(*p)的地址
- *p:T类型的变量,存放从地址p开始的sizeof(T)个字节的内容
- &:取地址运算符
- x:T类型的变量
- &x:T类型的指针,存放变量(x)的地址
- 测试:指针的用法
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char ch1 = 'A'; // 此时ch1的值为'A'
char *pc = &ch1; // pc指向变量ch1
*pc = 'B'; // *pc相当于ch1,此时ch1的值为'B'
char ch2 = *pc; // *pc相当于ch1,此时ch2的值为'B'
pc = &ch2; // pc指向变量ch2
*pc = 'D'; // *pc相当于ch2,此时ch2的值为'D'
return 0;
}
指针的意义和互相赋值
指针的作用
- 通过指针,程序能访问的内存区域不仅限于变量所占据的数据区域。
- 在C++中,指针p指向变量a的地址,对p进行加减操作,就能访问a前后的内存区域。
指针的互相赋值
- 不同类型的指针,如果不经过强制类型转换,不能直接互相赋值。
指针的运算
指针的运算
- 两个同类型的指针可以比较存放地址的大小。
- 两个同类型的指针可以相减。
- p1-p2:p1与p2之间可以存放多少T类型的变量
- 指针加减一个整数的结果是指针。
- p+n,p-n:p指向*p前后的内存区域
- 指针可以自增或自减。
- ++p,p++:p指向*p+sizeof(T)
- –p,p–:p指向*p-sizeof(T)
- 指针可以用下标运算符([])进行运算。
- p[n]:相当于*(p+n)
- 测试:指针的运算
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int *p1, *p2; // 定义int类型指针p1和p2
char *pc1, *pc2; // 定义char类型指针pc1和pc2
p1 = (int *)100; // p1的值为100,类型为int *
p2 = (int *)200; // p2的值为200,类型为int *
cout << "(1) " << p2 - p1 << endl; // 输出:(1) 25
// p2 - p1 = (200-100)/sizeof(int) = 100/4 = 25
pc1 = (char *)p1; // pc1的值为100,类型为char *
pc2 = (char *)p2; // pc1的值为200,类型为char *
cout << "(2) " << pc1 - pc2 << endl; // 输出:(2) -100
// pc2 - pc1 = (100-200)/sizeof(char) = -100/1 = -100
int n = 4;
int *p3 = p2 + n; // p3的值为216,类型为int *
cout << "(3) " << p3 - p1 << endl; // 输出:(3) 29
// p3 - p1 = (216-100)/sizeof(int) = 116/4 29
cout << "(4) " << (pc2 - 10) - pc1 << endl; // 输出:(4) 90
// (pc2 - 10) - pc1 = ((200-10)-100)/1 = 90
return 0;
}
通过指针实现自由访问内存
- 例题:如何访问int型变量a前面的一个字节?
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a; // 定义int型变量a
// int *p = &a; // &a是int类型的指针,int类型占4个字节
char *p = (char *)&a; // 需要强制类型转换,char类型占1个字节
p--; // p指向变量a前面的1个字节
printf("%c", *p); // 可能导致运行错误
*p = 'A'; // 可能导致运行错误
return 0;
}
指针作为函数参数
空指针
- 地址0不能访问,指向地址0的指针就是空指针,空指针的值为0或NULL。
- 指针可以作为条件表达式使用,如果指针的值为NULL,则表达式的值为false。
指针作为函数参数
- 测试:Swap函数
#include <iostream>
#include <cstdio>
using namespace std;
// 交换形参*p1和*p2
void Swap(int *p1, int *p2)
{
int tmp; // tmp是暂时保存数据的中间值
tmp = *p1; *p1 = *p2; *p2 = tmp; // 交换*p1和*p2的数据
return;
}
int main()
{
int a = 4, b = 5;
Swap(&a, &b); // 调用Swap函数,传递地址,修改指针指向的数据
cout << "a = " << a << ", b = " << b << endl; // 输出实参
return 0;
}
指针和数组
指针和数组
- 数组的名字是一个指针常量,指向数组的起始地址。
- 数组a在编译时其值已经确定,不能对a进行赋值,但可以用a对相同类型的指针赋值。
- 测试:指针和数组
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a[200]; // 定义数组a
int *p; // 定义指针p
p = a; // p指向数组a的起始地址,即p指向a[0]
*p = 10; // 相当于:a[0] = 10
*(p+1) = 20; // 相当于:a[1] = 20
p[0] = 30; // 相当于:a[0] = 30
p[4] = 40; //相当于:a[4] = 40
for(int i = 0; i < 10; i++) { // 对数组a的前10个元素赋值
*(p+i) = i; // 相当于:a[i] = i(p[i] = i)
}
p++; // p指向a[1]
cout << p[0] << endl; // p[0]的值为a[1],输出:1
p = a + 6; // p指向a[6]
cout << *p << endl; // *p的值为a[6](*p等价于p[0]),输出:6
return 0;
}
- 例题:颠倒一个数组。
#include <iostream>
#include <cstdio>
using namespace std;
// 求数组p的逆序数组
void Reverse(int *p, int size) // 形参中数组可以表示为指针形式
{
for(int i = 0; i < size/2; i++) {
int tmp = p[i]; // tmp是暂时保存数据的中间值
p[i] = p[size-1-i]; // p[i]为顺序取值
p[size-1-i] = tmp; // p[size-1-i]为逆序取值
}
return;
}
int main()
{
int a[5] = {1, 2, 3, 4, 5};
Reverse(a, sizeof(a)/sizeof(int));
for(int i = 0; i < 5; i++) {
cout << *(a+i) << ","; // 输出:5,4,3,2,1,
}
return 0;
}
指针和二维数组、指向指针的指针
指针和二维数组
- 定义N行M列的二维数组:T a[N][M];
- a[i](i是整数)是一维数组,可以看作是T类型的指针。
- a[i]的地址:数组a的起始地址+i×M×sizeof(T)
指向指针的指针
- 定义指向指针的指针:T **p;
- p是指向指针的指针,*p是指向变量的指针,**p是存放T类型的变量。
- 测试:指向指针的指针
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int *p; // p是指向int类型的指针
int **pp; // pp是指向int类型的指针的指针
int n = 1234; // n是int类型的变量
p = &n; // p指向n,即p存放的内容是n的地址
pp = &p; // pp指向p,即pp存放的内容是p的地址
cout << *(*pp) << endl; // *pp就是p,所以*(*pp)就是n,输出:1234
return 0;
}
指针和字符串
指针和字符串
- 测试:指针和字符串
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char *p = "Please input your name: \n"; // p是字符串常量
cout << p;
char name[20]; // name是字符串数组名
char *pName = name; // pName是char类型的指针,指向数组name的起始地址
cin >> pName; // pName也可以看作字符串数组名,等效于name
cout << "Your name is " << pName;
return 0;
}
字符串库函数
字符串库函数 | 功能 |
---|---|
strcat | 将一个字符串连接到另一个字符串后面 |
strncat | 将一个字符串的前n个字符连接到另一个字符串后面 |
strchr | 查找某字符在字符串中最先出现的位置 |
strrchr | 查找某字符在字符串中最后出现的位置 |
strstr | 求子串的位置 |
strcmp | 比较两个字符串的大小(大小写相关) |
stricmp | 比较两个字符串的大小(大小写无关) |
strncmp | 比较两个字符串的前n个字符 |
strcpy | 复制字符串 |
strncpy | 复制字符串的前n个字符 |
strlen | 求字符串长度 |
strlwr | 将字符串变成小写 |
strupr | 将字符串变成大写 |
strtok | 抽取被指定字符分隔的字符串 |
atoi | 将字符串转换为整数 |
atof | 将字符串转换为实数 |
itoa | 将整数转换为字符串 |
字符串库函数
- 测试:字符串库函数的使用
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char str1[100] = "12345";
char str2[100] = "abcdefg";
char str3[100] = "ABCDE";
strncat(str1, str2, 3); // 字符串拼接n个字符:str1 = "12345abc"
cout << "(1) " << str1 << endl; // 输出:(1) 12345abc
strncpy(str1, str3, 3); // 字符串复制n个字符:str1 = "ABC45abc"
cout << "(2) " << str1 << endl; // 输出:(2) ABC45abc
strncpy(str2, str3, 6); // 字符串复制n个字符:str2 = "ABCDE"
cout << "(3) " << str2 << endl; // 输出:(3) ABCDE
int cmpResult = strncmp(str1, str3, 3); // 字符串比较n个字符:str1和str3的前3个字符相等
cout << "(4) " << cmpResult << endl; // 输出:(4) 0
char *p = strchr(str1, 'B'); // 查找字符最先出现的位置:p指向str1中的字符'B'
if(p) // 如果p的值不为NULL,即str1中存在字符'B'
cout << "(5) " << p-str1 << ", " << *p << endl; // 输出:(5) 1, B
else // 如果str1中不存在字符'B'
cout << "(5) No Found" << endl;
p = strstr(str1, "45a"); // 查找子串出现的位置:p指向str1中的子串"45a"
if(p) // 如果p的值不为NULL,即str1中存在子串"45a"
cout << "(6) " << p-str1 << ", " << p << endl; // 输出:(6) 3, 45abc
else // 如果str1中不存在子串"45a"
cout << "(6) No Found" << endl;
cout << "strtok usage demo: " << endl; // 演示strtok的用法
char str[] = "- This, a sample string, OK.";
p = strtok(str, " ,.-"); // 从str中逐个抽取被指定字符( ,.-)分割的字符串
while(p != NULL) { // 如果p的值不为NULL,即找到一个子串
cout << p << endl;
p = strtok(NULL, " ,.-"); // 后续调用,第1个参数必须是NULL
}
return 0;
}
void指针和内存操作函数
void指针
- 可以用任何类型的指针对void指针进行赋值或初始化。
- 因为sizeof(void)没有定义,所以对于void指针p:*p,++p,–p,p+n,p-n等均没有定义。
内存操作库函数
- 内存初始化:memset
- memset(dest, ch, n):将从dest开始的n个字节都设置为ch(ch的最低字节),返回dest。
- 内存复制:memcpy
- memcpy(dest, src, n):将从src开始的n个字节复制到从dest开始的n个字节,返回dest。
- 测试:内存操作库函数的使用
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
// 如何编写自定义内存操作函数MyMemcpy
void *MyMemcpy(void *dest, const void *src, int n)
{
char *pDest = (char *)dest;
char *pSrc = (char *)src;
for(int i = 0; i < n; i++) { // 将源块的内容(*pSrc)复制到目的块(*pDest)
*(pDest+i) = *(pSrc+i); // 逐个字节复制
}
return dest;
// 如果源区域和目标区域有重叠,则会出错
// 目标区域的开始部分(源区域的结束部分)存放的内容会被改写
}
int main()
{
int a[100];
memset(a, 0, sizeof(a)); // 将数组a的元素设置为0,即初始化数组a
char szName[200] = " "; // 数组szName的元素都为'\0'
memset(szName, 'a', 10); // 将szName的前10个字节设置为'a'
cout << szName << endl; // 输出:aaaaaaaaaa
int a1[5] = {0, 1, 2, 3, 4};
int a2[5] = {5, 6, 7, 8, 9};
memcpy(a2, a1, 3*sizeof(int)); // 将数组a1的前3个元素的内容复制到数组a2
for(int i = 0; i < 5; i++) {
cout << a2[i] << ","; // 输出:0,1,2,8,9,
}
cout << endl;
MyMemcpy(a2, a1, 5*sizeof(int)); // 将数组a1的所有内容复制到数组a2
for(int i = 0; i < 5; i++) {
cout << a2[i] << ","; // 输出:0,1,2,3,4,
}
return 0;
}
函数指针
基本概念
- 程序运行期间,函数会占用一段连续的内存空间,函数名就是函数所占内存区域的起始地址。
- 使指针指向函数的起始地址,通过该函数指针就可以调用该函数。
函数指针
- 定义函数指针:int (*pf)(int, char);
- pf是函数指针,所指向的函数返回int类型,参数为int类型、char类型等。
- 可以用一个原型匹配的函数名给一个函数指针赋值。
- 测试:函数指针的使用
#include <iostream>
#include <cstdio>
using namespace std;
// 输出较小的数
void PrintMin(int a, int b)
{
if(a < b)
printf("%d", a);
else
printf("%d", b);
}
int main()
{
void (*pf)(int, int); // pf是函数指针
int x = 4, y = 6;
pf = PrintMin; // pf指向函数PrintMin
pf(x, y); // 通过pf调用指向的函数PrintMin
return 0;
}
函数指针和qsort库函数
- 快速排序:qsort
- qsort(base, nitems, size, pfCompare):可以对任意类型的数组进行排序。
- base:数组名(数组起始地址)。
- nitems:数组元素的个数。
- size:数组元素的大小(以字节为单位),由此可以计算每个元素的地址。
- pfCompare:函数指针,指向比较函数,由此可以确定元素谁在前谁在后的规则。
- 比较函数编写规则:pfCompare(elem1, elem2)
- 如果elem1应该排在elem2前面,则返回负整数。
- 如果elem2应该排在elem1前面,则返回正整数。
- 如果都可以排在前面,则返回0。
- 测试:qsort函数的使用
#include <iostream>
#include <cstdio>
using namespace std;
#define NUM 5
// 自定义比较函数
int MyCompare(const void *elem1, const void * elem2)
{
unsigned int *p1 = (unsigned int *)elem1; // 直接用*elem1是非法的,编译器不知道*elem1有多少字节
unsigned int *p2 = (unsigned int *)elem2;
return (*p1 % 10) - (*p2 % 10); // 比较*p1和*p2的个位数的大小,按从小到大排序
}
int main()
{
unsigned int an[NUM] = {8, 123, 11, 10, 4};
qsort(an, NUM, sizeof(unsigned int), MyCompare); // 参数:数组名,元素个数,元素大小,比较函数
// 函数原型:void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
for(int i = 0; i < NUM; i++) {
printf("%d,", an[i]); // 输出:10,11,123,4,8,
}
return 0;
}
结构(struct)
结构(struct)
- C++允许程序员定义新的数据类型,该数据类型(结构)可以用来定义变量(结构变量)。
- 如:struct Student { int ID; … }; Student Stu;
- 结构变量中的成员变量通常是连续存放在内存空间,因此结构变量大小就是所有成员变量大小之和。
- 两个同类型的结构变量,可以互相赋值,但不能进行比较运算。
- 结构的成员变量可以是任何类型,也可以是另一个结构类型,也可以是指向本结构类型的指针。
访问结构变量的成员变量
- 结构变量的成员变量可以和普通变量一样使用,也可以取得其地址。
- 如:Stu.ID = 12345; int *p = &Stu.ID;
结构数组
- 测试:结构数组的使用
#include <iostream>
#include <cstdio>
using namespace std;
struct Date {
int year;
int month;
int day;
};
struct StudentEx {
unsigned ID;
char szName[20];
float fGPA;
Date birthday;
};
int main()
{
// StudentEx MyClass[50];
StudentEx MyClass[50] = {
{1234, "Tom", 3.78, {1984, 12, 28}},
{1235, "Jack", 3.25, {1984, 12, 23}},
{1236, "Mary", 4.00, {1984, 12, 21}},
{1237, "Jone", 2.78, {1984, 2, 28}}
};
MyClass[1].ID = 1267;
MyClass[2].birthday.year = 1986;
int n = MyClass[2].birthday.month;
cin >> MyClass[0].szName;
return 0;
}
指向结构变量的指针
- 定义指向结构变量的指针,通过该指针可以访问其指向的结构变量的成员变量。
- 如:Student *pStu = &stu; pStu->ID = 12345;
全局变量、局部变量、静态变量
全局变量和局部变量
- 定义在所有函数外面的变量称为全局变量。
- 定义在函数内部的变量称为局部变量(函数的形参也是局部变量)。
- 全局变量在所有函数中均可以使用,局部变量只能在定义它的函数内部使用。
静态变量
- 全局变量都是静态变量。
- 局部变量定义为静态变量时,需要在前面加关键字static。
静态变量和非静态变量
- 静态变量的地址,在整个程序运行期间,都是固定不变的。
- 非静态变量的地址,每次函数调用时都可能不同,在函数的一次执行期间不变。
- 如果没有初始化,静态变量自动赋初值为0(每个bit都是0),非静态变量的值为随机。
- 测试:局部变量作为静态变量
#include <iostream>
#include <cstdio>
using namespace std;
void Func()
{
static int n = 4; // 静态变量只执行一次初始化语句
// int n = 4; // 非静态变量每次都执行初始化语句
cout << n << endl;
n++;
}
int main()
{
Func(); Func(); Func();
return 0;
}
- 例题:从字符串中逐个抽取被指定字符分割的子串。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
// 从字符串p中逐个抽取被指定字符sep分割的子串
char *Strtok(char *p, char *sep)
{
static char *start; // start是静态变量,只执行一次初始化语句
if(p) // 如果p的值不为NULL,即第1次调用函数
start = p; // 设置本次查找子串的起点
for( ; *start && strchr(sep, *start); start++) // 如果字符*start出现在字符串sep中,继续循环
; // 跳过分隔符号,直到遇到非分隔符号
if(*start == 0) { // 如果字符串start已经循环遍历到结尾字符'\0'
return NULL; // 返回空值
}
char *q = start; // q为下一个子串
for( ; *start && !strchr(sep, *start); start++) // 如果字符*start没有出现在字符串sep中,继续循环
; // 跳过非分隔符号,直到遇到分隔符号
if(*start) { // 如果*start不为NULL,即字符串没有到结尾
*start = 0; // 添加结尾字符'\0',独立分开子串
start++; // 设置后续调用的起点,延续指针start
}
return q; // 返回子串
}
int main()
{
char str[] = "- This, a sample string, OK.";
char *p = Strtok(str, " ,.-"); // 从str中逐个抽取被指定字符( ,.-)分割的字符串
while(p != NULL) { // 如果p的值不为NULL,即找到一个子串
cout << p << endl;
p = Strtok(NULL, " ,.-"); // 后续调用,第1个参数必须是NULL
}
return 0;
}
变量的作用域和生存期
标识符的作用域
- 变量名、函数名、类型名统称为标识符。
- 标识符能够起作用的范围,称为标识符的作用域。
- 在单文件的程序中,结构、函数和全局变量的作用域是其定义所在的整个文件。
- 局部变量的作用域是从定义语句开始到右大括号(})结束。
- 函数形参的作用域是整个函数。
- 在for循环中,循环控制变量的作用域是整个循环。
- 同名的标识符的作用域,在小的作用域中,作用域大的标识符会被屏蔽。
变量的生存期
- 变量的生存期指的是,在此期间变量占有的内存空间只能归它所有,不能存放别的东西。
- 全局变量的生存期,从程序被装入内存开始,到整个程序结束。
- 静态局部变量的生存期,从定义语句第一次执行开始,到整个程序结束。
- 非静态局部变量的生存期,从定义语句开始,到作用域之外结束。
- 函数形参的生存期,从函数执行开始,到函数返回结束。
选择排序、插入排序、冒泡排序
十大排序算法
- 冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、计数排序、桶排序、基数排序
- 菜鸟教程:https://www.runoob.com/w3cnote/ten-sorting-algorithm.html
选择排序
- 选择排序的算法描述
- 在未排序序列中找到最小/大元素,存放到排序序列的起始位置(最小/大元素与起始位置交换)。
- 从剩余未排序元素中继续寻找最小/大元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
- 例题:使用选择排序对数组从小到大排序。
// 选择排序函数
void SelectionSort(int a[], int size)
{
for(int i = 0; i < size-1; i++) { // 循环遍历数组a
int tmpMin = i; // tmpMin是剩下未排序的元素中的最小元素的下标
for(int j = i+1; j < size; j++) { // 循环遍历剩下未排序的元素
if(a[j] < a[tmpMin]) {
tmpMin = j; // 更新tmpMin
}
}
int tmp = a[i]; // 交换最小元素和排列序列末尾元素
a[i] = a[tmpMin];
a[tmpMin] = tmp;
}
}
插入排序
- 插入排序的算法描述
- 将序列的第1个元素看作是有序序列,将第2个元素到最后1个元素看作是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
- 如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。
- 例题:使用插入排序对数组从小到大排序。
// 插入排序函数
void InsertionSort(int a[], int size)
{
for(int i = 1; i < size; i++) { // 循环遍历未排序序列(右边)
for(int j = 0; j < i; j++) { // 循环遍历有序序列(左边)
if(a[i] < a[j]) { // a[i]正好小于a[j],需要插入到a[j]前面
int tmp = a[i]; // 暂时保存a[i]
for(int k = i; k > j; k--) { // 循环遍历j-1~i
a[k] = a[k-1]; // 从a[j]~a[i-1]逐个元素向后移1个元素
}
a[j] = tmp; // 把a[i]插入适当位置,即插入a[j]原来的位置
break;
}
}
}
}
冒泡排序
- 冒泡排序的算法描述
- 依次比较相邻的两个元素,如果前者比后者大,就交换它们的位置,移动结束后最大的元素会在最后。
- 针对所有的元素重复以上的步骤,除了最后的已排序序列。
- 持续每次对越来越少的元素重复以上的步骤,直到没有任何一对数字需要比较。
- 例题:使用冒泡排序对数组从小到大排序。
// 冒泡排序函数
void BubbleSort(int a[], int size)
{
for(int i = size-1; i > 0; i--) { // 循环遍历未排序序列(左边)
for(int j = 0; j < i; j++) { // 依次比较相邻的两个元素,移动结束后最大的元素会在最后
if(a[j] > a[j+1]) { // 如果前者比后者大
int tmp = a[j]; // 交换两个元素的位置
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
}
程序或算法的时间复杂度
程序或算法的时间复杂度
- 一个程序或算法的时间效率,称为时间复杂度,简称为复杂度。
- 复杂度常用字母O和字母n表示,如O(n),O(n2)。
- n代表问题的规模
- 平均复杂度和最坏复杂度可能一致,也可能不一致。
- 如果复杂度是多个n的函数之和,则只关心随n增长最快的函数。
- O(n3+n2)→O(n3)
- O(2n+n3)→O(2n)
- O(n!+n2)→O(n!)
- 常见的时间复杂度
- 常数复杂度:O(1)
- 对数复杂度:O(log(n))
- 线性复杂度:O(n)
- 多项式复杂度:O(nk)
- 指数复杂度:O(an)
- 阶乘复杂度:O(n!)
- 常见算法的时间复杂度
- 在无序数列中查找某个数(顺序查找):O(n)
- 在有序数列中查找某个数(二分查找):O(log(n))
- 平面上有n个点,求任意两点之间的距离:O(n2)
- 插入排序、选择排序、冒泡排序:O(n2)
- 快速排序:O(n*log(n))
STL排序算法
STL概述
- STL:标准模板库(Standard Template Library)
- 包含常用的算法,如排序查找;还包含常用的数据结构,如可变长数组、链表、字典等。
用sort进行排序
- 对基本类型的数组从小到大排序:sort(a+n1, a+n2);
- 排序范围是数组a的下标[n1, n2),如果n1是0则可以省略。
- 对基本类型的数组从大到小排序:sort(a+n1, a+n2, greater());
- functional提供了一堆基于模板的比较函数对象。
- 自定义比较函数:sort(begin, end, cmp);
- begin是要排序的数组的起始地址,end是结束地址,cmp是排序的方法。
- cmp(elem1, elem2):如果elem1应该排在elem2前面,则返回true。
- 重载结构体或类的比较运算符:sort(begin, end, Student());
- struct Student{ bool operator()(const T &elem1, const T &elem2) { … } };
- 测试:sort函数的使用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct Rule1 { // 按从大到小排序
bool operator() (const int &elem1, const int &elem2) {
return elem1 > elem2;
}
};
struct Rule2 { // 按个位数从小到大排序
bool operator() (const int &elem1, const int &elem2) {
return (elem1 % 10) < (elem2 % 10);
}
};
struct Student {
char name[20];
int id;
double gpa;
};
Student students[] = {
{"Jack", 112, 3.4},
{"Mary", 102, 3.8},
{"Mary", 117, 3.9},
{"Ala", 333, 3.5},
{"Zero", 101, 4.0}
};
struct StudentRule1 { // 按姓名从小到大排序
bool operator() (const Student &s1, const Student &s2) {
if(stricmp(s1.name, s2.name) < 0)
return true;
return false;
}
};
struct StudentRule2 { // 按ID从小到大排序
bool operator() (const Student &s1, const Student &s2) {
return s1.id < s2.id;
}
};
struct StudentRule3 { // 按GPA从高到低排序
bool operator() (const Student &s1, const Student &s2) {
return s1.gpa > s2.gpa;
}
};
void Print(int a[], int size)
{
for(int i = 0; i < size; i++) {
cout << a[i] << ",";
}
cout << endl;
}
void PrintStudents(Student stu[], int n)
{
for(int i = 0; i < n; i++) {
cout << "(" << stu[i].name << "," << stu[i].id << "," << stu[i].gpa << "),";
}
cout << endl;
}
int main()
{
int a[] = {12, 45, 3, 98, 21, 7};
int size = sizeof(a) / sizeof(int);
sort(a, a+size); // 按从小到大排序
cout << "(1) "; Print(a, size);
sort(a, a+size, Rule1()); // 按从大到小排序
cout << "(2) "; Print(a, size);
sort(a, a+size, Rule2()); // 按个位数从小到大排序
cout << "(3) "; Print(a, size);
int n = sizeof(students) / sizeof(Student);
sort(students, students+n, StudentRule1()); // 按姓名从小到大排序
cout << "(4) "; PrintStudents(students, n);
sort(students, students+n, StudentRule2()); // 按ID从小到大排序
cout << "(5) "; PrintStudents(students, n);
sort(students, students+n, StudentRule3()); // 按GPA从高到低排序
cout << "(6) "; PrintStudents(students, n);
return 0;
}
STL二分查找算法
用binary_search进行二分查找
- 在从小到大排序的基本类型的数组中进行二分查找:binary_search(a+n1, a+n2, value);
- 在下标范围是[n1, n2)的查找区间内查找等于value的元素,返回值是true或false。
- 重载结构体或类的比较运算符:binary_search(begin, end, value, Student());
- 查找时的排序规则,必须和排序时的排序规则一致。
- a与b相等:a必须在b前面、b必须在a前面,这两种情况都不成立。
- 测试:binary_search函数的使用
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct Rule { // 按个位数从小到大排序
bool operator() (const int &elem1, const int &elem2) {
return (elem1 % 10) < (elem2 % 10);
}
};
void Print(int a[], int size)
{
for(int i = 0; i < size; i++) {
cout << a[i] << ",";
}
cout << endl;
}
int main()
{
int a[] = {12, 45, 3, 98, 21, 7};
sort(a, a+6);
Print(a, 6);
cout << "result: " << binary_search(a, a+6, 12) << endl;
cout << "result: " << binary_search(a, a+6, 77) << endl;
sort(a, a+6, Rule()); // 按个位数从小到大排序
Print(a, 6);
cout << "result: " << binary_search(a, a+6, 7) << endl;
cout << "result: " << binary_search(a, a+6, 8, Rule()) << endl;
return 0;
}
用lower_bound进行二分查找下界
- 在从小到大排序的基本类型的数组中进行二分查找:lower_bound(a+n1, a+n2, value);
- 返回T类型的指针p,*p是查找区间里下标最小的、大于等于value的元素。
- 如果找不到,p指向下标是n2的元素。
- 重载结构体或类的比较运算符:lower_bound(begin, end, value, Student());
- *p是查找区间里下标最小的、按自定义排序规则可以排在value后面的元素。
用upper_bound进行二分查找上界
- 在从小到大排序的基本类型的数组中进行二分查找:upper_bound(a+n1, a+n2, value);
- *p是查找区间里下标最小的、大于value的元素。
- 重载结构体或类的比较运算符:upper_bound(begin, end, value, Student());
- *p是查找区间里下标最小的、按自定义排序规则必须排在value后面的元素。
- 测试:lower_bound和upper_bound函数的使用
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define NUM 7
struct Rule { // 按个位数从小到大排序
bool operator() (const int &elem1, const int &elem2) {
return (elem1 % 10) < (elem2 % 10);
}
};
void Print(int a[], int size)
{
for(int i = 0; i < size; i++) {
cout << a[i] << ",";
}
cout << endl;
}
int main()
{
int a[NUM] = {12, 5, 3, 5, 98, 21, 7};
int *p;
sort(a, a+NUM); // 按从小到大排序
Print(a, NUM); // 输出:3,5,5,7,12,21,98,
p = lower_bound(a, a+NUM, 5); // 找到下标最小的、大于等于5的元素
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 5, position: 1
p = upper_bound(a, a+NUM, 5); // 找到下标最小的、大于5的元素
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 7, position: 3
p = upper_bound(a, a+NUM, 13); // 找到下标最小的、大于13的元素
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 21, position: 5
sort(a, a+NUM, Rule()); // 按个位数从小到大排序
Print(a, NUM); // 输出:21,12,3,5,5,7,98
p = lower_bound(a, a+NUM, 16, Rule()); // 找到下标最小的、按自定义排序规则可以排在16后面的元素
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 7, position: 5
p = lower_bound(a, a+NUM, 25, Rule()); // 找到下标最小的、按自定义排序规则可以排在25后面的元素
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 5, position: 3
p = upper_bound(a, a+NUM, 5, Rule()); // 找到下标最小的、按自定义排序规则必须排在5后面的元素
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 7, position: 5
p = upper_bound(a, a+NUM, 18, Rule()); // 找到下标最小的、按自定义排序规则必须排在18后面的元素
if(p == a+NUM) // 如果找不到,p指向a[NUM],即空
cout << "Not Found." << endl;
cout << "value: " << *p << ", position: " << p-a << endl; // 输出:value: 0, position: 7
return 0;
}
multiset,set,multimap,map
平衡二叉树数据结构
- 在大量增加数据、删除数据的同时进行查找数据,不能用排序+二分查找,每次增删数据后要重新排序。
- 可以用平衡二叉树数据结构存放数据,在STL中有四种排序容器:multiset,set,multimap,map。
multiset
- 定义multiset变量:multiset st;
- st可以存放T类型的数据,数据可以重复,并且能时刻维持数据有序。
- st.insert插入数据,st.erase删除数据,st.find查找数据,复杂度都是O(log(n))。
- 测试:multiset的使用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set> // 使用multiset和set需要包含此头文件
using namespace std;
int main()
{
multiset<int> st;
int a[10] = {1, 14, 12, 13, 7, 13, 21, 19, 8, 8};
for(int i = 0; i < 10; i++) {
st.insert(a[i]); // 插入数据,容器st的数据是a[i]的复制数据
}
multiset<int>::iterator i; // 迭代器,相当于指针
for(i = st.begin(); i != st.end(); i++) { // 循环遍历容器st的数据
cout << *i << ","; // 输出:1,7,8,8,12,13,13,14,19,21,
}
cout << endl;
i = st.find(19); // 查找数据,返回值是迭代器
if(i == st.end()) { // 如果找不到,则返回end()
cout << "Not found." << endl;
}
else { // 如果找到,则返回指向找到的元素的迭代器
cout << "Found number: " << *i << endl;
}
i = st.lower_bound(13); // 返回13的第一个可插入位置,即大于等于13的最小元素
cout << "Found lower_bound: " << *i << endl; // 输出:Found lower_bound: 13
i = st.upper_bound(8); // 返回8的最后一个可插入位置,即大于8的最小元素
cout << "Found upper_bound: " << *i << endl; // 输出:Found upper_bound: 12
st.erase(i); // 删除迭代器i指向的元素,即12
for(i = st.begin(); i != st.end(); i++) {
cout << *i << ","; // 输出:1,7,8,8,13,13,14,19,21,
}
return 0;
}
multiset的迭代器
- 定义multiset的迭代器:multiset::iterator p;
- p是迭代器,相当于指针,指向multiset中的元素,访问multiset中的元素要通过迭代器。
- 迭代器可以++和–,用!=和==比较,不可以比较大小,加减整数。
- st.begin()是指向st第一个元素的迭代器,st.end()是指向st最后一个元素的迭代器。
自定义排序规则的multiset
- 测试:自定义排序规则的multiset的使用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
struct Rule { // 按个位数从小到大排序
bool operator() (const int &elem1, const int &elem2) {
return (elem1 % 10) < (elem2 % 10);
}
};
int main()
{
int a[10] = {1, 14, 12, 13, 7, 13, 21, 19, 8, 8};
multiset<int, greater<int>> st1; // 按从大到小排序
for(int i = 0; i < 10; i++)
st1.insert(a[i]); // 插入数据
multiset<int, greater<int>>::iterator i; // st1的迭代器
for(i = st1.begin(); i != st1.end(); i++)
cout << *i << ","; // 输出:21,19,14,13,13,12,8,8,7,1,
cout << endl;
multiset<int, Rule> st2; // 按个位数从小到大排序
for(int i = 0; i < 10; i++)
st2.insert(a[i]); // 插入数据
multiset<int, Rule>::iterator j; // st2的迭代器
for(j = st2.begin(); j != st2.end(); j++)
cout << *j << ","; // 输出:1,21,12,13,13,14,7,8,8,19,
cout << endl;
j = st2.find(133); // 查找数据,即与133相等的元素,根据自定义排序规则,133等同于13
// find(x):在排序容器中找元素y,使得x必须排在y前面、y必须排在x前面,这两种情况都不成立。
cout << "Found number: " << *j << endl; // 输出:Found number: 13
return 0;
}
- 测试:自定义排序规则的multiset的使用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
struct Student {
char name[20];
int id;
double score;
};
Student students[] = {
{"Jack", 112, 78},
{"Mary", 102, 85},
{"Ala", 333, 92},
{"Zero", 101, 70},
{"Cindy", 102, 78}
};
struct Rule { // 按分数从高到低排序,分数相同则按姓名从小到大排序
bool operator() (const Student &s1, const Student &s2) {
if(s1.score != s2.score)
return s1.score > s2.score;
else
return (strcmp(s1.name, s2.name) < 0);
}
};
int main()
{
multiset<Student, Rule> st;
for(int i = 0; i < 5; i++)
st.insert(students[i]); // 插入数据
multiset<Student, Rule>::iterator p;
for(p = st.begin(); p != st.end(); p++)
cout << p->score << " " << p->name << " " << p->id << endl;
Student s = {"Mary", 1000, 85};
p = st.find(s); // 查找数据
// 因为排序规则不考虑ID,两个Mary谁排在前面都可以,所以看作相等的数据
if(p != st.end()) // 如果找到等于s的数据
cout << p->score << " " << p->name << " " << p->id << endl; // 输出:85 Mary 102
return 0;
}
set
- set和multiset的区别是容器里不能有重复数据(重复数据是指两个元素排序无所谓前后)。
- set插入数据可能不成功。
- 测试:set的使用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
int main()
{
set<int> st;
int a[10] = {1, 14, 12, 13, 7, 13, 21, 19, 8, 8};
for(int i = 0; i < 10; i++)
st.insert(a[i]);
cout << st.size() << endl; // 输出:8
set<int>::iterator i;
for(i = st.begin(); i != st.end(); i++)
cout << *i << ","; // 输出:1,7,8,12,13,14,19,21
cout << endl;
pair<set<int>::iterator, bool> result = st.insert(12);
// pair<set<int>::iterator, bool>
// 等价于:
// struct {
// set<int>::iterator first;
// bool second;
// };
if(!result.second) // 如果插入不成功
cout << *result.first << " already exists." << endl;
else
cout << *result.first << " inserted." << endl;
return 0;
}
pair
- pair<T1, T2>类型等价于:struct { T1 first; T2 second; };
multimap
- 定义multimap变量:multimap<T1, T2> mp;
- mp里的元素都是pair形式,都等同于:struct { T1 first; T2 second; };
- multimap中的元素按照first排序和查找,默认排序规则是按从小到大排序。
- 例题:学生成绩录入和查询系统,可以录入(Add name id score)或查询(Query score),两种输入交替出现。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map> // 使用multimap和map需要包含此头文件
using namespace std;
struct StudentInfo {
int id;
char name[20];
};
struct Student {
int score; // 按分数从小到大排序
StudentInfo info;
};
int main()
{
multimap<int, StudentInfo> mp; // int对应score,StudentInfo对应info
char cmd[20];
while(cin >> cmd) {
if(cmd[0] == 'A') { // 如果输入Add,表示录入
Student st;
cin >> st.info.name >> st.info.id >> st.score; // 继续输入name、id、score
// 向mp插入数据,其first是st.score,second是st.info
mp.insert(make_pair(st.score, st.info)); // make_pair生成一个pair<int, StudentInfo>变量
}
else if(cmd[0] == 'Q') { // 如果输入Query,表示查询
int score;
cin >> score; // 继续输入score
// 从mp查询数据,查询下标最大的、小于score的数据
multimap<int, StudentInfo>::iterator p;
p = mp.lower_bound(score); // 此时[mp.begin(), p)都是小于score的数据
if(p == mp.begin()) { // 如果mp中没有比score低的数据
cout << "Nobody" << endl;
}
else { // 如果mp中有比score低的数据
p--; // 此时p指向mp中分数比score低的最高分获得者
score = p->first;
// 从mp查询数据,查询分数等于score、学号最大的数据
multimap<int, StudentInfo>::iterator maxp = p;
int maxid = p->second.id;
for(; p->first == score; p--) { // 循环遍历所有分数等于score的数据
if(p->second.id > maxid) { // 找出这些数据中学号最大的数据
maxp = p;
maxid = p->second.id;
}
if(p == mp.begin()) { // 如果循环到起点,mp中没有数据
break; // 结束循环
}
}
cout << maxp->second.name << " " << maxp->second.id << " " << maxp->first << endl;
}
}
}
return 0;
}
map
- map和multimap的区别是容器里不能有关键字重复的数据。
- 可以使用中括号([]),下标为关键字,返回first和关键字相同的second。
- map插入数据可能不成功。
- 测试:map的使用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
struct Student {
string name;
int score;
};
Student students[5] = {
{"Jack", 89},
{"Tom", 74},
{"Cindy", 87},
{"Alysa", 87},
{"Micheal", 98}
};
int main()
{
map<string, int> mp;
for(int i = 0; i < 5; i++) {
mp.insert(make_pair(students[i].name, students[i].score)); // 插入数据
}
cout << mp["Jack"] << endl; // 输出:89
mp["Jack"] = 60; // 修改关键字为"Jack"的元素的second
cout << mp["Jack"] << endl; // 输出:60
for(map<string, int>::iterator i = mp.begin(); i != mp.end(); i++) {
cout << "(" << i->first << ", " << i->second << ") ";
} // 输出:(Alysa, 87) (Cindy, 87) (Jack, 60) (Micheal, 98) (Tom, 74)
cout << endl;
Student st;
st.name = "Jack"; st.score = 99;
pair<map<string, int>::iterator, bool> p = mp.insert(make_pair(st.name, st.score));
if(p.second) // 如果插入数据成功
cout << "(" << p.first->first << ", " << p.first->second << ")" << endl;
else
cout << "Insertion failed." << endl;
mp["Harry"] = 78; // 插入新的元素,其first是"Harry",second是78
map<string, int>::iterator q = mp.find("Harry");
cout << "(" << q->first << ", " << q->second << ")" << endl; // 输出:(Harry, 78)
return 0;
}