信息在计算机中的表示

用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.45printf("%d %c %f", n, c, m);  // 输出:34 k 234.449997return 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;  // 输出:4294967295unsigned int n2 = n1 + 3;  // 0xffffffff + 3 = 0x100000002,导致溢出cout << n2 << endl;  // 输出:2return 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;  // 整数nscanf("%d", &n);  // 输入整数nswitch(n) {  // 表达式的值必须是整型case 1:  // 常量表达式必须是整型的常量printf("Monday"); break;  // 进入分支后会一直执行语句,直到碰到breakcase 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和mcin >> n >> m;  // 输入正整数n和mint 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;  // 正整数numint max = 0, min = 100, sum = 0;  // 最大值、最小值、所有数的和cin >> num;  // 输入正整数num,num不大于100while(num) {  // 如果num为0则结束循环if(num > max) {  // 如果num大于max,更新maxmax = num;}if(num < min) {  // 如果num小于min,更新minmin = num;}sum += num;  // 更新sumcin >> 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;  // 猜测一个值Xdouble 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和mcin >> n >> m;  // 输入正整数n和mint 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.txtint 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和正整数nint 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;  // 输入正整数kint a1 = 1, a2 = 1;  // a1和a2是斐波那契数列中的前两个数if((k == 1) || (k == 2)) {  // 如果k是1或2cout << 1 << endl;  // 直接输出结果1}else {  // 如果k大于2int sum;  // sum为斐波那契数列中的第k个数for(int i = 3; i <= k; i++) {  // 循环k-2次,直到i==ksum = 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;  // 输入正整数nint 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;  // 输出阶乘的和sumreturn 0;
}
  • 例题:输入正整数n(n>=2),输出不大于n的全部质数。
#include <iostream>
#include <cstdio>
using namespace std;int main()
{int n;cin >> n;  // 输入正整数ncout << 2 << endl;  // 输出第一个质数2for(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++) {  // 循环遍历数组isPrimeif(isPrime[i]) {  // 如果值为1cout << 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;  // 输入整数nif((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和nfor(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和qfor(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;elsereturn y;
}
  • 测试:isPrime函数
// 判断n是否是质数
bool IsPrime(unsigned int n)
{if(n <= 1) {  // 特殊情况说明return false;}for(int i = 2; i < n; i++) {  // 循环遍历2~n-1if(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;  // 猜测一个值Xdouble 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;elsereturn n * Factorial(n-1);  // 递归
}
  • 测试:Fibonacci函数
// 求斐波那契数列中的第n项
int Fibonacci(int n)
{if((n == 1) || (n == 2))  // 终止条件return 1;elsereturn 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 1111n1 >>= 2;  // n1右移2位:0000 0000 0000 0000 0000 0000 0000 0011printf("n1 = %d\n", n1);  // 输出:3short n2 = -15;  // n2:1111 1111 1111 0001n2 >>= 3;  // n2右移3位:1111 1111 1111 1110printf("n2 = %d\n", n2);  // 输出:-2unsigned short n3 = 0xffe0;  // n3:1111 1111 1110 0000n3 >>= 4;  // n3右移4位:1111 1111 1111 1110printf("n3 = %d\n", n3);  // 输出:0xffechar n4 = 15;  // n4:0000 1111n4 >>= 3;  // n4右移3位:0000 0001printf("n4 = %d\n", n4);  // 输出:1return 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小于等于str2cout << str1;else  // 如果str1大于str2cout << str2;
}int main()
{char str1[30], str2[40];strcpy(str1, "Hello");  // 字符串复制:str1 = "Hello"strcpy(str2, str1);  // 字符串复制:str2 = str1 = "Hello"cout << "(1) " << str2 << endl;  // 输出:(1) Hellostrcat(str1, ", world");  // 字符串拼接:str1 = "Hello, world"cout << "(2) " << str1 << endl;  // 输出:(2) Hello, worldcout << "(3) "; PrintSmall("abc", str2); cout << endl;  // 输出:(3) Hellocout << "(4) "; PrintSmall("abc", "aaa"); cout << endl;  // 输出:(4) aaaint len = strlen(str1);  // 求str1的长度lencout << "(5) " << len << endl;  // 输出:(5) 12strupr(str1);  // 字符串转换为大写:str1 = "HELLO, WORLD"cout << "(6) " << str1 << endl;  // 输出:(6) HELLO, WORLDchar s[100] = "test";for(int i = 0; i < strlen(s); i++) {  // 每次循环都调用strlen函数,造成浪费s[i] = s[i] + 1;  // 此句并不重要,只是为了说明要访问s[i]}// 修改1int length = strlen(s);  // 调用一次strlen函数for(int i = 0; i < length; i++) {s[i] = s[i] + 1;}// 修改2for(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) {  // 空串是任何串的子串,且出现位置为0return 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和p2char *pc1, *pc2;  // 定义char类型指针pc1和pc2p1 = (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 = 25pc1 = (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 = -100int n = 4;int *p3 = p2 + n;  // p3的值为216,类型为int *cout << "(3) " << p3 - p1 << endl;  // 输出:(3) 29// p3 - p1 = (216-100)/sizeof(int) = 116/4 29cout << "(4) " << (pc2 - 10) - pc1 << endl;  // 输出:(4) 90// (pc2 - 10) - pc1 = ((200-10)-100)/1 = 90return 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];  // 定义数组aint *p;  // 定义指针pp = a;  // p指向数组a的起始地址,即p指向a[0]*p = 10;  // 相当于:a[0] = 10*(p+1) = 20;  // 相当于:a[1] = 20p[0] = 30;  // 相当于:a[0] = 30p[4] = 40;  //相当于:a[4] = 40for(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],输出:1p = a + 6;  // p指向a[6]cout << *p << endl;  // *p的值为a[6](*p等价于p[0]),输出:6return 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,输出:1234return 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也可以看作字符串数组名,等效于namecout << "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) 12345abcstrncpy(str1, str3, 3);  // 字符串复制n个字符:str1 = "ABC45abc"cout << "(2) " << str1 << endl;  // 输出:(2) ABC45abcstrncpy(str2, str3, 6);  // 字符串复制n个字符:str2 = "ABCDE"cout << "(3) " << str2 << endl;  // 输出:(3) ABCDEint cmpResult = strncmp(str1, str3, 3);  // 字符串比较n个字符:str1和str3的前3个字符相等cout << "(4) " << cmpResult << endl;  // 输出:(4) 0char *p = strchr(str1, 'B');  // 查找字符最先出现的位置:p指向str1中的字符'B'if(p)  // 如果p的值不为NULL,即str1中存在字符'B'cout << "(5) " << p-str1 << ", " << *p << endl;  // 输出:(5) 1, Belse  // 如果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, 45abcelse  // 如果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,即初始化数组achar szName[200] = " ";  // 数组szName的元素都为'\0'memset(szName, 'a', 10);  // 将szName的前10个字节设置为'a'cout << szName << endl;  // 输出:aaaaaaaaaaint a1[5] = {0, 1, 2, 3, 4};int a2[5] = {5, 6, 7, 8, 9};memcpy(a2, a1, 3*sizeof(int));  // 将数组a1的前3个元素的内容复制到数组a2for(int i = 0; i < 5; i++) {cout << a2[i] << ",";  // 输出:0,1,2,8,9,}cout << endl;MyMemcpy(a2, a1, 5*sizeof(int));  // 将数组a1的所有内容复制到数组a2for(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);elseprintf("%d", b);
}int main()
{void (*pf)(int, int);  // pf是函数指针int x = 4, y = 6;pf = PrintMin;  // pf指向函数PrintMinpf(x, y);  // 通过pf调用指向的函数PrintMinreturn 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++) {  // 循环遍历数组aint 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~ia[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 7struct 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: 1p = upper_bound(a, a+NUM, 5);  // 找到下标最小的、大于5的元素cout << "value: " << *p << ", position: " << p-a << endl;  // 输出:value: 7, position: 3p = upper_bound(a, a+NUM, 13);  // 找到下标最小的、大于13的元素cout << "value: " << *p << ", position: " << p-a << endl;  // 输出:value: 21, position: 5sort(a, a+NUM, Rule());  // 按个位数从小到大排序Print(a, NUM);  // 输出:21,12,3,5,5,7,98p = lower_bound(a, a+NUM, 16, Rule());  // 找到下标最小的、按自定义排序规则可以排在16后面的元素cout << "value: " << *p << ", position: " << p-a << endl;  // 输出:value: 7, position: 5p = lower_bound(a, a+NUM, 25, Rule());  // 找到下标最小的、按自定义排序规则可以排在25后面的元素cout << "value: " << *p << ", position: " << p-a << endl;  // 输出:value: 5, position: 3p = upper_bound(a, a+NUM, 5, Rule());  // 找到下标最小的、按自定义排序规则必须排在5后面的元素cout << "value: " << *p << ", position: " << p-a << endl;  // 输出:value: 7, position: 5p = 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: 7return 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: 13i = st.upper_bound(8);  // 返回8的最后一个可插入位置,即大于8的最小元素cout << "Found upper_bound: " << *i << endl;  // 输出:Found upper_bound: 12st.erase(i);  // 删除迭代器i指向的元素,即12for(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: 13return 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;elsereturn (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 102return 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;  // 输出:8set<int>::iterator i;for(i = st.begin(); i != st.end(); i++)cout << *i << ",";  // 输出:1,7,8,12,13,14,19,21cout << 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;elsecout << *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对应infochar 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.infomp.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;  // 输出:89mp["Jack"] = 60;  // 修改关键字为"Jack"的元素的secondcout << mp["Jack"] << endl;  // 输出:60for(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;elsecout << "Insertion failed." << endl;mp["Harry"] = 78;  // 插入新的元素,其first是"Harry",second是78map<string, int>::iterator q = mp.find("Harry");cout << "(" << q->first << ", " << q->second << ")" << endl;  // 输出:(Harry, 78)return 0;
}

学习笔记:C语言程序设计相关推荐

  1. (转载)[python学习笔记]Python语言程序设计(北理工 嵩天)

    作者:九命猫幺 博客出处:http://www.cnblogs.com/yongestcat/ 欢迎转载,转载请标明出处. 如果你觉得本文还不错,对你的学习带来了些许帮助,请帮忙点击右下角的推荐 阅读 ...

  2. 编译原理学习笔记2——高级程序设计语言概述

    编译原理学习笔记2--高级程序设计语言概述 2.1常用的高级程序设计语言 2.2程序设计语言的定义 2.2.1语法 2.2.1语法 2.2.3程序语言的基本功能和层次机构 2.2.4程序语言成分的逻辑 ...

  3. IOS学习笔记03---C语言概述

    IOS学习笔记03---C语言概述 0 3.C语言1-C语言概述         qq交流群:创梦技术交流群:251572072                        创梦网络赚钱群:2483 ...

  4. x%3e=y%3e=z的c语言表达式,我的C语学习笔记-C语言教程(三).doc

    我的C语学习笔记- C语言教程(三) C语言教程---第一章: C语言概论 C语言教程---第二章: 数据类型.运算符.表达式 C语言教程---第三章: C语言程序设计初步 C语言教程---第四章: ...

  5. c语言第七章函数笔记,我的C语学习笔记-C语言教程(七).doc

    我的C语学习笔记- C语言教程(七) C语言教程---第一章: C语言概论 C语言教程---第二章: 数据类型.运算符.表达式 C语言教程---第三章: C语言程序设计初步 C语言教程---第四章: ...

  6. nltk和python的关系_NLTK学习笔记(一):语言处理和Python

    目录 nltk资料下载 import nltk nltk.download() 其中,download() 参数默认是all,可以在脚本里面加上nltk.download(需要的资料库) 来进行下载 ...

  7. C语言学习笔记---001C语言的介绍,编译过程原理,工具等

    C语言学习笔记---001C语言的介绍,编译过程原理,工具等 创梦技术交流平台:资源下载,技术交流,网络赚钱: 交流qq群:1群:248318056 2群:251572072 技术交流平台:cre.i ...

  8. IOS学习笔记07---C语言函数-scanf函数

    2013/8/7 IOS学习笔记07---C语言函数-scanf函数 ------------------------------ qq交流群:创梦技术交流群:251572072            ...

  9. IOS学习笔记07---C语言函数-printf函数

    IOS学习笔记07---C语言函数-printf函数 0 7.C语言5-printf函数 ------------------------- ----------------------------- ...

  10. IOS学习笔记06---C语言函数

    IOS学习笔记06---C语言函数 --------------------------------------------  qq交流群:创梦技术交流群:251572072              ...

最新文章

  1. 【论文解读】MV3D-Net、AVOD-Ne用于自动驾驶的多视图3D目标检测网络
  2. 阿里云云盾-风险识别-增强版模式发布
  3. 利用 Numpy 进行矩阵相关运算
  4. 一篇特别长的总结(C专家编程)
  5. MySQL max_allowed_packet设置及问题
  6. 产品经理如何锻炼自己看透事物本质的能力
  7. linux同步多台机器的时间
  8. 从项目的 GitHub 星星数看2018年 JavaScript 生态圈
  9. mysql锁问题吗_Mysql锁的问题和解析
  10. 文华财经期货买卖点指标源码,期货超短线指标公式源码
  11. C语言 全局变量和局部变量的区别
  12. Android程序员学WEB前端(12)-JavaScript(3)-正则表达式-Sublime
  13. 安装vue最新脚手架
  14. Python全栈 Web(前端三剑客之JavaScript 从小白鼠到武林盟主)
  15. web安全基础知识-part2
  16. https://jingyan.baidu.com/article/c45ad29cd06453051753e2e9.html
  17. 虚拟IP注册Nacos的问题
  18. 华为防火墙双机热备(link-group和Eth-trunk)
  19. 《Saladict》谷歌!有道!我全都要! 聚合词典, 并行翻译
  20. pymol作图-氢键

热门文章

  1. 房产门户企业织梦模板/DedeCMS房地产楼盘网站模板下载
  2. EMQX Windows部署 MQTT服务器 EMQX安装
  3. 数据库设计中的命名规范
  4. 肖特基、整流、开关、快恢复二极管的区别
  5. 关于卸载流氓软件的通用办法
  6. EMI/EMC设计经典70问答,拿好不谢
  7. Cpulimit---控制cpu百分比
  8. CISA必修列表未收录数十个已遭利用漏洞
  9. MyEclipse 下载
  10. 霍尔开关在行车记录仪中起速度检测作用