学习笔记:C语言程序设计
信息在计算机中的表示
用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语言程序设计相关推荐
- (转载)[python学习笔记]Python语言程序设计(北理工 嵩天)
作者:九命猫幺 博客出处:http://www.cnblogs.com/yongestcat/ 欢迎转载,转载请标明出处. 如果你觉得本文还不错,对你的学习带来了些许帮助,请帮忙点击右下角的推荐 阅读 ...
- 编译原理学习笔记2——高级程序设计语言概述
编译原理学习笔记2--高级程序设计语言概述 2.1常用的高级程序设计语言 2.2程序设计语言的定义 2.2.1语法 2.2.1语法 2.2.3程序语言的基本功能和层次机构 2.2.4程序语言成分的逻辑 ...
- IOS学习笔记03---C语言概述
IOS学习笔记03---C语言概述 0 3.C语言1-C语言概述 qq交流群:创梦技术交流群:251572072 创梦网络赚钱群:2483 ...
- x%3e=y%3e=z的c语言表达式,我的C语学习笔记-C语言教程(三).doc
我的C语学习笔记- C语言教程(三) C语言教程---第一章: C语言概论 C语言教程---第二章: 数据类型.运算符.表达式 C语言教程---第三章: C语言程序设计初步 C语言教程---第四章: ...
- c语言第七章函数笔记,我的C语学习笔记-C语言教程(七).doc
我的C语学习笔记- C语言教程(七) C语言教程---第一章: C语言概论 C语言教程---第二章: 数据类型.运算符.表达式 C语言教程---第三章: C语言程序设计初步 C语言教程---第四章: ...
- nltk和python的关系_NLTK学习笔记(一):语言处理和Python
目录 nltk资料下载 import nltk nltk.download() 其中,download() 参数默认是all,可以在脚本里面加上nltk.download(需要的资料库) 来进行下载 ...
- C语言学习笔记---001C语言的介绍,编译过程原理,工具等
C语言学习笔记---001C语言的介绍,编译过程原理,工具等 创梦技术交流平台:资源下载,技术交流,网络赚钱: 交流qq群:1群:248318056 2群:251572072 技术交流平台:cre.i ...
- IOS学习笔记07---C语言函数-scanf函数
2013/8/7 IOS学习笔记07---C语言函数-scanf函数 ------------------------------ qq交流群:创梦技术交流群:251572072 ...
- IOS学习笔记07---C语言函数-printf函数
IOS学习笔记07---C语言函数-printf函数 0 7.C语言5-printf函数 ------------------------- ----------------------------- ...
- IOS学习笔记06---C语言函数
IOS学习笔记06---C语言函数 -------------------------------------------- qq交流群:创梦技术交流群:251572072 ...
最新文章
- 【论文解读】MV3D-Net、AVOD-Ne用于自动驾驶的多视图3D目标检测网络
- 阿里云云盾-风险识别-增强版模式发布
- 利用 Numpy 进行矩阵相关运算
- 一篇特别长的总结(C专家编程)
- MySQL max_allowed_packet设置及问题
- 产品经理如何锻炼自己看透事物本质的能力
- linux同步多台机器的时间
- 从项目的 GitHub 星星数看2018年 JavaScript 生态圈
- mysql锁问题吗_Mysql锁的问题和解析
- 文华财经期货买卖点指标源码,期货超短线指标公式源码
- C语言 全局变量和局部变量的区别
- Android程序员学WEB前端(12)-JavaScript(3)-正则表达式-Sublime
- 安装vue最新脚手架
- Python全栈 Web(前端三剑客之JavaScript 从小白鼠到武林盟主)
- web安全基础知识-part2
- https://jingyan.baidu.com/article/c45ad29cd06453051753e2e9.html
- 虚拟IP注册Nacos的问题
- 华为防火墙双机热备(link-group和Eth-trunk)
- 《Saladict》谷歌!有道!我全都要! 聚合词典, 并行翻译
- pymol作图-氢键